1use plotly_derive::FieldSetter;
2use serde::Serialize;
3
4use crate::color::Color;
5use crate::common::{
6 Anchor, AxisSide, Calendar, ColorBar, ColorScale, DashType, ExponentFormat, Font,
7 TickFormatStop, TickMode, Title,
8};
9use crate::layout::RangeBreak;
10use crate::private::{NumOrString, NumOrStringCollection};
11
12#[derive(Serialize, Clone, Debug, PartialEq)]
13pub struct AxisRange(pub Vec<Option<NumOrString>>);
14
15impl AxisRange {
16 pub fn new(min: impl Into<NumOrString>, max: impl Into<NumOrString>) -> Self {
18 Self(vec![Some(min.into()), Some(max.into())])
19 }
20
21 pub fn lower(min: impl Into<NumOrString>) -> Self {
24 Self(vec![Some(min.into()), None])
25 }
26
27 pub fn upper(max: impl Into<NumOrString>) -> Self {
30 Self(vec![None, Some(max.into())])
31 }
32}
33
34impl From<Vec<Option<NumOrString>>> for AxisRange {
35 fn from(values: Vec<Option<NumOrString>>) -> Self {
36 Self(values)
37 }
38}
39
40impl From<Vec<NumOrString>> for AxisRange {
41 fn from(values: Vec<NumOrString>) -> Self {
42 Self(values.into_iter().map(Some).collect())
43 }
44}
45
46impl From<Vec<f64>> for AxisRange {
47 fn from(values: Vec<f64>) -> Self {
48 Self(
49 values
50 .into_iter()
51 .map(|v| Some(NumOrString::F(v)))
52 .collect(),
53 )
54 }
55}
56
57impl From<Vec<i64>> for AxisRange {
58 fn from(values: Vec<i64>) -> Self {
59 Self(
60 values
61 .into_iter()
62 .map(|v| Some(NumOrString::I(v)))
63 .collect(),
64 )
65 }
66}
67
68impl From<Vec<String>> for AxisRange {
69 fn from(values: Vec<String>) -> Self {
70 Self(
71 values
72 .into_iter()
73 .map(|v| Some(NumOrString::S(v)))
74 .collect(),
75 )
76 }
77}
78
79impl From<Vec<&str>> for AxisRange {
80 fn from(values: Vec<&str>) -> Self {
81 Self(
82 values
83 .into_iter()
84 .map(|v| Some(NumOrString::S(v.to_string())))
85 .collect(),
86 )
87 }
88}
89
90impl From<Vec<Option<f64>>> for AxisRange {
91 fn from(values: Vec<Option<f64>>) -> Self {
92 Self(values.into_iter().map(|v| v.map(NumOrString::F)).collect())
93 }
94}
95
96impl From<Vec<Option<i64>>> for AxisRange {
97 fn from(values: Vec<Option<i64>>) -> Self {
98 Self(values.into_iter().map(|v| v.map(NumOrString::I)).collect())
99 }
100}
101
102impl From<Vec<Option<&str>>> for AxisRange {
103 fn from(values: Vec<Option<&str>>) -> Self {
104 Self(
105 values
106 .into_iter()
107 .map(|v| v.map(|s| NumOrString::S(s.to_string())))
108 .collect(),
109 )
110 }
111}
112
113#[derive(Serialize, Debug, Clone)]
114#[serde(rename_all = "lowercase")]
115pub enum SpikeSnap {
116 Data,
117 Cursor,
118 #[serde(rename = "hovered data")]
119 HoveredData,
120}
121
122#[derive(Serialize, Debug, Clone)]
123#[serde(rename_all = "lowercase")]
124pub enum SpikeMode {
125 ToAxis,
126 Across,
127 Marker,
128 #[serde(rename = "toaxis+across")]
129 ToaxisAcross,
130 #[serde(rename = "toaxis+marker")]
131 ToAxisMarker,
132 #[serde(rename = "across+marker")]
133 AcrossMarker,
134 #[serde(rename = "toaxis+across+marker")]
135 ToaxisAcrossMarker,
136}
137
138#[derive(Serialize, Debug, Clone)]
139#[serde(rename_all = "lowercase")]
140pub enum TicksDirection {
141 Outside,
142 Inside,
143}
144
145#[derive(Serialize, Debug, Clone)]
146#[serde(rename_all = "lowercase")]
147pub enum TicksPosition {
148 Labels,
149 Boundaries,
150}
151
152#[derive(Serialize, Debug, Clone)]
153#[serde(rename_all = "lowercase")]
154pub enum AxisType {
155 #[serde(rename = "-")]
156 Default,
157 Linear,
158 Log,
159 Date,
160 Category,
161 MultiCategory,
162}
163
164#[derive(Serialize, Debug, Clone)]
165#[serde(rename_all = "lowercase")]
166pub enum AxisConstrain {
167 Range,
168 Domain,
169}
170
171#[serde_with::skip_serializing_none]
172#[derive(Serialize, Debug, Clone, FieldSetter)]
173pub struct ColorAxis {
174 cauto: Option<bool>,
175 cmin: Option<f64>,
176 cmax: Option<f64>,
177 cmid: Option<f64>,
178 #[serde(rename = "colorscale")]
179 color_scale: Option<ColorScale>,
180 #[serde(rename = "autocolorscale")]
181 auto_color_scale: Option<bool>,
182 #[serde(rename = "reversescale")]
183 reverse_scale: Option<bool>,
184 #[serde(rename = "showscale")]
185 show_scale: Option<bool>,
186 #[serde(rename = "colorbar")]
187 color_bar: Option<ColorBar>,
188}
189
190impl ColorAxis {
191 pub fn new() -> Self {
192 Default::default()
193 }
194}
195
196#[derive(Serialize, Debug, Clone)]
197#[serde(rename_all = "lowercase")]
198pub enum RangeMode {
199 Normal,
200 ToZero,
201 NonNegative,
202}
203
204#[derive(Serialize, Debug, Clone)]
205#[serde(rename_all = "lowercase")]
206pub enum ConstrainDirection {
207 Left,
208 Center,
209 Right,
210 Top,
211 Middle,
212 Bottom,
213}
214
215#[derive(Serialize, Debug, Clone)]
216#[serde(rename_all = "lowercase")]
217pub enum ArrayShow {
218 All,
219 First,
220 Last,
221 None,
222}
223
224#[derive(Serialize, Debug, Clone)]
225pub enum CategoryOrder {
226 #[serde(rename = "trace")]
227 Trace,
228 #[serde(rename = "category ascending")]
229 CategoryAscending,
230 #[serde(rename = "category descending")]
231 CategoryDescending,
232 #[serde(rename = "array")]
233 Array,
234 #[serde(rename = "total ascending")]
235 TotalAscending,
236 #[serde(rename = "total descending")]
237 TotalDescending,
238 #[serde(rename = "min ascending")]
239 MinAscending,
240 #[serde(rename = "min descending")]
241 MinDescending,
242 #[serde(rename = "max ascending")]
243 MaxAscending,
244 #[serde(rename = "max descending")]
245 MaxDescending,
246 #[serde(rename = "sum ascending")]
247 SumAscending,
248 #[serde(rename = "sum descending")]
249 SumDescending,
250 #[serde(rename = "mean ascending")]
251 MeanAscending,
252 #[serde(rename = "mean descending")]
253 MeanDescending,
254 #[serde(rename = "geometric mean ascending")]
255 GeometricMeanAscending,
256 #[serde(rename = "geometric mean descending")]
257 GeometricMeanDescending,
258 #[serde(rename = "median ascending")]
259 MedianAscending,
260 #[serde(rename = "median descending")]
261 MedianDescending,
262}
263
264#[serde_with::skip_serializing_none]
265#[derive(Serialize, Debug, Clone, FieldSetter)]
266pub struct RangeSlider {
267 #[serde(rename = "bgcolor")]
268 background_color: Option<Box<dyn Color>>,
269 #[serde(rename = "bordercolor")]
270 border_color: Option<Box<dyn Color>>,
271 #[serde(rename = "borderwidth")]
272 border_width: Option<u64>,
273 #[serde(rename = "autorange")]
274 auto_range: Option<bool>,
275 range: Option<NumOrStringCollection>,
276 thickness: Option<f64>,
277 visible: Option<bool>,
278 #[serde(rename = "yaxis")]
279 y_axis: Option<RangeSliderYAxis>,
280}
281
282impl RangeSlider {
283 pub fn new() -> Self {
284 Default::default()
285 }
286}
287
288#[serde_with::skip_serializing_none]
289#[derive(Serialize, Debug, Clone, FieldSetter)]
290pub struct RangeSliderYAxis {
291 #[serde(rename = "rangemode")]
292 range_mode: Option<SliderRangeMode>,
293 range: Option<NumOrStringCollection>,
294}
295
296impl RangeSliderYAxis {
297 pub fn new() -> Self {
298 Default::default()
299 }
300}
301
302#[derive(Serialize, Debug, Clone)]
303#[serde(rename_all = "lowercase")]
304pub enum SliderRangeMode {
305 Auto,
306 Fixed,
307 Match,
308}
309
310#[serde_with::skip_serializing_none]
311#[derive(Serialize, Debug, Clone, FieldSetter)]
312pub struct RangeSelector {
313 visible: Option<bool>,
314 buttons: Option<Vec<SelectorButton>>,
315 x: Option<f64>,
316 #[serde(rename = "xanchor")]
317 x_anchor: Option<Anchor>,
318 y: Option<f64>,
319 #[serde(rename = "yanchor")]
320 y_anchor: Option<Anchor>,
321 font: Option<Font>,
322 #[serde(rename = "bgcolor")]
323 background_color: Option<Box<dyn Color>>,
324 #[serde(rename = "activecolor")]
325 active_color: Option<Box<dyn Color>>,
326 #[serde(rename = "bordercolor")]
327 border_color: Option<Box<dyn Color>>,
328 #[serde(rename = "borderwidth")]
329 border_width: Option<usize>,
330}
331
332impl RangeSelector {
333 pub fn new() -> Self {
334 Default::default()
335 }
336}
337
338#[serde_with::skip_serializing_none]
339#[derive(Serialize, Debug, Clone, FieldSetter)]
340pub struct SelectorButton {
341 visible: Option<bool>,
342 step: Option<SelectorStep>,
343 #[serde(rename = "stepmode")]
344 step_mode: Option<StepMode>,
345 count: Option<usize>,
346 label: Option<String>,
347 name: Option<String>,
348 #[serde(rename = "templateitemname")]
349 template_item_name: Option<String>,
350}
351
352impl SelectorButton {
353 pub fn new() -> Self {
354 Default::default()
355 }
356}
357
358#[derive(Serialize, Debug, Clone)]
359#[serde(rename_all = "lowercase")]
360pub enum SelectorStep {
361 Month,
362 Year,
363 Day,
364 Hour,
365 Minute,
366 Second,
367 All,
368}
369
370#[derive(Serialize, Debug, Clone)]
371#[serde(rename_all = "lowercase")]
372pub enum StepMode {
373 Backward,
374 ToDate,
375}
376
377#[serde_with::skip_serializing_none]
378#[derive(Serialize, Debug, Clone, FieldSetter)]
379pub struct Axis {
380 visible: Option<bool>,
381 #[serde(rename = "categoryarray")]
385 category_array: Option<NumOrStringCollection>,
386 #[serde(rename = "categoryorder")]
402 category_order: Option<CategoryOrder>,
403 color: Option<Box<dyn Color>>,
404 title: Option<Title>,
405 #[field_setter(skip)]
406 r#type: Option<AxisType>,
407 #[serde(rename = "autorange")]
408 auto_range: Option<bool>,
409 #[serde(rename = "rangebreaks")]
410 range_breaks: Option<Vec<RangeBreak>>,
411 #[serde(rename = "rangemode")]
412 range_mode: Option<RangeMode>,
413 #[field_setter(skip)]
415 range: Option<AxisRange>,
416 #[serde(rename = "fixedrange")]
417 fixed_range: Option<bool>,
418 constrain: Option<AxisConstrain>,
419 #[serde(rename = "constraintoward")]
420 constrain_toward: Option<ConstrainDirection>,
421 #[serde(rename = "tickmode")]
422 tick_mode: Option<TickMode>,
423 #[serde(rename = "nticks")]
424 n_ticks: Option<usize>,
425
426 #[serde(rename = "scaleanchor")]
427 scale_anchor: Option<String>,
428 #[serde(rename = "scaleratio")]
429 scale_ratio: Option<f64>,
430
431 tick0: Option<f64>,
432 dtick: Option<f64>,
433
434 #[field_setter(skip)]
435 matches: Option<String>,
436
437 #[serde(rename = "tickvals")]
438 tick_values: Option<Vec<f64>>,
439 #[serde(rename = "ticktext")]
440 tick_text: Option<Vec<String>>,
441 ticks: Option<TicksDirection>,
442 #[serde(rename = "tickson")]
443 ticks_on: Option<TicksPosition>,
444 mirror: Option<bool>,
445 #[serde(rename = "ticklen")]
446 tick_length: Option<usize>,
447 #[serde(rename = "tickwidth")]
448 tick_width: Option<usize>,
449 #[serde(rename = "tickcolor")]
450 tick_color: Option<Box<dyn Color>>,
451 #[serde(rename = "showticklabels")]
452 show_tick_labels: Option<bool>,
453 #[serde(rename = "automargin")]
454 auto_margin: Option<bool>,
455 #[serde(rename = "showspikes")]
456 show_spikes: Option<bool>,
457 #[serde(rename = "spikecolor")]
458 spike_color: Option<Box<dyn Color>>,
459 #[serde(rename = "spikethickness")]
460 spike_thickness: Option<usize>,
461 #[serde(rename = "spikedash")]
462 spike_dash: Option<DashType>,
463 #[serde(rename = "spikemode")]
464 spike_mode: Option<SpikeMode>,
465 #[serde(rename = "spikesnap")]
466 spike_snap: Option<SpikeSnap>,
467 #[serde(rename = "tickfont")]
468 tick_font: Option<Font>,
469 #[serde(rename = "tickangle")]
470 tick_angle: Option<f64>,
471 #[serde(rename = "tickprefix")]
472 tick_prefix: Option<String>,
473 #[serde(rename = "showtickprefix")]
474 show_tick_prefix: Option<ArrayShow>,
475 #[serde(rename = "ticksuffix")]
476 tick_suffix: Option<String>,
477 #[serde(rename = "showticksuffix")]
478 show_tick_suffix: Option<ArrayShow>,
479 #[serde(rename = "showexponent")]
480 show_exponent: Option<ArrayShow>,
481 #[serde(rename = "exponentformat")]
482 exponent_format: Option<ExponentFormat>,
483 #[serde(rename = "separatethousands")]
484 separate_thousands: Option<bool>,
485 #[serde(rename = "tickformat")]
486 tick_format: Option<String>,
487 #[serde(rename = "tickformatstops")]
488 tick_format_stops: Option<Vec<TickFormatStop>>,
489 #[serde(rename = "hoverformat")]
490 hover_format: Option<String>,
491 #[serde(rename = "showline")]
492 show_line: Option<bool>,
493 #[serde(rename = "linecolor")]
494 line_color: Option<Box<dyn Color>>,
495 #[serde(rename = "linewidth")]
496 line_width: Option<usize>,
497 #[serde(rename = "showgrid")]
498 show_grid: Option<bool>,
499 #[serde(rename = "gridcolor")]
500 grid_color: Option<Box<dyn Color>>,
501 #[serde(rename = "gridwidth")]
502 grid_width: Option<usize>,
503 #[serde(rename = "zeroline")]
504 zero_line: Option<bool>,
505 #[serde(rename = "zerolinecolor")]
506 zero_line_color: Option<Box<dyn Color>>,
507 #[serde(rename = "zerolinewidth")]
508 zero_line_width: Option<usize>,
509 #[serde(rename = "showdividers")]
510 show_dividers: Option<bool>,
511 #[serde(rename = "dividercolor")]
512 divider_color: Option<Box<dyn Color>>,
513 #[serde(rename = "dividerwidth")]
514 divider_width: Option<usize>,
515 anchor: Option<String>,
516 side: Option<AxisSide>,
517 overlaying: Option<String>,
518 #[field_setter(skip)]
519 domain: Option<Vec<f64>>,
520 position: Option<f64>,
521 #[serde(rename = "rangeslider")]
522 range_slider: Option<RangeSlider>,
523 #[serde(rename = "rangeselector")]
524 range_selector: Option<RangeSelector>,
525 calendar: Option<Calendar>,
526}
527
528impl Axis {
529 pub fn new() -> Self {
530 Default::default()
531 }
532
533 pub fn matches(mut self, matches: &str) -> Self {
534 self.matches = Some(matches.to_string());
535 self
536 }
537
538 pub fn type_(mut self, t: AxisType) -> Self {
539 self.r#type = Some(t);
540 self
541 }
542
543 pub fn domain(mut self, domain: &[f64]) -> Self {
544 self.domain = Some(domain.to_vec());
545 self
546 }
547
548 pub fn range(mut self, range: impl Into<AxisRange>) -> Self {
552 self.range = Some(range.into());
553 self
554 }
555}
556
557#[cfg(test)]
558mod tests {
559 use serde_json::{json, to_value};
560
561 use super::*;
562 use crate::common::ColorScalePalette;
563
564 #[test]
565 #[rustfmt::skip]
566 fn serialize_axis_type() {
567 assert_eq!(to_value(AxisType::Default).unwrap(), json!("-"));
568 assert_eq!(to_value(AxisType::Linear).unwrap(), json!("linear"));
569 assert_eq!(to_value(AxisType::Log).unwrap(), json!("log"));
570 assert_eq!(to_value(AxisType::Date).unwrap(), json!("date"));
571 assert_eq!(to_value(AxisType::Category).unwrap(), json!("category"));
572 assert_eq!(to_value(AxisType::MultiCategory).unwrap(), json!("multicategory"));
573 }
574
575 #[test]
576 #[rustfmt::skip]
577 fn serialize_range_mode() {
578 assert_eq!(to_value(RangeMode::Normal).unwrap(), json!("normal"));
579 assert_eq!(to_value(RangeMode::ToZero).unwrap(), json!("tozero"));
580 assert_eq!(to_value(RangeMode::NonNegative).unwrap(), json!("nonnegative"));
581 }
582
583 #[test]
584 fn serialize_ticks_direction() {
585 assert_eq!(to_value(TicksDirection::Outside).unwrap(), json!("outside"));
586 assert_eq!(to_value(TicksDirection::Inside).unwrap(), json!("inside"));
587 }
588
589 #[test]
590 #[rustfmt::skip]
591 fn serialize_ticks_position() {
592 assert_eq!(to_value(TicksPosition::Labels).unwrap(), json!("labels"));
593 assert_eq!(to_value(TicksPosition::Boundaries).unwrap(), json!("boundaries"));
594 }
595
596 #[test]
597 fn serialize_axis_constrain() {
598 assert_eq!(to_value(AxisConstrain::Range).unwrap(), json!("range"));
599 assert_eq!(to_value(AxisConstrain::Domain).unwrap(), json!("domain"));
600 }
601
602 #[test]
603 fn serialize_array_show() {
604 assert_eq!(to_value(ArrayShow::All).unwrap(), json!("all"));
605 assert_eq!(to_value(ArrayShow::First).unwrap(), json!("first"));
606 assert_eq!(to_value(ArrayShow::Last).unwrap(), json!("last"));
607 assert_eq!(to_value(ArrayShow::None).unwrap(), json!("none"));
608 }
609
610 #[test]
611 fn serialize_color_axis() {
612 let color_axis = ColorAxis::new()
613 .auto_color_scale(false)
614 .cauto(true)
615 .cmax(1.0)
616 .cmid(0.5)
617 .cmin(0.0)
618 .color_bar(ColorBar::new())
619 .color_scale(ColorScale::Palette(ColorScalePalette::Greens))
620 .reverse_scale(false)
621 .show_scale(true);
622
623 let expected = json!({
624 "autocolorscale": false,
625 "cauto": true,
626 "cmin": 0.0,
627 "cmid": 0.5,
628 "cmax": 1.0,
629 "colorbar": {},
630 "colorscale": "Greens",
631 "reversescale": false,
632 "showscale": true,
633 });
634
635 assert_eq!(to_value(color_axis).unwrap(), expected);
636 }
637
638 #[test]
639 #[rustfmt::skip]
640 fn serialize_constrain_direction() {
641 assert_eq!(to_value(ConstrainDirection::Left).unwrap(), json!("left"));
642 assert_eq!(to_value(ConstrainDirection::Center).unwrap(), json!("center"));
643 assert_eq!(to_value(ConstrainDirection::Right).unwrap(), json!("right"));
644 assert_eq!(to_value(ConstrainDirection::Top).unwrap(), json!("top"));
645 assert_eq!(to_value(ConstrainDirection::Middle).unwrap(), json!("middle"));
646 assert_eq!(to_value(ConstrainDirection::Bottom).unwrap(), json!("bottom"));
647 }
648
649 #[test]
650 #[rustfmt::skip]
651 fn serialize_category_order() {
652 assert_eq!(to_value(CategoryOrder::Trace).unwrap(), json!("trace"));
653 assert_eq!(to_value(CategoryOrder::CategoryAscending).unwrap(), json!("category ascending"));
654 assert_eq!(to_value(CategoryOrder::CategoryDescending).unwrap(), json!("category descending"));
655 assert_eq!(to_value(CategoryOrder::Array).unwrap(), json!("array"));
656 assert_eq!(to_value(CategoryOrder::TotalAscending).unwrap(), json!("total ascending"));
657 assert_eq!(to_value(CategoryOrder::TotalDescending).unwrap(), json!("total descending"));
658 assert_eq!(to_value(CategoryOrder::MinAscending).unwrap(), json!("min ascending"));
659 assert_eq!(to_value(CategoryOrder::MinDescending).unwrap(), json!("min descending"));
660 assert_eq!(to_value(CategoryOrder::MaxAscending).unwrap(), json!("max ascending"));
661 assert_eq!(to_value(CategoryOrder::MaxDescending).unwrap(), json!("max descending"));
662 assert_eq!(to_value(CategoryOrder::SumAscending).unwrap(), json!("sum ascending"));
663 assert_eq!(to_value(CategoryOrder::SumDescending).unwrap(), json!("sum descending"));
664 assert_eq!(to_value(CategoryOrder::MeanAscending).unwrap(), json!("mean ascending"));
665 assert_eq!(to_value(CategoryOrder::MeanDescending).unwrap(), json!("mean descending"));
666 assert_eq!(to_value(CategoryOrder::GeometricMeanAscending).unwrap(), json!("geometric mean ascending"));
667 assert_eq!(to_value(CategoryOrder::GeometricMeanDescending).unwrap(), json!("geometric mean descending"));
668 assert_eq!(to_value(CategoryOrder::MedianAscending).unwrap(), json!("median ascending"));
669 assert_eq!(to_value(CategoryOrder::MedianDescending).unwrap(), json!("median descending"));
670 }
671
672 #[test]
673 #[rustfmt::skip]
674 fn serialize_axis_side() {
675 assert_eq!(to_value(AxisSide::Left).unwrap(), json!("left"));
676 assert_eq!(to_value(AxisSide::Top).unwrap(), json!("top"));
677 assert_eq!(to_value(AxisSide::Right).unwrap(), json!("right"));
678 assert_eq!(to_value(AxisSide::Bottom).unwrap(), json!("bottom"));
679 }
680
681 #[test]
682 fn serialize_selector_step() {
683 assert_eq!(to_value(SelectorStep::Month).unwrap(), json!("month"));
684 assert_eq!(to_value(SelectorStep::Year).unwrap(), json!("year"));
685 assert_eq!(to_value(SelectorStep::Day).unwrap(), json!("day"));
686 assert_eq!(to_value(SelectorStep::Hour).unwrap(), json!("hour"));
687 assert_eq!(to_value(SelectorStep::Minute).unwrap(), json!("minute"));
688 assert_eq!(to_value(SelectorStep::Second).unwrap(), json!("second"));
689 assert_eq!(to_value(SelectorStep::All).unwrap(), json!("all"));
690 }
691
692 #[test]
693 fn serialize_step_mode() {
694 assert_eq!(to_value(StepMode::Backward).unwrap(), json!("backward"));
695 assert_eq!(to_value(StepMode::ToDate).unwrap(), json!("todate"));
696 }
697
698 #[test]
699 #[rustfmt::skip]
700 fn serialize_spike_mode() {
701 assert_eq!(to_value(SpikeMode::ToAxis).unwrap(), json!("toaxis"));
702 assert_eq!(to_value(SpikeMode::Across).unwrap(), json!("across"));
703 assert_eq!(to_value(SpikeMode::Marker).unwrap(), json!("marker"));
704 assert_eq!(to_value(SpikeMode::ToaxisAcross).unwrap(), json!("toaxis+across"));
705 assert_eq!(to_value(SpikeMode::ToAxisMarker).unwrap(), json!("toaxis+marker"));
706 assert_eq!(to_value(SpikeMode::AcrossMarker).unwrap(), json!("across+marker"));
707 assert_eq!(to_value(SpikeMode::ToaxisAcrossMarker).unwrap(), json!("toaxis+across+marker"));
708 }
709
710 #[test]
711 #[rustfmt::skip]
712 fn serialize_spike_snap() {
713 assert_eq!(to_value(SpikeSnap::Data).unwrap(), json!("data"));
714 assert_eq!(to_value(SpikeSnap::Cursor).unwrap(), json!("cursor"));
715 assert_eq!(to_value(SpikeSnap::HoveredData).unwrap(), json!("hovered data"));
716 }
717
718 #[test]
719 fn serialize_selector_button() {
720 let selector_button = SelectorButton::new()
721 .visible(false)
722 .step(SelectorStep::Hour)
723 .step_mode(StepMode::ToDate)
724 .count(42)
725 .label("label")
726 .name("name")
727 .template_item_name("something");
728
729 let expected = json!({
730 "visible": false,
731 "step": "hour",
732 "stepmode": "todate",
733 "count": 42,
734 "label": "label",
735 "name": "name",
736 "templateitemname": "something",
737 });
738
739 assert_eq!(to_value(selector_button).unwrap(), expected);
740 }
741
742 #[test]
743 fn serialize_range_selector() {
744 let range_selector = RangeSelector::new()
745 .visible(true)
746 .buttons(vec![SelectorButton::new()])
747 .x(2.0)
748 .x_anchor(Anchor::Middle)
749 .y(4.0)
750 .y_anchor(Anchor::Top)
751 .font(Font::new())
752 .background_color("#123ABC")
753 .border_color("#ABC123")
754 .border_width(1000)
755 .active_color("#888999");
756
757 let expected = json!({
758 "visible": true,
759 "buttons": [{}],
760 "x": 2.0,
761 "xanchor": "middle",
762 "y": 4.0,
763 "yanchor": "top",
764 "font": {},
765 "bgcolor": "#123ABC",
766 "bordercolor": "#ABC123",
767 "borderwidth": 1000,
768 "activecolor": "#888999",
769 });
770
771 assert_eq!(to_value(range_selector).unwrap(), expected);
772 }
773
774 #[test]
775 fn serialize_range_slider() {
776 let range_slider = RangeSlider::new()
777 .background_color("#123ABC")
778 .border_color("#ABC123")
779 .border_width(1000)
780 .auto_range(false)
781 .range(vec![5_i32])
782 .thickness(2000.)
783 .visible(true)
784 .y_axis(RangeSliderYAxis::new());
785
786 let expected = json!({
787 "bgcolor": "#123ABC",
788 "bordercolor": "#ABC123",
789 "borderwidth": 1000,
790 "autorange": false,
791 "range": [5],
792 "thickness": 2000.0,
793 "visible": true,
794 "yaxis": {}
795 });
796
797 assert_eq!(to_value(range_slider).unwrap(), expected);
798 }
799
800 #[test]
801 fn serialize_range_slider_y_axis() {
802 let range_slider_y_axis = RangeSliderYAxis::new()
803 .range_mode(SliderRangeMode::Match)
804 .range(vec![0.2]);
805 let expected = json!({
806 "rangemode": "match",
807 "range": [0.2]
808 });
809
810 assert_eq!(to_value(range_slider_y_axis).unwrap(), expected);
811 }
812
813 #[test]
814 fn serialize_slider_range_mode() {
815 assert_eq!(to_value(SliderRangeMode::Auto).unwrap(), json!("auto"));
816 assert_eq!(to_value(SliderRangeMode::Fixed).unwrap(), json!("fixed"));
817 assert_eq!(to_value(SliderRangeMode::Match).unwrap(), json!("match"));
818 }
819
820 #[test]
821 fn serialize_axis() {
822 let axis = Axis::new()
823 .visible(false)
824 .color("#678123")
825 .title(Title::with_text("title"))
826 .type_(AxisType::Date)
827 .auto_range(false)
828 .range_mode(RangeMode::NonNegative)
829 .range(vec![2.0])
830 .fixed_range(true)
831 .constrain(AxisConstrain::Range)
832 .constrain_toward(ConstrainDirection::Middle)
833 .tick_mode(TickMode::Auto)
834 .n_ticks(600)
835 .tick0(5.0)
836 .dtick(10.0)
837 .matches("x")
838 .tick_values(vec![1.0, 2.0])
839 .tick_text(vec!["one".to_string(), "two".to_string()])
840 .ticks(TicksDirection::Inside)
841 .ticks_on(TicksPosition::Boundaries)
842 .mirror(false)
843 .tick_length(77)
844 .tick_width(99)
845 .tick_color("#101010")
846 .show_tick_labels(false)
847 .auto_margin(true)
848 .show_spikes(false)
849 .spike_color("#ABABAB")
850 .spike_thickness(501)
851 .spike_dash(DashType::DashDot)
852 .spike_mode(SpikeMode::AcrossMarker)
853 .spike_snap(SpikeSnap::Data)
854 .tick_font(Font::new())
855 .tick_angle(2.1)
856 .tick_prefix("prefix")
857 .show_tick_prefix(ArrayShow::Last)
858 .tick_suffix("suffix")
859 .show_tick_suffix(ArrayShow::None)
860 .show_exponent(ArrayShow::All)
861 .exponent_format(ExponentFormat::SmallE)
862 .separate_thousands(false)
863 .tick_format("tickfmt")
864 .tick_format_stops(vec![TickFormatStop::new()])
865 .hover_format("hoverfmt")
866 .show_line(true)
867 .line_color("#CCCDDD")
868 .line_width(9)
869 .show_grid(false)
870 .grid_color("#fff000")
871 .grid_width(8)
872 .zero_line(true)
873 .zero_line_color("#f0f0f0")
874 .zero_line_width(7)
875 .show_dividers(false)
876 .divider_color("#AFAFAF")
877 .divider_width(55)
878 .anchor("anchor")
879 .side(AxisSide::Right)
880 .overlaying("overlaying")
881 .domain(&[0.0, 1.0])
882 .position(0.6)
883 .range_slider(RangeSlider::new())
884 .range_selector(RangeSelector::new())
885 .calendar(Calendar::Coptic)
886 .category_order(CategoryOrder::Array)
887 .category_array(vec!["Category0", "Category1"]);
888
889 let expected = json!({
890 "visible": false,
891 "color": "#678123",
892 "title": {"text": "title"},
893 "type": "date",
894 "autorange": false,
895 "rangemode": "nonnegative",
896 "range": [2.0],
897 "fixedrange": true,
898 "constrain": "range",
899 "constraintoward": "middle",
900 "tickmode": "auto",
901 "nticks": 600,
902 "tick0": 5.0,
903 "dtick": 10.0,
904 "matches": "x",
905 "tickvals": [1.0, 2.0],
906 "ticktext": ["one", "two"],
907 "ticks": "inside",
908 "tickson": "boundaries",
909 "mirror": false,
910 "ticklen": 77,
911 "tickwidth": 99,
912 "tickcolor": "#101010",
913 "showticklabels": false,
914 "automargin": true,
915 "showspikes": false,
916 "spikecolor": "#ABABAB",
917 "spikethickness": 501,
918 "spikedash": "dashdot",
919 "spikemode": "across+marker",
920 "spikesnap": "data",
921 "tickfont": {},
922 "tickangle": 2.1,
923 "tickprefix": "prefix",
924 "showtickprefix": "last",
925 "ticksuffix": "suffix",
926 "showticksuffix": "none",
927 "showexponent": "all",
928 "exponentformat": "e",
929 "separatethousands": false,
930 "tickformat": "tickfmt",
931 "tickformatstops": [{"enabled": true}],
932 "hoverformat": "hoverfmt",
933 "showline": true,
934 "linecolor": "#CCCDDD",
935 "linewidth": 9,
936 "showgrid": false,
937 "gridcolor": "#fff000",
938 "gridwidth": 8,
939 "zeroline": true,
940 "zerolinecolor": "#f0f0f0",
941 "zerolinewidth": 7,
942 "showdividers": false,
943 "dividercolor": "#AFAFAF",
944 "dividerwidth": 55,
945 "anchor": "anchor",
946 "side": "right",
947 "overlaying": "overlaying",
948 "domain": [0.0, 1.0],
949 "position": 0.6,
950 "rangeslider": {},
951 "rangeselector": {},
952 "calendar": "coptic",
953 "categoryorder": "array",
954 "categoryarray": ["Category0", "Category1"]
955 });
956
957 assert_eq!(to_value(axis).unwrap(), expected);
958 }
959
960 #[test]
961 fn serialize_axis_range_lower_only() {
962 let axis = Axis::new().range(AxisRange::lower(5.0));
963 let expected = json!({ "range": [5.0, null] });
964 assert_eq!(to_value(axis).unwrap(), expected);
965
966 let axis = Axis::new().range(vec![Some(5.0), None]);
967 let expected = json!({ "range": [5.0, null] });
968 assert_eq!(to_value(axis).unwrap(), expected);
969 }
970
971 #[test]
972 fn serialize_axis_range_upper_only() {
973 let axis = Axis::new().range(AxisRange::upper(10.0));
974 let expected = json!({ "range": [null, 10.0] });
975 assert_eq!(to_value(axis).unwrap(), expected);
976
977 let axis = Axis::new().range(vec![None, Some(10.0)]);
978 let expected = json!({ "range": [null, 10.0] });
979 assert_eq!(to_value(axis).unwrap(), expected);
980 }
981
982 #[test]
983 fn serialize_axis_range_both() {
984 let axis = Axis::new().range(AxisRange::new(1.0, 5.0));
985 let expected = json!({ "range": [1.0, 5.0] });
986 assert_eq!(to_value(axis).unwrap(), expected);
987
988 let axis = Axis::new().range(vec![Some(1.0), Some(5.0)]);
989 let expected = json!({ "range": [1.0, 5.0] });
990 assert_eq!(to_value(axis).unwrap(), expected);
991
992 let axis = Axis::new().range(vec![1.0, 5.0]);
994 let expected = json!({ "range": [1.0, 5.0] });
995 assert_eq!(to_value(axis).unwrap(), expected);
996 }
997
998 #[test]
999 fn serialize_axis_range_with_strings() {
1000 let axis = Axis::new().range(AxisRange::lower("2020-01-01"));
1001 let expected = json!({ "range": ["2020-01-01", null] });
1002 assert_eq!(to_value(axis).unwrap(), expected);
1003
1004 let axis = Axis::new().range(vec![Some("2020-01-01"), None]);
1005 let expected = json!({ "range": ["2020-01-01", null] });
1006 assert_eq!(to_value(axis).unwrap(), expected);
1007
1008 let axis = Axis::new().range(vec!["2020-01-01", "2020-01-02"]);
1010 let expected = json!({ "range": ["2020-01-01", "2020-01-02"] });
1011 assert_eq!(to_value(axis).unwrap(), expected);
1012 }
1013}