summaryrefslogtreecommitdiffstats
path: root/vendor/plotters/src/element
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/plotters/src/element')
-rw-r--r--vendor/plotters/src/element/basic_shapes.rs358
-rw-r--r--vendor/plotters/src/element/basic_shapes_3d.rs108
-rw-r--r--vendor/plotters/src/element/boxplot.rs288
-rw-r--r--vendor/plotters/src/element/candlestick.rs100
-rw-r--r--vendor/plotters/src/element/composable.rs242
-rw-r--r--vendor/plotters/src/element/dynelem.rs84
-rw-r--r--vendor/plotters/src/element/errorbar.rs223
-rw-r--r--vendor/plotters/src/element/image.rs228
-rw-r--r--vendor/plotters/src/element/mod.rs290
-rw-r--r--vendor/plotters/src/element/pie.rs240
-rw-r--r--vendor/plotters/src/element/points.rs154
-rw-r--r--vendor/plotters/src/element/text.rs242
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(&center, &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(())
+ }
+}