Skip to main content

box_plotters/
box_plot.rs

1use std::marker::PhantomData;
2
3use plotters::{element::{Drawable, PointCollection}, style::{BLACK, ShapeStyle, TRANSPARENT}};
4use plotters_backend::{DrawingBackend, DrawingErrorKind};
5
6use crate::{box_plot::orientation::{Horizontal, Orientation, Vertical}, quartiles::Quartiles};
7
8pub mod orientation;
9
10const DEFAULT_WIDTH: f64 = 10.0;
11const DEFAULT_WHISKER_WIDTH: f64 = 10.0;
12
13pub struct BoxPlot<K, O> {
14    line_style: ShapeStyle,
15    box_style: ShapeStyle,
16    width: f64,
17    whisker_width: f64,
18    key: K,
19    quartiles: Quartiles,
20    _phantom: PhantomData<O>,
21}
22
23impl<K> BoxPlot<K, Vertical<K, f64>> {
24    /// Constructs a new vertical box plot based on a key and quartiles.
25    pub fn vertical_from_key_quartiles(key: K, quartiles: Quartiles) -> Self {
26        Self {
27            line_style: BLACK.into(),
28            box_style: TRANSPARENT.into(),
29            width: DEFAULT_WIDTH,
30            whisker_width: DEFAULT_WHISKER_WIDTH,
31            key,
32            quartiles,
33            _phantom: PhantomData,
34        }
35    }
36}
37
38impl<K> BoxPlot<K, Horizontal<K, f64>> {
39    /// Constructs a new horizontal box plot based on a key and quartiles.
40    pub fn horizontal_from_key_quartiles(key: K, quartiles: Quartiles) -> Self {
41        Self {
42            line_style: BLACK.into(),
43            box_style: TRANSPARENT.into(),
44            width: DEFAULT_WIDTH,
45            whisker_width: DEFAULT_WHISKER_WIDTH,
46            key,
47            quartiles,
48            _phantom: PhantomData,
49        }
50    }
51}
52
53impl<K, O> BoxPlot<K, O> {
54    /// Sets the line style used by the box plot
55    pub fn with_line_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
56        self.line_style = style.into();
57        self
58    }
59
60    /// Sets the box style used to fill the box plot
61    pub fn with_box_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
62        self.box_style = style.into();
63        self
64    }
65
66    /// Sets the width of the box.
67    pub fn with_width(&mut self, width: f64) -> &mut Self {
68        self.width = width;
69        self
70    }
71
72    /// Sets the width of the whiskers.
73    pub fn with_whisker_width(&mut self, width: f64) -> &mut Self {
74        self.whisker_width = width;
75        self
76    }
77}
78
79impl<'a, K: Clone, O: Orientation<K, f64>> PointCollection<'a, (O::XType, O::YType)>
80    for &'a BoxPlot<K, O>
81{
82    type Point = (O::XType, O::YType);
83    type IntoIter = Vec<Self::Point>;
84    fn point_iter(self) -> Self::IntoIter {
85        self.quartiles
86            .values()
87            .iter()
88            .map(|v| O::make_coord(self.key.clone(), *v))
89            .collect()
90    }
91}
92
93impl<K, DB: DrawingBackend, O: Orientation<K, f64>> Drawable<DB> for BoxPlot<K, O> {
94    fn draw<I: Iterator<Item = <plotters::element::BackendCoordOnly as plotters::element::CoordMapper>::Output>>(
95        &self,
96        pos: I,
97        backend: &mut DB,
98        _parent_dim: (u32, u32),
99    ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
100        let points = pos.collect::<Vec<_>>();
101        let whisker_start_offset = |p| O::with_offset(p, (-self.whisker_width / 2.0) as i32);
102        let whisker_end_offset   = |p| O::with_offset(p, ( self.whisker_width / 2.0) as i32);
103        let bar_start_offset     = |p| O::with_offset(p, (-self.width / 2.0) as i32);
104        let bar_end_offset       = |p| O::with_offset(p, ( self.width / 2.0) as i32);
105
106        let lower_whisker_start = whisker_start_offset(points[0]);
107        let lower_whisker_end = whisker_end_offset(points[0]);
108        let lower_quartile_start = bar_start_offset(points[1]);
109        let lower_quartile_end = bar_end_offset(points[1]);
110        let median_start = bar_start_offset(points[2]);
111        let median_end = bar_end_offset(points[2]);
112        let upper_quartile_start = bar_start_offset(points[3]);
113        let upper_quartile_end = bar_end_offset(points[3]);
114        let upper_whisker_start = whisker_start_offset(points[4]);
115        let upper_whisker_end = whisker_end_offset(points[4]);
116
117        backend.draw_line(lower_whisker_start, lower_whisker_end, &self.line_style)?;
118        backend.draw_line(points[0], points[1], &self.line_style)?;
119        
120        let corners = [
121            lower_quartile_start,
122            lower_quartile_end,
123            upper_quartile_start,
124            upper_quartile_end
125        ];
126
127        let x_min = corners.iter().map(|p| p.0).min().unwrap();
128        let x_max = corners.iter().map(|p| p.0).max().unwrap();
129        let y_min = corners.iter().map(|p| p.1).min().unwrap();
130        let y_max = corners.iter().map(|p| p.1).max().unwrap();
131
132        backend.draw_rect((x_min, y_min), (x_max, y_max), &self.box_style, true)?;
133        backend.draw_rect((x_min, y_min), (x_max, y_max), &self.line_style, false)?;
134        backend.draw_line(median_start, median_end, &self.line_style)?;
135
136        backend.draw_line(points[3], points[4], &self.line_style)?;
137        backend.draw_line(upper_whisker_start, upper_whisker_end, &self.line_style)?;
138
139        Ok(())
140    }
141}