use std::marker::PhantomData; use crate::data::Quartiles; use crate::element::{Drawable, PointCollection}; use crate::style::{Color, ShapeStyle, BLACK}; use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; /// The boxplot orientation trait pub trait BoxplotOrient { type XType; type YType; fn make_coord(key: K, val: V) -> (Self::XType, Self::YType); fn with_offset(coord: BackendCoord, offset: f64) -> BackendCoord; } /// The vertical boxplot phantom pub struct BoxplotOrientV(PhantomData<(K, V)>); /// The horizontal boxplot phantom pub struct BoxplotOrientH(PhantomData<(K, V)>); impl BoxplotOrient for BoxplotOrientV { type XType = K; type YType = V; fn make_coord(key: K, val: V) -> (K, V) { (key, val) } fn with_offset(coord: BackendCoord, offset: f64) -> BackendCoord { (coord.0 + offset as i32, coord.1) } } impl BoxplotOrient for BoxplotOrientH { type XType = V; type YType = K; fn make_coord(key: K, val: V) -> (V, K) { (val, key) } fn with_offset(coord: BackendCoord, offset: f64) -> BackendCoord { (coord.0, coord.1 + offset as i32) } } const DEFAULT_WIDTH: u32 = 10; /// The boxplot element pub struct Boxplot> { style: ShapeStyle, width: u32, whisker_width: f64, offset: f64, key: K, values: [f32; 5], _p: PhantomData, } impl Boxplot> { /// Create a new vertical boxplot element. /// /// - `key`: The key (the X axis value) /// - `quartiles`: The quartiles values for the Y axis /// - **returns** The newly created boxplot element /// /// ```rust /// use plotters::prelude::*; /// /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); /// let plot = Boxplot::new_vertical("group", &quartiles); /// ``` pub fn new_vertical(key: K, quartiles: &Quartiles) -> Self { Self { style: Into::::into(&BLACK), width: DEFAULT_WIDTH, whisker_width: 1.0, offset: 0.0, key, values: quartiles.values(), _p: PhantomData, } } } impl Boxplot> { /// Create a new horizontal boxplot element. /// /// - `key`: The key (the Y axis value) /// - `quartiles`: The quartiles values for the X axis /// - **returns** The newly created boxplot element /// /// ```rust /// use plotters::prelude::*; /// /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); /// let plot = Boxplot::new_horizontal("group", &quartiles); /// ``` pub fn new_horizontal(key: K, quartiles: &Quartiles) -> Self { Self { style: Into::::into(&BLACK), width: DEFAULT_WIDTH, whisker_width: 1.0, offset: 0.0, key, values: quartiles.values(), _p: PhantomData, } } } impl> Boxplot { /// Set the style of the boxplot. /// /// - `S`: The required style /// - **returns** The up-to-dated boxplot element /// /// ```rust /// use plotters::prelude::*; /// /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); /// let plot = Boxplot::new_horizontal("group", &quartiles).style(&BLUE); /// ``` pub fn style>(mut self, style: S) -> Self { self.style = style.into(); self } /// Set the bar width. /// /// - `width`: The required width /// - **returns** The up-to-dated boxplot element /// /// ```rust /// use plotters::prelude::*; /// /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); /// let plot = Boxplot::new_horizontal("group", &quartiles).width(10); /// ``` pub fn width(mut self, width: u32) -> Self { self.width = width; self } /// Set the width of the whiskers as a fraction of the bar width. /// /// - `whisker_width`: The required fraction /// - **returns** The up-to-dated boxplot element /// /// ```rust /// use plotters::prelude::*; /// /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); /// let plot = Boxplot::new_horizontal("group", &quartiles).whisker_width(0.5); /// ``` pub fn whisker_width(mut self, whisker_width: f64) -> Self { self.whisker_width = whisker_width; self } /// Set the element offset on the key axis. /// /// - `offset`: The required offset (on the X axis for vertical, on the Y axis for horizontal) /// - **returns** The up-to-dated boxplot element /// /// ```rust /// use plotters::prelude::*; /// /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); /// let plot = Boxplot::new_horizontal("group", &quartiles).offset(-5); /// ``` pub fn offset + Copy>(mut self, offset: T) -> Self { self.offset = offset.into(); self } } impl<'a, K: Clone, O: BoxplotOrient> PointCollection<'a, (O::XType, O::YType)> for &'a Boxplot { type Point = (O::XType, O::YType); type IntoIter = Vec; fn point_iter(self) -> Self::IntoIter { self.values .iter() .map(|v| O::make_coord(self.key.clone(), *v)) .collect() } } impl> Drawable for Boxplot { fn draw>( &self, points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind> { let points: Vec<_> = points.take(5).collect(); if points.len() == 5 { let width = f64::from(self.width); let moved = |coord| O::with_offset(coord, self.offset); let start_bar = |coord| O::with_offset(moved(coord), -width / 2.0); let end_bar = |coord| O::with_offset(moved(coord), width / 2.0); let start_whisker = |coord| O::with_offset(moved(coord), -width * self.whisker_width / 2.0); let end_whisker = |coord| O::with_offset(moved(coord), width * self.whisker_width / 2.0); // |---[ | ]----| // ^________________ backend.draw_line( start_whisker(points[0]), end_whisker(points[0]), &self.style, )?; // |---[ | ]----| // _^^^_____________ backend.draw_line( moved(points[0]), moved(points[1]), &self.style.color.to_backend_color(), )?; // |---[ | ]----| // ____^______^_____ let corner1 = start_bar(points[3]); let corner2 = end_bar(points[1]); let upper_left = (corner1.0.min(corner2.0), corner1.1.min(corner2.1)); let bottom_right = (corner1.0.max(corner2.0), corner1.1.max(corner2.1)); backend.draw_rect(upper_left, bottom_right, &self.style, false)?; // |---[ | ]----| // ________^________ backend.draw_line(start_bar(points[2]), end_bar(points[2]), &self.style)?; // |---[ | ]----| // ____________^^^^_ backend.draw_line(moved(points[3]), moved(points[4]), &self.style)?; // |---[ | ]----| // ________________^ backend.draw_line( start_whisker(points[4]), end_whisker(points[4]), &self.style, )?; } Ok(()) } } #[cfg(test)] mod test { use super::*; use crate::prelude::*; #[test] fn test_draw_v() { let root = MockedBackend::new(1024, 768).into_drawing_area(); let chart = ChartBuilder::on(&root) .build_cartesian_2d(0..2, 0f32..100f32) .unwrap(); let values = Quartiles::new(&[6]); assert!(chart .plotting_area() .draw(&Boxplot::new_vertical(1, &values)) .is_ok()); } #[test] fn test_draw_h() { let root = MockedBackend::new(1024, 768).into_drawing_area(); let chart = ChartBuilder::on(&root) .build_cartesian_2d(0f32..100f32, 0..2) .unwrap(); let values = Quartiles::new(&[6]); assert!(chart .plotting_area() .draw(&Boxplot::new_horizontal(1, &values)) .is_ok()); } }