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
26pub 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#[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#[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 #[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 scene: Option<LayoutScene>,
323 geo: Option<LayoutGeo>,
324 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}