plotpy/barplot.rs
1use super::{generate_list_quoted, vector_to_array, AsVector, GraphMaker};
2use num_traits::Num;
3use std::fmt::Write;
4
5/// Generates a Barplot plot
6///
7/// [See Matplotlib's documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.bar.html)
8///
9/// # Examples
10///
11/// ## Basic bar plot
12///
13/// ```
14/// use plotpy::{Barplot, Plot, StrError};
15/// use std::collections::HashMap;
16///
17/// fn main() -> Result<(), StrError> {
18/// // data
19/// let x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
20/// let y = [5, 4, 3, 2, 1, 0, 1, 2, 3, 4];
21///
22/// // barplot object and options
23/// let mut bar = Barplot::new();
24/// bar.draw(&x, &y);
25///
26/// // save figure
27/// let mut plot = Plot::new();
28/// plot.add(&bar)
29/// .save("/tmp/plotpy/doc_tests/doc_barplot_1.svg")?;
30/// Ok(())
31/// }
32/// ```
33///
34/// 
35///
36/// ## Using string as labels
37///
38/// The code below implements the [Bar Label Demo from Matplotlib documentation](https://matplotlib.org/stable/gallery/lines_bars_and_markers/bar_label_demo.html#sphx-glr-gallery-lines-bars-and-markers-bar-label-demo-py)
39///
40/// ```
41/// use plotpy::{Barplot, Plot, StrError};
42/// use std::collections::HashMap;
43///
44/// fn main() -> Result<(), StrError> {
45/// // data
46/// let species = ["Adelie", "Chinstrap", "Gentoo"];
47/// let sex_counts = HashMap::from([
48/// ("Male", [73.0, 34.0, 61.0]), //
49/// ("Female", [73.0, 34.0, 58.0]),
50/// ]);
51///
52/// // barplot object and options
53/// let mut bar = Barplot::new();
54/// bar.set_with_text("center");
55///
56/// // draw bars
57/// let mut bottom = [0.0, 0.0, 0.0];
58/// for (sex, sex_count) in &sex_counts {
59/// bar.set_label(sex)
60/// .set_bottom(&bottom)
61/// .draw_with_str(&species, sex_count);
62/// for i in 0..sex_count.len() {
63/// bottom[i] += sex_count[i];
64/// }
65/// }
66///
67/// // add barplot to plot and save figure
68/// let mut plot = Plot::new();
69/// plot.add(&bar)
70/// .set_title("Number of penguins by sex")
71/// .legend()
72/// .save("/tmp/plotpy/doc_tests/doc_barplot_2.svg")?;
73/// Ok(())
74/// }
75/// ```
76///
77/// 
78///
79/// ## Horizontal bars
80///
81/// ```
82/// use plotpy::{Barplot, Plot, StrError};
83///
84/// fn main() -> Result<(), StrError> {
85/// // data
86/// let fruits = ["Apple", "Banana", "Orange"];
87/// let prices = [10.0, 20.0, 30.0];
88/// let errors = [3.0, 2.0, 1.0];
89///
90/// // barplot object and options
91/// let mut bar = Barplot::new();
92/// bar.set_errors(&errors)
93/// .set_horizontal(true)
94/// .set_with_text("edge")
95/// .draw_with_str(&fruits, &prices);
96///
97/// // save figure
98/// let mut plot = Plot::new();
99/// plot.set_inv_y()
100/// .add(&bar)
101/// .set_title("Fruits")
102/// .set_label_x("price")
103/// .save("/tmp/plotpy/doc_tests/doc_barplot_3.svg")?;
104/// Ok(())
105/// }
106/// ```
107///
108/// 
109///
110/// ## More examples
111///
112/// See also integration test in the **tests** directory.
113pub struct Barplot {
114 label: String, // Name of this bar in the legend
115 colors: Vec<String>, // Colors for each bar
116 width: f64, // Width of the bars
117 bottom: Vec<f64>, // bottom coordinates to stack bars
118 with_text: Option<String>, // Text to be added to each bar (aka, bar_label)
119 horizontal: bool, // Horizontal barplot
120 errors: Vec<f64>, // Shows error icons on bars
121 extra: String, // Extra commands (comma separated)
122 buffer: String, // buffer
123}
124
125impl Barplot {
126 /// Creates a new Barplot object
127 pub fn new() -> Self {
128 Barplot {
129 label: String::new(),
130 colors: Vec::new(),
131 width: 0.0,
132 bottom: Vec::new(),
133 with_text: None,
134 horizontal: false,
135 errors: Vec::new(),
136 extra: String::new(),
137 buffer: String::new(),
138 }
139 }
140
141 /// Draws the bar plot
142 pub fn draw<'a, T, U>(&mut self, x: &'a T, y: &'a T)
143 where
144 T: AsVector<'a, U>,
145 U: 'a + std::fmt::Display + Num,
146 {
147 vector_to_array(&mut self.buffer, "x", x);
148 vector_to_array(&mut self.buffer, "y", y);
149 let opt = self.options();
150 if self.colors.len() > 0 {
151 generate_list_quoted(&mut self.buffer, "colors", self.colors.as_slice());
152 }
153 if self.bottom.len() > 0 {
154 vector_to_array(&mut self.buffer, "bottom", &self.bottom);
155 }
156 if self.errors.len() > 0 {
157 vector_to_array(&mut self.buffer, "err", &self.errors);
158 }
159 if self.horizontal {
160 write!(&mut self.buffer, "p=plt.barh(x,y{})\n", &opt).unwrap();
161 } else {
162 write!(&mut self.buffer, "p=plt.bar(x,y{})\n", &opt).unwrap();
163 }
164 if let Some(t) = &self.with_text {
165 write!(&mut self.buffer, "plt.gca().bar_label(p,label_type='{}')\n", t).unwrap();
166 }
167 }
168
169 /// Draws the bar plot with strings
170 pub fn draw_with_str<'a, T, U>(&mut self, x: &[&str], y: &'a T)
171 where
172 T: AsVector<'a, U>,
173 U: 'a + std::fmt::Display + Num,
174 {
175 generate_list_quoted(&mut self.buffer, "x", x);
176 vector_to_array(&mut self.buffer, "y", y);
177 let opt = self.options();
178 if self.colors.len() > 0 {
179 generate_list_quoted(&mut self.buffer, "colors", self.colors.as_slice());
180 }
181 if self.bottom.len() > 0 {
182 vector_to_array(&mut self.buffer, "bottom", &self.bottom);
183 }
184 if self.errors.len() > 0 {
185 vector_to_array(&mut self.buffer, "err", &self.errors);
186 }
187 if self.horizontal {
188 write!(&mut self.buffer, "p=plt.barh(x,y{})\n", &opt).unwrap();
189 } else {
190 write!(&mut self.buffer, "p=plt.bar(x,y{})\n", &opt).unwrap();
191 }
192 if let Some(t) = &self.with_text {
193 write!(&mut self.buffer, "plt.gca().bar_label(p,label_type='{}')\n", t).unwrap();
194 }
195 }
196
197 /// Sets the name of this bar in the legend
198 pub fn set_label(&mut self, label: &str) -> &mut Self {
199 self.label = String::from(label);
200 self
201 }
202
203 /// Sets the colors for each bar
204 pub fn set_colors(&mut self, colors: &[&str]) -> &mut Self {
205 self.colors = colors.iter().map(|color| color.to_string()).collect();
206 self
207 }
208
209 /// Sets the width of the bars
210 pub fn set_width(&mut self, width: f64) -> &mut Self {
211 self.width = width;
212 self
213 }
214
215 /// Sets the vertical offset to stack bars
216 pub fn set_bottom(&mut self, bottom: &[f64]) -> &mut Self {
217 self.bottom = Vec::from(bottom);
218 self
219 }
220
221 /// Sets an option to show the text (labels) of each bar
222 ///
223 /// # Input
224 ///
225 /// `position` -- "edge" or "center"; Use "" to remove the label
226 ///
227 /// See [Matplotlib documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.bar_label.html#matplotlib.axes.Axes.bar_label)
228 pub fn set_with_text(&mut self, position: &str) -> &mut Self {
229 if position == "" {
230 self.with_text = None
231 } else {
232 self.with_text = Some(position.to_string());
233 }
234 self
235 }
236
237 /// Enables drawing horizontal bars
238 pub fn set_horizontal(&mut self, flag: bool) -> &mut Self {
239 self.horizontal = flag;
240 self
241 }
242
243 /// Enables error indicators
244 pub fn set_errors(&mut self, errors: &[f64]) -> &mut Self {
245 self.errors = errors.to_vec();
246 self
247 }
248
249 /// Sets extra matplotlib commands (comma separated)
250 ///
251 /// **Important:** The extra commands must be comma separated. For example:
252 ///
253 /// ```text
254 /// param1=123,param2='hello'
255 /// ```
256 ///
257 /// [See Matplotlib's documentation for extra parameters](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.bar.html)
258 pub fn set_extra(&mut self, extra: &str) -> &mut Self {
259 self.extra = extra.to_string();
260 self
261 }
262
263 /// Returns options for barplot
264 fn options(&self) -> String {
265 let mut opt = String::new();
266 if self.label != "" {
267 write!(&mut opt, ",label=r'{}'", self.label).unwrap();
268 }
269 if self.colors.len() > 0 {
270 write!(&mut opt, ",color=colors").unwrap();
271 }
272 if self.width > 0.0 {
273 write!(&mut opt, ",width={}", self.width).unwrap();
274 }
275 if self.bottom.len() > 0 {
276 write!(&mut opt, ",bottom=bottom").unwrap();
277 }
278 if self.errors.len() > 0 {
279 if self.horizontal {
280 write!(&mut opt, ",xerr=err").unwrap();
281 } else {
282 write!(&mut opt, ",yerr=err").unwrap();
283 }
284 }
285 if self.extra != "" {
286 write!(&mut opt, ",{}", self.extra).unwrap();
287 }
288 opt
289 }
290}
291
292impl GraphMaker for Barplot {
293 fn get_buffer<'a>(&'a self) -> &'a String {
294 &self.buffer
295 }
296 fn clear_buffer(&mut self) {
297 self.buffer.clear();
298 }
299}
300
301////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
302
303#[cfg(test)]
304mod tests {
305 use super::Barplot;
306 use crate::GraphMaker;
307
308 #[test]
309 fn new_works() {
310 let barplot = Barplot::new();
311 assert_eq!(barplot.label.len(), 0);
312 assert_eq!(barplot.colors.len(), 0);
313 assert_eq!(barplot.width, 0.0);
314 assert_eq!(barplot.bottom.len(), 0);
315 assert_eq!(barplot.with_text, None);
316 assert_eq!(barplot.buffer.len(), 0);
317 }
318
319 #[test]
320 fn draw_works_1() {
321 let xx = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
322 let yy = [5, 4, 3, 2, 1, 0, 1, 2, 3, 4];
323 let mut bar = Barplot::new();
324 bar.draw(&xx, &yy);
325 let b: &str = "x=np.array([0,1,2,3,4,5,6,7,8,9,],dtype=float)\n\
326 y=np.array([5,4,3,2,1,0,1,2,3,4,],dtype=float)\n\
327 p=plt.bar(x,y)\n";
328 assert_eq!(bar.buffer, b);
329 bar.clear_buffer();
330 assert_eq!(bar.buffer, "");
331 }
332
333 #[test]
334 fn draw_works_2() {
335 let xx = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
336 let yy = [5, 4, 3, 2, 1, 0, 1, 2, 3, 4];
337 let mut bar = Barplot::new();
338 bar.set_label("LABEL")
339 .set_colors(&vec!["red", "green"])
340 .set_width(10.0)
341 .set_bottom(&[1.0, 2.0, 3.0])
342 .set_with_text("center")
343 .set_extra("edgecolor='black'")
344 .draw(&xx, &yy);
345 let b: &str = "x=np.array([0,1,2,3,4,5,6,7,8,9,],dtype=float)\n\
346 y=np.array([5,4,3,2,1,0,1,2,3,4,],dtype=float)\n\
347 colors=['red','green',]\n\
348 bottom=np.array([1,2,3,],dtype=float)\n\
349 p=plt.bar(x,y\
350 ,label=r'LABEL'\
351 ,color=colors\
352 ,width=10\
353 ,bottom=bottom\
354 ,edgecolor='black')\n\
355 plt.gca().bar_label(p,label_type='center')\n";
356 assert_eq!(bar.buffer, b);
357 bar.clear_buffer();
358 bar.set_with_text("");
359 assert_eq!(bar.buffer, "");
360 }
361
362 #[test]
363 fn draw_with_str_works_1() {
364 let xx = ["one", "two", "three"];
365 let yy = [1, 2, 3];
366 let mut bar = Barplot::new();
367 bar.draw_with_str(&xx, &yy);
368 let b: &str = "x=['one','two','three',]\n\
369 y=np.array([1,2,3,],dtype=float)\n\
370 p=plt.bar(x,y)\n";
371 assert_eq!(bar.buffer, b);
372 }
373
374 #[test]
375 fn draw_with_str_works_2() {
376 let xx = ["one", "two", "three"];
377 let yy = [1, 2, 3];
378 let mut bar = Barplot::new();
379 bar.set_label("LABEL")
380 .set_colors(&vec!["red", "green"])
381 .set_width(10.0)
382 .set_bottom(&[1.0, 2.0, 3.0])
383 .set_with_text("center")
384 .set_extra("edgecolor='black'")
385 .draw_with_str(&xx, &yy);
386 let b: &str = "x=['one','two','three',]\n\
387 y=np.array([1,2,3,],dtype=float)\n\
388 colors=['red','green',]\n\
389 bottom=np.array([1,2,3,],dtype=float)\n\
390 p=plt.bar(x,y\
391 ,label=r'LABEL'\
392 ,color=colors\
393 ,width=10\
394 ,bottom=bottom\
395 ,edgecolor='black')\n\
396 plt.gca().bar_label(p,label_type='center')\n";
397 assert_eq!(bar.buffer, b);
398 }
399}