diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
commit | 9835e2ae736235810b4ea1c162ca5e65c547e770 (patch) | |
tree | 3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/plotters/src/element | |
parent | Releasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff) | |
download | rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip |
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/plotters/src/element')
-rw-r--r-- | vendor/plotters/src/element/basic_shapes.rs | 358 | ||||
-rw-r--r-- | vendor/plotters/src/element/basic_shapes_3d.rs | 108 | ||||
-rw-r--r-- | vendor/plotters/src/element/boxplot.rs | 288 | ||||
-rw-r--r-- | vendor/plotters/src/element/candlestick.rs | 100 | ||||
-rw-r--r-- | vendor/plotters/src/element/composable.rs | 242 | ||||
-rw-r--r-- | vendor/plotters/src/element/dynelem.rs | 84 | ||||
-rw-r--r-- | vendor/plotters/src/element/errorbar.rs | 223 | ||||
-rw-r--r-- | vendor/plotters/src/element/image.rs | 228 | ||||
-rw-r--r-- | vendor/plotters/src/element/mod.rs | 290 | ||||
-rw-r--r-- | vendor/plotters/src/element/pie.rs | 240 | ||||
-rw-r--r-- | vendor/plotters/src/element/points.rs | 154 | ||||
-rw-r--r-- | vendor/plotters/src/element/text.rs | 242 |
12 files changed, 2557 insertions, 0 deletions
diff --git a/vendor/plotters/src/element/basic_shapes.rs b/vendor/plotters/src/element/basic_shapes.rs new file mode 100644 index 000000000..7c0b9d4f5 --- /dev/null +++ b/vendor/plotters/src/element/basic_shapes.rs @@ -0,0 +1,358 @@ +use super::{Drawable, PointCollection}; +use crate::style::{Color, ShapeStyle, SizeDesc}; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +/** +An element representing a single pixel. + +See [`crate::element::EmptyElement`] for more information and examples. +*/ +pub struct Pixel<Coord> { + pos: Coord, + style: ShapeStyle, +} + +impl<Coord> Pixel<Coord> { + /** + Creates a new pixel. + + See [`crate::element::EmptyElement`] for more information and examples. + */ + pub fn new<P: Into<Coord>, S: Into<ShapeStyle>>(pos: P, style: S) -> Self { + Self { + pos: pos.into(), + style: style.into(), + } + } +} + +impl<'a, Coord> PointCollection<'a, Coord> for &'a Pixel<Coord> { + type Point = &'a Coord; + type IntoIter = std::iter::Once<&'a Coord>; + fn point_iter(self) -> Self::IntoIter { + std::iter::once(&self.pos) + } +} + +impl<Coord, DB: DrawingBackend> Drawable<DB> for Pixel<Coord> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some((x, y)) = points.next() { + return backend.draw_pixel((x, y), self.style.color.to_backend_color()); + } + Ok(()) + } +} + +#[cfg(test)] +#[test] +fn test_pixel_element() { + use crate::prelude::*; + let da = crate::create_mocked_drawing_area(300, 300, |m| { + m.check_draw_pixel(|c, (x, y)| { + assert_eq!(x, 150); + assert_eq!(y, 152); + assert_eq!(c, RED.to_rgba()); + }); + + m.drop_check(|b| { + assert_eq!(b.num_draw_pixel_call, 1); + assert_eq!(b.draw_count, 1); + }); + }); + da.draw(&Pixel::new((150, 152), &RED)) + .expect("Drawing Failure"); +} + +/// This is a deprecated type. Please use new name [`PathElement`] instead. +#[deprecated(note = "Use new name PathElement instead")] +pub type Path<Coord> = PathElement<Coord>; + +/// An element of a series of connected lines +pub struct PathElement<Coord> { + points: Vec<Coord>, + style: ShapeStyle, +} +impl<Coord> PathElement<Coord> { + /// Create a new path + /// - `points`: The iterator of the points + /// - `style`: The shape style + /// - returns the created element + pub fn new<P: Into<Vec<Coord>>, S: Into<ShapeStyle>>(points: P, style: S) -> Self { + Self { + points: points.into(), + style: style.into(), + } + } +} + +impl<'a, Coord> PointCollection<'a, Coord> for &'a PathElement<Coord> { + type Point = &'a Coord; + type IntoIter = &'a [Coord]; + fn point_iter(self) -> &'a [Coord] { + &self.points + } +} + +impl<Coord, DB: DrawingBackend> Drawable<DB> for PathElement<Coord> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + backend.draw_path(points, &self.style) + } +} + +#[cfg(test)] +#[test] +fn test_path_element() { + use crate::prelude::*; + let da = crate::create_mocked_drawing_area(300, 300, |m| { + m.check_draw_path(|c, s, path| { + assert_eq!(c, BLUE.to_rgba()); + assert_eq!(s, 5); + assert_eq!(path, vec![(100, 101), (105, 107), (150, 157)]); + }); + m.drop_check(|b| { + assert_eq!(b.num_draw_path_call, 1); + assert_eq!(b.draw_count, 1); + }); + }); + da.draw(&PathElement::new( + vec![(100, 101), (105, 107), (150, 157)], + Into::<ShapeStyle>::into(&BLUE).stroke_width(5), + )) + .expect("Drawing Failure"); +} + +/// A rectangle element +pub struct Rectangle<Coord> { + points: [Coord; 2], + style: ShapeStyle, + margin: (u32, u32, u32, u32), +} + +impl<Coord> Rectangle<Coord> { + /// Create a new path + /// - `points`: The left upper and right lower corner of the rectangle + /// - `style`: The shape style + /// - returns the created element + pub fn new<S: Into<ShapeStyle>>(points: [Coord; 2], style: S) -> Self { + Self { + points, + style: style.into(), + margin: (0, 0, 0, 0), + } + } + + /// Set the margin of the rectangle + /// - `t`: The top margin + /// - `b`: The bottom margin + /// - `l`: The left margin + /// - `r`: The right margin + pub fn set_margin(&mut self, t: u32, b: u32, l: u32, r: u32) -> &mut Self { + self.margin = (t, b, l, r); + self + } +} + +impl<'a, Coord> PointCollection<'a, Coord> for &'a Rectangle<Coord> { + type Point = &'a Coord; + type IntoIter = &'a [Coord]; + fn point_iter(self) -> &'a [Coord] { + &self.points + } +} + +impl<Coord, DB: DrawingBackend> Drawable<DB> for Rectangle<Coord> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + match (points.next(), points.next()) { + (Some(a), Some(b)) => { + let (mut a, mut b) = ((a.0.min(b.0), a.1.min(b.1)), (a.0.max(b.0), a.1.max(b.1))); + a.1 += self.margin.0 as i32; + b.1 -= self.margin.1 as i32; + a.0 += self.margin.2 as i32; + b.0 -= self.margin.3 as i32; + backend.draw_rect(a, b, &self.style, self.style.filled) + } + _ => Ok(()), + } + } +} + +#[cfg(test)] +#[test] +fn test_rect_element() { + use crate::prelude::*; + { + let da = crate::create_mocked_drawing_area(300, 300, |m| { + m.check_draw_rect(|c, s, f, u, d| { + assert_eq!(c, BLUE.to_rgba()); + assert_eq!(f, false); + assert_eq!(s, 5); + assert_eq!([u, d], [(100, 101), (105, 107)]); + }); + m.drop_check(|b| { + assert_eq!(b.num_draw_rect_call, 1); + assert_eq!(b.draw_count, 1); + }); + }); + da.draw(&Rectangle::new( + [(100, 101), (105, 107)], + Color::stroke_width(&BLUE, 5), + )) + .expect("Drawing Failure"); + } + + { + let da = crate::create_mocked_drawing_area(300, 300, |m| { + m.check_draw_rect(|c, _, f, u, d| { + assert_eq!(c, BLUE.to_rgba()); + assert_eq!(f, true); + assert_eq!([u, d], [(100, 101), (105, 107)]); + }); + m.drop_check(|b| { + assert_eq!(b.num_draw_rect_call, 1); + assert_eq!(b.draw_count, 1); + }); + }); + da.draw(&Rectangle::new([(100, 101), (105, 107)], BLUE.filled())) + .expect("Drawing Failure"); + } +} + +/// A circle element +pub struct Circle<Coord, Size: SizeDesc> { + center: Coord, + size: Size, + style: ShapeStyle, +} + +impl<Coord, Size: SizeDesc> Circle<Coord, Size> { + /// Create a new circle element + /// - `coord` The center of the circle + /// - `size` The radius of the circle + /// - `style` The style of the circle + /// - Return: The newly created circle element + pub fn new<S: Into<ShapeStyle>>(coord: Coord, size: Size, style: S) -> Self { + Self { + center: coord, + size, + style: style.into(), + } + } +} + +impl<'a, Coord, Size: SizeDesc> PointCollection<'a, Coord> for &'a Circle<Coord, Size> { + type Point = &'a Coord; + type IntoIter = std::iter::Once<&'a Coord>; + fn point_iter(self) -> std::iter::Once<&'a Coord> { + std::iter::once(&self.center) + } +} + +impl<Coord, DB: DrawingBackend, Size: SizeDesc> Drawable<DB> for Circle<Coord, Size> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut points: I, + backend: &mut DB, + ps: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some((x, y)) = points.next() { + let size = self.size.in_pixels(&ps).max(0) as u32; + return backend.draw_circle((x, y), size, &self.style, self.style.filled); + } + Ok(()) + } +} + +#[cfg(test)] +#[test] +fn test_circle_element() { + use crate::prelude::*; + let da = crate::create_mocked_drawing_area(300, 300, |m| { + m.check_draw_circle(|c, _, f, s, r| { + assert_eq!(c, BLUE.to_rgba()); + assert_eq!(f, false); + assert_eq!(s, (150, 151)); + assert_eq!(r, 20); + }); + m.drop_check(|b| { + assert_eq!(b.num_draw_circle_call, 1); + assert_eq!(b.draw_count, 1); + }); + }); + da.draw(&Circle::new((150, 151), 20, &BLUE)) + .expect("Drawing Failure"); +} + +/// An element of a filled polygon +pub struct Polygon<Coord> { + points: Vec<Coord>, + style: ShapeStyle, +} +impl<Coord> Polygon<Coord> { + /// Create a new polygon + /// - `points`: The iterator of the points + /// - `style`: The shape style + /// - returns the created element + pub fn new<P: Into<Vec<Coord>>, S: Into<ShapeStyle>>(points: P, style: S) -> Self { + Self { + points: points.into(), + style: style.into(), + } + } +} + +impl<'a, Coord> PointCollection<'a, Coord> for &'a Polygon<Coord> { + type Point = &'a Coord; + type IntoIter = &'a [Coord]; + fn point_iter(self) -> &'a [Coord] { + &self.points + } +} + +impl<Coord, DB: DrawingBackend> Drawable<DB> for Polygon<Coord> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + backend.fill_polygon(points, &self.style.color.to_backend_color()) + } +} + +#[cfg(test)] +#[test] +fn test_polygon_element() { + use crate::prelude::*; + let points = vec![(100, 100), (50, 500), (300, 400), (200, 300), (550, 200)]; + let expected_points = points.clone(); + + let da = crate::create_mocked_drawing_area(800, 800, |m| { + m.check_fill_polygon(move |c, p| { + assert_eq!(c, BLUE.to_rgba()); + assert_eq!(expected_points.len(), p.len()); + assert_eq!(expected_points, p); + }); + m.drop_check(|b| { + assert_eq!(b.num_fill_polygon_call, 1); + assert_eq!(b.draw_count, 1); + }); + }); + + da.draw(&Polygon::new(points.clone(), &BLUE)) + .expect("Drawing Failure"); +} diff --git a/vendor/plotters/src/element/basic_shapes_3d.rs b/vendor/plotters/src/element/basic_shapes_3d.rs new file mode 100644 index 000000000..97b15e62d --- /dev/null +++ b/vendor/plotters/src/element/basic_shapes_3d.rs @@ -0,0 +1,108 @@ +use super::{BackendCoordAndZ, Drawable, PointCollection}; +use crate::style::ShapeStyle; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +/** +Represents a cuboid, a six-faced solid. + +# Examples + +``` +use plotters::prelude::*; +let drawing_area = SVGBackend::new("cuboid.svg", (300, 200)).into_drawing_area(); +drawing_area.fill(&WHITE).unwrap(); +let mut chart_builder = ChartBuilder::on(&drawing_area); +let mut chart_context = chart_builder.margin(20).build_cartesian_3d(0.0..3.5, 0.0..2.5, 0.0..1.5).unwrap(); +chart_context.configure_axes().x_labels(4).y_labels(3).z_labels(2).draw().unwrap(); +let cubiod = Cubiod::new([(0.,0.,0.), (3.,2.,1.)], BLUE.mix(0.2), BLUE); +chart_context.draw_series(std::iter::once(cubiod)).unwrap(); +``` + +The result is a semi-transparent cuboid with blue edges: + +![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@b6703f7/apidoc/cuboid.svg) +*/ +pub struct Cubiod<X, Y, Z> { + face_style: ShapeStyle, + edge_style: ShapeStyle, + vert: [(X, Y, Z); 8], +} + +impl<X: Clone, Y: Clone, Z: Clone> Cubiod<X, Y, Z> { + /** + Creates a cuboid. + + See [`Cubiod`] for more information and examples. + */ + #[allow(clippy::redundant_clone)] + pub fn new<FS: Into<ShapeStyle>, ES: Into<ShapeStyle>>( + [(x0, y0, z0), (x1, y1, z1)]: [(X, Y, Z); 2], + face_style: FS, + edge_style: ES, + ) -> Self { + Self { + face_style: face_style.into(), + edge_style: edge_style.into(), + vert: [ + (x0.clone(), y0.clone(), z0.clone()), + (x0.clone(), y0.clone(), z1.clone()), + (x0.clone(), y1.clone(), z0.clone()), + (x0.clone(), y1.clone(), z1.clone()), + (x1.clone(), y0.clone(), z0.clone()), + (x1.clone(), y0.clone(), z1.clone()), + (x1.clone(), y1.clone(), z0.clone()), + (x1.clone(), y1.clone(), z1.clone()), + ], + } + } +} + +impl<'a, X: 'a, Y: 'a, Z: 'a> PointCollection<'a, (X, Y, Z), BackendCoordAndZ> + for &'a Cubiod<X, Y, Z> +{ + type Point = &'a (X, Y, Z); + type IntoIter = &'a [(X, Y, Z)]; + fn point_iter(self) -> Self::IntoIter { + &self.vert + } +} + +impl<X, Y, Z, DB: DrawingBackend> Drawable<DB, BackendCoordAndZ> for Cubiod<X, Y, Z> { + fn draw<I: Iterator<Item = (BackendCoord, i32)>>( + &self, + points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + let vert: Vec<_> = points.collect(); + let mut polygon = vec![]; + for mask in [1, 2, 4].iter().cloned() { + let mask_a = if mask == 4 { 1 } else { mask * 2 }; + let mask_b = if mask == 1 { 4 } else { mask / 2 }; + let a = 0; + let b = a | mask_a; + let c = a | mask_a | mask_b; + let d = a | mask_b; + polygon.push([vert[a], vert[b], vert[c], vert[d]]); + polygon.push([ + vert[a | mask], + vert[b | mask], + vert[c | mask], + vert[d | mask], + ]); + } + polygon.sort_by_cached_key(|t| std::cmp::Reverse(t[0].1 + t[1].1 + t[2].1 + t[3].1)); + + for p in polygon { + backend.fill_polygon(p.iter().map(|(coord, _)| *coord), &self.face_style)?; + backend.draw_path( + p.iter() + .map(|(coord, _)| *coord) + .chain(std::iter::once(p[0].0)), + &self.edge_style, + )?; + } + + Ok(()) + } +} 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()); + } +} diff --git a/vendor/plotters/src/element/candlestick.rs b/vendor/plotters/src/element/candlestick.rs new file mode 100644 index 000000000..e28645431 --- /dev/null +++ b/vendor/plotters/src/element/candlestick.rs @@ -0,0 +1,100 @@ +/*! + The candlestick element, which showing the high/low/open/close price +*/ + +use std::cmp::Ordering; + +use crate::element::{Drawable, PointCollection}; +use crate::style::ShapeStyle; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +/// The candlestick data point element +pub struct CandleStick<X, Y: PartialOrd> { + style: ShapeStyle, + width: u32, + points: [(X, Y); 4], +} + +impl<X: Clone, Y: PartialOrd> CandleStick<X, Y> { + /// Create a new candlestick element, which requires the Y coordinate can be compared + /// + /// - `x`: The x coordinate + /// - `open`: The open value + /// - `high`: The high value + /// - `low`: The low value + /// - `close`: The close value + /// - `gain_style`: The style for gain + /// - `loss_style`: The style for loss + /// - `width`: The width + /// - **returns** The newly created candlestick element + /// + /// ```rust + /// use chrono::prelude::*; + /// use plotters::prelude::*; + /// + /// let candlestick = CandleStick::new(Local::now(), 130.0600, 131.3700, 128.8300, 129.1500, &GREEN, &RED, 15); + /// ``` + #[allow(clippy::too_many_arguments)] + pub fn new<GS: Into<ShapeStyle>, LS: Into<ShapeStyle>>( + x: X, + open: Y, + high: Y, + low: Y, + close: Y, + gain_style: GS, + loss_style: LS, + width: u32, + ) -> Self { + Self { + style: match open.partial_cmp(&close) { + Some(Ordering::Less) => gain_style.into(), + _ => loss_style.into(), + }, + width, + points: [ + (x.clone(), open), + (x.clone(), high), + (x.clone(), low), + (x, close), + ], + } + } +} + +impl<'a, X: 'a, Y: PartialOrd + 'a> PointCollection<'a, (X, Y)> for &'a CandleStick<X, Y> { + type Point = &'a (X, Y); + type IntoIter = &'a [(X, Y)]; + fn point_iter(self) -> &'a [(X, Y)] { + &self.points + } +} + +impl<X, Y: PartialOrd, DB: DrawingBackend> Drawable<DB> for CandleStick<X, Y> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + let mut points: Vec<_> = points.take(4).collect(); + if points.len() == 4 { + let fill = self.style.filled; + if points[0].1 > points[3].1 { + points.swap(0, 3); + } + let (l, r) = ( + self.width as i32 / 2, + self.width as i32 - self.width as i32 / 2, + ); + + backend.draw_line(points[0], points[1], &self.style)?; + backend.draw_line(points[2], points[3], &self.style)?; + + points[0].0 -= l; + points[3].0 += r; + + backend.draw_rect(points[0], points[3], &self.style, fill)?; + } + Ok(()) + } +} diff --git a/vendor/plotters/src/element/composable.rs b/vendor/plotters/src/element/composable.rs new file mode 100644 index 000000000..d79c505c8 --- /dev/null +++ b/vendor/plotters/src/element/composable.rs @@ -0,0 +1,242 @@ +use super::*; +use plotters_backend::DrawingBackend; +use std::borrow::Borrow; +use std::iter::{once, Once}; +use std::marker::PhantomData; +use std::ops::Add; + +/** +An empty composable element. This is the starting point of a composed element. + +# Example + +``` +use plotters::prelude::*; +let data = [(1.0, 3.3), (2., 2.1), (3., 1.5), (4., 1.9), (5., 1.0)]; +let drawing_area = SVGBackend::new("composable.svg", (300, 200)).into_drawing_area(); +drawing_area.fill(&WHITE).unwrap(); +let mut chart_builder = ChartBuilder::on(&drawing_area); +chart_builder.margin(7).set_left_and_bottom_label_area_size(20); +let mut chart_context = chart_builder.build_cartesian_2d(0.0..5.5, 0.0..5.5).unwrap(); +chart_context.configure_mesh().draw().unwrap(); +chart_context.draw_series(data.map(|(x, y)| { + EmptyElement::at((x, y)) // Use the guest coordinate system with EmptyElement + + Circle::new((0, 0), 10, BLUE) // Use backend coordinates with the rest + + Cross::new((4, 4), 3, RED) + + Pixel::new((4, -4), RED) + + TriangleMarker::new((-4, -4), 4, RED) +})).unwrap(); +``` + +The result is a data series where each point consists of a circle, a cross, a pixel, and a triangle: + +![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@06d370f/apidoc/composable.svg) + +*/ +pub struct EmptyElement<Coord, DB: DrawingBackend> { + coord: Coord, + phantom: PhantomData<DB>, +} + +impl<Coord, DB: DrawingBackend> EmptyElement<Coord, DB> { + /** + An empty composable element. This is the starting point of a composed element. + + See [`EmptyElement`] for more information and examples. + */ + pub fn at(coord: Coord) -> Self { + Self { + coord, + phantom: PhantomData, + } + } +} + +impl<Coord, Other, DB: DrawingBackend> Add<Other> for EmptyElement<Coord, DB> +where + Other: Drawable<DB>, + for<'a> &'a Other: PointCollection<'a, BackendCoord>, +{ + type Output = BoxedElement<Coord, DB, Other>; + fn add(self, other: Other) -> Self::Output { + BoxedElement { + offset: self.coord, + inner: other, + phantom: PhantomData, + } + } +} + +impl<'a, Coord, DB: DrawingBackend> PointCollection<'a, Coord> for &'a EmptyElement<Coord, DB> { + type Point = &'a Coord; + type IntoIter = Once<&'a Coord>; + fn point_iter(self) -> Self::IntoIter { + once(&self.coord) + } +} + +impl<Coord, DB: DrawingBackend> Drawable<DB> for EmptyElement<Coord, DB> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + _pos: I, + _backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + Ok(()) + } +} + +/** +A container for one drawable element, used for composition. + +This is used internally by Plotters and should probably not be included in user code. +See [`EmptyElement`] for more information and examples. +*/ +pub struct BoxedElement<Coord, DB: DrawingBackend, A: Drawable<DB>> { + inner: A, + offset: Coord, + phantom: PhantomData<DB>, +} + +impl<'b, Coord, DB: DrawingBackend, A: Drawable<DB>> PointCollection<'b, Coord> + for &'b BoxedElement<Coord, DB, A> +{ + type Point = &'b Coord; + type IntoIter = Once<&'b Coord>; + fn point_iter(self) -> Self::IntoIter { + once(&self.offset) + } +} + +impl<Coord, DB: DrawingBackend, A> Drawable<DB> for BoxedElement<Coord, DB, A> +where + for<'a> &'a A: PointCollection<'a, BackendCoord>, + A: Drawable<DB>, +{ + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut pos: I, + backend: &mut DB, + ps: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some((x0, y0)) = pos.next() { + self.inner.draw( + self.inner.point_iter().into_iter().map(|p| { + let p = p.borrow(); + (p.0 + x0, p.1 + y0) + }), + backend, + ps, + )?; + } + Ok(()) + } +} + +impl<Coord, DB: DrawingBackend, My, Yours> Add<Yours> for BoxedElement<Coord, DB, My> +where + My: Drawable<DB>, + for<'a> &'a My: PointCollection<'a, BackendCoord>, + Yours: Drawable<DB>, + for<'a> &'a Yours: PointCollection<'a, BackendCoord>, +{ + type Output = ComposedElement<Coord, DB, My, Yours>; + fn add(self, yours: Yours) -> Self::Output { + ComposedElement { + offset: self.offset, + first: self.inner, + second: yours, + phantom: PhantomData, + } + } +} + +/** +A container for two drawable elements, used for composition. + +This is used internally by Plotters and should probably not be included in user code. +See [`EmptyElement`] for more information and examples. +*/ +pub struct ComposedElement<Coord, DB: DrawingBackend, A, B> +where + A: Drawable<DB>, + B: Drawable<DB>, +{ + first: A, + second: B, + offset: Coord, + phantom: PhantomData<DB>, +} + +impl<'b, Coord, DB: DrawingBackend, A, B> PointCollection<'b, Coord> + for &'b ComposedElement<Coord, DB, A, B> +where + A: Drawable<DB>, + B: Drawable<DB>, +{ + type Point = &'b Coord; + type IntoIter = Once<&'b Coord>; + fn point_iter(self) -> Self::IntoIter { + once(&self.offset) + } +} + +impl<Coord, DB: DrawingBackend, A, B> Drawable<DB> for ComposedElement<Coord, DB, A, B> +where + for<'a> &'a A: PointCollection<'a, BackendCoord>, + for<'b> &'b B: PointCollection<'b, BackendCoord>, + A: Drawable<DB>, + B: Drawable<DB>, +{ + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut pos: I, + backend: &mut DB, + ps: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some((x0, y0)) = pos.next() { + self.first.draw( + self.first.point_iter().into_iter().map(|p| { + let p = p.borrow(); + (p.0 + x0, p.1 + y0) + }), + backend, + ps, + )?; + self.second.draw( + self.second.point_iter().into_iter().map(|p| { + let p = p.borrow(); + (p.0 + x0, p.1 + y0) + }), + backend, + ps, + )?; + } + Ok(()) + } +} + +impl<Coord, DB: DrawingBackend, A, B, C> Add<C> for ComposedElement<Coord, DB, A, B> +where + A: Drawable<DB>, + for<'a> &'a A: PointCollection<'a, BackendCoord>, + B: Drawable<DB>, + for<'a> &'a B: PointCollection<'a, BackendCoord>, + C: Drawable<DB>, + for<'a> &'a C: PointCollection<'a, BackendCoord>, +{ + type Output = ComposedElement<Coord, DB, A, ComposedElement<BackendCoord, DB, B, C>>; + fn add(self, rhs: C) -> Self::Output { + ComposedElement { + offset: self.offset, + first: self.first, + second: ComposedElement { + offset: (0, 0), + first: self.second, + second: rhs, + phantom: PhantomData, + }, + phantom: PhantomData, + } + } +} diff --git a/vendor/plotters/src/element/dynelem.rs b/vendor/plotters/src/element/dynelem.rs new file mode 100644 index 000000000..b2bd178ed --- /dev/null +++ b/vendor/plotters/src/element/dynelem.rs @@ -0,0 +1,84 @@ +use super::{Drawable, PointCollection}; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +use std::borrow::Borrow; + +trait DynDrawable<DB: DrawingBackend> { + fn draw_dyn( + &self, + points: &mut dyn Iterator<Item = BackendCoord>, + backend: &mut DB, + parent_dim: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>>; +} + +impl<DB: DrawingBackend, T: Drawable<DB>> DynDrawable<DB> for T { + fn draw_dyn( + &self, + points: &mut dyn Iterator<Item = BackendCoord>, + backend: &mut DB, + parent_dim: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + T::draw(self, points, backend, parent_dim) + } +} + +/// The container for a dynamically dispatched element +pub struct DynElement<'a, DB, Coord> +where + DB: DrawingBackend, + Coord: Clone, +{ + points: Vec<Coord>, + drawable: Box<dyn DynDrawable<DB> + 'a>, +} + +impl<'a, 'b: 'a, DB: DrawingBackend, Coord: Clone> PointCollection<'a, Coord> + for &'a DynElement<'b, DB, Coord> +{ + type Point = &'a Coord; + type IntoIter = &'a Vec<Coord>; + fn point_iter(self) -> Self::IntoIter { + &self.points + } +} + +impl<'a, DB: DrawingBackend, Coord: Clone> Drawable<DB> for DynElement<'a, DB, Coord> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut pos: I, + backend: &mut DB, + parent_dim: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + self.drawable.draw_dyn(&mut pos, backend, parent_dim) + } +} + +/// The trait that makes the conversion from the statically dispatched element +/// to the dynamically dispatched element +pub trait IntoDynElement<'a, DB: DrawingBackend, Coord: Clone> +where + Self: 'a, +{ + /// Make the conversion + fn into_dyn(self) -> DynElement<'a, DB, Coord>; +} + +impl<'b, T, DB, Coord> IntoDynElement<'b, DB, Coord> for T +where + T: Drawable<DB> + 'b, + for<'a> &'a T: PointCollection<'a, Coord>, + Coord: Clone, + DB: DrawingBackend, +{ + fn into_dyn(self) -> DynElement<'b, DB, Coord> { + DynElement { + points: self + .point_iter() + .into_iter() + .map(|x| x.borrow().clone()) + .collect(), + drawable: Box::new(self), + } + } +} diff --git a/vendor/plotters/src/element/errorbar.rs b/vendor/plotters/src/element/errorbar.rs new file mode 100644 index 000000000..4e0acf78d --- /dev/null +++ b/vendor/plotters/src/element/errorbar.rs @@ -0,0 +1,223 @@ +use std::marker::PhantomData; + +use crate::element::{Drawable, PointCollection}; +use crate::style::ShapeStyle; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +/** +Used to reuse code between horizontal and vertical error bars. + +This is used internally by Plotters and should probably not be included in user code. +See [`ErrorBar`] for more information and examples. +*/ +pub trait ErrorBarOrient<K, V> { + type XType; + type YType; + + fn make_coord(key: K, val: V) -> (Self::XType, Self::YType); + fn ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord); +} + +/** +Used for the production of horizontal error bars. + +This is used internally by Plotters and should probably not be included in user code. +See [`ErrorBar`] for more information and examples. +*/ +pub struct ErrorBarOrientH<K, V>(PhantomData<(K, V)>); + +/** +Used for the production of vertical error bars. + +This is used internally by Plotters and should probably not be included in user code. +See [`ErrorBar`] for more information and examples. +*/ +pub struct ErrorBarOrientV<K, V>(PhantomData<(K, V)>); + +impl<K, V> ErrorBarOrient<K, V> for ErrorBarOrientH<K, V> { + type XType = V; + type YType = K; + + fn make_coord(key: K, val: V) -> (V, K) { + (val, key) + } + + fn ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord) { + ( + (coord.0, coord.1 - w as i32 / 2), + (coord.0, coord.1 + w as i32 / 2), + ) + } +} + +impl<K, V> ErrorBarOrient<K, V> for ErrorBarOrientV<K, V> { + type XType = K; + type YType = V; + + fn make_coord(key: K, val: V) -> (K, V) { + (key, val) + } + + fn ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord) { + ( + (coord.0 - w as i32 / 2, coord.1), + (coord.0 + w as i32 / 2, coord.1), + ) + } +} + +/** +An error bar, which visualizes the minimum, average, and maximum of a dataset. + +Unlike [`crate::series::Histogram`], the `ErrorBar` code does not classify or aggregate data. +These operations must be done before building error bars. + +# Examples + +``` +use plotters::prelude::*; +let data = [(1.0, 3.3), (2., 2.1), (3., 1.5), (4., 1.9), (5., 1.0)]; +let drawing_area = SVGBackend::new("error_bars_vertical.svg", (300, 200)).into_drawing_area(); +drawing_area.fill(&WHITE).unwrap(); +let mut chart_builder = ChartBuilder::on(&drawing_area); +chart_builder.margin(10).set_left_and_bottom_label_area_size(20); +let mut chart_context = chart_builder.build_cartesian_2d(0.0..6.0, 0.0..6.0).unwrap(); +chart_context.configure_mesh().draw().unwrap(); +chart_context.draw_series(data.map(|(x, y)| { + ErrorBar::new_vertical(x, y - 0.4, y, y + 0.3, BLUE.filled(), 10) +})).unwrap(); +chart_context.draw_series(data.map(|(x, y)| { + ErrorBar::new_vertical(x, y + 1.0, y + 1.9, y + 2.4, RED, 10) +})).unwrap(); +``` + +This code produces two series of five error bars each, showing minima, maxima, and average values: + +![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@06d370f/apidoc/error_bars_vertical.svg) + +[`ErrorBar::new_vertical()`] is used to create vertical error bars. Here is an example using +[`ErrorBar::new_horizontal()`] instead: + +![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@06d370f/apidoc/error_bars_horizontal.svg) +*/ +pub struct ErrorBar<K, V, O: ErrorBarOrient<K, V>> { + style: ShapeStyle, + width: u32, + key: K, + values: [V; 3], + _p: PhantomData<O>, +} + +impl<K, V> ErrorBar<K, V, ErrorBarOrientV<K, V>> { + /** + Creates a vertical error bar. + ` + - `key`: Horizontal position of the bar + - `min`: Minimum of the data + - `avg`: Average of the data + - `max`: Maximum of the data + - `style`: Color, transparency, and fill of the error bar. See [`ShapeStyle`] for more information and examples. + - `width`: Width of the error marks in backend coordinates. + + See [`ErrorBar`] for more information and examples. + */ + pub fn new_vertical<S: Into<ShapeStyle>>( + key: K, + min: V, + avg: V, + max: V, + style: S, + width: u32, + ) -> Self { + Self { + style: style.into(), + width, + key, + values: [min, avg, max], + _p: PhantomData, + } + } +} + +impl<K, V> ErrorBar<K, V, ErrorBarOrientH<K, V>> { + /** + Creates a horizontal error bar. + + - `key`: Vertical position of the bar + - `min`: Minimum of the data + - `avg`: Average of the data + - `max`: Maximum of the data + - `style`: Color, transparency, and fill of the error bar. See [`ShapeStyle`] for more information and examples. + - `width`: Width of the error marks in backend coordinates. + + See [`ErrorBar`] for more information and examples. + */ + pub fn new_horizontal<S: Into<ShapeStyle>>( + key: K, + min: V, + avg: V, + max: V, + style: S, + width: u32, + ) -> Self { + Self { + style: style.into(), + width, + key, + values: [min, avg, max], + _p: PhantomData, + } + } +} + +impl<'a, K: Clone, V: Clone, O: ErrorBarOrient<K, V>> PointCollection<'a, (O::XType, O::YType)> + for &'a ErrorBar<K, V, 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.clone())) + .collect() + } +} + +impl<K, V, O: ErrorBarOrient<K, V>, DB: DrawingBackend> Drawable<DB> for ErrorBar<K, V, O> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + let points: Vec<_> = points.take(3).collect(); + + let (from, to) = O::ending_coord(points[0], self.width); + backend.draw_line(from, to, &self.style)?; + + let (from, to) = O::ending_coord(points[2], self.width); + backend.draw_line(from, to, &self.style)?; + + backend.draw_line(points[0], points[2], &self.style)?; + + backend.draw_circle(points[1], self.width / 2, &self.style, self.style.filled)?; + + Ok(()) + } +} + +#[cfg(test)] +#[test] +fn test_preserve_stroke_width() { + let v = ErrorBar::new_vertical(100, 20, 50, 70, WHITE.filled().stroke_width(5), 3); + let h = ErrorBar::new_horizontal(100, 20, 50, 70, WHITE.filled().stroke_width(5), 3); + + use crate::prelude::*; + let da = crate::create_mocked_drawing_area(300, 300, |m| { + m.check_draw_line(|_, w, _, _| { + assert_eq!(w, 5); + }); + }); + da.draw(&h).expect("Drawing Failure"); + da.draw(&v).expect("Drawing Failure"); +} diff --git a/vendor/plotters/src/element/image.rs b/vendor/plotters/src/element/image.rs new file mode 100644 index 000000000..f50ce77bc --- /dev/null +++ b/vendor/plotters/src/element/image.rs @@ -0,0 +1,228 @@ +#[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + feature = "image" +))] +use image::{DynamicImage, GenericImageView}; + +use super::{Drawable, PointCollection}; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +use plotters_bitmap::bitmap_pixel::{PixelFormat, RGBPixel}; + +#[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + feature = "image" +))] +use plotters_bitmap::bitmap_pixel::BGRXPixel; + +use plotters_bitmap::BitMapBackend; + +use std::borrow::Borrow; +use std::marker::PhantomData; + +enum Buffer<'a> { + Owned(Vec<u8>), + Borrowed(&'a [u8]), + BorrowedMut(&'a mut [u8]), +} + +impl<'a> Borrow<[u8]> for Buffer<'a> { + fn borrow(&self) -> &[u8] { + self.as_ref() + } +} + +impl AsRef<[u8]> for Buffer<'_> { + fn as_ref(&self) -> &[u8] { + match self { + Buffer::Owned(owned) => owned.as_ref(), + Buffer::Borrowed(target) => target, + Buffer::BorrowedMut(target) => target, + } + } +} + +impl<'a> Buffer<'a> { + fn to_mut(&mut self) -> &mut [u8] { + let owned = match self { + Buffer::Owned(owned) => return &mut owned[..], + Buffer::BorrowedMut(target) => return target, + Buffer::Borrowed(target) => { + let mut value = vec![]; + value.extend_from_slice(target); + value + } + }; + + *self = Buffer::Owned(owned); + self.to_mut() + } +} + +/// The element that contains a bitmap on it +pub struct BitMapElement<'a, Coord, P: PixelFormat = RGBPixel> { + image: Buffer<'a>, + size: (u32, u32), + pos: Coord, + phantom: PhantomData<P>, +} + +impl<'a, Coord, P: PixelFormat> BitMapElement<'a, Coord, P> { + /// Create a new empty bitmap element. This can be use as + /// the draw and blit pattern. + /// + /// - `pos`: The left upper coordinate for the element + /// - `size`: The size of the bitmap + pub fn new(pos: Coord, size: (u32, u32)) -> Self { + Self { + image: Buffer::Owned(vec![0; (size.0 * size.1) as usize * P::PIXEL_SIZE]), + size, + pos, + phantom: PhantomData, + } + } + + /// Create a new bitmap element with an pre-allocated owned buffer, this function will + /// take the ownership of the buffer. + /// + /// - `pos`: The left upper coordinate of the elelent + /// - `size`: The size of the bitmap + /// - `buf`: The buffer to use + /// - **returns**: The newly created image element, if the buffer isn't fit the image + /// dimension, this will returns an `None`. + pub fn with_owned_buffer(pos: Coord, size: (u32, u32), buf: Vec<u8>) -> Option<Self> { + if buf.len() < (size.0 * size.1) as usize * P::PIXEL_SIZE { + return None; + } + + Some(Self { + image: Buffer::Owned(buf), + size, + pos, + phantom: PhantomData, + }) + } + + /// Create a new bitmap element with a mut borrow to an existing buffer + /// + /// - `pos`: The left upper coordinate of the elelent + /// - `size`: The size of the bitmap + /// - `buf`: The buffer to use + /// - **returns**: The newly created image element, if the buffer isn't fit the image + /// dimension, this will returns an `None`. + pub fn with_mut(pos: Coord, size: (u32, u32), buf: &'a mut [u8]) -> Option<Self> { + if buf.len() < (size.0 * size.1) as usize * P::PIXEL_SIZE { + return None; + } + + Some(Self { + image: Buffer::BorrowedMut(buf), + size, + pos, + phantom: PhantomData, + }) + } + + /// Create a new bitmap element with a shared borrowed buffer. This means if we want to modifiy + /// the content of the image, the buffer is automatically copied + /// + /// - `pos`: The left upper coordinate of the elelent + /// - `size`: The size of the bitmap + /// - `buf`: The buffer to use + /// - **returns**: The newly created image element, if the buffer isn't fit the image + /// dimension, this will returns an `None`. + pub fn with_ref(pos: Coord, size: (u32, u32), buf: &'a [u8]) -> Option<Self> { + if buf.len() < (size.0 * size.1) as usize * P::PIXEL_SIZE { + return None; + } + + Some(Self { + image: Buffer::Borrowed(buf), + size, + pos, + phantom: PhantomData, + }) + } + + /// Copy the existing bitmap element to another location + /// + /// - `pos`: The new location to copy + pub fn copy_to<Coord2>(&self, pos: Coord2) -> BitMapElement<Coord2, P> { + BitMapElement { + image: Buffer::Borrowed(self.image.borrow()), + size: self.size, + pos, + phantom: PhantomData, + } + } + + /// Move the existing bitmap element to a new position + /// + /// - `pos`: The new position + pub fn move_to(&mut self, pos: Coord) { + self.pos = pos; + } + + /// Make the bitmap element as a bitmap backend, so that we can use + /// plotters drawing functionality on the bitmap element + pub fn as_bitmap_backend(&mut self) -> BitMapBackend<P> { + BitMapBackend::with_buffer_and_format(self.image.to_mut(), self.size).unwrap() + } +} + +#[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + feature = "image" +))] +impl<'a, Coord> From<(Coord, DynamicImage)> for BitMapElement<'a, Coord, RGBPixel> { + fn from((pos, image): (Coord, DynamicImage)) -> Self { + let (w, h) = image.dimensions(); + let rgb_image = image.to_rgb8().into_raw(); + Self { + pos, + image: Buffer::Owned(rgb_image), + size: (w, h), + phantom: PhantomData, + } + } +} + +#[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + feature = "image" +))] +impl<'a, Coord> From<(Coord, DynamicImage)> for BitMapElement<'a, Coord, BGRXPixel> { + fn from((pos, image): (Coord, DynamicImage)) -> Self { + let (w, h) = image.dimensions(); + let rgb_image = image.to_rgb8().into_raw(); + Self { + pos, + image: Buffer::Owned(rgb_image), + size: (w, h), + phantom: PhantomData, + } + } +} + +impl<'a, 'b, Coord> PointCollection<'a, Coord> for &'a BitMapElement<'b, Coord> { + type Point = &'a Coord; + type IntoIter = std::iter::Once<&'a Coord>; + fn point_iter(self) -> Self::IntoIter { + std::iter::once(&self.pos) + } +} + +impl<'a, Coord, DB: DrawingBackend> Drawable<DB> for BitMapElement<'a, Coord> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some((x, y)) = points.next() { + // TODO: convert the pixel format when needed + return backend.blit_bitmap((x, y), self.size, self.image.as_ref()); + } + Ok(()) + } +} diff --git a/vendor/plotters/src/element/mod.rs b/vendor/plotters/src/element/mod.rs new file mode 100644 index 000000000..e2790051f --- /dev/null +++ b/vendor/plotters/src/element/mod.rs @@ -0,0 +1,290 @@ +/*! + Defines the drawing elements, the high-level drawing unit in Plotters drawing system + + ## Introduction + An element is the drawing unit for Plotter's high-level drawing API. + Different from low-level drawing API, an element is a logic unit of component in the image. + There are few built-in elements, including `Circle`, `Pixel`, `Rectangle`, `Path`, `Text`, etc. + + All element can be drawn onto the drawing area using API `DrawingArea::draw(...)`. + Plotters use "iterator of elements" as the abstraction of any type of plot. + + ## Implementing your own element + You can also define your own element, `CandleStick` is a good sample of implementing complex + element. There are two trait required for an element: + + - `PointCollection` - the struct should be able to return an iterator of key-points under guest coordinate + - `Drawable` - the struct is a pending drawing operation on a drawing backend with pixel-based coordinate + + An example of element that draws a red "X" in a red rectangle onto the backend: + + ```rust + use std::iter::{Once, once}; + use plotters::element::{PointCollection, Drawable}; + use plotters_backend::{BackendCoord, DrawingErrorKind, BackendStyle}; + use plotters::style::IntoTextStyle; + use plotters::prelude::*; + + // Any example drawing a red X + struct RedBoxedX((i32, i32)); + + // For any reference to RedX, we can convert it into an iterator of points + impl <'a> PointCollection<'a, (i32, i32)> for &'a RedBoxedX { + type Point = &'a (i32, i32); + type IntoIter = Once<&'a (i32, i32)>; + fn point_iter(self) -> Self::IntoIter { + once(&self.0) + } + } + + // How to actually draw this element + impl <DB:DrawingBackend> Drawable<DB> for RedBoxedX { + fn draw<I:Iterator<Item = BackendCoord>>( + &self, + mut pos: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + let pos = pos.next().unwrap(); + backend.draw_rect(pos, (pos.0 + 10, pos.1 + 12), &RED, false)?; + let text_style = &("sans-serif", 20).into_text_style(&backend.get_size()).color(&RED); + backend.draw_text("X", text_style, pos) + } + } + + fn main() -> Result<(), Box<dyn std::error::Error>> { + let root = BitMapBackend::new( + "plotters-doc-data/element-0.png", + (640, 480) + ).into_drawing_area(); + root.draw(&RedBoxedX((200, 200)))?; + Ok(()) + } + ``` + ![](https://plotters-rs.github.io/plotters-doc-data/element-0.png) + + ## Composable Elements + You also have an convenient way to build an element that isn't built into the Plotters library by + combining existing elements into a logic group. To build an composable element, you need to use an + logic empty element that draws nothing to the backend but denotes the relative zero point of the logical + group. Any element defined with pixel based offset coordinate can be added into the group later using + the `+` operator. + + For example, the red boxed X element can be implemented with Composable element in the following way: + ```rust + use plotters::prelude::*; + fn main() -> Result<(), Box<dyn std::error::Error>> { + let root = BitMapBackend::new( + "plotters-doc-data/element-1.png", + (640, 480) + ).into_drawing_area(); + let font:FontDesc = ("sans-serif", 20).into(); + root.draw(&(EmptyElement::at((200, 200)) + + Text::new("X", (0, 0), &"sans-serif".into_font().resize(20.0).color(&RED)) + + Rectangle::new([(0,0), (10, 12)], &RED) + ))?; + Ok(()) + } + ``` + ![](https://plotters-rs.github.io/plotters-doc-data/element-1.png) + + ## Dynamic Elements + By default, Plotters uses static dispatch for all the elements and series. For example, + the `ChartContext::draw_series` method accepts an iterator of `T` where type `T` implements + all the traits a element should implement. Although, we can use the series of composable element + for complex series drawing. But sometimes, we still want to make the series heterogynous, which means + the iterator should be able to holds elements in different type. + For example, a point series with cross and circle. This requires the dynamically dispatched elements. + In plotters, all the elements can be converted into `DynElement`, the dynamic dispatch container for + all elements (include external implemented ones). + Plotters automatically implements `IntoDynElement` for all elements, by doing so, any dynamic element should have + `into_dyn` function which would wrap the element into a dynamic element wrapper. + + For example, the following code counts the number of factors of integer and mark all prime numbers in cross. + ```rust + use plotters::prelude::*; + fn num_of_factor(n: i32) -> i32 { + let mut ret = 2; + for i in 2..n { + if i * i > n { + break; + } + + if n % i == 0 { + if i * i != n { + ret += 2; + } else { + ret += 1; + } + } + } + return ret; + } + fn main() -> Result<(), Box<dyn std::error::Error>> { + let root = + BitMapBackend::new("plotters-doc-data/element-3.png", (640, 480)) + .into_drawing_area(); + root.fill(&WHITE)?; + let mut chart = ChartBuilder::on(&root) + .x_label_area_size(40) + .y_label_area_size(40) + .margin(5) + .build_cartesian_2d(0..50, 0..10)?; + + chart + .configure_mesh() + .disable_x_mesh() + .disable_y_mesh() + .draw()?; + + chart.draw_series((0..50).map(|x| { + let center = (x, num_of_factor(x)); + // Although the arms of if statement has different types, + // but they can be placed into a dynamic element wrapper, + // by doing so, the type is unified. + if center.1 == 2 { + Cross::new(center, 4, Into::<ShapeStyle>::into(&RED).filled()).into_dyn() + } else { + Circle::new(center, 4, Into::<ShapeStyle>::into(&GREEN).filled()).into_dyn() + } + }))?; + + Ok(()) + } + ``` + ![](https://plotters-rs.github.io/plotters-doc-data/element-3.png) +*/ +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; +use std::borrow::Borrow; + +mod basic_shapes; +pub use basic_shapes::*; + +mod basic_shapes_3d; +pub use basic_shapes_3d::*; + +mod text; +pub use text::*; + +mod points; +pub use points::*; + +mod composable; +pub use composable::{ComposedElement, EmptyElement}; + +#[cfg(feature = "candlestick")] +mod candlestick; +#[cfg(feature = "candlestick")] +pub use candlestick::CandleStick; + +#[cfg(feature = "errorbar")] +mod errorbar; +#[cfg(feature = "errorbar")] +pub use errorbar::{ErrorBar, ErrorBarOrientH, ErrorBarOrientV}; + +#[cfg(feature = "boxplot")] +mod boxplot; +#[cfg(feature = "boxplot")] +pub use boxplot::Boxplot; + +#[cfg(feature = "bitmap_backend")] +mod image; +#[cfg(feature = "bitmap_backend")] +pub use self::image::BitMapElement; + +mod dynelem; +pub use dynelem::{DynElement, IntoDynElement}; + +mod pie; +pub use pie::Pie; + +use crate::coord::CoordTranslate; +use crate::drawing::Rect; + +/// A type which is logically a collection of points, under any given coordinate system. +/// Note: Ideally, a point collection trait should be any type of which coordinate elements can be +/// iterated. This is similar to `iter` method of many collection types in std. +/// +/// ```ignore +/// trait PointCollection<Coord> { +/// type PointIter<'a> : Iterator<Item = &'a Coord>; +/// fn iter(&self) -> PointIter<'a>; +/// } +/// ``` +/// +/// However, +/// [Generic Associated Types](https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md) +/// is far away from stablize. +/// So currently we have the following workaround: +/// +/// Instead of implement the PointCollection trait on the element type itself, it implements on the +/// reference to the element. By doing so, we now have a well-defined lifetime for the iterator. +/// +/// In addition, for some element, the coordinate is computed on the fly, thus we can't hard-code +/// the iterator's return type is `&'a Coord`. +/// `Borrow` trait seems to strict in this case, since we don't need the order and hash +/// preservation properties at this point. However, `AsRef` doesn't work with `Coord` +/// +/// This workaround also leads overly strict lifetime bound on `ChartContext::draw_series`. +/// +/// TODO: Once GAT is ready on stable Rust, we should simplify the design. +/// +pub trait PointCollection<'a, Coord, CM = BackendCoordOnly> { + /// The item in point iterator + type Point: Borrow<Coord> + 'a; + + /// The point iterator + type IntoIter: IntoIterator<Item = Self::Point>; + + /// framework to do the coordinate mapping + fn point_iter(self) -> Self::IntoIter; +} +/// The trait indicates we are able to draw it on a drawing area +pub trait Drawable<DB: DrawingBackend, CM: CoordMapper = BackendCoordOnly> { + /// Actually draws the element. The key points is already translated into the + /// image coordinate and can be used by DC directly + fn draw<I: Iterator<Item = CM::Output>>( + &self, + pos: I, + backend: &mut DB, + parent_dim: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>>; +} + +/// Useful to translate from guest coordinates to backend coordinates +pub trait CoordMapper { + /// Specifies the output data from the translation + type Output; + /// Performs the translation from guest coordinates to backend coordinates + fn map<CT: CoordTranslate>(coord_trans: &CT, from: &CT::From, rect: &Rect) -> Self::Output; +} + +/// Used for 2d coordinate transformations. +pub struct BackendCoordOnly; + +impl CoordMapper for BackendCoordOnly { + type Output = BackendCoord; + fn map<CT: CoordTranslate>(coord_trans: &CT, from: &CT::From, rect: &Rect) -> BackendCoord { + rect.truncate(coord_trans.translate(from)) + } +} + +/** +Used for 3d coordinate transformations. + +See [`Cubiod`] for more information and an example. +*/ +pub struct BackendCoordAndZ; + +impl CoordMapper for BackendCoordAndZ { + type Output = (BackendCoord, i32); + fn map<CT: CoordTranslate>( + coord_trans: &CT, + from: &CT::From, + rect: &Rect, + ) -> (BackendCoord, i32) { + let coord = rect.truncate(coord_trans.translate(from)); + let z = coord_trans.depth(from); + (coord, z) + } +} diff --git a/vendor/plotters/src/element/pie.rs b/vendor/plotters/src/element/pie.rs new file mode 100644 index 000000000..95298345a --- /dev/null +++ b/vendor/plotters/src/element/pie.rs @@ -0,0 +1,240 @@ +use crate::{ + element::{Drawable, PointCollection}, + style::{IntoFont, RGBColor, TextStyle, BLACK}, +}; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; +use std::{error::Error, f64::consts::PI, fmt::Display}; + +#[derive(Debug)] +enum PieError { + LengthMismatch, +} +impl Display for PieError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + &PieError::LengthMismatch => write!(f, "Length Mismatch"), + } + } +} + +impl Error for PieError {} + +/// A Pie Graph +pub struct Pie<'a, Coord, Label: Display> { + center: &'a Coord, // cartesian coord + radius: &'a f64, + sizes: &'a [f64], + colors: &'a [RGBColor], + labels: &'a [Label], + total: f64, + start_radian: f64, + label_style: TextStyle<'a>, + label_offset: f64, + percentage_style: Option<TextStyle<'a>>, +} + +impl<'a, Label: Display> Pie<'a, (i32, i32), Label> { + /// Build a Pie object. + /// Assumes a start angle at 0.0, which is aligned to the horizontal axis. + pub fn new( + center: &'a (i32, i32), + radius: &'a f64, + sizes: &'a [f64], + colors: &'a [RGBColor], + labels: &'a [Label], + ) -> Self { + // fold iterator to pre-calculate total from given slice sizes + let total = sizes.iter().sum(); + + // default label style and offset as 5% of the radius + let radius_5pct = radius * 0.05; + + // strong assumption that the background is white for legibility. + let label_style = TextStyle::from(("sans-serif", radius_5pct).into_font()).color(&BLACK); + Self { + center, + radius, + sizes, + colors, + labels, + total, + start_radian: 0.0, + label_style, + label_offset: radius_5pct, + percentage_style: None, + } + } + + /// Pass an angle in degrees to change the default. + /// Default is set to start at 0, which is aligned on the x axis. + /// ``` + /// use plotters::prelude::*; + /// let mut pie = Pie::new(&(50,50), &10.0, &[50.0, 25.25, 20.0, 5.5], &[RED, BLUE, GREEN, WHITE], &["Red", "Blue", "Green", "White"]); + /// pie.start_angle(-90.0); // retract to a right angle, so it starts aligned to a vertical Y axis. + /// ``` + pub fn start_angle(&mut self, start_angle: f64) { + // angle is more intuitive in degrees as an API, but we use it as radian offset internally. + self.start_radian = start_angle.to_radians(); + } + + /// + pub fn label_style<T: Into<TextStyle<'a>>>(&mut self, label_style: T) { + self.label_style = label_style.into(); + } + + /// Sets the offset to labels, to distanciate them further/closer from the center. + pub fn label_offset(&mut self, offset_to_radius: f64) { + self.label_offset = offset_to_radius + } + + /// enables drawing the wedge's percentage in the middle of the wedge, with the given style + pub fn percentages<T: Into<TextStyle<'a>>>(&mut self, label_style: T) { + self.percentage_style = Some(label_style.into()); + } +} + +impl<'a, DB: DrawingBackend, Label: Display> Drawable<DB> for Pie<'a, (i32, i32), Label> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + _pos: I, + backend: &mut DB, + _parent_dim: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + let mut offset_theta = self.start_radian; + + // const reused for every radian calculation + // the bigger the radius, the more fine-grained it should calculate + // to avoid being aliasing from being too noticeable. + // this all could be avoided if backend could draw a curve/bezier line as part of a polygon. + let radian_increment = PI / 180.0 / self.radius.sqrt() * 2.0; + let mut perc_labels = Vec::new(); + for (index, slice) in self.sizes.iter().enumerate() { + let slice_style = + self.colors + .get(index) + .ok_or_else(|| DrawingErrorKind::FontError(Box::new( + PieError::LengthMismatch, + )))?; + let label = self + .labels + .get(index) + .ok_or_else(|| DrawingErrorKind::FontError(Box::new( + PieError::LengthMismatch, + )))?; + // start building wedge line against the previous edge + let mut points = vec![*self.center]; + let ratio = slice / self.total; + let theta_final = ratio * 2.0 * PI + offset_theta; // end radian for the wedge + + // calculate middle for labels before mutating offset + let middle_theta = ratio * PI + offset_theta; + + // calculate every fraction of radian for the wedge, offsetting for every iteration, clockwise + // + // a custom Range such as `for theta in offset_theta..=theta_final` would be more elegant + // but f64 doesn't implement the Range trait, and it would requires the Step trait (increment by 1.0 or 0.0001?) + // which is unstable therefore cannot be implemented outside of std, even as a newtype for radians. + while offset_theta <= theta_final { + let coord = theta_to_ordinal_coord(*self.radius, offset_theta, self.center); + points.push(coord); + offset_theta += radian_increment; + } + // final point of the wedge may not fall exactly on a radian, so add it extra + let final_coord = theta_to_ordinal_coord(*self.radius, theta_final, self.center); + points.push(final_coord); + // next wedge calculation will start from previous wedges's last radian + offset_theta = theta_final; + + // draw wedge + // TODO: Currently the backend doesn't have API to draw an arc. We need add that in the + // future + backend.fill_polygon(points, slice_style)?; + + // label coords from the middle + let mut mid_coord = + theta_to_ordinal_coord(self.radius + self.label_offset, middle_theta, self.center); + + // ensure label's doesn't fall in the circle + let label_size = backend.estimate_text_size(&label.to_string(), &self.label_style)?; + // if on the left hand side of the pie, offset whole label to the left + if mid_coord.0 <= self.center.0 { + mid_coord.0 -= label_size.0 as i32; + } + // put label + backend.draw_text(&label.to_string(), &self.label_style, mid_coord)?; + if let Some(percentage_style) = &self.percentage_style { + let perc_label = format!("{:.1}%", (ratio * 100.0)); + let label_size = backend.estimate_text_size(&perc_label, percentage_style)?; + let text_x_mid = (label_size.0 as f64 / 2.0).round() as i32; + let text_y_mid = (label_size.1 as f64 / 2.0).round() as i32; + let perc_coord = theta_to_ordinal_coord( + self.radius / 2.0, + middle_theta, + &(self.center.0 - text_x_mid, self.center.1 - text_y_mid), + ); + // perc_coord.0 -= middle_label_size.0.round() as i32; + perc_labels.push((perc_label, perc_coord)); + } + } + // while percentages are generated during the first main iterations, + // they have to go on top of the already drawn wedges, so require a new iteration. + for (label, coord) in perc_labels { + let style = self.percentage_style.as_ref().unwrap(); + backend.draw_text(&label, style, coord)?; + } + Ok(()) + } +} + +impl<'a, Label: Display> PointCollection<'a, (i32, i32)> for &'a Pie<'a, (i32, i32), Label> { + type Point = &'a (i32, i32); + type IntoIter = std::iter::Once<&'a (i32, i32)>; + fn point_iter(self) -> std::iter::Once<&'a (i32, i32)> { + std::iter::once(self.center) + } +} + +fn theta_to_ordinal_coord(radius: f64, theta: f64, ordinal_offset: &(i32, i32)) -> (i32, i32) { + // polar coordinates are (r, theta) + // convert to (x, y) coord, with center as offset + + let (sin, cos) = theta.sin_cos(); + ( + // casting f64 to discrete i32 pixels coordinates is inevitably going to lose precision + // if plotters can support float coordinates, this place would surely benefit, especially for small sizes. + // so far, the result isn't so bad though + (radius * cos + ordinal_offset.0 as f64).round() as i32, // x + (radius * sin + ordinal_offset.1 as f64).round() as i32, // y + ) +} +#[cfg(test)] +mod test { + use super::*; + // use crate::prelude::*; + + #[test] + fn polar_coord_to_cartestian_coord() { + let coord = theta_to_ordinal_coord(800.0, 1.5_f64.to_radians(), &(5, 5)); + // rounded tends to be more accurate. this gets truncated to (804, 25) without rounding. + assert_eq!(coord, (805, 26)); //coord calculated from theta + } + #[test] + fn pie_calculations() { + let mut center = (5, 5); + let mut radius = 800.0; + + let sizes = vec![50.0, 25.0]; + // length isn't validated in new() + let colors = vec![]; + let labels: Vec<&str> = vec![]; + let pie = Pie::new(¢er, &radius, &sizes, &colors, &labels); + assert_eq!(pie.total, 75.0); // total calculated from sizes + + // not ownership greedy + center.1 += 1; + radius += 1.0; + assert!(colors.get(0).is_none()); + assert!(labels.get(0).is_none()); + assert_eq!(radius, 801.0); + } +} diff --git a/vendor/plotters/src/element/points.rs b/vendor/plotters/src/element/points.rs new file mode 100644 index 000000000..423625b08 --- /dev/null +++ b/vendor/plotters/src/element/points.rs @@ -0,0 +1,154 @@ +use super::*; +use super::{Drawable, PointCollection}; +use crate::style::{Color, ShapeStyle, SizeDesc}; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +/** +A common trait for elements that can be interpreted as points: A cross, a circle, a triangle marker... + +This is used internally by Plotters and should probably not be included in user code. +See [`EmptyElement`] for more information and examples. +*/ +pub trait PointElement<Coord, Size: SizeDesc> { + /** + Point creator. + + This is used internally by Plotters and should probably not be included in user code. + See [`EmptyElement`] for more information and examples. + */ + fn make_point(pos: Coord, size: Size, style: ShapeStyle) -> Self; +} + +/** +A cross marker for visualizing data series. + +See [`EmptyElement`] for more information and examples. +*/ +pub struct Cross<Coord, Size: SizeDesc> { + center: Coord, + size: Size, + style: ShapeStyle, +} + +impl<Coord, Size: SizeDesc> Cross<Coord, Size> { + /** + Creates a cross marker. + + See [`EmptyElement`] for more information and examples. + */ + pub fn new<T: Into<ShapeStyle>>(coord: Coord, size: Size, style: T) -> Self { + Self { + center: coord, + size, + style: style.into(), + } + } +} + +impl<'a, Coord: 'a, Size: SizeDesc> PointCollection<'a, Coord> for &'a Cross<Coord, Size> { + type Point = &'a Coord; + type IntoIter = std::iter::Once<&'a Coord>; + fn point_iter(self) -> std::iter::Once<&'a Coord> { + std::iter::once(&self.center) + } +} + +impl<Coord, DB: DrawingBackend, Size: SizeDesc> Drawable<DB> for Cross<Coord, Size> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut points: I, + backend: &mut DB, + ps: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some((x, y)) = points.next() { + let size = self.size.in_pixels(&ps); + let (x0, y0) = (x - size, y - size); + let (x1, y1) = (x + size, y + size); + backend.draw_line((x0, y0), (x1, y1), &self.style)?; + backend.draw_line((x0, y1), (x1, y0), &self.style)?; + } + Ok(()) + } +} + +/** +A triangle marker for visualizing data series. + +See [`EmptyElement`] for more information and examples. +*/ +pub struct TriangleMarker<Coord, Size: SizeDesc> { + center: Coord, + size: Size, + style: ShapeStyle, +} + +impl<Coord, Size: SizeDesc> TriangleMarker<Coord, Size> { + /** + Creates a triangle marker. + + See [`EmptyElement`] for more information and examples. + */ + pub fn new<T: Into<ShapeStyle>>(coord: Coord, size: Size, style: T) -> Self { + Self { + center: coord, + size, + style: style.into(), + } + } +} + +impl<'a, Coord: 'a, Size: SizeDesc> PointCollection<'a, Coord> for &'a TriangleMarker<Coord, Size> { + type Point = &'a Coord; + type IntoIter = std::iter::Once<&'a Coord>; + fn point_iter(self) -> std::iter::Once<&'a Coord> { + std::iter::once(&self.center) + } +} + +impl<Coord, DB: DrawingBackend, Size: SizeDesc> Drawable<DB> for TriangleMarker<Coord, Size> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut points: I, + backend: &mut DB, + ps: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some((x, y)) = points.next() { + let size = self.size.in_pixels(&ps); + let points = [-90, -210, -330] + .iter() + .map(|deg| f64::from(*deg) * std::f64::consts::PI / 180.0) + .map(|rad| { + ( + (rad.cos() * f64::from(size) + f64::from(x)).ceil() as i32, + (rad.sin() * f64::from(size) + f64::from(y)).ceil() as i32, + ) + }); + backend.fill_polygon(points, &self.style.color.to_backend_color())?; + } + Ok(()) + } +} + +impl<Coord, Size: SizeDesc> PointElement<Coord, Size> for Cross<Coord, Size> { + fn make_point(pos: Coord, size: Size, style: ShapeStyle) -> Self { + Self::new(pos, size, style) + } +} + +impl<Coord, Size: SizeDesc> PointElement<Coord, Size> for TriangleMarker<Coord, Size> { + fn make_point(pos: Coord, size: Size, style: ShapeStyle) -> Self { + Self::new(pos, size, style) + } +} + +impl<Coord, Size: SizeDesc> PointElement<Coord, Size> for Circle<Coord, Size> { + fn make_point(pos: Coord, size: Size, style: ShapeStyle) -> Self { + Self::new(pos, size, style) + } +} + +impl<Coord, Size: SizeDesc> PointElement<Coord, Size> for Pixel<Coord> { + fn make_point(pos: Coord, _: Size, style: ShapeStyle) -> Self { + Self::new(pos, style) + } +} diff --git a/vendor/plotters/src/element/text.rs b/vendor/plotters/src/element/text.rs new file mode 100644 index 000000000..ca813c7c9 --- /dev/null +++ b/vendor/plotters/src/element/text.rs @@ -0,0 +1,242 @@ +use std::borrow::Borrow; +use std::i32; + +use super::{Drawable, PointCollection}; +use crate::style::{FontDesc, FontResult, LayoutBox, TextStyle}; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +/// A single line text element. This can be owned or borrowed string, dependents on +/// `String` or `str` moved into. +pub struct Text<'a, Coord, T: Borrow<str>> { + text: T, + coord: Coord, + style: TextStyle<'a>, +} + +impl<'a, Coord, T: Borrow<str>> Text<'a, Coord, T> { + /// Create a new text element + /// - `text`: The text for the element + /// - `points`: The upper left conner for the text element + /// - `style`: The text style + /// - Return the newly created text element + pub fn new<S: Into<TextStyle<'a>>>(text: T, points: Coord, style: S) -> Self { + Self { + text, + coord: points, + style: style.into(), + } + } +} + +impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord> for &'a Text<'b, Coord, T> { + type Point = &'a Coord; + type IntoIter = std::iter::Once<&'a Coord>; + fn point_iter(self) -> Self::IntoIter { + std::iter::once(&self.coord) + } +} + +impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB> for Text<'a, Coord, T> { + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some(a) = points.next() { + return backend.draw_text(self.text.borrow(), &self.style, a); + } + Ok(()) + } +} + +/// An multi-line text element. The `Text` element allows only single line text +/// and the `MultiLineText` supports drawing multiple lines +pub struct MultiLineText<'a, Coord, T: Borrow<str>> { + lines: Vec<T>, + coord: Coord, + style: TextStyle<'a>, + line_height: f64, +} + +impl<'a, Coord, T: Borrow<str>> MultiLineText<'a, Coord, T> { + /// Create an empty multi-line text element. + /// Lines can be append to the empty multi-line by calling `push_line` method + /// + /// `pos`: The upper left corner + /// `style`: The style of the text + pub fn new<S: Into<TextStyle<'a>>>(pos: Coord, style: S) -> Self { + MultiLineText { + lines: vec![], + coord: pos, + style: style.into(), + line_height: 1.25, + } + } + + /// Set the line height of the multi-line text element + pub fn set_line_height(&mut self, value: f64) -> &mut Self { + self.line_height = value; + self + } + + /// Push a new line into the given multi-line text + /// `line`: The line to be pushed + pub fn push_line<L: Into<T>>(&mut self, line: L) { + self.lines.push(line.into()); + } + + /// Estimate the multi-line text element's dimension + pub fn estimate_dimension(&self) -> FontResult<(i32, i32)> { + let (mut mx, mut my) = (0, 0); + + for ((x, y), t) in self.layout_lines((0, 0)).zip(self.lines.iter()) { + let (dx, dy) = self.style.font.box_size(t.borrow())?; + mx = mx.max(x + dx as i32); + my = my.max(y + dy as i32); + } + + Ok((mx, my)) + } + + /// Move the location to the specified location + pub fn relocate(&mut self, coord: Coord) { + self.coord = coord + } + + fn layout_lines(&self, (x0, y0): BackendCoord) -> impl Iterator<Item = BackendCoord> { + let font_height = self.style.font.get_size(); + let actual_line_height = font_height * self.line_height; + (0..self.lines.len() as u32).map(move |idx| { + let y = f64::from(y0) + f64::from(idx) * actual_line_height; + // TODO: Support text alignment as well, currently everything is left aligned + let x = f64::from(x0); + (x.round() as i32, y.round() as i32) + }) + } +} + +fn layout_multiline_text<'a, F: FnMut(&'a str)>( + text: &'a str, + max_width: u32, + font: FontDesc<'a>, + mut func: F, +) { + for line in text.lines() { + if max_width == 0 || line.is_empty() { + func(line); + } else { + let mut remaining = &line[0..]; + + while !remaining.is_empty() { + let mut left = 0; + while left < remaining.len() { + let width = font.box_size(&remaining[0..=left]).unwrap_or((0, 0)).0 as i32; + + if width > max_width as i32 { + break; + } + left += 1; + } + + if left == 0 { + left += 1; + } + + let cur_line = &remaining[..left]; + remaining = &remaining[left..]; + + func(cur_line); + } + } + } +} + +impl<'a, T: Borrow<str>> MultiLineText<'a, BackendCoord, T> { + /// Compute the line layout + pub fn compute_line_layout(&self) -> FontResult<Vec<LayoutBox>> { + let mut ret = vec![]; + for ((x, y), t) in self.layout_lines(self.coord).zip(self.lines.iter()) { + let (dx, dy) = self.style.font.box_size(t.borrow())?; + ret.push(((x, y), (x + dx as i32, y + dy as i32))); + } + Ok(ret) + } +} + +impl<'a, Coord> MultiLineText<'a, Coord, &'a str> { + /// Parse a multi-line text into an multi-line element. + /// + /// `text`: The text that is parsed + /// `pos`: The position of the text + /// `style`: The style for this text + /// `max_width`: The width of the multi-line text element, the line will break + /// into two lines if the line is wider than the max_width. If 0 is given, do not + /// do any line wrapping + pub fn from_str<ST: Into<&'a str>, S: Into<TextStyle<'a>>>( + text: ST, + pos: Coord, + style: S, + max_width: u32, + ) -> Self { + let text = text.into(); + let mut ret = MultiLineText::new(pos, style); + + layout_multiline_text(text, max_width, ret.style.font.clone(), |l| { + ret.push_line(l) + }); + ret + } +} + +impl<'a, Coord> MultiLineText<'a, Coord, String> { + /// Parse a multi-line text into an multi-line element. + /// + /// `text`: The text that is parsed + /// `pos`: The position of the text + /// `style`: The style for this text + /// `max_width`: The width of the multi-line text element, the line will break + /// into two lines if the line is wider than the max_width. If 0 is given, do not + /// do any line wrapping + pub fn from_string<S: Into<TextStyle<'a>>>( + text: String, + pos: Coord, + style: S, + max_width: u32, + ) -> Self { + let mut ret = MultiLineText::new(pos, style); + + layout_multiline_text(text.as_str(), max_width, ret.style.font.clone(), |l| { + ret.push_line(l.to_string()) + }); + ret + } +} + +impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord> + for &'a MultiLineText<'b, Coord, T> +{ + type Point = &'a Coord; + type IntoIter = std::iter::Once<&'a Coord>; + fn point_iter(self) -> Self::IntoIter { + std::iter::once(&self.coord) + } +} + +impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB> + for MultiLineText<'a, Coord, T> +{ + fn draw<I: Iterator<Item = BackendCoord>>( + &self, + mut points: I, + backend: &mut DB, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some(a) = points.next() { + for (point, text) in self.layout_lines(a).zip(self.lines.iter()) { + backend.draw_text(text.borrow(), &self.style, point)?; + } + } + Ok(()) + } +} |