plotly/
configuration.rs

1use serde::{ser::Serializer, Serialize};
2use serde_repr::Serialize_repr;
3
4#[derive(Serialize, Debug, Clone)]
5#[serde(rename_all = "lowercase")]
6pub enum ImageButtonFormats {
7    Png,
8    Svg,
9    Jpeg,
10    Webp,
11}
12
13#[serde_with::skip_serializing_none]
14#[derive(Serialize, Debug, Default, Clone)]
15pub struct ToImageButtonOptions {
16    format: Option<ImageButtonFormats>,
17    filename: Option<String>,
18    height: Option<usize>,
19    width: Option<usize>,
20    scale: Option<usize>,
21}
22
23impl ToImageButtonOptions {
24    /// Create a new empty `ToImageButtonOptions` configuration struct.
25    pub fn new() -> Self {
26        Default::default()
27    }
28
29    /// Set the file format of the downloaded plot image.
30    pub fn format(mut self, format: ImageButtonFormats) -> Self {
31        self.format = Some(format);
32        self
33    }
34
35    /// Set the filename of the downloaded plot image.
36    pub fn filename(mut self, filename: &str) -> Self {
37        self.filename = Some(filename.to_string());
38        self
39    }
40
41    /// Set the height, in pixels, of the downloaded plot image.
42    pub fn height(mut self, height: usize) -> Self {
43        self.height = Some(height);
44        self
45    }
46
47    /// Set the width, in pixels, of the downloaded plot image.
48    pub fn width(mut self, width: usize) -> Self {
49        self.width = Some(width);
50        self
51    }
52
53    /// Set the scale of the downloaded plot image. Title, legend, axis and
54    /// canvas sizes will all be multiplied by `scale`.
55    pub fn scale(mut self, scale: usize) -> Self {
56        self.scale = Some(scale);
57        self
58    }
59}
60
61#[derive(Debug, Clone)]
62pub enum DisplayModeBar {
63    Hover,
64    True,
65    False,
66}
67
68impl Serialize for DisplayModeBar {
69    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70    where
71        S: Serializer,
72    {
73        match *self {
74            Self::Hover => serializer.serialize_str("hover"),
75            Self::True => serializer.serialize_bool(true),
76            Self::False => serializer.serialize_bool(false),
77        }
78    }
79}
80
81#[derive(Serialize, Debug, Clone)]
82#[serde(rename_all = "camelCase")]
83pub enum ModeBarButtonName {
84    Zoom2d,
85    Pan2d,
86    Select2d,
87    Lasso2d,
88    ZoomIn2d,
89    ZoomOut2d,
90    AutoScale2d,
91    ResetScale2d,
92    Zoom3d,
93    Pan3d,
94    OrbitRotation,
95    TableRotation,
96    ResetCameraDefault3d,
97    ResetCameraLastSave3d,
98    HoverClosest3d,
99    HoverClosestCartesian,
100    HoverCompareCartesian,
101    ZoomInGeo,
102    ZoomOutGeo,
103    ResetGeo,
104    HoverClosestGeo,
105    HoverClosestGl2d,
106    HoverClosestPie,
107    ToggleHover,
108    ResetViews,
109    ToImage,
110    SendDataToCloud,
111    ToggleSpikelines,
112    ResetViewMapbox,
113    ZoomInMapbox,
114    ZoomOutMapbox,
115}
116
117#[derive(Debug, Clone)]
118pub enum DoubleClick {
119    False,
120    Reset,
121    AutoSize,
122    ResetAutoSize,
123}
124
125impl Serialize for DoubleClick {
126    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
127    where
128        S: Serializer,
129    {
130        match *self {
131            Self::False => serializer.serialize_bool(false),
132            Self::Reset => serializer.serialize_str("reset"),
133            Self::AutoSize => serializer.serialize_str("autosize"),
134            Self::ResetAutoSize => serializer.serialize_str("reset+autosize"),
135        }
136    }
137}
138
139#[derive(Serialize_repr, Debug, Clone)]
140#[repr(u8)]
141pub enum PlotGLPixelRatio {
142    One = 1,
143    Two,
144    Three,
145    Four,
146}
147
148#[serde_with::skip_serializing_none]
149#[derive(Serialize, Debug, Default, Clone)]
150#[serde(rename_all = "camelCase")]
151pub struct Configuration {
152    // reference is here: https://github.com/plotly/plotly.js/blob/master/src/plot_api/plot_config.js
153    // missing: edits, show_sources, mode_bar_buttons, set_background, logging, notify_on_logging,
154    // global_transforms, mode_bar_buttons_to_add and locales
155    typeset_math: Option<bool>,
156    autosizable: Option<bool>,
157    scroll_zoom: Option<bool>,
158    fill_frame: Option<bool>,
159    frame_margins: Option<f64>,
160    editable: Option<bool>,
161    static_plot: Option<bool>,
162    to_image_button_options: Option<ToImageButtonOptions>,
163    display_mode_bar: Option<DisplayModeBar>,
164    mode_bar_buttons_to_remove: Option<Vec<ModeBarButtonName>>,
165    show_link: Option<bool>,
166    #[serde(rename = "plotlyServerURL")]
167    plotly_server_url: Option<String>,
168    #[serde(rename = "topojsonURL")]
169    topojson_url: Option<String>,
170    link_text: Option<String>,
171    mapbox_access_token: Option<String>,
172    show_edit_in_chart_studio: Option<bool>,
173    locale: Option<String>,
174    #[serde(rename = "displaylogo")]
175    display_logo: Option<bool>,
176    responsive: Option<bool>,
177    double_click: Option<DoubleClick>,
178    double_click_delay: Option<usize>,
179    show_axis_drag_handles: Option<bool>,
180    show_axis_range_entry_boxes: Option<bool>,
181    show_tips: Option<bool>,
182    send_data: Option<bool>,
183    watermark: Option<bool>,
184    #[serde(rename = "plotGlPixelRatio")]
185    plot_gl_pixel_ratio: Option<PlotGLPixelRatio>,
186    show_send_to_cloud: Option<bool>,
187    queue_length: Option<usize>,
188}
189
190impl Configuration {
191    /// Create a new default `Configuration` object. Options can be configured
192    /// using the provided setter methods.
193    pub fn new() -> Self {
194        Default::default()
195    }
196
197    /// Determines whether the graphs are interactive or not. If `false`, no
198    /// interactivity, for export or image generation.
199    pub fn static_plot(mut self, static_plot: bool) -> Self {
200        self.static_plot = Some(static_plot);
201        self
202    }
203
204    /// Determines whether math should be typeset or not, when MathJax (either
205    /// v2 or v3) is present on the page.
206    pub fn typeset_math(mut self, typeset_math: bool) -> Self {
207        self.typeset_math = Some(typeset_math);
208        self
209    }
210
211    /// When set it determines base URL for the "Edit in Chart Studio"
212    /// `show_edit_in_chart_studio`/`show_send_to_cloud` mode bar button and
213    /// the show_link/send_data on-graph link. To enable sending your data to
214    /// Chart Studio Cloud, you need to set both `plotly_server_url` to <https://chart-studio.plotly.com> and
215    /// also set `showSendToCloud` to `true`.
216    pub fn plotly_server_url(mut self, plotly_server_url: &str) -> Self {
217        self.plotly_server_url = Some(plotly_server_url.to_string());
218        self
219    }
220
221    /// Determines whether the graph is editable or not. Sets all pieces of
222    /// `edits` unless a separate `edits` config item overrides individual
223    /// parts.
224    pub fn editable(mut self, editable: bool) -> Self {
225        self.editable = Some(editable);
226        self
227    }
228
229    /// Determines whether the graphs are plotted with respect to
230    /// layout.auto_size: true and infer its container size.
231    pub fn autosizable(mut self, autosizable: bool) -> Self {
232        self.autosizable = Some(autosizable);
233        self
234    }
235
236    /// Determines whether to change the layout size when window is resized. In
237    /// v3, this option will be removed and will always be true.
238    pub fn responsive(mut self, responsive: bool) -> Self {
239        self.responsive = Some(responsive);
240        self
241    }
242
243    /// When `layout.auto_size` is turned on, determines whether the graph fills
244    /// the container (the default) or the screen (if set to `true`).
245    pub fn fill_frame(mut self, fill_frame: bool) -> Self {
246        self.fill_frame = Some(fill_frame);
247        self
248    }
249
250    /// When `layout.auto_size` is turned on, set the frame margins in fraction
251    /// of the graph size.
252    pub fn frame_margins(mut self, frame_margins: f64) -> Self {
253        // TODO: plotly supports a minimum value of 0 and a maximum value of 0.5
254        self.frame_margins = Some(frame_margins);
255        self
256    }
257
258    /// Determines whether mouse wheel or two-finger scroll zooms is enable.
259    /// Turned on by default for gl3d, geo and mapbox subplots (as these
260    /// subplot types do not have zoombox via pan), but turned off by
261    /// default for cartesian subplots. Set `scroll_zoom` to `false` to disable
262    /// scrolling for all subplots.
263    pub fn scroll_zoom(mut self, scroll_zoom: bool) -> Self {
264        self.scroll_zoom = Some(scroll_zoom);
265        self
266    }
267
268    /// Sets the double click interaction mode. Has an effect only in cartesian
269    /// plots. If `false`, double click is disable. If `reset`, double click
270    /// resets the axis ranges to their initial values. If `autosize`,
271    /// double click set the axis ranges to their autorange values. If
272    /// `reset+autosize`, the odd double clicks resets the axis ranges to
273    /// their initial values and even double clicks set the axis ranges to
274    /// their autorange values.
275    pub fn double_click(mut self, double_click: DoubleClick) -> Self {
276        self.double_click = Some(double_click);
277        self
278    }
279
280    /// Sets the delay for registering a double-click in ms. This is the time
281    /// interval (in ms) between first mousedown and 2nd mouseup to
282    /// constitute a double-click. This setting propagates to all on-subplot
283    /// double clicks (except for geo and mapbox) and on-legend double clicks.
284    pub fn double_click_delay(mut self, double_click_delay: usize) -> Self {
285        self.double_click_delay = Some(double_click_delay);
286        self
287    }
288
289    /// Set to `false` to omit cartesian axis pan/zoom drag handles.
290    pub fn show_axis_drag_handles(mut self, show_axis_drag_handles: bool) -> Self {
291        self.show_axis_drag_handles = Some(show_axis_drag_handles);
292        self
293    }
294
295    /// Set to `false` to omit direct range entry at the pan/zoom drag points,
296    /// note that `show_axis_drag_handles` must be enabled to have an
297    /// effect.
298    pub fn show_axis_range_entry_boxes(mut self, show_axis_range_entry_boxes: bool) -> Self {
299        self.show_axis_range_entry_boxes = Some(show_axis_range_entry_boxes);
300        self
301    }
302
303    /// Determines whether or not tips are shown while interacting with the
304    /// resulting graphs.
305    pub fn show_tips(mut self, show_tips: bool) -> Self {
306        self.show_tips = Some(show_tips);
307        self
308    }
309
310    /// Determines whether a link to Chart Studio Cloud is displayed at the
311    /// bottom right corner of resulting graphs. Use with `send_data` and
312    /// `link_text`.
313    pub fn show_link(mut self, show_link: bool) -> Self {
314        self.show_link = Some(show_link);
315        self
316    }
317
318    /// Sets the text appearing in the `showLink` link.
319    pub fn link_text(mut self, link_text: &str) -> Self {
320        self.link_text = Some(link_text.to_string());
321        self
322    }
323
324    /// If `show_link` is true, does it contain data just link to a Chart Studio
325    /// Cloud file?
326    pub fn send_data(mut self, send_data: bool) -> Self {
327        self.send_data = Some(send_data);
328        self
329    }
330
331    /// Determines the mode bar display mode. If `true`, the mode bar is always
332    /// visible. If `false`, the mode bar is always hidden. If `hover`, the
333    /// mode bar is visible while the mouse cursor is on the graph
334    /// container.
335    pub fn display_mode_bar(mut self, display_mode_bar: DisplayModeBar) -> Self {
336        self.display_mode_bar = Some(display_mode_bar);
337        self
338    }
339
340    /// Should we include a ModeBar button, labeled "Edit in Chart Studio" that
341    /// sends this chart to chart-studio.plotly.com (formerly plot.ly) or
342    /// another plotly server as specified by `plotly_server_url`
343    /// for editing, export, etc? Prior to version 1.43.0 this button was
344    /// included by default, now it is opt-in using this flag. Note that
345    /// this button can (depending on `plotly_server_url` being set) send your
346    /// data to an external server. However that server does not persist your
347    /// data until you arrive at the Chart Studio and explicitly click
348    /// "Save".
349    pub fn show_send_to_cloud(mut self, show_send_to_cloud: bool) -> Self {
350        self.show_send_to_cloud = Some(show_send_to_cloud);
351        self
352    }
353
354    /// Same as `show_send_to_cloud`, but use a pencil icon instead of a
355    /// floppy-disk. Note that if both `show_send_to_cloud` and
356    /// `show_edit_in_chart_studio` are turned on, only
357    /// `show_edit_in_chart_studio` will be honored.
358    pub fn show_edit_in_chart_studio(mut self, show_edit_in_chart_studio: bool) -> Self {
359        self.show_edit_in_chart_studio = Some(show_edit_in_chart_studio);
360        self
361    }
362
363    /// Remove mode bar buttons by name.
364    pub fn mode_bar_buttons_to_remove(
365        mut self,
366        mode_bar_buttons_to_remove: Vec<ModeBarButtonName>,
367    ) -> Self {
368        self.mode_bar_buttons_to_remove = Some(mode_bar_buttons_to_remove);
369        self
370    }
371
372    /// Statically override options for toImage modebar button.
373    pub fn to_image_button_options(
374        mut self,
375        to_image_button_options: ToImageButtonOptions,
376    ) -> Self {
377        self.to_image_button_options = Some(to_image_button_options);
378        self
379    }
380
381    /// Determines whether or not the plotly logo is displayed on the end of the
382    /// mode bar.
383    pub fn display_logo(mut self, display_logo: bool) -> Self {
384        self.display_logo = Some(display_logo);
385        self
386    }
387
388    /// Watermark the images with the company's logo.
389    pub fn watermark(mut self, watermark: bool) -> Self {
390        self.watermark = Some(watermark);
391        self
392    }
393
394    /// Set the pixel ratio during WebGL image export.
395    pub fn plot_gl_pixel_ratio(mut self, plot_gl_pixel_ratio: PlotGLPixelRatio) -> Self {
396        self.plot_gl_pixel_ratio = Some(plot_gl_pixel_ratio);
397        self
398    }
399
400    /// Set the URL to topojson used in geo charts. By default, the topojson
401    /// files are fetched from cdn.plot.ly. For example, set this option to:
402    /// "<path-to-plotly.js>/dist/topojson/" to render geographical feature
403    /// using the topojson files that ship with the plotly.js module.
404    pub fn topojson_url(mut self, topojson_url: &str) -> Self {
405        self.topojson_url = Some(topojson_url.to_string());
406        self
407    }
408
409    /// Mapbox access token (required to plot mapbox trace types). If using an
410    /// Mapbox Atlas server, set this option to "" so that plotly.js won't
411    /// attempt to authenticate to the public Mapbox server.
412    pub fn mapbox_access_token(mut self, mapbox_access_token: &str) -> Self {
413        self.mapbox_access_token = Some(mapbox_access_token.to_string());
414        self
415    }
416
417    /// Sets the length of the undo/redo queue.
418    pub fn queue_length(mut self, queue_length: usize) -> Self {
419        self.queue_length = Some(queue_length);
420        self
421    }
422
423    /// Sets which localization to use. When using this setting, make sure that
424    /// the appropriate locale is present in the HTML file. For example, to
425    /// use the "fr" locale, <script src="https://cdn.plot.ly/plotly-locale-fr-latest.js"></script> must be present.
426    pub fn locale(mut self, locale: &str) -> Self {
427        self.locale = Some(locale.to_string());
428        self
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use serde_json::{json, to_value};
435
436    use super::*;
437
438    #[test]
439    fn serialize_image_button_formats() {
440        assert_eq!(to_value(ImageButtonFormats::Png).unwrap(), json!("png"));
441        assert_eq!(to_value(ImageButtonFormats::Svg).unwrap(), json!("svg"));
442        assert_eq!(to_value(ImageButtonFormats::Jpeg).unwrap(), json!("jpeg"));
443        assert_eq!(to_value(ImageButtonFormats::Webp).unwrap(), json!("webp"));
444    }
445    #[test]
446    fn serialize_to_image_button_options() {
447        let options = ToImageButtonOptions::new()
448            .format(ImageButtonFormats::Jpeg)
449            .filename("filename")
450            .height(500)
451            .width(250)
452            .scale(2);
453        let expected = json!({
454            "format": "jpeg",
455            "filename": "filename",
456            "height": 500,
457            "width": 250,
458            "scale": 2
459        });
460
461        assert_eq!(to_value(options).unwrap(), expected)
462    }
463
464    #[test]
465    fn serialize_display_mode_bar() {
466        assert_eq!(to_value(DisplayModeBar::Hover).unwrap(), json!("hover"));
467        assert_eq!(to_value(DisplayModeBar::True).unwrap(), json!(true));
468        assert_eq!(to_value(DisplayModeBar::False).unwrap(), json!(false));
469    }
470
471    #[test]
472    #[rustfmt::skip]
473    fn serialize_mode_bar_button_name() {
474        assert_eq!(to_value(ModeBarButtonName::Zoom2d).unwrap(), json!("zoom2d"));
475        assert_eq!(to_value(ModeBarButtonName::Pan2d).unwrap(), json!("pan2d"));
476        assert_eq!(to_value(ModeBarButtonName::Select2d).unwrap(), json!("select2d"));
477        assert_eq!(to_value(ModeBarButtonName::Lasso2d).unwrap(), json!("lasso2d"));
478        assert_eq!(to_value(ModeBarButtonName::ZoomIn2d).unwrap(), json!("zoomIn2d"));
479        assert_eq!(to_value(ModeBarButtonName::ZoomOut2d).unwrap(), json!("zoomOut2d"));
480        assert_eq!(to_value(ModeBarButtonName::AutoScale2d).unwrap(), json!("autoScale2d"));
481        assert_eq!(to_value(ModeBarButtonName::ResetScale2d).unwrap(), json!("resetScale2d"));
482        assert_eq!(to_value(ModeBarButtonName::Zoom3d).unwrap(), json!("zoom3d"));
483        assert_eq!(to_value(ModeBarButtonName::Pan3d).unwrap(), json!("pan3d"));
484        assert_eq!(to_value(ModeBarButtonName::ResetCameraDefault3d).unwrap(), json!("resetCameraDefault3d"));
485        assert_eq!(to_value(ModeBarButtonName::ResetCameraLastSave3d).unwrap(), json!("resetCameraLastSave3d"));
486        assert_eq!(to_value(ModeBarButtonName::HoverClosest3d).unwrap(), json!("hoverClosest3d"));
487        assert_eq!(to_value(ModeBarButtonName::OrbitRotation).unwrap(), json!("orbitRotation"));
488        assert_eq!(to_value(ModeBarButtonName::TableRotation).unwrap(), json!("tableRotation"));
489        assert_eq!(to_value(ModeBarButtonName::HoverClosestCartesian).unwrap(), json!("hoverClosestCartesian"));
490        assert_eq!(to_value(ModeBarButtonName::HoverCompareCartesian).unwrap(), json!("hoverCompareCartesian"));
491        assert_eq!(to_value(ModeBarButtonName::ZoomInGeo).unwrap(), json!("zoomInGeo"));
492        assert_eq!(to_value(ModeBarButtonName::ZoomOutGeo).unwrap(), json!("zoomOutGeo"));
493        assert_eq!(to_value(ModeBarButtonName::ResetGeo).unwrap(), json!("resetGeo"));
494        assert_eq!(to_value(ModeBarButtonName::HoverClosestGeo).unwrap(), json!("hoverClosestGeo"));
495        assert_eq!(to_value(ModeBarButtonName::HoverClosestGl2d).unwrap(), json!("hoverClosestGl2d"));
496        assert_eq!(to_value(ModeBarButtonName::HoverClosestPie).unwrap(), json!("hoverClosestPie"));
497        assert_eq!(to_value(ModeBarButtonName::ToggleHover).unwrap(), json!("toggleHover"));
498        assert_eq!(to_value(ModeBarButtonName::ResetViews).unwrap(), json!("resetViews"));
499        assert_eq!(to_value(ModeBarButtonName::ToImage).unwrap(), json!("toImage"));
500        assert_eq!(to_value(ModeBarButtonName::SendDataToCloud).unwrap(), json!("sendDataToCloud"));
501        assert_eq!(to_value(ModeBarButtonName::ToggleSpikelines).unwrap(), json!("toggleSpikelines"));
502        assert_eq!(to_value(ModeBarButtonName::ResetViewMapbox).unwrap(), json!("resetViewMapbox"));
503        assert_eq!(to_value(ModeBarButtonName::ZoomInMapbox).unwrap(), json!("zoomInMapbox"));
504        assert_eq!(to_value(ModeBarButtonName::ZoomOutMapbox).unwrap(), json!("zoomOutMapbox"));
505    }
506
507    #[test]
508    #[rustfmt::skip]
509    fn serialize_double_click() {
510        assert_eq!(to_value(DoubleClick::False).unwrap(), json!(false));
511        assert_eq!(to_value(DoubleClick::Reset).unwrap(), json!("reset"));
512        assert_eq!(to_value(DoubleClick::AutoSize).unwrap(), json!("autosize"));
513        assert_eq!(to_value(DoubleClick::ResetAutoSize).unwrap(), json!("reset+autosize"));
514    }
515
516    #[test]
517    fn serialize_plot_gl_pixel_ratio() {
518        assert_eq!(to_value(PlotGLPixelRatio::One).unwrap(), json!(1));
519        assert_eq!(to_value(PlotGLPixelRatio::Two).unwrap(), json!(2));
520        assert_eq!(to_value(PlotGLPixelRatio::Three).unwrap(), json!(3));
521        assert_eq!(to_value(PlotGLPixelRatio::Four).unwrap(), json!(4));
522    }
523
524    #[test]
525    fn serialize_configuration() {
526        let config = Configuration::new()
527            .static_plot(true)
528            .typeset_math(true)
529            .plotly_server_url("server_url")
530            .editable(false)
531            .autosizable(false)
532            .responsive(true)
533            .fill_frame(false)
534            .frame_margins(2.0)
535            .scroll_zoom(false)
536            .double_click(DoubleClick::ResetAutoSize)
537            .double_click_delay(50)
538            .show_axis_drag_handles(false)
539            .show_axis_range_entry_boxes(true)
540            .show_tips(false)
541            .show_link(true)
542            .link_text("link text")
543            .send_data(false)
544            .display_mode_bar(DisplayModeBar::Hover)
545            .show_send_to_cloud(true)
546            .show_edit_in_chart_studio(false)
547            .mode_bar_buttons_to_remove(vec![ModeBarButtonName::Zoom2d])
548            .to_image_button_options(ToImageButtonOptions::new())
549            .display_logo(false)
550            .watermark(true)
551            .plot_gl_pixel_ratio(PlotGLPixelRatio::Four)
552            .topojson_url("topojson_url")
553            .mapbox_access_token("123")
554            .queue_length(100)
555            .locale("en");
556
557        let expected = json!({
558            "staticPlot": true,
559            "typesetMath": true,
560            "plotlyServerURL": "server_url",
561            "editable": false,
562            "autosizable": false,
563            "responsive": true,
564            "fillFrame": false,
565            "frameMargins": 2.0,
566            "scrollZoom": false,
567            "doubleClick": "reset+autosize",
568            "doubleClickDelay": 50,
569            "showAxisDragHandles": false,
570            "showAxisRangeEntryBoxes": true,
571            "showTips": false,
572            "showLink": true,
573            "linkText": "link text",
574            "sendData": false,
575            "displayModeBar": "hover",
576            "showSendToCloud": true,
577            "showEditInChartStudio": false,
578            "modeBarButtonsToRemove": ["zoom2d"],
579            "toImageButtonOptions": {},
580            "displaylogo": false,
581            "watermark": true,
582            "plotGlPixelRatio": 4,
583            "topojsonURL": "topojson_url",
584            "mapboxAccessToken": "123",
585            "queueLength": 100,
586            "locale": "en"
587        });
588
589        assert_eq!(to_value(config).unwrap(), expected);
590    }
591}