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/// 
46///
47/// See also integration test in the **tests** directory.
48///
49/// Output from some integration tests:
50///
51/// 
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}