1use std::marker::PhantomData;
2
3use crate::data::Quartiles;
4use crate::element::{Drawable, PointCollection};
5use crate::style::{Color, 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
222 backend.draw_line(
223 moved(points[0]),
224 moved(points[1]),
225 &self.style.color.to_backend_color(),
226 )?;
227
228 // |---[ | ]----|
229 // ____^______^_____
230 let corner1 = start_bar(points[3]);
231 let corner2 = end_bar(points[1]);
232 let upper_left = (corner1.0.min(corner2.0), corner1.1.min(corner2.1));
233 let bottom_right = (corner1.0.max(corner2.0), corner1.1.max(corner2.1));
234 backend.draw_rect(upper_left, bottom_right, &self.style, false)?;
235
236 // |---[ | ]----|
237 // ________^________
238 backend.draw_line(start_bar(points[2]), end_bar(points[2]), &self.style)?;
239
240 // |---[ | ]----|
241 // ____________^^^^_
242 backend.draw_line(moved(points[3]), moved(points[4]), &self.style)?;
243
244 // |---[ | ]----|
245 // ________________^
246 backend.draw_line(
247 start_whisker(points[4]),
248 end_whisker(points[4]),
249 &self.style,
250 )?;
251 }
252 Ok(())
253 }
254}
255
256#[cfg(test)]
257mod test {
258 use super::*;
259 use crate::prelude::*;
260
261 #[test]
262 fn test_draw_v() {
263 let root = MockedBackend::new(1024, 768).into_drawing_area();
264 let chart = ChartBuilder::on(&root)
265 .build_cartesian_2d(0..2, 0f32..100f32)
266 .unwrap();
267
268 let values = Quartiles::new(&[6]);
269 assert!(chart
270 .plotting_area()
271 .draw(&Boxplot::new_vertical(1, &values))
272 .is_ok());
273 }
274
275 #[test]
276 fn test_draw_h() {
277 let root = MockedBackend::new(1024, 768).into_drawing_area();
278 let chart = ChartBuilder::on(&root)
279 .build_cartesian_2d(0f32..100f32, 0..2)
280 .unwrap();
281
282 let values = Quartiles::new(&[6]);
283 assert!(chart
284 .plotting_area()
285 .draw(&Boxplot::new_horizontal(1, &values))
286 .is_ok());
287 }
288}
289