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 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 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 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 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 pub fn with_width(&mut self, width: f64) -> &mut Self {
68 self.width = width;
69 self
70 }
71
72 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}