1 use std::marker::PhantomData;
2 
3 use crate::data::Quartiles;
4 use crate::element::{Drawable, PointCollection};
5 use crate::style::{ShapeStyle, BLACK};
6 use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
7 
8 /// The boxplot orientation trait
9 pub trait BoxplotOrient<K, V> {
10     type XType;
11     type YType;
12 
make_coord(key: K, val: V) -> (Self::XType, Self::YType)13     fn make_coord(key: K, val: V) -> (Self::XType, Self::YType);
with_offset(coord: BackendCoord, offset: f64) -> BackendCoord14     fn with_offset(coord: BackendCoord, offset: f64) -> BackendCoord;
15 }
16 
17 /// The vertical boxplot phantom
18 pub struct BoxplotOrientV<K, V>(PhantomData<(K, V)>);
19 
20 /// The horizontal boxplot phantom
21 pub struct BoxplotOrientH<K, V>(PhantomData<(K, V)>);
22 
23 impl<K, V> BoxplotOrient<K, V> for BoxplotOrientV<K, V> {
24     type XType = K;
25     type YType = V;
26 
make_coord(key: K, val: V) -> (K, V)27     fn make_coord(key: K, val: V) -> (K, V) {
28         (key, val)
29     }
30 
with_offset(coord: BackendCoord, offset: f64) -> BackendCoord31     fn with_offset(coord: BackendCoord, offset: f64) -> BackendCoord {
32         (coord.0 + offset as i32, coord.1)
33     }
34 }
35 
36 impl<K, V> BoxplotOrient<K, V> for BoxplotOrientH<K, V> {
37     type XType = V;
38     type YType = K;
39 
make_coord(key: K, val: V) -> (V, K)40     fn make_coord(key: K, val: V) -> (V, K) {
41         (val, key)
42     }
43 
with_offset(coord: BackendCoord, offset: f64) -> BackendCoord44     fn with_offset(coord: BackendCoord, offset: f64) -> BackendCoord {
45         (coord.0, coord.1 + offset as i32)
46     }
47 }
48 
49 const DEFAULT_WIDTH: u32 = 10;
50 
51 /// The boxplot element
52 pub struct Boxplot<K, O: BoxplotOrient<K, f32>> {
53     style: ShapeStyle,
54     width: u32,
55     whisker_width: f64,
56     offset: f64,
57     key: K,
58     values: [f32; 5],
59     _p: PhantomData<O>,
60 }
61 
62 impl<K: Clone> Boxplot<K, BoxplotOrientV<K, f32>> {
63     /// Create a new vertical boxplot element.
64     ///
65     /// - `key`: The key (the X axis value)
66     /// - `quartiles`: The quartiles values for the Y axis
67     /// - **returns** The newly created boxplot element
68     ///
69     /// ```rust
70     /// use plotters::prelude::*;
71     ///
72     /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]);
73     /// let plot = Boxplot::new_vertical("group", &quartiles);
74     /// ```
new_vertical(key: K, quartiles: &Quartiles) -> Self75     pub fn new_vertical(key: K, quartiles: &Quartiles) -> Self {
76         Self {
77             style: Into::<ShapeStyle>::into(&BLACK),
78             width: DEFAULT_WIDTH,
79             whisker_width: 1.0,
80             offset: 0.0,
81             key,
82             values: quartiles.values(),
83             _p: PhantomData,
84         }
85     }
86 }
87 
88 impl<K: Clone> Boxplot<K, BoxplotOrientH<K, f32>> {
89     /// Create a new horizontal boxplot element.
90     ///
91     /// - `key`: The key (the Y axis value)
92     /// - `quartiles`: The quartiles values for the X axis
93     /// - **returns** The newly created boxplot element
94     ///
95     /// ```rust
96     /// use plotters::prelude::*;
97     ///
98     /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]);
99     /// let plot = Boxplot::new_horizontal("group", &quartiles);
100     /// ```
new_horizontal(key: K, quartiles: &Quartiles) -> Self101     pub fn new_horizontal(key: K, quartiles: &Quartiles) -> Self {
102         Self {
103             style: Into::<ShapeStyle>::into(&BLACK),
104             width: DEFAULT_WIDTH,
105             whisker_width: 1.0,
106             offset: 0.0,
107             key,
108             values: quartiles.values(),
109             _p: PhantomData,
110         }
111     }
112 }
113 
114 impl<K, O: BoxplotOrient<K, f32>> Boxplot<K, O> {
115     /// Set the style of the boxplot.
116     ///
117     /// - `S`: The required style
118     /// - **returns** The up-to-dated boxplot element
119     ///
120     /// ```rust
121     /// use plotters::prelude::*;
122     ///
123     /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]);
124     /// let plot = Boxplot::new_horizontal("group", &quartiles).style(&BLUE);
125     /// ```
style<S: Into<ShapeStyle>>(mut self, style: S) -> Self126     pub fn style<S: Into<ShapeStyle>>(mut self, style: S) -> Self {
127         self.style = style.into();
128         self
129     }
130 
131     /// Set the bar width.
132     ///
133     /// - `width`: The required width
134     /// - **returns** The up-to-dated boxplot element
135     ///
136     /// ```rust
137     /// use plotters::prelude::*;
138     ///
139     /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]);
140     /// let plot = Boxplot::new_horizontal("group", &quartiles).width(10);
141     /// ```
width(mut self, width: u32) -> Self142     pub fn width(mut self, width: u32) -> Self {
143         self.width = width;
144         self
145     }
146 
147     /// Set the width of the whiskers as a fraction of the bar width.
148     ///
149     /// - `whisker_width`: The required fraction
150     /// - **returns** The up-to-dated boxplot element
151     ///
152     /// ```rust
153     /// use plotters::prelude::*;
154     ///
155     /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]);
156     /// let plot = Boxplot::new_horizontal("group", &quartiles).whisker_width(0.5);
157     /// ```
whisker_width(mut self, whisker_width: f64) -> Self158     pub fn whisker_width(mut self, whisker_width: f64) -> Self {
159         self.whisker_width = whisker_width;
160         self
161     }
162 
163     /// Set the element offset on the key axis.
164     ///
165     /// - `offset`: The required offset (on the X axis for vertical, on the Y axis for horizontal)
166     /// - **returns** The up-to-dated boxplot element
167     ///
168     /// ```rust
169     /// use plotters::prelude::*;
170     ///
171     /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]);
172     /// let plot = Boxplot::new_horizontal("group", &quartiles).offset(-5);
173     /// ```
offset<T: Into<f64> + Copy>(mut self, offset: T) -> Self174     pub fn offset<T: Into<f64> + Copy>(mut self, offset: T) -> Self {
175         self.offset = offset.into();
176         self
177     }
178 }
179 
180 impl<'a, K: Clone, O: BoxplotOrient<K, f32>> PointCollection<'a, (O::XType, O::YType)>
181     for &'a Boxplot<K, O>
182 {
183     type Point = (O::XType, O::YType);
184     type IntoIter = Vec<Self::Point>;
point_iter(self) -> Self::IntoIter185     fn point_iter(self) -> Self::IntoIter {
186         self.values
187             .iter()
188             .map(|v| O::make_coord(self.key.clone(), *v))
189             .collect()
190     }
191 }
192 
193 impl<K, DB: DrawingBackend, O: BoxplotOrient<K, f32>> Drawable<DB> for Boxplot<K, O> {
draw<I: Iterator<Item = BackendCoord>>( &self, points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>194     fn draw<I: Iterator<Item = BackendCoord>>(
195         &self,
196         points: I,
197         backend: &mut DB,
198         _: (u32, u32),
199     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
200         let points: Vec<_> = points.take(5).collect();
201         if points.len() == 5 {
202             let width = f64::from(self.width);
203             let moved = |coord| O::with_offset(coord, self.offset);
204             let start_bar = |coord| O::with_offset(moved(coord), -width / 2.0);
205             let end_bar = |coord| O::with_offset(moved(coord), width / 2.0);
206             let start_whisker =
207                 |coord| O::with_offset(moved(coord), -width * self.whisker_width / 2.0);
208             let end_whisker =
209                 |coord| O::with_offset(moved(coord), width * self.whisker_width / 2.0);
210 
211             // |---[   |  ]----|
212             // ^________________
213             backend.draw_line(
214                 start_whisker(points[0]),
215                 end_whisker(points[0]),
216                 &self.style,
217             )?;
218 
219             // |---[   |  ]----|
220             // _^^^_____________
221             backend.draw_line(moved(points[0]), moved(points[1]), &self.style.color)?;
222 
223             // |---[   |  ]----|
224             // ____^______^_____
225             let corner1 = start_bar(points[3]);
226             let corner2 = end_bar(points[1]);
227             let upper_left = (corner1.0.min(corner2.0), corner1.1.min(corner2.1));
228             let bottom_right = (corner1.0.max(corner2.0), corner1.1.max(corner2.1));
229             backend.draw_rect(upper_left, bottom_right, &self.style, false)?;
230 
231             // |---[   |  ]----|
232             // ________^________
233             backend.draw_line(start_bar(points[2]), end_bar(points[2]), &self.style)?;
234 
235             // |---[   |  ]----|
236             // ____________^^^^_
237             backend.draw_line(moved(points[3]), moved(points[4]), &self.style)?;
238 
239             // |---[   |  ]----|
240             // ________________^
241             backend.draw_line(
242                 start_whisker(points[4]),
243                 end_whisker(points[4]),
244                 &self.style,
245             )?;
246         }
247         Ok(())
248     }
249 }
250 
251 #[cfg(test)]
252 mod test {
253     use super::*;
254     use crate::prelude::*;
255 
256     #[test]
test_draw_v()257     fn test_draw_v() {
258         let root = MockedBackend::new(1024, 768).into_drawing_area();
259         let chart = ChartBuilder::on(&root)
260             .build_cartesian_2d(0..2, 0f32..100f32)
261             .unwrap();
262 
263         let values = Quartiles::new(&[6]);
264         assert!(chart
265             .plotting_area()
266             .draw(&Boxplot::new_vertical(1, &values))
267             .is_ok());
268     }
269 
270     #[test]
test_draw_h()271     fn test_draw_h() {
272         let root = MockedBackend::new(1024, 768).into_drawing_area();
273         let chart = ChartBuilder::on(&root)
274             .build_cartesian_2d(0f32..100f32, 0..2)
275             .unwrap();
276 
277         let values = Quartiles::new(&[6]);
278         assert!(chart
279             .plotting_area()
280             .draw(&Boxplot::new_horizontal(1, &values))
281             .is_ok());
282     }
283 }
284