plotters_unstable/element/
boxplot.rs

1use std::marker::PhantomData;
2
3use crate::data::Quartiles;
4use crate::element::{Drawable, PointCollection};
5use crate::style::{ShapeStyle, BLACK};
6use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
7
8/// The boxplot orientation trait
9pub trait BoxplotOrient<K, V> {
10    type XType;
11    type YType;
12
13    fn make_coord(key: K, val: V) -> (Self::XType, Self::YType);
14    fn with_offset(coord: BackendCoord, offset: f64) -> BackendCoord;
15}
16
17/// The vertical boxplot phantom
18pub struct BoxplotOrientV<K, V>(PhantomData<(K, V)>);
19
20/// The horizontal boxplot phantom
21pub struct BoxplotOrientH<K, V>(PhantomData<(K, V)>);
22
23impl<K, V> BoxplotOrient<K, V> for BoxplotOrientV<K, V> {
24    type XType = K;
25    type YType = V;
26
27    fn make_coord(key: K, val: V) -> (K, V) {
28        (key, val)
29    }
30
31    fn with_offset(coord: BackendCoord, offset: f64) -> BackendCoord {
32        (coord.0 + offset as i32, coord.1)
33    }
34}
35
36impl<K, V> BoxplotOrient<K, V> for BoxplotOrientH<K, V> {
37    type XType = V;
38    type YType = K;
39
40    fn make_coord(key: K, val: V) -> (V, K) {
41        (val, key)
42    }
43
44    fn with_offset(coord: BackendCoord, offset: f64) -> BackendCoord {
45        (coord.0, coord.1 + offset as i32)
46    }
47}
48
49const DEFAULT_WIDTH: u32 = 10;
50
51/// The boxplot element
52pub 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
62impl<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    /// ```
75    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
88impl<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    /// ```
101    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
114impl<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    /// ```
126    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    /// ```
142    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    /// ```
158    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    /// ```
174    pub fn offset<T: Into<f64> + Copy>(mut self, offset: T) -> Self {
175        self.offset = offset.into();
176        self
177    }
178}
179
180impl<'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>;
185    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
193impl<K, DB: DrawingBackend, O: BoxplotOrient<K, f32>> Drawable<DB> for Boxplot<K, O> {
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)]
252mod test {
253    use super::*;
254    use crate::prelude::*;
255
256    #[test]
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]
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}