1 use std::marker::PhantomData; 2 3 use crate::data::Quartiles; 4 use crate::element::{Drawable, PointCollection}; 5 use crate::style::{Color, 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 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)] 257 mod test { 258 use super::*; 259 use crate::prelude::*; 260 261 #[test] test_draw_v()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] test_draw_h()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