1use 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#[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}