plotly_fork/traces/
box_plot.rs

1//! Box trace
2
3use plotly_derive::FieldSetter;
4use serde::{Serialize, Serializer};
5
6use crate::{
7    color::Color,
8    common::{
9        Calendar, Dim, HoverInfo, Label, LegendGroupTitle, Line, Marker, Orientation, PlotType,
10        Visible,
11    },
12    Trace,
13};
14
15#[derive(Debug, Clone)]
16pub enum BoxMean {
17    True,
18    False,
19    StandardDeviation,
20}
21
22impl Serialize for BoxMean {
23    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
24    where
25        S: Serializer,
26    {
27        match *self {
28            Self::True => serializer.serialize_bool(true),
29            Self::False => serializer.serialize_bool(false),
30            Self::StandardDeviation => serializer.serialize_str("sd"),
31        }
32    }
33}
34
35#[derive(Debug, Clone)]
36pub enum BoxPoints {
37    All,
38    Outliers,
39    SuspectedOutliers,
40    False,
41}
42
43impl Serialize for BoxPoints {
44    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45    where
46        S: Serializer,
47    {
48        match *self {
49            Self::All => serializer.serialize_str("all"),
50            Self::Outliers => serializer.serialize_str("outliers"),
51            Self::SuspectedOutliers => serializer.serialize_str("suspectedoutliers"),
52            Self::False => serializer.serialize_bool(false),
53        }
54    }
55}
56
57#[derive(Serialize, Debug, Clone)]
58#[serde(rename_all = "lowercase")]
59pub enum QuartileMethod {
60    Linear,
61    Exclusive,
62    Inclusive,
63}
64
65#[derive(Serialize, Clone, Debug)]
66#[serde(rename_all = "lowercase")]
67pub enum HoverOn {
68    Boxes,
69    Points,
70    #[serde(rename = "boxes+points")]
71    BoxesAndPoints,
72}
73
74/// Construct a box trace.
75///
76/// # Examples
77///
78/// ```
79/// use plotly::{BoxPlot, box_plot::BoxPoints};
80///
81/// let trace = BoxPlot::new(vec![0, 1, 2, 3, 4, 5])
82///     .box_points(BoxPoints::All)
83///     .jitter(0.3)
84///     .point_pos(-1.8);
85///
86/// let expected = serde_json::json!({
87///     "type": "box",
88///     "y": [0, 1, 2, 3, 4, 5],
89///     "boxpoints": "all",
90///     "jitter": 0.3,
91///     "pointpos": -1.8
92/// });
93///
94/// assert_eq!(serde_json::to_value(trace).unwrap(), expected);
95/// ```
96#[serde_with::skip_serializing_none]
97#[derive(Serialize, Debug, Clone, FieldSetter)]
98#[field_setter(box_self, kind = "trace")]
99pub struct BoxPlot<X, Y>
100where
101    X: Serialize + Clone,
102    Y: Serialize + Clone,
103{
104    #[field_setter(default = "PlotType::Box")]
105    r#type: PlotType,
106    x: Option<Vec<X>>,
107    y: Option<Vec<Y>>,
108    name: Option<String>,
109    visible: Option<Visible>,
110    #[serde(rename = "showlegend")]
111    show_legend: Option<bool>,
112    #[serde(rename = "legendgroup")]
113    legend_group: Option<String>,
114    #[serde(rename = "legendgrouptitle")]
115    legend_group_title: Option<LegendGroupTitle>,
116    opacity: Option<f64>,
117    ids: Option<Vec<String>>,
118    width: Option<usize>,
119    text: Option<Dim<String>>,
120    #[serde(rename = "hovertext")]
121    hover_text: Option<Dim<String>>,
122    #[serde(rename = "hoverinfo")]
123    hover_info: Option<HoverInfo>,
124    #[serde(rename = "hovertemplate")]
125    hover_template: Option<Dim<String>>,
126    #[serde(rename = "xaxis")]
127    x_axis: Option<String>,
128    #[serde(rename = "yaxis")]
129    y_axis: Option<String>,
130    orientation: Option<Orientation>,
131    #[serde(rename = "alignmentgroup")]
132    alignment_group: Option<String>,
133    #[serde(rename = "offsetgroup")]
134    offset_group: Option<String>,
135    marker: Option<Marker>,
136    line: Option<Line>,
137    #[serde(rename = "boxmean")]
138    box_mean: Option<BoxMean>,
139    #[serde(rename = "boxpoints")]
140    box_points: Option<BoxPoints>,
141    notched: Option<bool>,
142    #[serde(rename = "notchwidth")]
143    notch_width: Option<f64>,
144    #[serde(rename = "whiskerwidth")]
145    whisker_width: Option<f64>,
146    q1: Option<Vec<f64>>,
147    median: Option<Vec<f64>>,
148    q3: Option<Vec<f64>>,
149    #[serde(rename = "upperfence")]
150    upper_fence: Option<Vec<f64>>,
151    #[serde(rename = "lowerfence")]
152    lower_fence: Option<Vec<f64>>,
153    #[serde(rename = "notchspan")]
154    notch_span: Option<Vec<f64>>,
155    mean: Option<Vec<f64>>,
156    #[serde(rename = "sd")]
157    standard_deviation: Option<Vec<f64>>,
158    #[serde(rename = "quartilemethod")]
159    quartile_method: Option<QuartileMethod>,
160    #[serde(rename = "fillcolor")]
161    fill_color: Option<Box<dyn Color>>,
162    #[serde(rename = "hoverlabel")]
163    hover_label: Option<Label>,
164    #[serde(rename = "hoveron")]
165    hover_on: Option<HoverOn>,
166    #[serde(rename = "pointpos")]
167    point_pos: Option<f64>,
168    jitter: Option<f64>,
169    #[serde(rename = "xcalendar")]
170    x_calendar: Option<Calendar>,
171    #[serde(rename = "ycalendar")]
172    y_calendar: Option<Calendar>,
173}
174
175impl<Y> BoxPlot<f64, Y>
176where
177    Y: Serialize + Clone,
178{
179    pub fn new(y: Vec<Y>) -> Box<BoxPlot<f64, Y>> {
180        Box::new(BoxPlot {
181            y: Some(y),
182            ..Default::default()
183        })
184    }
185}
186
187impl<X, Y> BoxPlot<X, Y>
188where
189    X: Serialize + Clone,
190    Y: Serialize + Clone,
191{
192    pub fn new_xy(x: Vec<X>, y: Vec<Y>) -> Box<Self> {
193        Box::new(BoxPlot {
194            x: Some(x),
195            y: Some(y),
196            ..Default::default()
197        })
198    }
199}
200
201impl<X, Y> Trace for BoxPlot<X, Y>
202where
203    X: Serialize + Clone,
204    Y: Serialize + Clone,
205{
206    fn to_json(&self) -> String {
207        serde_json::to_string(self).unwrap()
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use serde_json::{json, to_value};
214
215    use super::*;
216
217    #[test]
218    fn test_serialize_box_mean() {
219        assert_eq!(to_value(BoxMean::True).unwrap(), json!(true));
220        assert_eq!(to_value(BoxMean::False).unwrap(), json!(false));
221        assert_eq!(to_value(BoxMean::StandardDeviation).unwrap(), json!("sd"));
222    }
223
224    #[test]
225    #[rustfmt::skip]
226    fn test_serialize_box_points() {
227        assert_eq!(to_value(BoxPoints::All).unwrap(), json!("all"));
228        assert_eq!(to_value(BoxPoints::Outliers).unwrap(), json!("outliers"));
229        assert_eq!(to_value(BoxPoints::SuspectedOutliers).unwrap(), json!("suspectedoutliers"));
230        assert_eq!(to_value(BoxPoints::False).unwrap(), json!(false));
231    }
232
233    #[test]
234    #[rustfmt::skip]
235    fn test_serialize_quartile_method() {
236        assert_eq!(to_value(QuartileMethod::Linear).unwrap(), json!("linear"));
237        assert_eq!(to_value(QuartileMethod::Exclusive).unwrap(), json!("exclusive"));
238        assert_eq!(to_value(QuartileMethod::Inclusive).unwrap(), json!("inclusive"));
239    }
240
241    #[test]
242    #[rustfmt::skip]
243    fn test_serialize_hover_on() {
244        assert_eq!(to_value(HoverOn::Boxes).unwrap(), json!("boxes"));
245        assert_eq!(to_value(HoverOn::Points).unwrap(), json!("points"));
246        assert_eq!(to_value(HoverOn::BoxesAndPoints).unwrap(), json!("boxes+points"));
247    }
248
249    #[test]
250    fn test_default_box_plot() {
251        let trace: BoxPlot<i32, i32> = BoxPlot::default();
252        let expected = json!({"type": "box"}).to_string();
253
254        assert_eq!(trace.to_json(), expected);
255    }
256
257    #[test]
258    fn test_box_plot_new() {
259        let trace = BoxPlot::new(vec![0.0, 0.1]);
260        let expected = json!({
261            "type": "box",
262            "y": [0.0, 0.1]
263        });
264
265        assert_eq!(to_value(trace).unwrap(), expected);
266    }
267
268    #[test]
269    fn test_serialize_box_plot() {
270        let trace = BoxPlot::new_xy(vec![1, 2, 3], vec![4, 5, 6])
271            .alignment_group("alignment_group")
272            .box_mean(BoxMean::StandardDeviation)
273            .box_points(BoxPoints::All)
274            .fill_color("#522622")
275            .hover_info(HoverInfo::Name)
276            .hover_label(Label::new())
277            .hover_on(HoverOn::BoxesAndPoints)
278            .hover_template("templ2")
279            .hover_template_array(vec!["templ1", "templ2"])
280            .hover_text("ok")
281            .hover_text_array(vec!["okey", "dokey"])
282            .ids(vec!["1", "2"])
283            .jitter(0.5)
284            .line(Line::new())
285            .legend_group("one")
286            .legend_group_title(LegendGroupTitle::new("Legend Group Title"))
287            .lower_fence(vec![0., 1.])
288            .marker(Marker::new())
289            .mean(vec![12., 13.])
290            .median(vec![4., 5.])
291            .name("box")
292            .notch_span(vec![10., 11.])
293            .notch_width(0.1)
294            .notched(true)
295            .offset_group("offset_group")
296            .opacity(0.6)
297            .orientation(Orientation::Horizontal)
298            .point_pos(-1.)
299            .q1(vec![2., 3.])
300            .q3(vec![6., 7.])
301            .quartile_method(QuartileMethod::Exclusive)
302            .show_legend(false)
303            .standard_deviation(vec![14., 15.])
304            .text("hi")
305            .text_array(vec!["hi", "there"])
306            .upper_fence(vec![8., 9.])
307            .visible(Visible::LegendOnly)
308            .whisker_width(0.2)
309            .width(50)
310            .x_axis("xaxis")
311            .x_calendar(Calendar::Chinese)
312            .y_axis("yaxis")
313            .y_calendar(Calendar::Coptic);
314
315        let expected = json!({
316            "type": "box",
317            "alignmentgroup": "alignment_group",
318            "boxmean": "sd",
319            "boxpoints": "all",
320            "fillcolor": "#522622",
321            "ids": ["1", "2"],
322            "hoverinfo": "name",
323            "hoverlabel": {},
324            "hoveron": "boxes+points",
325            "hovertemplate": ["templ1", "templ2"],
326            "hovertext": ["okey", "dokey"],
327            "jitter": 0.5,
328            "legendgroup": "one",
329            "legendgrouptitle": {"text": "Legend Group Title"},
330            "line": {},
331            "lowerfence": [0.0, 1.0],
332            "marker": {},
333            "mean": [12.0, 13.0],
334            "median": [4.0, 5.0],
335            "name": "box",
336            "notchspan": [10.0, 11.0],
337            "notched": true,
338            "notchwidth": 0.1,
339            "offsetgroup": "offset_group",
340            "opacity": 0.6,
341            "orientation": "h",
342            "pointpos": -1.0,
343            "q1": [2.0, 3.0],
344            "q3": [6.0, 7.0],
345            "quartilemethod": "exclusive",
346            "sd": [14.0, 15.0],
347            "showlegend": false,
348            "text": ["hi", "there"],
349            "upperfence": [8.0, 9.0],
350            "visible": "legendonly",
351            "whiskerwidth": 0.2,
352            "width": 50,
353            "x": [1, 2, 3],
354            "xaxis": "xaxis",
355            "xcalendar": "chinese",
356            "y": [4, 5, 6],
357            "yaxis": "yaxis",
358            "ycalendar": "coptic"
359        });
360
361        assert_eq!(to_value(trace).unwrap(), expected);
362    }
363}