plotpy/
histogram.rs

1use super::{generate_list_quoted, generate_nested_list, GraphMaker};
2use num_traits::Num;
3use std::fmt::Write;
4
5/// Generates a Histogram plot
6///
7/// [See Matplotlib's documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html)
8///
9/// # Example
10///
11/// ```
12/// use plotpy::{Histogram, Plot, StrError};
13///
14/// fn main() -> Result<(), StrError> {
15///     // set values
16///     let values = vec![
17///         vec![1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 4, 5, 6], // first series
18///         vec![-1, -1, 0, 1, 2, 3],                    // second series
19///         vec![5, 6, 7, 8],                            // third series
20///     ];
21///
22///     // set labels
23///     let labels = ["first", "second", "third"];
24///
25///     // configure and draw histogram
26///     let mut histogram = Histogram::new();
27///     histogram.set_colors(&["#9de19a", "#e7eca3", "#98a7f2"])
28///         .set_line_width(10.0)
29///         .set_stacked(true)
30///         .set_style("step");
31///     histogram.draw(&values, &labels);
32///
33///     // add histogram to plot
34///     let mut plot = Plot::new();
35///     plot.add(&histogram)
36///         .set_frame_border(true, false, true, false)
37///         .grid_labels_legend("values", "count");
38///
39///     // save figure
40///     plot.save("/tmp/plotpy/doc_tests/doc_histogram.svg")?;
41///     Ok(())
42/// }
43/// ```
44///
45/// ![doc_histogram.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_histogram.svg)
46///
47/// See also integration test in the **tests** directory.
48///
49/// Output from some integration tests:
50///
51/// ![integ_histogram_1.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_histogram_1.svg)
52pub struct Histogram {
53    colors: Vec<String>, // Colors for each bar
54    line_width: f64,     // Line width
55    style: String,       // Type of histogram; e.g. "bar"
56    stacked: bool,       // Draws stacked histogram
57    no_fill: bool,       // Skip filling bars
58    number_bins: usize,  // Number of bins
59    extra: String,       // Extra commands (comma separated)
60    buffer: String,      // buffer
61}
62
63impl Histogram {
64    /// Creates a new Histogram object
65    pub fn new() -> Self {
66        Histogram {
67            colors: Vec::new(),
68            line_width: 0.0,
69            style: String::new(),
70            stacked: false,
71            no_fill: false,
72            number_bins: 0,
73            extra: String::new(),
74            buffer: String::new(),
75        }
76    }
77
78    /// Draws histogram
79    ///
80    /// # Input
81    ///
82    /// * `values` -- holds the values
83    /// * `labels` -- holds the labels
84    pub fn draw<T, U>(&mut self, values: &Vec<Vec<T>>, labels: &[U])
85    where
86        T: std::fmt::Display + Num,
87        U: std::fmt::Display,
88    {
89        let opt = self.options();
90        generate_nested_list(&mut self.buffer, "values", values);
91        generate_list_quoted(&mut self.buffer, "labels", labels);
92        if self.colors.len() > 0 {
93            generate_list_quoted(&mut self.buffer, "colors", self.colors.as_slice());
94        }
95        write!(&mut self.buffer, "plt.hist(values,label=labels{})\n", &opt).unwrap();
96    }
97
98    /// Sets the colors for each bar
99    pub fn set_colors(&mut self, colors: &[&str]) -> &mut Self {
100        self.colors = colors.iter().map(|color| color.to_string()).collect();
101        self
102    }
103
104    /// Sets the width of the lines
105    pub fn set_line_width(&mut self, width: f64) -> &mut Self {
106        self.line_width = width;
107        self
108    }
109
110    /// Sets the type of histogram
111    ///
112    /// Options:
113    ///
114    /// * `bar` is a traditional bar-type histogram. If multiple data are given the bars are arranged side by side.
115    /// * `barstacked` is a bar-type histogram where multiple data are stacked on top of each other.
116    /// * `step` generates a lineplot that is by default unfilled.
117    /// * `stepfilled` generates a lineplot that is by default filled.
118    /// * As defined in <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html>
119    pub fn set_style(&mut self, style: &str) -> &mut Self {
120        self.style = String::from(style);
121        self
122    }
123
124    /// Sets option to draw stacked histogram
125    pub fn set_stacked(&mut self, flag: bool) -> &mut Self {
126        self.stacked = flag;
127        self
128    }
129
130    /// Sets option to skip filling bars
131    pub fn set_no_fill(&mut self, flag: bool) -> &mut Self {
132        self.no_fill = flag;
133        self
134    }
135
136    /// Sets the number of bins
137    pub fn set_number_bins(&mut self, bins: usize) -> &mut Self {
138        self.number_bins = bins;
139        self
140    }
141
142    /// Sets extra matplotlib commands (comma separated)
143    ///
144    /// **Important:** The extra commands must be comma separated. For example:
145    ///
146    /// ```text
147    /// param1=123,param2='hello'
148    /// ```
149    ///
150    /// [See Matplotlib's documentation for extra parameters](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html)
151    pub fn set_extra(&mut self, extra: &str) -> &mut Self {
152        self.extra = extra.to_string();
153        self
154    }
155
156    /// Returns options for histogram
157    fn options(&self) -> String {
158        let mut opt = String::new();
159        if self.colors.len() > 0 {
160            write!(&mut opt, ",color=colors").unwrap();
161        }
162        if self.line_width > 0.0 {
163            write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
164        }
165        if self.style != "" {
166            write!(&mut opt, ",histtype='{}'", self.style).unwrap();
167        }
168        if self.stacked {
169            write!(&mut opt, ",stacked=True").unwrap();
170        }
171        if self.no_fill {
172            write!(&mut opt, ",fill=False").unwrap();
173        }
174        if self.number_bins > 0 {
175            write!(&mut opt, ",bins={}", self.number_bins).unwrap();
176        }
177        if self.extra != "" {
178            write!(&mut opt, ",{}", self.extra).unwrap();
179        }
180        opt
181    }
182}
183
184impl GraphMaker for Histogram {
185    fn get_buffer<'a>(&'a self) -> &'a String {
186        &self.buffer
187    }
188    fn clear_buffer(&mut self) {
189        self.buffer.clear();
190    }
191}
192
193////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
194
195#[cfg(test)]
196mod tests {
197    use super::Histogram;
198    use crate::GraphMaker;
199
200    #[test]
201    fn new_works() {
202        let histogram = Histogram::new();
203        assert_eq!(histogram.colors.len(), 0);
204        assert_eq!(histogram.line_width, 0.0);
205        assert_eq!(histogram.style.len(), 0);
206        assert_eq!(histogram.stacked, false);
207        assert_eq!(histogram.no_fill, false);
208        assert_eq!(histogram.number_bins, 0);
209        assert_eq!(histogram.buffer.len(), 0);
210    }
211
212    #[test]
213    fn options_works() {
214        let mut histogram = Histogram::new();
215        histogram
216            .set_colors(&vec!["red", "green"])
217            .set_line_width(10.0)
218            .set_style("step")
219            .set_stacked(true)
220            .set_no_fill(true)
221            .set_number_bins(8);
222        let opt = histogram.options();
223        assert_eq!(
224            opt,
225            ",color=colors\
226             ,linewidth=10\
227             ,histtype='step'\
228             ,stacked=True\
229             ,fill=False\
230             ,bins=8"
231        );
232    }
233
234    #[test]
235    fn draw_works() {
236        let values = vec![vec![1, 1, 1, 2, 2, 2, 2, 2, 3, 3], vec![5, 6, 7, 8]];
237        let labels = ["first", "second"];
238        let mut histogram = Histogram::new();
239        histogram.set_colors(&vec!["red", "green"]);
240        histogram.draw(&values, &labels);
241        let b: &str = "values=[[1,1,1,2,2,2,2,2,3,3,],[5,6,7,8,],]\n\
242                       labels=['first','second',]\n\
243                       colors=['red','green',]\n\
244                       plt.hist(values,label=labels,color=colors)\n";
245        assert_eq!(histogram.buffer, b);
246        histogram.clear_buffer();
247        assert_eq!(histogram.buffer, "");
248    }
249}