1#[cfg(feature = "plotly_ndarray")]
4use ndarray::{Array, Ix1, Ix2};
5use plotly_derive::FieldSetter;
6use serde::Serialize;
7
8#[cfg(feature = "plotly_ndarray")]
9use crate::ndarray::ArrayTraces;
10use crate::{
11 common::{
12 Calendar, Dim, ErrorData, HoverInfo, Label, LegendGroupTitle, Marker, Orientation,
13 PlotType, Visible,
14 },
15 Trace,
16};
17
18#[derive(Serialize, Clone, Debug)]
19pub struct Bins {
20 start: f64,
21 end: f64,
22 size: f64,
23}
24
25impl Bins {
26 pub fn new(start: f64, end: f64, size: f64) -> Self {
27 Self { start, end, size }
28 }
29}
30
31#[serde_with::skip_serializing_none]
32#[derive(Serialize, Clone, Debug, FieldSetter)]
33pub struct Cumulative {
34 enabled: Option<bool>,
35 direction: Option<HistDirection>,
36 #[serde(rename = "currentbin")]
37 current_bin: Option<CurrentBin>,
38}
39
40impl Cumulative {
41 pub fn new() -> Self {
42 Default::default()
43 }
44}
45
46#[derive(Serialize, Clone, Debug)]
47#[serde(rename_all = "lowercase")]
48pub enum CurrentBin {
49 Include,
50 Exclude,
51 Half,
52}
53
54#[derive(Serialize, Clone, Debug)]
55#[serde(rename_all = "lowercase")]
56pub enum HistDirection {
57 Increasing,
58 Decreasing,
59}
60
61#[derive(Serialize, Clone, Debug)]
62#[serde(rename_all = "lowercase")]
63pub enum HistFunc {
64 Count,
65 Sum,
66 #[serde(rename = "avg")]
67 Average,
68 #[serde(rename = "min")]
69 Minimum,
70 #[serde(rename = "max")]
71 Maximum,
72}
73
74#[derive(Serialize, Clone, Debug)]
75#[serde(rename_all = "lowercase")]
76pub enum HistNorm {
77 #[serde(rename = "")]
78 Default,
79 Percent,
80 Probability,
81 Density,
82 #[serde(rename = "probability density")]
83 ProbabilityDensity,
84}
85
86#[serde_with::skip_serializing_none]
107#[derive(Serialize, Clone, Debug, FieldSetter)]
108#[field_setter(box_self, kind = "trace")]
109pub struct Histogram<H>
110where
111 H: Serialize + Clone,
112{
113 #[field_setter(default = "PlotType::Histogram")]
114 r#type: PlotType,
115 #[serde(rename = "alignmentgroup")]
116 alignment_group: Option<String>,
117 #[serde(rename = "autobinx")]
118 auto_bin_x: Option<bool>,
119 #[serde(rename = "autobiny")]
120 auto_bin_y: Option<bool>,
121 cumulative: Option<Cumulative>,
122 #[serde(rename = "bingroup")]
123 bin_group: Option<String>,
124 error_x: Option<ErrorData>,
125 error_y: Option<ErrorData>,
126 #[serde(rename = "histfunc")]
127 hist_func: Option<HistFunc>,
128 #[serde(rename = "histnorm")]
129 hist_norm: Option<HistNorm>,
130 #[serde(rename = "hoverinfo")]
131 hover_info: Option<HoverInfo>,
132 #[serde(rename = "hoverlabel")]
133 hover_label: Option<Label>,
134 #[serde(rename = "hovertemplate")]
135 hover_template: Option<Dim<String>>,
136 #[serde(rename = "hovertext")]
137 hover_text: Option<Dim<String>>,
138 #[serde(rename = "legendgroup")]
139 legend_group: Option<String>,
140 #[serde(rename = "legendgrouptitle")]
141 legend_group_title: Option<LegendGroupTitle>,
142 marker: Option<Marker>,
143 #[serde(rename = "nbinsx")]
144 n_bins_x: Option<usize>,
145 #[serde(rename = "nbinsy")]
146 n_bins_y: Option<usize>,
147 name: Option<String>,
148 #[serde(rename = "offsetgroup")]
149 offset_group: Option<String>,
150 opacity: Option<f64>,
151 orientation: Option<Orientation>,
152 #[serde(rename = "showlegend")]
153 show_legend: Option<bool>,
154 text: Option<Dim<String>>,
155 visible: Option<Visible>,
156 x: Option<Vec<H>>,
157 #[serde(rename = "xaxis")]
158 x_axis: Option<String>,
159 #[serde(rename = "xbins")]
160 x_bins: Option<Bins>,
161 #[serde(rename = "xcalendar")]
162 x_calendar: Option<Calendar>,
163 y: Option<Vec<H>>,
164 #[serde(rename = "yaxis")]
165 y_axis: Option<String>,
166 #[serde(rename = "ybins")]
167 y_bins: Option<Bins>,
168 #[serde(rename = "ycalendar")]
169 y_calendar: Option<Calendar>,
170}
171
172impl<H> Histogram<H>
173where
174 H: Serialize + Clone + 'static,
175{
176 pub fn new(x: Vec<H>) -> Box<Self> {
177 Box::new(Self {
178 x: Some(x),
179 ..Default::default()
180 })
181 }
182
183 pub fn new_xy(x: Vec<H>, y: Vec<H>) -> Box<Self> {
184 Box::new(Self {
185 x: Some(x),
186 y: Some(y),
187 ..Default::default()
188 })
189 }
190
191 pub fn new_vertical(y: Vec<H>) -> Box<Self> {
192 Box::new(Self {
193 y: Some(y),
194 ..Default::default()
195 })
196 }
197
198 #[cfg(feature = "plotly_ndarray")]
258 pub fn to_traces(
259 &self,
260 traces_matrix: Array<H, Ix2>,
261 array_traces: ArrayTraces,
262 ) -> Vec<Box<dyn Trace>> {
263 let mut traces: Vec<Box<dyn Trace>> = Vec::new();
264 let mut trace_vectors = crate::private::trace_vectors_from(traces_matrix, array_traces);
265 trace_vectors.reverse();
266 while !trace_vectors.is_empty() {
267 let mut sc = Box::new(self.clone());
268 let data = trace_vectors.pop();
269 if let Some(d) = data {
270 sc.x = Some(d);
271 traces.push(sc);
272 }
273 }
274
275 traces
276 }
277
278 #[cfg(feature = "plotly_ndarray")]
279 pub fn from_array(x: Array<H, Ix1>) -> Box<Self> {
280 Box::new(Histogram {
281 x: Some(x.to_vec()),
282 ..Default::default()
283 })
284 }
285}
286
287impl<H> Trace for Histogram<H>
288where
289 H: Serialize + Clone,
290{
291 fn to_json(&self) -> String {
292 serde_json::to_string(self).unwrap()
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use serde_json::{json, to_value};
299
300 use super::*;
301 use crate::common::ErrorType;
302
303 #[test]
304 fn test_serialize_bins() {
305 let bins = Bins::new(0.0, 10.0, 5.0);
306 let expected = json!({
307 "start": 0.0,
308 "end": 10.0,
309 "size": 5.0
310 });
311
312 assert_eq!(to_value(bins).unwrap(), expected);
313 }
314
315 #[test]
316 fn test_serialize_cumulative() {
317 let cumulative = Cumulative::new()
318 .enabled(true)
319 .direction(HistDirection::Decreasing)
320 .current_bin(CurrentBin::Exclude);
321
322 let expected = json!({
323 "enabled": true,
324 "direction": "decreasing",
325 "currentbin": "exclude"
326 });
327
328 assert_eq!(to_value(cumulative).unwrap(), expected);
329 }
330
331 #[test]
332 fn test_serialize_current_bin() {
333 assert_eq!(to_value(CurrentBin::Include).unwrap(), json!("include"));
334 assert_eq!(to_value(CurrentBin::Exclude).unwrap(), json!("exclude"));
335 assert_eq!(to_value(CurrentBin::Half).unwrap(), json!("half"));
336 }
337
338 #[test]
339 #[rustfmt::skip]
340 fn test_serialize_hist_direction() {
341 assert_eq!(to_value(HistDirection::Increasing).unwrap(), json!("increasing"));
342 assert_eq!(to_value(HistDirection::Decreasing).unwrap(), json!("decreasing"));
343 }
344
345 #[test]
346 fn test_serialize_hist_func() {
347 assert_eq!(to_value(HistFunc::Count).unwrap(), json!("count"));
348 assert_eq!(to_value(HistFunc::Sum).unwrap(), json!("sum"));
349 assert_eq!(to_value(HistFunc::Average).unwrap(), json!("avg"));
350 assert_eq!(to_value(HistFunc::Minimum).unwrap(), json!("min"));
351 assert_eq!(to_value(HistFunc::Maximum).unwrap(), json!("max"));
352 }
353 #[test]
354 #[rustfmt::skip]
355 fn test_serialize_hist_norm() {
356 assert_eq!(to_value(HistNorm::Default).unwrap(), json!(""));
357 assert_eq!(to_value(HistNorm::Percent).unwrap(), json!("percent"));
358 assert_eq!(to_value(HistNorm::Probability).unwrap(), json!("probability"));
359 assert_eq!(to_value(HistNorm::Density).unwrap(), json!("density"));
360 assert_eq!(to_value(HistNorm::ProbabilityDensity).unwrap(), json!("probability density"));
361 }
362
363 #[test]
364 fn test_serialize_default_histogram() {
365 let trace = Histogram::<i32>::default();
366 let expected = json!({"type": "histogram"});
367
368 assert_eq!(to_value(trace).unwrap(), expected);
369 }
370
371 #[test]
372 fn test_serialize_new_xy_histogram() {
373 let trace = Histogram::new_xy(vec![0, 1, 2, 3], vec![4, 5, 6, 7]);
374 let expected = json!({
375 "type": "histogram",
376 "x": [0, 1, 2, 3],
377 "y": [4, 5, 6, 7],
378 });
379
380 assert_eq!(to_value(trace).unwrap(), expected);
381 }
382
383 #[test]
384 fn test_serialize_new_vertical_histogram() {
385 let trace = Histogram::new_vertical(vec![0, 1, 2, 3]);
386 let expected = json!({
387 "type": "histogram",
388 "y": [0, 1, 2, 3]
389 });
390
391 assert_eq!(to_value(trace).unwrap(), expected);
392 }
393
394 #[test]
395 fn test_serialize_histogram() {
396 let trace = Histogram::new(vec![0, 1, 2])
397 .alignment_group("alignmentgroup")
398 .auto_bin_x(true)
399 .auto_bin_y(false)
400 .bin_group("bingroup")
401 .cumulative(Cumulative::new())
402 .error_x(ErrorData::new(ErrorType::SquareRoot))
403 .error_y(ErrorData::new(ErrorType::Constant))
404 .hist_func(HistFunc::Average)
405 .hist_norm(HistNorm::Default)
406 .hover_info(HoverInfo::Skip)
407 .hover_label(Label::new())
408 .hover_template("hovertemplate")
409 .hover_template_array(vec!["hover_template_1", "hover_template_2"])
410 .hover_text("hover_text")
411 .hover_text_array(vec!["hover_text_1", "hover_text_2"])
412 .legend_group("legendgroup")
413 .legend_group_title(LegendGroupTitle::new("Legend Group Title"))
414 .marker(Marker::new())
415 .n_bins_x(5)
416 .n_bins_y(10)
417 .name("histogram_trace")
418 .offset_group("offsetgroup")
419 .opacity(0.1)
420 .show_legend(true)
421 .text("text")
422 .text_array(vec!["text_1", "text_2"])
423 .visible(Visible::True)
424 .x_axis("xaxis")
425 .x_bins(Bins::new(1.0, 2.0, 1.0))
426 .x_calendar(Calendar::Julian)
427 .y_axis("yaxis")
428 .y_bins(Bins::new(2.0, 3.0, 4.0))
429 .y_calendar(Calendar::Mayan);
430
431 let expected = json!({
432 "type": "histogram",
433 "alignmentgroup": "alignmentgroup",
434 "autobinx": true,
435 "autobiny": false,
436 "bingroup": "bingroup",
437 "cumulative": {},
438 "error_x": {"type": "sqrt"},
439 "error_y": {"type": "constant"},
440 "histfunc": "avg",
441 "histnorm": "",
442 "hoverinfo": "skip",
443 "hoverlabel": {},
444 "hovertemplate": ["hover_template_1", "hover_template_2"],
445 "hovertext": ["hover_text_1", "hover_text_2"],
446 "legendgroup": "legendgroup",
447 "legendgrouptitle": {"text": "Legend Group Title"},
448 "marker": {},
449 "nbinsx": 5,
450 "nbinsy": 10,
451 "name": "histogram_trace",
452 "offsetgroup": "offsetgroup",
453 "opacity": 0.1,
454 "showlegend": true,
455 "text": ["text_1", "text_2"],
456 "visible": true,
457 "x": [0, 1, 2],
458 "xaxis": "xaxis",
459 "xbins": {"start": 1.0, "end": 2.0, "size": 1.0},
460 "xcalendar": "julian",
461 "yaxis": "yaxis",
462 "ybins": {"start": 2.0, "end": 3.0, "size": 4.0},
463 "ycalendar": "mayan"
464 });
465
466 assert_eq!(to_value(trace).unwrap(), expected);
467 }
468}