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