summaryrefslogtreecommitdiffstats
path: root/vendor/plotters/src/element/boxplot.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/plotters/src/element/boxplot.rs')
-rw-r--r--vendor/plotters/src/element/boxplot.rs288
1 files changed, 288 insertions, 0 deletions
diff --git a/vendor/plotters/src/element/boxplot.rs b/vendor/plotters/src/element/boxplot.rs
new file mode 100644
index 000000000..2de2bd062
--- /dev/null
+++ b/vendor/plotters/src/element/boxplot.rs
@@ -0,0 +1,288 @@
+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<K, V> {
+ 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<K, V>(PhantomData<(K, V)>);
+
+/// The horizontal boxplot phantom
+pub struct BoxplotOrientH<K, V>(PhantomData<(K, V)>);
+
+impl<K, V> BoxplotOrient<K, V> for BoxplotOrientV<K, V> {
+ 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<K, V> BoxplotOrient<K, V> for BoxplotOrientH<K, V> {
+ 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<K, O: BoxplotOrient<K, f32>> {
+ style: ShapeStyle,
+ width: u32,
+ whisker_width: f64,
+ offset: f64,
+ key: K,
+ values: [f32; 5],
+ _p: PhantomData<O>,
+}
+
+impl<K: Clone> Boxplot<K, BoxplotOrientV<K, f32>> {
+ /// 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::<ShapeStyle>::into(&BLACK),
+ width: DEFAULT_WIDTH,
+ whisker_width: 1.0,
+ offset: 0.0,
+ key,
+ values: quartiles.values(),
+ _p: PhantomData,
+ }
+ }
+}
+
+impl<K: Clone> Boxplot<K, BoxplotOrientH<K, f32>> {
+ /// 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::<ShapeStyle>::into(&BLACK),
+ width: DEFAULT_WIDTH,
+ whisker_width: 1.0,
+ offset: 0.0,
+ key,
+ values: quartiles.values(),
+ _p: PhantomData,
+ }
+ }
+}
+
+impl<K, O: BoxplotOrient<K, f32>> Boxplot<K, O> {
+ /// 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<S: Into<ShapeStyle>>(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<T: Into<f64> + Copy>(mut self, offset: T) -> Self {
+ self.offset = offset.into();
+ self
+ }
+}
+
+impl<'a, K: Clone, O: BoxplotOrient<K, f32>> PointCollection<'a, (O::XType, O::YType)>
+ for &'a Boxplot<K, O>
+{
+ type Point = (O::XType, O::YType);
+ type IntoIter = Vec<Self::Point>;
+ fn point_iter(self) -> Self::IntoIter {
+ self.values
+ .iter()
+ .map(|v| O::make_coord(self.key.clone(), *v))
+ .collect()
+ }
+}
+
+impl<K, DB: DrawingBackend, O: BoxplotOrient<K, f32>> Drawable<DB> for Boxplot<K, O> {
+ fn draw<I: Iterator<Item = BackendCoord>>(
+ &self,
+ points: I,
+ backend: &mut DB,
+ _: (u32, u32),
+ ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
+ 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());
+ }
+}