plotly/layout/
axis.rs

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    /// Create a new axis range with both upper and lower values (min, max)
17    pub fn new(min: impl Into<NumOrString>, max: impl Into<NumOrString>) -> Self {
18        Self(vec![Some(min.into()), Some(max.into())])
19    }
20
21    /// Create a range with only a lower bound, the upper bound is not set:
22    /// (min, None)
23    pub fn lower(min: impl Into<NumOrString>) -> Self {
24        Self(vec![Some(min.into()), None])
25    }
26
27    /// Create a range with only an upper bound, the lower bound is not set:
28    /// (None, max)
29    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    /// Sets the order in which categories on this axis appear. Only has an
382    /// effect if `category_order` is set to [`CategoryOrder::Array`].
383    /// Used with `category_order`.
384    #[serde(rename = "categoryarray")]
385    category_array: Option<NumOrStringCollection>,
386    /// Specifies the ordering logic for the case of categorical variables.
387    /// By default, plotly uses [`CategoryOrder::Trace`], which specifies
388    /// the order that is present in the data supplied. Set `category_order` to
389    /// [`CategoryOrder::CategoryAscending`] or
390    /// [`CategoryOrder::CategoryDescending`] if order should be determined
391    /// by the alphanumerical order of the category names. Set `category_order`
392    /// to [`CategoryOrder::Array`] to derive the ordering from the attribute
393    /// `category_array`. If a category is not found in the `category_array`
394    /// array, the sorting behavior for that attribute will be identical to the
395    /// [`CategoryOrder::Trace`] mode. The unspecified categories will follow
396    /// the categories in `category_array`. Set `category_order` to
397    /// [`CategoryOrder::TotalAscending`] or
398    /// [`CategoryOrder::TotalDescending`] if order should be determined by the
399    /// numerical order of the values. Similarly, the order can be determined
400    /// by the min, max, sum, mean, geometric mean or median of all the values.
401    #[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    /// Set the range of the axis.
414    #[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    /// Set the range of the axis. This method accepts various types and
549    /// converts them to `AxisRange` while also keeping backward compatibility
550    /// with API prior to AxisRange introduction.
551    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        // Backward compatible range() setter version with both ends
993        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        // Backward compatible range() setter version with both ends
1009        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}