plotly_fork/traces/
histogram.rs

1//! Histogram plot
2
3#[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/// Construct a histogram trace.
87///
88/// # Examples
89///
90/// ```
91/// use plotly::Histogram;
92///
93/// let trace = Histogram::new_xy(
94///     vec![0, 1, 2, 3],
95///     vec![5, 8, 2, 3]
96/// );
97///
98/// let expected = serde_json::json!({
99///     "type": "histogram",
100///     "x": [0, 1, 2, 3],
101///     "y": [5, 8, 2, 3],
102/// });
103///
104/// assert_eq!(serde_json::to_value(trace).unwrap(), expected);
105/// ```
106#[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    /// Produces `Histogram` traces from a 2 dimensional tensor
199    /// (`traces_matrix`) indexed by `x`. This function requires the
200    /// `ndarray` feature.
201    ///
202    /// # Arguments
203    /// * `x`             - One dimensional array (or view) that represents the
204    ///   `x` axis coordinates.
205    /// * `traces_matrix` - Two dimensional array (or view) containing the `y`
206    ///   axis coordinates of
207    /// the traces.
208    /// * `array_traces`  - Determines whether the traces are arranged in the
209    ///   matrix over the
210    /// columns (`ArrayTraces::OverColumns`) or over the rows
211    /// (`ArrayTraces::OverRows`).
212    ///
213    /// # Examples
214    ///
215    /// ```
216    /// use plotly::common::Mode;
217    /// use plotly::{Plot, Histogram, ArrayTraces};
218    /// use ndarray::{Array, Ix1, Ix2};
219    /// use rand_distr::{Distribution, Normal};
220    /// use plotly::Layout;
221    /// use plotly::layout::BarMode;
222    ///
223    /// fn ndarray_to_traces() {
224    ///     let n: usize = 1_250;
225    ///     let mut rng = rand::thread_rng();
226    ///     let t: Array<f64, Ix1> = Array::range(0., 10., 10. / n as f64);
227    ///     let mut ys: Array<f64, Ix2> = Array::zeros((n, 4));
228    ///     let mut count = 0.;
229    ///     for mut row in ys.columns_mut() {
230    ///         let tmp: Vec<f64> = Normal::new(4. * count, 1.).unwrap().sample_iter(&mut rng).take(n).collect();
231    ///         for i in 0..row.len() {
232    ///             row[i] = tmp[i];
233    ///         }
234    ///         count += 1.;
235    ///     }
236    ///
237    ///     let traces = Histogram::default()
238    ///         .opacity(0.5)
239    ///         .auto_bin_x(true)
240    ///         .to_traces(ys, ArrayTraces::OverColumns);
241    ///
242    ///     let layout = Layout::new().bar_mode(BarMode::Overlay);
243    ///
244    ///     let mut plot = Plot::new();
245    ///     plot.set_layout(layout);
246    ///     plot.add_traces(traces);
247    ///
248    ///     # if false {  // Prevent this line from running in the doctest.
249    ///     plot.show();
250    ///     # }
251    /// }
252    /// fn main() -> std::io::Result<()> {
253    ///     ndarray_to_traces();
254    ///     Ok(())
255    /// }
256    /// ```
257    #[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}