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