plotly/layout/
mod.rs

1use std::borrow::Cow;
2
3use plotly_derive::layout_structs;
4use plotly_derive::FieldSetter;
5use serde::Serialize;
6use update_menu::UpdateMenu;
7
8use crate::color::Color;
9use crate::common::{Calendar, ColorScale, Font, Label, Orientation, Title};
10
11pub mod themes;
12pub mod update_menu;
13
14mod annotation;
15mod axis;
16mod geo;
17mod grid;
18mod legend;
19mod mapbox;
20mod modes;
21mod rangebreaks;
22mod scene;
23mod shape;
24mod slider;
25
26// Re-export layout sub-module types
27pub use self::annotation::{Annotation, ArrowSide, ClickToShow};
28pub use self::axis::{
29    ArrayShow, Axis, AxisConstrain, AxisRange, AxisType, CategoryOrder, ColorAxis,
30    ConstrainDirection, RangeMode, RangeSelector, RangeSlider, RangeSliderYAxis, SelectorButton,
31    SelectorStep, SliderRangeMode, SpikeMode, SpikeSnap, StepMode, TicksDirection, TicksPosition,
32};
33pub use self::geo::LayoutGeo;
34pub use self::grid::{GridDomain, GridPattern, GridXSide, GridYSide, LayoutGrid, RowOrder};
35pub use self::legend::{GroupClick, ItemClick, ItemSizing, Legend, TraceOrder};
36pub use self::mapbox::{Center, Mapbox, MapboxStyle};
37pub use self::modes::{
38    AspectMode, BarMode, BarNorm, BoxMode, ClickMode, UniformTextMode, ViolinMode, WaterfallMode,
39};
40pub use self::rangebreaks::RangeBreak;
41pub use self::scene::{
42    AspectRatio, Camera, CameraCenter, DragMode, DragMode3D, Eye, HoverMode, LayoutScene,
43    Projection, ProjectionType, Rotation, Up,
44};
45pub use self::shape::{
46    ActiveShape, DrawDirection, FillRule, NewShape, Shape, ShapeLayer, ShapeLine, ShapeSizeMode,
47    ShapeType,
48};
49pub use self::slider::{
50    Slider, SliderCurrentValue, SliderCurrentValueXAnchor, SliderMethod, SliderStep,
51    SliderStepBuilder, SliderTransition, SliderTransitionEasing,
52};
53
54/// Error type for ControlBuilder operations
55#[derive(Debug)]
56pub enum ControlBuilderError {
57    RestyleSerializationError(String),
58    RelayoutSerializationError(String),
59    ValueSerializationError(String),
60    InvalidRestyleObject(String),
61    InvalidRelayoutObject(String),
62}
63
64impl std::fmt::Display for ControlBuilderError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            ControlBuilderError::RestyleSerializationError(e) => {
68                write!(f, "Failed to serialize restyle: {e}")
69            }
70            ControlBuilderError::RelayoutSerializationError(e) => {
71                write!(f, "Failed to serialize relayout: {e}")
72            }
73            ControlBuilderError::ValueSerializationError(e) => {
74                write!(f, "Failed to serialize value: {e}")
75            }
76            ControlBuilderError::InvalidRestyleObject(s) => {
77                write!(f, "Invalid restyle object: expected object but got {s}")
78            }
79            ControlBuilderError::InvalidRelayoutObject(s) => {
80                write!(f, "Invalid relayout object: expected object but got {s}")
81            }
82        }
83    }
84}
85
86impl std::error::Error for ControlBuilderError {}
87
88#[derive(Serialize, Debug, Clone)]
89#[serde(rename_all = "lowercase")]
90pub enum VAlign {
91    Top,
92    Middle,
93    Bottom,
94}
95
96#[derive(Serialize, Debug, Clone)]
97#[serde(rename_all = "lowercase")]
98pub enum HAlign {
99    Left,
100    Center,
101    Right,
102}
103
104#[serde_with::skip_serializing_none]
105#[derive(Serialize, Debug, Clone, FieldSetter)]
106pub struct Margin {
107    #[serde(rename = "l")]
108    left: Option<usize>,
109    #[serde(rename = "r")]
110    right: Option<usize>,
111    #[serde(rename = "t")]
112    top: Option<usize>,
113    #[serde(rename = "b")]
114    bottom: Option<usize>,
115    pad: Option<usize>,
116    #[serde(rename = "autoexpand")]
117    auto_expand: Option<bool>,
118}
119
120impl Margin {
121    pub fn new() -> Self {
122        Default::default()
123    }
124}
125
126#[serde_with::skip_serializing_none]
127#[derive(Serialize, Debug, Clone, FieldSetter)]
128pub struct LayoutColorScale {
129    sequential: Option<ColorScale>,
130    #[serde(rename = "sequentialminus")]
131    sequential_minus: Option<ColorScale>,
132    diverging: Option<ColorScale>,
133}
134
135impl LayoutColorScale {
136    pub fn new() -> Self {
137        Default::default()
138    }
139}
140
141#[serde_with::skip_serializing_none]
142#[derive(Serialize, Debug, Clone, FieldSetter)]
143pub struct UniformText {
144    mode: Option<UniformTextMode>,
145    #[serde(rename = "minsize")]
146    min_size: Option<usize>,
147}
148
149impl UniformText {
150    pub fn new() -> Self {
151        Default::default()
152    }
153}
154
155#[serde_with::skip_serializing_none]
156#[derive(Serialize, Debug, Clone, FieldSetter)]
157pub struct ModeBar {
158    orientation: Option<Orientation>,
159    #[serde(rename = "bgcolor")]
160    background_color: Option<Box<dyn Color>>,
161    color: Option<Box<dyn Color>>,
162    #[serde(rename = "activecolor")]
163    active_color: Option<Box<dyn Color>>,
164}
165
166impl ModeBar {
167    pub fn new() -> Self {
168        Default::default()
169    }
170}
171
172#[derive(Serialize, Debug, Clone)]
173#[serde(rename_all = "lowercase")]
174pub enum SelectDirection {
175    #[serde(rename = "h")]
176    Horizontal,
177    #[serde(rename = "v")]
178    Vertical,
179    #[serde(rename = "d")]
180    Diagonal,
181    Any,
182}
183
184#[serde_with::skip_serializing_none]
185#[derive(Serialize, Debug, Clone, FieldSetter)]
186pub struct Template {
187    layout: Option<LayoutTemplate>,
188}
189
190impl Template {
191    pub fn new() -> Self {
192        Default::default()
193    }
194}
195
196#[allow(clippy::from_over_into)]
197impl Into<Cow<'static, Template>> for Template {
198    fn into(self) -> Cow<'static, Template> {
199        Cow::Owned(self)
200    }
201}
202
203#[allow(clippy::from_over_into)]
204impl Into<Cow<'static, Template>> for &'static Template {
205    fn into(self) -> Cow<'static, Template> {
206        Cow::Borrowed(self)
207    }
208}
209
210/// Generates Layout and LayoutTemplate
211/// LayoutTemplate matches Layout except it lacks a field for template
212/// See layout/layout.rs for the full field list and doc comments.
213/// Layout is identical to LayoutTemplate except it has a `template` field and
214/// #[field_setter(kind = "layout")] See layout/layout.rs for the full field
215/// list and doc comments.
216#[layout_structs]
217pub struct LayoutFields {
218    title: Option<Title>,
219    #[serde(rename = "showlegend")]
220    show_legend: Option<bool>,
221    legend: Option<Legend>,
222    margin: Option<Margin>,
223    #[serde(rename = "autosize")]
224    auto_size: Option<bool>,
225    width: Option<usize>,
226    height: Option<usize>,
227    font: Option<Font>,
228    #[serde(rename = "uniformtext")]
229    uniform_text: Option<UniformText>,
230    separators: Option<String>,
231    #[serde(rename = "paper_bgcolor")]
232    paper_background_color: Option<Box<dyn Color>>,
233    #[serde(rename = "plot_bgcolor")]
234    plot_background_color: Option<Box<dyn Color>>,
235    #[serde(rename = "colorscale")]
236    color_scale: Option<LayoutColorScale>,
237    colorway: Option<Vec<Box<dyn Color>>>,
238    #[serde(rename = "coloraxis")]
239    color_axis: Option<ColorAxis>,
240    #[serde(rename = "modebar")]
241    mode_bar: Option<ModeBar>,
242    /// Determines the mode of hover interactions. If "closest", a single
243    /// hoverlabel will appear for the "closest" point within the
244    /// `hoverdistance`. If "x" (or "y"), multiple hoverlabels will appear for
245    /// multiple points at the "closest" x- (or y-) coordinate within the
246    /// `hoverdistance`, with the caveat that no more than one hoverlabel
247    /// will appear per trace. If "x unified" (or "y unified"), a single
248    /// hoverlabel will appear multiple points at the closest x- (or y-)
249    /// coordinate within the `hoverdistance` with the caveat that no more than
250    /// one hoverlabel will appear per trace. In this mode, spikelines are
251    /// enabled by default perpendicular to the specified axis.
252    /// If false, hover interactions are disabled. If `clickmode` includes the
253    /// "select" flag, `hovermode` defaults to "closest". If `clickmode`
254    /// lacks the "select" flag, it defaults to "x" or "y" (depending on the
255    /// trace's `orientation` value) for plots based on cartesian coordinates.
256    /// For anything else the default value is "closest".
257    #[serde(rename = "hovermode")]
258    hover_mode: Option<HoverMode>,
259    #[serde(rename = "clickmode")]
260    click_mode: Option<ClickMode>,
261    #[serde(rename = "dragmode")]
262    drag_mode: Option<DragMode>,
263    #[serde(rename = "selectdirection")]
264    select_direction: Option<SelectDirection>,
265    #[serde(rename = "hoverdistance")]
266    hover_distance: Option<i32>,
267    #[serde(rename = "spikedistance")]
268    spike_distance: Option<i32>,
269    #[serde(rename = "hoverlabel")]
270    hover_label: Option<Label>,
271    grid: Option<LayoutGrid>,
272    calendar: Option<Calendar>,
273    #[serde(rename = "xaxis")]
274    x_axis: Option<Box<Axis>>,
275    #[serde(rename = "yaxis")]
276    y_axis: Option<Box<Axis>>,
277    #[serde(rename = "zaxis")]
278    z_axis: Option<Box<Axis>>,
279    #[serde(rename = "xaxis2")]
280    x_axis2: Option<Box<Axis>>,
281    #[serde(rename = "yaxis2")]
282    y_axis2: Option<Box<Axis>>,
283    #[serde(rename = "zaxis2")]
284    z_axis2: Option<Box<Axis>>,
285    #[serde(rename = "xaxis3")]
286    x_axis3: Option<Box<Axis>>,
287    #[serde(rename = "yaxis3")]
288    y_axis3: Option<Box<Axis>>,
289    #[serde(rename = "zaxis3")]
290    z_axis3: Option<Box<Axis>>,
291    #[serde(rename = "xaxis4")]
292    x_axis4: Option<Box<Axis>>,
293    #[serde(rename = "yaxis4")]
294    y_axis4: Option<Box<Axis>>,
295    #[serde(rename = "zaxis4")]
296    z_axis4: Option<Box<Axis>>,
297    #[serde(rename = "xaxis5")]
298    x_axis5: Option<Box<Axis>>,
299    #[serde(rename = "yaxis5")]
300    y_axis5: Option<Box<Axis>>,
301    #[serde(rename = "zaxis5")]
302    z_axis5: Option<Box<Axis>>,
303    #[serde(rename = "xaxis6")]
304    x_axis6: Option<Box<Axis>>,
305    #[serde(rename = "yaxis6")]
306    y_axis6: Option<Box<Axis>>,
307    #[serde(rename = "zaxis6")]
308    z_axis6: Option<Box<Axis>>,
309    #[serde(rename = "xaxis7")]
310    x_axis7: Option<Box<Axis>>,
311    #[serde(rename = "yaxis7")]
312    y_axis7: Option<Box<Axis>>,
313    #[serde(rename = "zaxis7")]
314    z_axis7: Option<Box<Axis>>,
315    #[serde(rename = "xaxis8")]
316    x_axis8: Option<Box<Axis>>,
317    #[serde(rename = "yaxis8")]
318    y_axis8: Option<Box<Axis>>,
319    #[serde(rename = "zaxis8")]
320    z_axis8: Option<Box<Axis>>,
321    // ternary: Option<LayoutTernary>,
322    scene: Option<LayoutScene>,
323    geo: Option<LayoutGeo>,
324    // polar: Option<LayoutPolar>,
325    annotations: Option<Vec<Annotation>>,
326    shapes: Option<Vec<Shape>>,
327    #[serde(rename = "newshape")]
328    new_shape: Option<NewShape>,
329    #[serde(rename = "activeshape")]
330    active_shape: Option<ActiveShape>,
331    #[serde(rename = "boxmode")]
332    box_mode: Option<BoxMode>,
333    #[serde(rename = "boxgap")]
334    box_gap: Option<f64>,
335    #[serde(rename = "boxgroupgap")]
336    box_group_gap: Option<f64>,
337    #[serde(rename = "barmode")]
338    bar_mode: Option<BarMode>,
339    #[serde(rename = "barnorm")]
340    bar_norm: Option<BarNorm>,
341    #[serde(rename = "bargap")]
342    bar_gap: Option<f64>,
343    #[serde(rename = "bargroupgap")]
344    bar_group_gap: Option<f64>,
345    #[serde(rename = "violinmode")]
346    violin_mode: Option<ViolinMode>,
347    #[serde(rename = "violingap")]
348    violin_gap: Option<f64>,
349    #[serde(rename = "violingroupgap")]
350    violin_group_gap: Option<f64>,
351    #[serde(rename = "waterfallmode")]
352    waterfall_mode: Option<WaterfallMode>,
353    #[serde(rename = "waterfallgap")]
354    waterfall_gap: Option<f64>,
355    #[serde(rename = "waterfallgroupgap")]
356    waterfall_group_gap: Option<f64>,
357    #[serde(rename = "piecolorway")]
358    pie_colorway: Option<Vec<Box<dyn Color>>>,
359    #[serde(rename = "extendpiecolors")]
360    extend_pie_colors: Option<bool>,
361    #[serde(rename = "sunburstcolorway")]
362    sunburst_colorway: Option<Vec<Box<dyn Color>>>,
363    #[serde(rename = "extendsunburstcolors")]
364    extend_sunburst_colors: Option<bool>,
365    mapbox: Option<Mapbox>,
366    #[serde(rename = "updatemenus")]
367    update_menus: Option<Vec<UpdateMenu>>,
368    sliders: Option<Vec<Slider>>,
369}
370
371#[cfg(test)]
372mod tests {
373    use serde_json::{json, to_value};
374
375    use super::*;
376    use crate::common::ColorScalePalette;
377
378    #[test]
379    fn serialize_valign() {
380        assert_eq!(to_value(VAlign::Top).unwrap(), json!("top"));
381        assert_eq!(to_value(VAlign::Middle).unwrap(), json!("middle"));
382        assert_eq!(to_value(VAlign::Bottom).unwrap(), json!("bottom"));
383    }
384
385    #[test]
386    fn serialize_halign() {
387        assert_eq!(to_value(HAlign::Left).unwrap(), json!("left"));
388        assert_eq!(to_value(HAlign::Center).unwrap(), json!("center"));
389        assert_eq!(to_value(HAlign::Right).unwrap(), json!("right"));
390    }
391    #[test]
392    fn serialize_margin() {
393        let margin = Margin::new()
394            .left(1)
395            .right(2)
396            .top(3)
397            .bottom(4)
398            .pad(5)
399            .auto_expand(true);
400        let expected = json!({
401            "l": 1,
402            "r": 2,
403            "t": 3,
404            "b": 4,
405            "pad": 5,
406            "autoexpand": true,
407        });
408
409        assert_eq!(to_value(margin).unwrap(), expected);
410    }
411
412    #[test]
413    fn serialize_layout_color_scale() {
414        let layout_color_scale = LayoutColorScale::new()
415            .sequential(ColorScale::Palette(ColorScalePalette::Greys))
416            .sequential_minus(ColorScale::Palette(ColorScalePalette::Blues))
417            .diverging(ColorScale::Palette(ColorScalePalette::Hot));
418        let expected = json!({
419            "sequential": "Greys",
420            "sequentialminus": "Blues",
421            "diverging": "Hot"
422        });
423
424        assert_eq!(to_value(layout_color_scale).unwrap(), expected);
425    }
426
427    #[test]
428    fn serialize_mode_bar() {
429        let mode_bar = ModeBar::new()
430            .orientation(Orientation::Horizontal)
431            .background_color("#FFF000")
432            .color("#000FFF")
433            .active_color("#AAABBB");
434        let expected = json!({
435            "orientation": "h",
436            "bgcolor": "#FFF000",
437            "color": "#000FFF",
438            "activecolor": "#AAABBB",
439        });
440
441        assert_eq!(to_value(mode_bar).unwrap(), expected);
442    }
443
444    #[test]
445    fn serialize_arrow_side() {
446        assert_eq!(to_value(ArrowSide::End).unwrap(), json!("end"));
447        assert_eq!(to_value(ArrowSide::Start).unwrap(), json!("start"));
448        assert_eq!(to_value(ArrowSide::StartEnd).unwrap(), json!("end+start"));
449        assert_eq!(to_value(ArrowSide::None).unwrap(), json!("none"));
450    }
451
452    #[test]
453    fn serialize_select_direction() {
454        assert_eq!(to_value(SelectDirection::Horizontal).unwrap(), json!("h"));
455        assert_eq!(to_value(SelectDirection::Vertical).unwrap(), json!("v"));
456        assert_eq!(to_value(SelectDirection::Diagonal).unwrap(), json!("d"));
457        assert_eq!(to_value(SelectDirection::Any).unwrap(), json!("any"));
458    }
459
460    #[test]
461    fn serialize_layout_template() {
462        let layout_template = LayoutTemplate::new()
463            .title("Title")
464            .show_legend(false)
465            .legend(Legend::new())
466            .margin(Margin::new())
467            .auto_size(true)
468            .width(10)
469            .height(20)
470            .font(Font::new())
471            .uniform_text(UniformText::new())
472            .separators("_")
473            .paper_background_color("#FFFFFF")
474            .plot_background_color("#151515")
475            .color_scale(LayoutColorScale::new())
476            .colorway(vec!["#123123"])
477            .color_axis(ColorAxis::new())
478            .mode_bar(ModeBar::new())
479            .hover_mode(HoverMode::Closest)
480            .click_mode(ClickMode::EventAndSelect)
481            .drag_mode(DragMode::Turntable)
482            .select_direction(SelectDirection::Diagonal)
483            .hover_distance(321)
484            .spike_distance(12)
485            .hover_label(Label::new())
486            .grid(LayoutGrid::new())
487            .calendar(Calendar::Jalali)
488            .x_axis(Axis::new())
489            .x_axis2(Axis::new())
490            .x_axis3(Axis::new())
491            .x_axis4(Axis::new())
492            .x_axis5(Axis::new())
493            .x_axis6(Axis::new())
494            .x_axis7(Axis::new())
495            .x_axis8(Axis::new())
496            .y_axis(Axis::new())
497            .y_axis2(Axis::new())
498            .y_axis3(Axis::new())
499            .y_axis4(Axis::new())
500            .y_axis5(Axis::new())
501            .y_axis6(Axis::new())
502            .y_axis7(Axis::new())
503            .y_axis8(Axis::new())
504            .z_axis(Axis::new())
505            .z_axis2(Axis::new())
506            .z_axis3(Axis::new())
507            .z_axis4(Axis::new())
508            .z_axis5(Axis::new())
509            .z_axis6(Axis::new())
510            .z_axis7(Axis::new())
511            .z_axis8(Axis::new())
512            .annotations(vec![Annotation::new()])
513            .shapes(vec![Shape::new()])
514            .new_shape(NewShape::new())
515            .active_shape(ActiveShape::new())
516            .box_mode(BoxMode::Group)
517            .box_gap(1.)
518            .box_group_gap(2.)
519            .bar_mode(BarMode::Overlay)
520            .bar_norm(BarNorm::Empty)
521            .bar_gap(3.)
522            .bar_group_gap(4.)
523            .violin_mode(ViolinMode::Overlay)
524            .violin_gap(5.)
525            .violin_group_gap(6.)
526            .waterfall_mode(WaterfallMode::Group)
527            .waterfall_gap(7.)
528            .waterfall_group_gap(8.)
529            .pie_colorway(vec!["#789789"])
530            .extend_pie_colors(true)
531            .sunburst_colorway(vec!["#654654"])
532            .extend_sunburst_colors(false)
533            .mapbox(Mapbox::new())
534            .update_menus(vec![UpdateMenu::new()])
535            .sliders(vec![Slider::new()]);
536
537        let expected = json!({
538            "title": {"text": "Title"},
539            "showlegend": false,
540            "legend": {},
541            "margin": {},
542            "autosize": true,
543            "width": 10,
544            "height": 20,
545            "font": {},
546            "uniformtext": {},
547            "separators": "_",
548            "paper_bgcolor": "#FFFFFF",
549            "plot_bgcolor": "#151515",
550            "colorscale": {},
551            "colorway": ["#123123"],
552            "coloraxis": {},
553            "modebar": {},
554            "hovermode": "closest",
555            "clickmode": "event+select",
556            "dragmode": "turntable",
557            "selectdirection": "d",
558            "hoverdistance": 321,
559            "spikedistance": 12,
560            "hoverlabel": {},
561            "grid": {},
562            "calendar": "jalali",
563            "xaxis": {},
564            "xaxis2": {},
565            "xaxis3": {},
566            "xaxis4": {},
567            "xaxis5": {},
568            "xaxis6": {},
569            "xaxis7": {},
570            "xaxis8": {},
571            "yaxis": {},
572            "yaxis2": {},
573            "yaxis3": {},
574            "yaxis4": {},
575            "yaxis5": {},
576            "yaxis6": {},
577            "yaxis7": {},
578            "yaxis8": {},
579            "zaxis": {},
580            "zaxis2": {},
581            "zaxis3": {},
582            "zaxis4": {},
583            "zaxis5": {},
584            "zaxis6": {},
585            "zaxis7": {},
586            "zaxis8": {},
587            "annotations": [{}],
588            "shapes": [{}],
589            "newshape": {},
590            "activeshape": {},
591            "boxmode": "group",
592            "boxgap": 1.0,
593            "boxgroupgap": 2.0,
594            "barmode": "overlay",
595            "barnorm": "",
596            "bargap": 3.0,
597            "bargroupgap": 4.0,
598            "violinmode": "overlay",
599            "violingap": 5.0,
600            "violingroupgap": 6.0,
601            "waterfallmode": "group",
602            "waterfallgap": 7.0,
603            "waterfallgroupgap": 8.0,
604            "piecolorway": ["#789789"],
605            "extendpiecolors": true,
606            "sunburstcolorway": ["#654654"],
607            "extendsunburstcolors": false,
608            "mapbox": {},
609            "updatemenus": [{}],
610            "sliders": [{}],
611        });
612
613        assert_eq!(to_value(layout_template).unwrap(), expected);
614    }
615
616    #[test]
617    fn serialize_template() {
618        let template = Template::new().layout(LayoutTemplate::new());
619        let expected = json!({"layout": {}});
620
621        assert_eq!(to_value(template).unwrap(), expected);
622    }
623
624    #[test]
625    fn serialize_layout() {
626        let layout = Layout::new()
627            .title("Title")
628            .title(String::from("Title"))
629            .title(Title::with_text("Title"))
630            .show_legend(false)
631            .legend(Legend::new())
632            .margin(Margin::new())
633            .auto_size(true)
634            .width(10)
635            .height(20)
636            .font(Font::new())
637            .uniform_text(UniformText::new())
638            .separators("_")
639            .paper_background_color("#FFFFFF")
640            .plot_background_color("#151515")
641            .color_scale(LayoutColorScale::new())
642            .colorway(vec!["#123123"])
643            .color_axis(ColorAxis::new())
644            .mode_bar(ModeBar::new())
645            .hover_mode(HoverMode::Closest)
646            .click_mode(ClickMode::EventAndSelect)
647            .drag_mode(DragMode::Turntable)
648            .select_direction(SelectDirection::Diagonal)
649            .hover_distance(321)
650            .spike_distance(12)
651            .hover_label(Label::new())
652            .template(Template::new())
653            .grid(LayoutGrid::new())
654            .calendar(Calendar::Jalali)
655            .x_axis(Axis::new())
656            .x_axis2(Axis::new())
657            .x_axis3(Axis::new())
658            .x_axis4(Axis::new())
659            .x_axis5(Axis::new())
660            .x_axis6(Axis::new())
661            .x_axis7(Axis::new())
662            .x_axis8(Axis::new())
663            .y_axis(Axis::new())
664            .y_axis2(Axis::new())
665            .y_axis3(Axis::new())
666            .y_axis4(Axis::new())
667            .y_axis5(Axis::new())
668            .y_axis6(Axis::new())
669            .y_axis7(Axis::new())
670            .y_axis8(Axis::new())
671            .annotations(vec![Annotation::new()])
672            .shapes(vec![Shape::new()])
673            .new_shape(NewShape::new())
674            .active_shape(ActiveShape::new())
675            .box_mode(BoxMode::Group)
676            .box_gap(1.)
677            .box_group_gap(2.)
678            .bar_mode(BarMode::Overlay)
679            .bar_norm(BarNorm::Empty)
680            .bar_gap(3.)
681            .bar_group_gap(4.)
682            .violin_mode(ViolinMode::Overlay)
683            .violin_gap(5.)
684            .violin_group_gap(6.)
685            .waterfall_mode(WaterfallMode::Group)
686            .waterfall_gap(7.)
687            .waterfall_group_gap(8.)
688            .pie_colorway(vec!["#789789"])
689            .extend_pie_colors(true)
690            .sunburst_colorway(vec!["#654654"])
691            .extend_sunburst_colors(false)
692            .z_axis(Axis::new())
693            .scene(LayoutScene::new());
694
695        let expected = json!({
696            "title": {"text": "Title"},
697            "showlegend": false,
698            "legend": {},
699            "margin": {},
700            "autosize": true,
701            "width": 10,
702            "height": 20,
703            "font": {},
704            "uniformtext": {},
705            "separators": "_",
706            "paper_bgcolor": "#FFFFFF",
707            "plot_bgcolor": "#151515",
708            "colorscale": {},
709            "colorway": ["#123123"],
710            "coloraxis": {},
711            "modebar": {},
712            "hovermode": "closest",
713            "clickmode": "event+select",
714            "dragmode": "turntable",
715            "selectdirection": "d",
716            "hoverdistance": 321,
717            "spikedistance": 12,
718            "hoverlabel": {},
719            "template": {},
720            "grid": {},
721            "calendar": "jalali",
722            "xaxis": {},
723            "xaxis2": {},
724            "xaxis3": {},
725            "xaxis4": {},
726            "xaxis5": {},
727            "xaxis6": {},
728            "xaxis7": {},
729            "xaxis8": {},
730            "yaxis": {},
731            "yaxis2": {},
732            "yaxis3": {},
733            "yaxis4": {},
734            "yaxis5": {},
735            "yaxis6": {},
736            "yaxis7": {},
737            "yaxis8": {},
738            "annotations": [{}],
739            "shapes": [{}],
740            "newshape": {},
741            "activeshape": {},
742            "boxmode": "group",
743            "boxgap": 1.0,
744            "boxgroupgap": 2.0,
745            "barmode": "overlay",
746            "barnorm": "",
747            "bargap": 3.0,
748            "bargroupgap": 4.0,
749            "violinmode": "overlay",
750            "violingap": 5.0,
751            "violingroupgap": 6.0,
752            "waterfallmode": "group",
753            "waterfallgap": 7.0,
754            "waterfallgroupgap": 8.0,
755            "piecolorway": ["#789789"],
756            "extendpiecolors": true,
757            "sunburstcolorway": ["#654654"],
758            "extendsunburstcolors": false,
759            "zaxis": {},
760            "scene": {}
761        });
762
763        assert_eq!(to_value(layout).unwrap(), expected);
764    }
765}