summaryrefslogtreecommitdiffstats
path: root/vendor/plotters/src/chart
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/plotters/src/chart')
-rw-r--r--vendor/plotters/src/chart/axes3d.rs317
-rw-r--r--vendor/plotters/src/chart/builder.rs568
-rw-r--r--vendor/plotters/src/chart/context.rs221
-rw-r--r--vendor/plotters/src/chart/context/cartesian2d/draw_impl.rs372
-rw-r--r--vendor/plotters/src/chart/context/cartesian2d/mod.rs90
-rw-r--r--vendor/plotters/src/chart/context/cartesian3d/draw_impl.rs309
-rw-r--r--vendor/plotters/src/chart/context/cartesian3d/mod.rs130
-rw-r--r--vendor/plotters/src/chart/dual_coord.rs242
-rw-r--r--vendor/plotters/src/chart/mesh.rs533
-rw-r--r--vendor/plotters/src/chart/mod.rs30
-rw-r--r--vendor/plotters/src/chart/series.rs301
-rw-r--r--vendor/plotters/src/chart/state.rs112
12 files changed, 3225 insertions, 0 deletions
diff --git a/vendor/plotters/src/chart/axes3d.rs b/vendor/plotters/src/chart/axes3d.rs
new file mode 100644
index 000000000..33d7de39c
--- /dev/null
+++ b/vendor/plotters/src/chart/axes3d.rs
@@ -0,0 +1,317 @@
+use std::marker::PhantomData;
+
+use super::ChartContext;
+use crate::coord::cartesian::Cartesian3d;
+use crate::coord::ranged1d::{BoldPoints, LightPoints, Ranged, ValueFormatter};
+use crate::style::colors::{BLACK, TRANSPARENT};
+use crate::style::Color;
+use crate::style::{AsRelative, ShapeStyle, SizeDesc, TextStyle};
+
+use super::Coord3D;
+
+use crate::drawing::DrawingAreaErrorKind;
+
+use plotters_backend::DrawingBackend;
+
+/**
+Implements 3D plot axes configurations.
+
+The best way to use this struct is by way of the [`configure_axes()`] function.
+See [`ChartContext::configure_axes()`] for more information and examples.
+*/
+pub struct Axes3dStyle<'a, 'b, X: Ranged, Y: Ranged, Z: Ranged, DB: DrawingBackend> {
+ pub(super) parent_size: (u32, u32),
+ pub(super) target: Option<&'b mut ChartContext<'a, DB, Cartesian3d<X, Y, Z>>>,
+ pub(super) tick_size: i32,
+ pub(super) light_lines_limit: [usize; 3],
+ pub(super) n_labels: [usize; 3],
+ pub(super) bold_line_style: ShapeStyle,
+ pub(super) light_line_style: ShapeStyle,
+ pub(super) axis_panel_style: ShapeStyle,
+ pub(super) axis_style: ShapeStyle,
+ pub(super) label_style: TextStyle<'b>,
+ pub(super) format_x: &'b dyn Fn(&X::ValueType) -> String,
+ pub(super) format_y: &'b dyn Fn(&Y::ValueType) -> String,
+ pub(super) format_z: &'b dyn Fn(&Z::ValueType) -> String,
+ _phantom: PhantomData<&'a (X, Y, Z)>,
+}
+
+impl<'a, 'b, X, Y, Z, XT, YT, ZT, DB> Axes3dStyle<'a, 'b, X, Y, Z, DB>
+where
+ X: Ranged<ValueType = XT> + ValueFormatter<XT>,
+ Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
+ Z: Ranged<ValueType = ZT> + ValueFormatter<ZT>,
+ DB: DrawingBackend,
+{
+ /**
+ Set the size of the tick marks.
+
+ - `value` Desired tick mark size, in pixels.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn tick_size<Size: SizeDesc>(&mut self, size: Size) -> &mut Self {
+ let actual_size = size.in_pixels(&self.parent_size);
+ self.tick_size = actual_size;
+ self
+ }
+
+ /**
+ Set the maximum number of divisions for the minor grid in the X axis.
+
+ - `value`: Maximum desired divisions between two consecutive X labels.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn x_max_light_lines(&mut self, value: usize) -> &mut Self {
+ self.light_lines_limit[0] = value;
+ self
+ }
+
+ /**
+ Set the maximum number of divisions for the minor grid in the Y axis.
+
+ - `value`: Maximum desired divisions between two consecutive Y labels.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn y_max_light_lines(&mut self, value: usize) -> &mut Self {
+ self.light_lines_limit[1] = value;
+ self
+ }
+
+ /**
+ Set the maximum number of divisions for the minor grid in the Z axis.
+
+ - `value`: Maximum desired divisions between two consecutive Z labels.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn z_max_light_lines(&mut self, value: usize) -> &mut Self {
+ self.light_lines_limit[2] = value;
+ self
+ }
+
+ /**
+ Set the maximum number of divisions for the minor grid.
+
+ - `value`: Maximum desired divisions between two consecutive labels in X, Y, and Z.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn max_light_lines(&mut self, value: usize) -> &mut Self {
+ self.light_lines_limit[0] = value;
+ self.light_lines_limit[1] = value;
+ self.light_lines_limit[2] = value;
+ self
+ }
+
+ /**
+ Set the number of labels on the X axes.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn x_labels(&mut self, n: usize) -> &mut Self {
+ self.n_labels[0] = n;
+ self
+ }
+
+ /**
+ Set the number of labels on the Y axes.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn y_labels(&mut self, n: usize) -> &mut Self {
+ self.n_labels[1] = n;
+ self
+ }
+
+ /**
+ Set the number of labels on the Z axes.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn z_labels(&mut self, n: usize) -> &mut Self {
+ self.n_labels[2] = n;
+ self
+ }
+
+ /**
+ Sets the style of the panels in the background.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn axis_panel_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
+ self.axis_panel_style = style.into();
+ self
+ }
+
+ /**
+ Sets the style of the major grid lines.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn bold_grid_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
+ self.bold_line_style = style.into();
+ self
+ }
+
+ /**
+ Sets the style of the minor grid lines.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn light_grid_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
+ self.light_line_style = style.into();
+ self
+ }
+
+ /**
+ Sets the text style of the axis labels.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn label_style<S: Into<TextStyle<'b>>>(&mut self, style: S) -> &mut Self {
+ self.label_style = style.into();
+ self
+ }
+
+ /**
+ Specifies the string format of the X axis labels.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn x_formatter<F: Fn(&X::ValueType) -> String>(&mut self, f: &'b F) -> &mut Self {
+ self.format_x = f;
+ self
+ }
+
+ /**
+ Specifies the string format of the Y axis labels.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn y_formatter<F: Fn(&Y::ValueType) -> String>(&mut self, f: &'b F) -> &mut Self {
+ self.format_y = f;
+ self
+ }
+
+ /**
+ Specifies the string format of the Z axis labels.
+
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub fn z_formatter<F: Fn(&Z::ValueType) -> String>(&mut self, f: &'b F) -> &mut Self {
+ self.format_z = f;
+ self
+ }
+
+ /**
+ Constructs a new configuration object and defines the defaults.
+
+ This is used internally by Plotters and should probably not be included in user code.
+ See [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ pub(crate) fn new(chart: &'b mut ChartContext<'a, DB, Cartesian3d<X, Y, Z>>) -> Self {
+ let parent_size = chart.drawing_area.dim_in_pixel();
+ let base_tick_size = (5u32).percent().max(5).in_pixels(chart.plotting_area());
+ let tick_size = base_tick_size;
+ Self {
+ parent_size,
+ tick_size,
+ light_lines_limit: [10, 10, 10],
+ n_labels: [10, 10, 10],
+ bold_line_style: Into::<ShapeStyle>::into(&BLACK.mix(0.2)),
+ light_line_style: Into::<ShapeStyle>::into(&TRANSPARENT),
+ axis_panel_style: Into::<ShapeStyle>::into(&BLACK.mix(0.1)),
+ axis_style: Into::<ShapeStyle>::into(&BLACK.mix(0.8)),
+ label_style: ("sans-serif", (12).percent().max(12).in_pixels(&parent_size)).into(),
+ format_x: &X::format,
+ format_y: &Y::format,
+ format_z: &Z::format,
+ _phantom: PhantomData,
+ target: Some(chart),
+ }
+ }
+
+ pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
+ where
+ XT: Clone,
+ YT: Clone,
+ ZT: Clone,
+ {
+ let chart = self.target.take().unwrap();
+ let kps_bold = chart.get_key_points(
+ BoldPoints(self.n_labels[0]),
+ BoldPoints(self.n_labels[1]),
+ BoldPoints(self.n_labels[2]),
+ );
+ let kps_light = chart.get_key_points(
+ LightPoints::new(
+ self.n_labels[0],
+ self.n_labels[0] * self.light_lines_limit[0],
+ ),
+ LightPoints::new(
+ self.n_labels[1],
+ self.n_labels[1] * self.light_lines_limit[1],
+ ),
+ LightPoints::new(
+ self.n_labels[2],
+ self.n_labels[2] * self.light_lines_limit[2],
+ ),
+ );
+
+ let panels = chart.draw_axis_panels(
+ &kps_bold,
+ &kps_light,
+ self.axis_panel_style,
+ self.bold_line_style,
+ self.light_line_style,
+ )?;
+
+ for i in 0..3 {
+ let axis = chart.draw_axis(i, &panels, self.axis_style)?;
+ let labels: Vec<_> = match i {
+ 0 => kps_bold
+ .x_points
+ .iter()
+ .map(|x| {
+ let x_text = (self.format_x)(x);
+ let mut p = axis[0].clone();
+ p[0] = Coord3D::X(x.clone());
+ (p, x_text)
+ })
+ .collect(),
+ 1 => kps_bold
+ .y_points
+ .iter()
+ .map(|y| {
+ let y_text = (self.format_y)(y);
+ let mut p = axis[0].clone();
+ p[1] = Coord3D::Y(y.clone());
+ (p, y_text)
+ })
+ .collect(),
+ _ => kps_bold
+ .z_points
+ .iter()
+ .map(|z| {
+ let z_text = (self.format_z)(z);
+ let mut p = axis[0].clone();
+ p[2] = Coord3D::Z(z.clone());
+ (p, z_text)
+ })
+ .collect(),
+ };
+ chart.draw_axis_ticks(
+ axis,
+ &labels[..],
+ self.tick_size,
+ self.axis_style,
+ self.label_style.clone(),
+ )?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/vendor/plotters/src/chart/builder.rs b/vendor/plotters/src/chart/builder.rs
new file mode 100644
index 000000000..cf804c6e6
--- /dev/null
+++ b/vendor/plotters/src/chart/builder.rs
@@ -0,0 +1,568 @@
+use super::context::ChartContext;
+
+use crate::coord::cartesian::{Cartesian2d, Cartesian3d};
+use crate::coord::ranged1d::AsRangedCoord;
+use crate::coord::Shift;
+
+use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
+use crate::style::{IntoTextStyle, SizeDesc, TextStyle};
+
+use plotters_backend::DrawingBackend;
+
+/**
+Specifies one of the four label positions around the figure.
+
+This is used to configure the label area size with function
+[`ChartBuilder::set_label_area_size()`].
+
+# Example
+
+```
+use plotters::prelude::*;
+let drawing_area = SVGBackend::new("label_area_position.svg", (300, 200)).into_drawing_area();
+drawing_area.fill(&WHITE).unwrap();
+let mut chart_builder = ChartBuilder::on(&drawing_area);
+chart_builder.set_label_area_size(LabelAreaPosition::Bottom, 60).set_label_area_size(LabelAreaPosition::Left, 35);
+let mut chart_context = chart_builder.build_cartesian_2d(0.0..4.0, 0.0..3.0).unwrap();
+chart_context.configure_mesh().x_desc("Spacious X label area").y_desc("Narrow Y label area").draw().unwrap();
+```
+
+The result is a chart with a spacious X label area and a narrow Y label area:
+
+![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@9ca6541/apidoc/label_area_position.svg)
+
+# See also
+
+[`ChartBuilder::set_left_and_bottom_label_area_size()`]
+*/
+#[derive(Copy, Clone)]
+pub enum LabelAreaPosition {
+ /// Top of the figure
+ Top = 0,
+ /// Bottom of the figure
+ Bottom = 1,
+ /// Left side of the figure
+ Left = 2,
+ /// Right side of the figure
+ Right = 3,
+}
+
+/**
+The helper object to create a chart context, which is used for the high-level figure drawing.
+
+With the help of this object, we can convert a basic drawing area into a chart context, which
+allows the high-level charting API being used on the drawing area.
+
+See [`ChartBuilder::on()`] for more information and examples.
+*/
+pub struct ChartBuilder<'a, 'b, DB: DrawingBackend> {
+ label_area_size: [u32; 4], // [upper, lower, left, right]
+ overlap_plotting_area: [bool; 4],
+ root_area: &'a DrawingArea<DB, Shift>,
+ title: Option<(String, TextStyle<'b>)>,
+ margin: [u32; 4],
+}
+
+impl<'a, 'b, DB: DrawingBackend> ChartBuilder<'a, 'b, DB> {
+ /**
+ Create a chart builder on the given drawing area
+
+ - `root`: The root drawing area
+ - Returns: The chart builder object
+
+ # Example
+
+ ```
+ use plotters::prelude::*;
+ let drawing_area = SVGBackend::new("chart_builder_on.svg", (300, 200)).into_drawing_area();
+ drawing_area.fill(&WHITE).unwrap();
+ let mut chart_builder = ChartBuilder::on(&drawing_area);
+ chart_builder.margin(5).set_left_and_bottom_label_area_size(35)
+ .caption("Figure title or caption", ("Calibri", 20, FontStyle::Italic, &RED).into_text_style(&drawing_area));
+ let mut chart_context = chart_builder.build_cartesian_2d(0.0..3.8, 0.0..2.8).unwrap();
+ chart_context.configure_mesh().draw().unwrap();
+ ```
+ The result is a chart with customized margins, label area sizes, and title:
+
+ ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@42ecf52/apidoc/chart_builder_on.svg)
+
+ */
+ pub fn on(root: &'a DrawingArea<DB, Shift>) -> Self {
+ Self {
+ label_area_size: [0; 4],
+ root_area: root,
+ title: None,
+ margin: [0; 4],
+ overlap_plotting_area: [false; 4],
+ }
+ }
+
+ /**
+ Sets the size of the four margins of the chart.
+
+ - `size`: The desired size of the four chart margins in backend units (pixels).
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn margin<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ let size = size.in_pixels(self.root_area).max(0) as u32;
+ self.margin = [size, size, size, size];
+ self
+ }
+
+ /**
+ Sets the size of the top margin of the chart.
+
+ - `size`: The desired size of the margin in backend units (pixels).
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn margin_top<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ let size = size.in_pixels(self.root_area).max(0) as u32;
+ self.margin[0] = size;
+ self
+ }
+
+ /**
+ Sets the size of the bottom margin of the chart.
+
+ - `size`: The desired size of the margin in backend units (pixels).
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn margin_bottom<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ let size = size.in_pixels(self.root_area).max(0) as u32;
+ self.margin[1] = size;
+ self
+ }
+
+ /**
+ Sets the size of the left margin of the chart.
+
+ - `size`: The desired size of the margin in backend units (pixels).
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn margin_left<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ let size = size.in_pixels(self.root_area).max(0) as u32;
+ self.margin[2] = size;
+ self
+ }
+
+ /**
+ Sets the size of the right margin of the chart.
+
+ - `size`: The desired size of the margin in backend units (pixels).
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn margin_right<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ let size = size.in_pixels(self.root_area).max(0) as u32;
+ self.margin[3] = size;
+ self
+ }
+
+ /**
+ Sets the size of the four label areas of the chart.
+
+ - `size`: The desired size of the four label areas in backend units (pixels).
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn set_all_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ let size = size.in_pixels(self.root_area);
+ self.set_label_area_size(LabelAreaPosition::Top, size)
+ .set_label_area_size(LabelAreaPosition::Bottom, size)
+ .set_label_area_size(LabelAreaPosition::Left, size)
+ .set_label_area_size(LabelAreaPosition::Right, size)
+ }
+
+ /**
+ Sets the size of the left and bottom label areas of the chart.
+
+ - `size`: The desired size of the left and bottom label areas in backend units (pixels).
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn set_left_and_bottom_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ let size = size.in_pixels(self.root_area);
+ self.set_label_area_size(LabelAreaPosition::Left, size)
+ .set_label_area_size(LabelAreaPosition::Bottom, size)
+ }
+
+ /**
+ Sets the size of the X label area at the bottom of the chart.
+
+ - `size`: The desired size of the X label area in backend units (pixels).
+ If set to 0, the X label area is removed.
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ self.set_label_area_size(LabelAreaPosition::Bottom, size)
+ }
+
+ /**
+ Sets the size of the Y label area to the left of the chart.
+
+ - `size`: The desired size of the Y label area in backend units (pixels).
+ If set to 0, the Y label area is removed.
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ self.set_label_area_size(LabelAreaPosition::Left, size)
+ }
+
+ /**
+ Sets the size of the X label area at the top of the chart.
+
+ - `size`: The desired size of the top X label area in backend units (pixels).
+ If set to 0, the top X label area is removed.
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn top_x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ self.set_label_area_size(LabelAreaPosition::Top, size)
+ }
+
+ /**
+ Sets the size of the Y label area to the right of the chart.
+
+ - `size`: The desired size of the Y label area in backend units (pixels).
+ If set to 0, the Y label area to the right is removed.
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn right_y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ self.set_label_area_size(LabelAreaPosition::Right, size)
+ }
+
+ /**
+ Sets the size of a chart label area.
+
+ - `pos`: The position of the desired label area to adjust
+ - `size`: The desired size of the label area in backend units (pixels).
+ If set to 0, the label area is removed.
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn set_label_area_size<S: SizeDesc>(
+ &mut self,
+ pos: LabelAreaPosition,
+ size: S,
+ ) -> &mut Self {
+ let size = size.in_pixels(self.root_area);
+ self.label_area_size[pos as usize] = size.unsigned_abs();
+ self.overlap_plotting_area[pos as usize] = size < 0;
+ self
+ }
+
+ /**
+ Sets the title or caption of the chart.
+
+ - `caption`: The caption of the chart
+ - `style`: The text style
+
+ The title or caption will be centered at the top of the drawing area.
+
+ See [`ChartBuilder::on()`] for more information and examples.
+ */
+ pub fn caption<S: AsRef<str>, Style: IntoTextStyle<'b>>(
+ &mut self,
+ caption: S,
+ style: Style,
+ ) -> &mut Self {
+ self.title = Some((
+ caption.as_ref().to_string(),
+ style.into_text_style(self.root_area),
+ ));
+ self
+ }
+
+ /// This function has been renamed to [`ChartBuilder::build_cartesian_2d()`] and is to be removed in the future.
+ #[allow(clippy::type_complexity)]
+ #[deprecated(
+ note = "`build_ranged` has been renamed to `build_cartesian_2d` and is to be removed in the future."
+ )]
+ pub fn build_ranged<X: AsRangedCoord, Y: AsRangedCoord>(
+ &mut self,
+ x_spec: X,
+ y_spec: Y,
+ ) -> Result<
+ ChartContext<'a, DB, Cartesian2d<X::CoordDescType, Y::CoordDescType>>,
+ DrawingAreaErrorKind<DB::ErrorType>,
+ > {
+ self.build_cartesian_2d(x_spec, y_spec)
+ }
+
+ /**
+ Builds a chart with a 2D Cartesian coordinate system.
+
+ - `x_spec`: Specifies the X axis range and data properties
+ - `y_spec`: Specifies the Y axis range and data properties
+ - Returns: A `ChartContext` object, ready to visualize data.
+
+ See [`ChartBuilder::on()`] and [`ChartContext::configure_mesh()`] for more information and examples.
+ */
+ #[allow(clippy::type_complexity)]
+ pub fn build_cartesian_2d<X: AsRangedCoord, Y: AsRangedCoord>(
+ &mut self,
+ x_spec: X,
+ y_spec: Y,
+ ) -> Result<
+ ChartContext<'a, DB, Cartesian2d<X::CoordDescType, Y::CoordDescType>>,
+ DrawingAreaErrorKind<DB::ErrorType>,
+ > {
+ let mut label_areas = [None, None, None, None];
+
+ let mut drawing_area = DrawingArea::clone(self.root_area);
+
+ if *self.margin.iter().max().unwrap_or(&0) > 0 {
+ drawing_area = drawing_area.margin(
+ self.margin[0] as i32,
+ self.margin[1] as i32,
+ self.margin[2] as i32,
+ self.margin[3] as i32,
+ );
+ }
+
+ let (title_dx, title_dy) = if let Some((ref title, ref style)) = self.title {
+ let (origin_dx, origin_dy) = drawing_area.get_base_pixel();
+ drawing_area = drawing_area.titled(title, style.clone())?;
+ let (current_dx, current_dy) = drawing_area.get_base_pixel();
+ (current_dx - origin_dx, current_dy - origin_dy)
+ } else {
+ (0, 0)
+ };
+
+ let (w, h) = drawing_area.dim_in_pixel();
+
+ let mut actual_drawing_area_pos = [0, h as i32, 0, w as i32];
+
+ const DIR: [(i16, i16); 4] = [(0, -1), (0, 1), (-1, 0), (1, 0)];
+
+ for (idx, (dx, dy)) in (0..4).map(|idx| (idx, DIR[idx])) {
+ if self.overlap_plotting_area[idx] {
+ continue;
+ }
+
+ let size = self.label_area_size[idx] as i32;
+
+ let split_point = if dx + dy < 0 { size } else { -size };
+
+ actual_drawing_area_pos[idx] += split_point;
+ }
+
+ // Now the root drawing area is to be split into
+ //
+ // +----------+------------------------------+------+
+ // | 0 | 1 (Top Label Area) | 2 |
+ // +----------+------------------------------+------+
+ // | 3 | | 5 |
+ // | Left | 4 (Plotting Area) | Right|
+ // | Labels | | Label|
+ // +----------+------------------------------+------+
+ // | 6 | 7 (Bottom Labels) | 8 |
+ // +----------+------------------------------+------+
+
+ let mut split: Vec<_> = drawing_area
+ .split_by_breakpoints(
+ &actual_drawing_area_pos[2..4],
+ &actual_drawing_area_pos[0..2],
+ )
+ .into_iter()
+ .map(Some)
+ .collect();
+
+ // Take out the plotting area
+ std::mem::swap(&mut drawing_area, split[4].as_mut().unwrap());
+
+ // Initialize the label areas - since the label area might be overlapping
+ // with the plotting area, in this case, we need handle them differently
+ for (src_idx, dst_idx) in [1, 7, 3, 5].iter().zip(0..4) {
+ if !self.overlap_plotting_area[dst_idx] {
+ let (h, w) = split[*src_idx].as_ref().unwrap().dim_in_pixel();
+ if h > 0 && w > 0 {
+ std::mem::swap(&mut label_areas[dst_idx], &mut split[*src_idx]);
+ }
+ } else if self.label_area_size[dst_idx] != 0 {
+ let size = self.label_area_size[dst_idx] as i32;
+ let (dw, dh) = drawing_area.dim_in_pixel();
+ let x0 = if DIR[dst_idx].0 > 0 {
+ dw as i32 - size
+ } else {
+ 0
+ };
+ let y0 = if DIR[dst_idx].1 > 0 {
+ dh as i32 - size
+ } else {
+ 0
+ };
+ let x1 = if DIR[dst_idx].0 >= 0 { dw as i32 } else { size };
+ let y1 = if DIR[dst_idx].1 >= 0 { dh as i32 } else { size };
+
+ label_areas[dst_idx] = Some(
+ drawing_area
+ .clone()
+ .shrink((x0, y0), ((x1 - x0), (y1 - y0))),
+ );
+ }
+ }
+
+ let mut pixel_range = drawing_area.get_pixel_range();
+ pixel_range.0.end -= 1;
+ pixel_range.1.end -= 1;
+ pixel_range.1 = pixel_range.1.end..pixel_range.1.start;
+
+ let mut x_label_area = [None, None];
+ let mut y_label_area = [None, None];
+
+ std::mem::swap(&mut x_label_area[0], &mut label_areas[0]);
+ std::mem::swap(&mut x_label_area[1], &mut label_areas[1]);
+ std::mem::swap(&mut y_label_area[0], &mut label_areas[2]);
+ std::mem::swap(&mut y_label_area[1], &mut label_areas[3]);
+
+ Ok(ChartContext {
+ x_label_area,
+ y_label_area,
+ drawing_area: drawing_area.apply_coord_spec(Cartesian2d::new(
+ x_spec,
+ y_spec,
+ pixel_range,
+ )),
+ series_anno: vec![],
+ drawing_area_pos: (
+ actual_drawing_area_pos[2] + title_dx + self.margin[2] as i32,
+ actual_drawing_area_pos[0] + title_dy + self.margin[0] as i32,
+ ),
+ })
+ }
+
+ /**
+ Builds a chart with a 3D Cartesian coordinate system.
+
+ - `x_spec`: Specifies the X axis range and data properties
+ - `y_spec`: Specifies the Y axis range and data properties
+ - `z_sepc`: Specifies the Z axis range and data properties
+ - Returns: A `ChartContext` object, ready to visualize data.
+
+ See [`ChartBuilder::on()`] and [`ChartContext::configure_axes()`] for more information and examples.
+ */
+ #[allow(clippy::type_complexity)]
+ pub fn build_cartesian_3d<X: AsRangedCoord, Y: AsRangedCoord, Z: AsRangedCoord>(
+ &mut self,
+ x_spec: X,
+ y_spec: Y,
+ z_spec: Z,
+ ) -> Result<
+ ChartContext<'a, DB, Cartesian3d<X::CoordDescType, Y::CoordDescType, Z::CoordDescType>>,
+ DrawingAreaErrorKind<DB::ErrorType>,
+ > {
+ let mut drawing_area = DrawingArea::clone(self.root_area);
+
+ if *self.margin.iter().max().unwrap_or(&0) > 0 {
+ drawing_area = drawing_area.margin(
+ self.margin[0] as i32,
+ self.margin[1] as i32,
+ self.margin[2] as i32,
+ self.margin[3] as i32,
+ );
+ }
+
+ let (title_dx, title_dy) = if let Some((ref title, ref style)) = self.title {
+ let (origin_dx, origin_dy) = drawing_area.get_base_pixel();
+ drawing_area = drawing_area.titled(title, style.clone())?;
+ let (current_dx, current_dy) = drawing_area.get_base_pixel();
+ (current_dx - origin_dx, current_dy - origin_dy)
+ } else {
+ (0, 0)
+ };
+
+ let pixel_range = drawing_area.get_pixel_range();
+
+ Ok(ChartContext {
+ x_label_area: [None, None],
+ y_label_area: [None, None],
+ drawing_area: drawing_area.apply_coord_spec(Cartesian3d::new(
+ x_spec,
+ y_spec,
+ z_spec,
+ pixel_range,
+ )),
+ series_anno: vec![],
+ drawing_area_pos: (
+ title_dx + self.margin[2] as i32,
+ title_dy + self.margin[0] as i32,
+ ),
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::prelude::*;
+ #[test]
+ fn test_label_area_size() {
+ let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
+ let mut chart = ChartBuilder::on(&drawing_area);
+
+ chart
+ .x_label_area_size(10)
+ .y_label_area_size(20)
+ .top_x_label_area_size(30)
+ .right_y_label_area_size(40);
+ assert_eq!(chart.label_area_size[1], 10);
+ assert_eq!(chart.label_area_size[2], 20);
+ assert_eq!(chart.label_area_size[0], 30);
+ assert_eq!(chart.label_area_size[3], 40);
+
+ chart.set_label_area_size(LabelAreaPosition::Left, 100);
+ chart.set_label_area_size(LabelAreaPosition::Right, 200);
+ chart.set_label_area_size(LabelAreaPosition::Top, 300);
+ chart.set_label_area_size(LabelAreaPosition::Bottom, 400);
+
+ assert_eq!(chart.label_area_size[0], 300);
+ assert_eq!(chart.label_area_size[1], 400);
+ assert_eq!(chart.label_area_size[2], 100);
+ assert_eq!(chart.label_area_size[3], 200);
+ }
+
+ #[test]
+ fn test_margin_configure() {
+ let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
+ let mut chart = ChartBuilder::on(&drawing_area);
+
+ chart.margin(5);
+ assert_eq!(chart.margin[0], 5);
+ assert_eq!(chart.margin[1], 5);
+ assert_eq!(chart.margin[2], 5);
+ assert_eq!(chart.margin[3], 5);
+
+ chart.margin_top(10);
+ chart.margin_bottom(11);
+ chart.margin_left(12);
+ chart.margin_right(13);
+ assert_eq!(chart.margin[0], 10);
+ assert_eq!(chart.margin[1], 11);
+ assert_eq!(chart.margin[2], 12);
+ assert_eq!(chart.margin[3], 13);
+ }
+
+ #[test]
+ fn test_caption() {
+ let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
+ let mut chart = ChartBuilder::on(&drawing_area);
+
+ chart.caption("This is a test case", ("serif", 10));
+
+ assert_eq!(chart.title.as_ref().unwrap().0, "This is a test case");
+ assert_eq!(chart.title.as_ref().unwrap().1.font.get_name(), "serif");
+ assert_eq!(chart.title.as_ref().unwrap().1.font.get_size(), 10.0);
+ check_color(chart.title.as_ref().unwrap().1.color, BLACK.to_rgba());
+
+ chart.caption("This is a test case", ("serif", 10));
+ assert_eq!(chart.title.as_ref().unwrap().1.font.get_name(), "serif");
+ }
+}
diff --git a/vendor/plotters/src/chart/context.rs b/vendor/plotters/src/chart/context.rs
new file mode 100644
index 000000000..ef91af195
--- /dev/null
+++ b/vendor/plotters/src/chart/context.rs
@@ -0,0 +1,221 @@
+use std::borrow::Borrow;
+
+use plotters_backend::{BackendCoord, DrawingBackend};
+
+use crate::chart::{SeriesAnno, SeriesLabelStyle};
+use crate::coord::{CoordTranslate, ReverseCoordTranslate, Shift};
+use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
+use crate::element::{CoordMapper, Drawable, PointCollection};
+
+pub(super) mod cartesian2d;
+pub(super) mod cartesian3d;
+
+pub(super) use cartesian3d::Coord3D;
+
+/**
+The context of the chart. This is the core object of Plotters.
+
+Any plot/chart is abstracted as this type, and any data series can be placed to the chart context.
+
+- To draw a series on a chart context, use [`ChartContext::draw_series()`].
+- To draw a single element on the chart, you may want to use [`ChartContext::plotting_area()`].
+
+See [`crate::series::LineSeries`] and [`ChartContext::configure_series_labels()`] for more information and examples
+*/
+pub struct ChartContext<'a, DB: DrawingBackend, CT: CoordTranslate> {
+ pub(crate) x_label_area: [Option<DrawingArea<DB, Shift>>; 2],
+ pub(crate) y_label_area: [Option<DrawingArea<DB, Shift>>; 2],
+ pub(crate) drawing_area: DrawingArea<DB, CT>,
+ pub(crate) series_anno: Vec<SeriesAnno<'a, DB>>,
+ pub(crate) drawing_area_pos: (i32, i32),
+}
+
+impl<'a, DB: DrawingBackend, CT: ReverseCoordTranslate> ChartContext<'a, DB, CT> {
+ /// Convert the chart context into an closure that can be used for coordinate translation
+ pub fn into_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT::From> {
+ let coord_spec = self.drawing_area.into_coord_spec();
+ move |coord| coord_spec.reverse_translate(coord)
+ }
+}
+
+impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> {
+ /**
+ Configure the styles for drawing series labels in the chart
+
+ # 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("configure_series_labels.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(LineSeries::new(data, BLACK)).unwrap().label("Series 1")
+ .legend(|(x,y)| Rectangle::new([(x - 15, y + 1), (x, y)], BLACK));
+ chart_context.configure_series_labels().position(SeriesLabelPosition::UpperRight).margin(20)
+ .legend_area_size(5).border_style(BLUE).background_style(BLUE.mix(0.1)).label_font(("Calibri", 20)).draw().unwrap();
+ ```
+
+ The result is a chart with one data series labeled "Series 1" in a blue legend box:
+
+ ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@8e0fe60/apidoc/configure_series_labels.svg)
+
+ # See also
+
+ See [`crate::series::LineSeries`] for more information and examples
+ */
+ pub fn configure_series_labels<'b>(&'b mut self) -> SeriesLabelStyle<'a, 'b, DB, CT>
+ where
+ DB: 'a,
+ {
+ SeriesLabelStyle::new(self)
+ }
+
+ /// Get a reference of underlying plotting area
+ pub fn plotting_area(&self) -> &DrawingArea<DB, CT> {
+ &self.drawing_area
+ }
+
+ /// Cast the reference to a chart context to a reference to underlying coordinate specification.
+ pub fn as_coord_spec(&self) -> &CT {
+ self.drawing_area.as_coord_spec()
+ }
+
+ // TODO: All draw_series_impl is overly strict about lifetime, because we don't have stable HKT,
+ // what we can ensure is for all lifetime 'b the element reference &'b E is a iterator
+ // of points reference with the same lifetime.
+ // However, this doesn't work if the coordinate doesn't live longer than the backend,
+ // this is unnecessarily strict
+ pub(crate) fn draw_series_impl<B, E, R, S>(
+ &mut self,
+ series: S,
+ ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
+ where
+ B: CoordMapper,
+ for<'b> &'b E: PointCollection<'b, CT::From, B>,
+ E: Drawable<DB, B>,
+ R: Borrow<E>,
+ S: IntoIterator<Item = R>,
+ {
+ for element in series {
+ self.drawing_area.draw(element.borrow())?;
+ }
+ Ok(())
+ }
+
+ pub(crate) fn alloc_series_anno(&mut self) -> &mut SeriesAnno<'a, DB> {
+ let idx = self.series_anno.len();
+ self.series_anno.push(SeriesAnno::new());
+ &mut self.series_anno[idx]
+ }
+
+ /**
+ Draws a data series. A data series in Plotters is abstracted as an iterator of elements.
+
+ See [`crate::series::LineSeries`] and [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn draw_series<B, E, R, S>(
+ &mut self,
+ series: S,
+ ) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>>
+ where
+ B: CoordMapper,
+ for<'b> &'b E: PointCollection<'b, CT::From, B>,
+ E: Drawable<DB, B>,
+ R: Borrow<E>,
+ S: IntoIterator<Item = R>,
+ {
+ self.draw_series_impl(series)?;
+ Ok(self.alloc_series_anno())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::prelude::*;
+
+ #[test]
+ fn test_chart_context() {
+ let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
+
+ drawing_area.fill(&WHITE).expect("Fill");
+
+ let mut chart = ChartBuilder::on(&drawing_area)
+ .caption("Test Title", ("serif", 10))
+ .x_label_area_size(20)
+ .y_label_area_size(20)
+ .set_label_area_size(LabelAreaPosition::Top, 20)
+ .set_label_area_size(LabelAreaPosition::Right, 20)
+ .build_cartesian_2d(0..10, 0..10)
+ .expect("Create chart")
+ .set_secondary_coord(0.0..1.0, 0.0..1.0);
+
+ chart
+ .configure_mesh()
+ .x_desc("X")
+ .y_desc("Y")
+ .draw()
+ .expect("Draw mesh");
+ chart
+ .configure_secondary_axes()
+ .x_desc("X")
+ .y_desc("Y")
+ .draw()
+ .expect("Draw Secondary axes");
+
+ // test that chart states work correctly with dual coord charts
+ let cs = chart.into_chart_state();
+ let mut chart = cs.clone().restore(&drawing_area);
+
+ chart
+ .draw_series(std::iter::once(Circle::new((5, 5), 5, &RED)))
+ .expect("Drawing error");
+ chart
+ .draw_secondary_series(std::iter::once(Circle::new((0.3, 0.8), 5, &GREEN)))
+ .expect("Drawing error")
+ .label("Test label")
+ .legend(|(x, y)| Rectangle::new([(x - 10, y - 5), (x, y + 5)], &GREEN));
+
+ chart
+ .configure_series_labels()
+ .position(SeriesLabelPosition::UpperMiddle)
+ .draw()
+ .expect("Drawing error");
+ }
+
+ #[test]
+ fn test_chart_context_3d() {
+ let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
+
+ drawing_area.fill(&WHITE).expect("Fill");
+
+ let mut chart = ChartBuilder::on(&drawing_area)
+ .caption("Test Title", ("serif", 10))
+ .x_label_area_size(20)
+ .y_label_area_size(20)
+ .set_label_area_size(LabelAreaPosition::Top, 20)
+ .set_label_area_size(LabelAreaPosition::Right, 20)
+ .build_cartesian_3d(0..10, 0..10, 0..10)
+ .expect("Create chart");
+
+ chart.with_projection(|mut pb| {
+ pb.yaw = 0.5;
+ pb.pitch = 0.5;
+ pb.scale = 0.5;
+ pb.into_matrix()
+ });
+
+ chart.configure_axes().draw().expect("Drawing axes");
+
+ // test that chart states work correctly with 3d coordinates
+ let cs = chart.into_chart_state();
+ let mut chart = cs.clone().restore(&drawing_area);
+
+ chart
+ .draw_series(std::iter::once(Circle::new((5, 5, 5), 5, &RED)))
+ .expect("Drawing error");
+ }
+}
diff --git a/vendor/plotters/src/chart/context/cartesian2d/draw_impl.rs b/vendor/plotters/src/chart/context/cartesian2d/draw_impl.rs
new file mode 100644
index 000000000..6dafa0879
--- /dev/null
+++ b/vendor/plotters/src/chart/context/cartesian2d/draw_impl.rs
@@ -0,0 +1,372 @@
+use std::ops::Range;
+
+use plotters_backend::DrawingBackend;
+
+use crate::chart::ChartContext;
+use crate::coord::{
+ cartesian::{Cartesian2d, MeshLine},
+ ranged1d::{KeyPointHint, Ranged},
+ Shift,
+};
+use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
+use crate::element::PathElement;
+use crate::style::{
+ text_anchor::{HPos, Pos, VPos},
+ FontTransform, ShapeStyle, TextStyle,
+};
+
+impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Cartesian2d<X, Y>> {
+ /// The actual function that draws the mesh lines.
+ /// It also returns the label that suppose to be there.
+ #[allow(clippy::type_complexity)]
+ fn draw_mesh_lines<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>(
+ &mut self,
+ (r, c): (YH, XH),
+ (x_mesh, y_mesh): (bool, bool),
+ mesh_line_style: &ShapeStyle,
+ mut fmt_label: FmtLabel,
+ ) -> Result<(Vec<(i32, String)>, Vec<(i32, String)>), DrawingAreaErrorKind<DB::ErrorType>>
+ where
+ FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>,
+ {
+ let mut x_labels = vec![];
+ let mut y_labels = vec![];
+ let xr = self.drawing_area.as_coord_spec().x_spec();
+ let yr = self.drawing_area.as_coord_spec().y_spec();
+ self.drawing_area.draw_mesh(
+ |b, l| {
+ let draw = match l {
+ MeshLine::XMesh((x, _), _, _) => {
+ if let Some(label_text) = fmt_label(xr, yr, &l) {
+ x_labels.push((x, label_text));
+ }
+ x_mesh
+ }
+ MeshLine::YMesh((_, y), _, _) => {
+ if let Some(label_text) = fmt_label(xr, yr, &l) {
+ y_labels.push((y, label_text));
+ }
+ y_mesh
+ }
+ };
+ if draw {
+ l.draw(b, mesh_line_style)
+ } else {
+ Ok(())
+ }
+ },
+ r,
+ c,
+ )?;
+ Ok((x_labels, y_labels))
+ }
+
+ fn draw_axis(
+ &self,
+ area: &DrawingArea<DB, Shift>,
+ axis_style: Option<&ShapeStyle>,
+ orientation: (i16, i16),
+ inward_labels: bool,
+ ) -> Result<Range<i32>, DrawingAreaErrorKind<DB::ErrorType>> {
+ let (x0, y0) = self.drawing_area.get_base_pixel();
+ let (tw, th) = area.dim_in_pixel();
+
+ let mut axis_range = if orientation.0 == 0 {
+ self.drawing_area.get_x_axis_pixel_range()
+ } else {
+ self.drawing_area.get_y_axis_pixel_range()
+ };
+
+ // At this point, the coordinate system tells us the pixel range after the translation.
+ // However, we need to use the logic coordinate system for drawing.
+ if orientation.0 == 0 {
+ axis_range.start -= x0;
+ axis_range.end -= x0;
+ } else {
+ axis_range.start -= y0;
+ axis_range.end -= y0;
+ }
+
+ if let Some(axis_style) = axis_style {
+ let mut x0 = if orientation.0 > 0 { 0 } else { tw as i32 - 1 };
+ let mut y0 = if orientation.1 > 0 { 0 } else { th as i32 - 1 };
+ let mut x1 = if orientation.0 >= 0 { 0 } else { tw as i32 - 1 };
+ let mut y1 = if orientation.1 >= 0 { 0 } else { th as i32 - 1 };
+
+ if inward_labels {
+ if orientation.0 == 0 {
+ if y0 == 0 {
+ y0 = th as i32 - 1;
+ y1 = th as i32 - 1;
+ } else {
+ y0 = 0;
+ y1 = 0;
+ }
+ } else if x0 == 0 {
+ x0 = tw as i32 - 1;
+ x1 = tw as i32 - 1;
+ } else {
+ x0 = 0;
+ x1 = 0;
+ }
+ }
+
+ if orientation.0 == 0 {
+ x0 = axis_range.start;
+ x1 = axis_range.end;
+ } else {
+ y0 = axis_range.start;
+ y1 = axis_range.end;
+ }
+
+ area.draw(&PathElement::new(
+ vec![(x0, y0), (x1, y1)],
+ *axis_style,
+ ))?;
+ }
+
+ Ok(axis_range)
+ }
+
+ // TODO: consider make this function less complicated
+ #[allow(clippy::too_many_arguments)]
+ #[allow(clippy::cognitive_complexity)]
+ fn draw_axis_and_labels(
+ &self,
+ area: Option<&DrawingArea<DB, Shift>>,
+ axis_style: Option<&ShapeStyle>,
+ labels: &[(i32, String)],
+ label_style: &TextStyle,
+ label_offset: i32,
+ orientation: (i16, i16),
+ axis_desc: Option<(&str, &TextStyle)>,
+ tick_size: i32,
+ ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
+ let area = if let Some(target) = area {
+ target
+ } else {
+ return Ok(());
+ };
+
+ let (x0, y0) = self.drawing_area.get_base_pixel();
+ let (tw, th) = area.dim_in_pixel();
+
+ /* This is the minimal distance from the axis to the box of the labels */
+ let label_dist = tick_size.abs() * 2;
+
+ /* Draw the axis and get the axis range so that we can do further label
+ * and tick mark drawing */
+ let axis_range = self.draw_axis(area, axis_style, orientation, tick_size < 0)?;
+
+ /* To make the right label area looks nice, it's a little bit tricky, since for a that is
+ * very long, we actually prefer left alignment instead of right alignment.
+ * Otherwise, the right alignment looks better. So we estimate the max and min label width
+ * So that we are able decide if we should apply right alignment for the text. */
+ let label_width: Vec<_> = labels
+ .iter()
+ .map(|(_, text)| {
+ if orientation.0 > 0 && orientation.1 == 0 && tick_size >= 0 {
+ self.drawing_area
+ .estimate_text_size(text, label_style)
+ .map(|(w, _)| w)
+ .unwrap_or(0) as i32
+ } else {
+ // Don't ever do the layout estimationfor the drawing area that is either not
+ // the right one or the tick mark is inward.
+ 0
+ }
+ })
+ .collect();
+
+ let min_width = *label_width.iter().min().unwrap_or(&1).max(&1);
+ let max_width = *label_width
+ .iter()
+ .filter(|&&x| x < min_width * 2)
+ .max()
+ .unwrap_or(&min_width);
+ let right_align_width = (min_width * 2).min(max_width);
+
+ /* Then we need to draw the tick mark and the label */
+ for ((p, t), w) in labels.iter().zip(label_width.into_iter()) {
+ /* Make sure we are actually in the visible range */
+ let rp = if orientation.0 == 0 { *p - x0 } else { *p - y0 };
+
+ if rp < axis_range.start.min(axis_range.end)
+ || axis_range.end.max(axis_range.start) < rp
+ {
+ continue;
+ }
+
+ let (cx, cy, h_pos, v_pos) = if tick_size >= 0 {
+ match orientation {
+ // Right
+ (dx, dy) if dx > 0 && dy == 0 => {
+ if w >= right_align_width {
+ (label_dist, *p - y0, HPos::Left, VPos::Center)
+ } else {
+ (
+ label_dist + right_align_width,
+ *p - y0,
+ HPos::Right,
+ VPos::Center,
+ )
+ }
+ }
+ // Left
+ (dx, dy) if dx < 0 && dy == 0 => {
+ (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
+ }
+ // Bottom
+ (dx, dy) if dx == 0 && dy > 0 => (*p - x0, label_dist, HPos::Center, VPos::Top),
+ // Top
+ (dx, dy) if dx == 0 && dy < 0 => {
+ (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom)
+ }
+ _ => panic!("Bug: Invalid orientation specification"),
+ }
+ } else {
+ match orientation {
+ // Right
+ (dx, dy) if dx > 0 && dy == 0 => {
+ (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center)
+ }
+ // Left
+ (dx, dy) if dx < 0 && dy == 0 => {
+ (label_dist, *p - y0, HPos::Left, VPos::Center)
+ }
+ // Bottom
+ (dx, dy) if dx == 0 && dy > 0 => {
+ (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom)
+ }
+ // Top
+ (dx, dy) if dx == 0 && dy < 0 => (*p - x0, label_dist, HPos::Center, VPos::Top),
+ _ => panic!("Bug: Invalid orientation specification"),
+ }
+ };
+
+ let (text_x, text_y) = if orientation.0 == 0 {
+ (cx + label_offset, cy)
+ } else {
+ (cx, cy + label_offset)
+ };
+
+ let label_style = &label_style.pos(Pos::new(h_pos, v_pos));
+ area.draw_text(t, label_style, (text_x, text_y))?;
+
+ if tick_size != 0 {
+ if let Some(style) = axis_style {
+ let xmax = tw as i32 - 1;
+ let ymax = th as i32 - 1;
+ let (kx0, ky0, kx1, ky1) = if tick_size > 0 {
+ match orientation {
+ (dx, dy) if dx > 0 && dy == 0 => (0, *p - y0, tick_size, *p - y0),
+ (dx, dy) if dx < 0 && dy == 0 => {
+ (xmax - tick_size, *p - y0, xmax, *p - y0)
+ }
+ (dx, dy) if dx == 0 && dy > 0 => (*p - x0, 0, *p - x0, tick_size),
+ (dx, dy) if dx == 0 && dy < 0 => {
+ (*p - x0, ymax - tick_size, *p - x0, ymax)
+ }
+ _ => panic!("Bug: Invalid orientation specification"),
+ }
+ } else {
+ match orientation {
+ (dx, dy) if dx > 0 && dy == 0 => {
+ (xmax, *p - y0, xmax + tick_size, *p - y0)
+ }
+ (dx, dy) if dx < 0 && dy == 0 => (0, *p - y0, -tick_size, *p - y0),
+ (dx, dy) if dx == 0 && dy > 0 => {
+ (*p - x0, ymax, *p - x0, ymax + tick_size)
+ }
+ (dx, dy) if dx == 0 && dy < 0 => (*p - x0, 0, *p - x0, -tick_size),
+ _ => panic!("Bug: Invalid orientation specification"),
+ }
+ };
+ let line = PathElement::new(vec![(kx0, ky0), (kx1, ky1)], *style);
+ area.draw(&line)?;
+ }
+ }
+ }
+
+ if let Some((text, style)) = axis_desc {
+ let actual_style = if orientation.0 == 0 {
+ style.clone()
+ } else if orientation.0 == -1 {
+ style.transform(FontTransform::Rotate270)
+ } else {
+ style.transform(FontTransform::Rotate90)
+ };
+
+ let (x0, y0, h_pos, v_pos) = match orientation {
+ // Right
+ (dx, dy) if dx > 0 && dy == 0 => (tw, th / 2, HPos::Center, VPos::Top),
+ // Left
+ (dx, dy) if dx < 0 && dy == 0 => (0, th / 2, HPos::Center, VPos::Top),
+ // Bottom
+ (dx, dy) if dx == 0 && dy > 0 => (tw / 2, th, HPos::Center, VPos::Bottom),
+ // Top
+ (dx, dy) if dx == 0 && dy < 0 => (tw / 2, 0, HPos::Center, VPos::Top),
+ _ => panic!("Bug: Invalid orientation specification"),
+ };
+
+ let actual_style = &actual_style.pos(Pos::new(h_pos, v_pos));
+ area.draw_text(text, actual_style, (x0 as i32, y0 as i32))?;
+ }
+
+ Ok(())
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ pub(crate) fn draw_mesh<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>(
+ &mut self,
+ (r, c): (YH, XH),
+ mesh_line_style: &ShapeStyle,
+ x_label_style: &TextStyle,
+ y_label_style: &TextStyle,
+ fmt_label: FmtLabel,
+ x_mesh: bool,
+ y_mesh: bool,
+ x_label_offset: i32,
+ y_label_offset: i32,
+ x_axis: bool,
+ y_axis: bool,
+ axis_style: &ShapeStyle,
+ axis_desc_style: &TextStyle,
+ x_desc: Option<String>,
+ y_desc: Option<String>,
+ x_tick_size: [i32; 2],
+ y_tick_size: [i32; 2],
+ ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
+ where
+ FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>,
+ {
+ let (x_labels, y_labels) =
+ self.draw_mesh_lines((r, c), (x_mesh, y_mesh), mesh_line_style, fmt_label)?;
+
+ for idx in 0..2 {
+ self.draw_axis_and_labels(
+ self.x_label_area[idx].as_ref(),
+ if x_axis { Some(axis_style) } else { None },
+ &x_labels[..],
+ x_label_style,
+ x_label_offset,
+ (0, -1 + idx as i16 * 2),
+ x_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)),
+ x_tick_size[idx],
+ )?;
+
+ self.draw_axis_and_labels(
+ self.y_label_area[idx].as_ref(),
+ if y_axis { Some(axis_style) } else { None },
+ &y_labels[..],
+ y_label_style,
+ y_label_offset,
+ (-1 + idx as i16 * 2, 0),
+ y_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)),
+ y_tick_size[idx],
+ )?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/vendor/plotters/src/chart/context/cartesian2d/mod.rs b/vendor/plotters/src/chart/context/cartesian2d/mod.rs
new file mode 100644
index 000000000..fd1aef272
--- /dev/null
+++ b/vendor/plotters/src/chart/context/cartesian2d/mod.rs
@@ -0,0 +1,90 @@
+use std::ops::Range;
+
+use plotters_backend::{BackendCoord, DrawingBackend};
+
+use crate::chart::{ChartContext, DualCoordChartContext, MeshStyle};
+use crate::coord::{
+ cartesian::Cartesian2d,
+ ranged1d::{AsRangedCoord, Ranged, ValueFormatter},
+ Shift,
+};
+use crate::drawing::DrawingArea;
+
+mod draw_impl;
+
+impl<'a, DB, XT, YT, X, Y> ChartContext<'a, DB, Cartesian2d<X, Y>>
+where
+ DB: DrawingBackend,
+ X: Ranged<ValueType = XT> + ValueFormatter<XT>,
+ Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
+{
+ pub(crate) fn is_overlapping_drawing_area(
+ &self,
+ area: Option<&DrawingArea<DB, Shift>>,
+ ) -> bool {
+ if let Some(area) = area {
+ let (x0, y0) = area.get_base_pixel();
+ let (w, h) = area.dim_in_pixel();
+ let (x1, y1) = (x0 + w as i32, y0 + h as i32);
+ let (dx0, dy0) = self.drawing_area.get_base_pixel();
+ let (w, h) = self.drawing_area.dim_in_pixel();
+ let (dx1, dy1) = (dx0 + w as i32, dy0 + h as i32);
+
+ let (ox0, ox1) = (x0.max(dx0), x1.min(dx1));
+ let (oy0, oy1) = (y0.max(dy0), y1.min(dy1));
+
+ ox1 > ox0 && oy1 > oy0
+ } else {
+ false
+ }
+ }
+
+ /// Initialize a mesh configuration object and mesh drawing can be finalized by calling
+ /// the function `MeshStyle::draw`.
+ pub fn configure_mesh(&mut self) -> MeshStyle<'a, '_, X, Y, DB> {
+ MeshStyle::new(self)
+ }
+}
+
+impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Cartesian2d<X, Y>> {
+ /// Get the range of X axis
+ pub fn x_range(&self) -> Range<X::ValueType> {
+ self.drawing_area.get_x_range()
+ }
+
+ /// Get range of the Y axis
+ pub fn y_range(&self) -> Range<Y::ValueType> {
+ self.drawing_area.get_y_range()
+ }
+
+ /// Maps the coordinate to the backend coordinate. This is typically used
+ /// with an interactive chart.
+ pub fn backend_coord(&self, coord: &(X::ValueType, Y::ValueType)) -> BackendCoord {
+ self.drawing_area.map_coordinate(coord)
+ }
+}
+
+impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Cartesian2d<X, Y>> {
+ /// Convert this chart context into a dual axis chart context and attach a second coordinate spec
+ /// on the chart context. For more detailed information, see documentation for [struct DualCoordChartContext](struct.DualCoordChartContext.html)
+ ///
+ /// - `x_coord`: The coordinate spec for the X axis
+ /// - `y_coord`: The coordinate spec for the Y axis
+ /// - **returns** The newly created dual spec chart context
+ #[allow(clippy::type_complexity)]
+ pub fn set_secondary_coord<SX: AsRangedCoord, SY: AsRangedCoord>(
+ self,
+ x_coord: SX,
+ y_coord: SY,
+ ) -> DualCoordChartContext<
+ 'a,
+ DB,
+ Cartesian2d<X, Y>,
+ Cartesian2d<SX::CoordDescType, SY::CoordDescType>,
+ > {
+ let mut pixel_range = self.drawing_area.get_pixel_range();
+ pixel_range.1 = pixel_range.1.end..pixel_range.1.start;
+
+ DualCoordChartContext::new(self, Cartesian2d::new(x_coord, y_coord, pixel_range))
+ }
+}
diff --git a/vendor/plotters/src/chart/context/cartesian3d/draw_impl.rs b/vendor/plotters/src/chart/context/cartesian3d/draw_impl.rs
new file mode 100644
index 000000000..fcc4c4f7b
--- /dev/null
+++ b/vendor/plotters/src/chart/context/cartesian3d/draw_impl.rs
@@ -0,0 +1,309 @@
+use std::cmp::Ordering;
+
+use plotters_backend::DrawingBackend;
+
+use crate::chart::ChartContext;
+use crate::coord::{
+ cartesian::Cartesian3d,
+ ranged1d::{KeyPointHint, Ranged},
+ CoordTranslate,
+};
+use crate::drawing::DrawingAreaErrorKind;
+use crate::element::{EmptyElement, PathElement, Polygon, Text};
+use crate::style::{
+ text_anchor::{HPos, Pos, VPos},
+ ShapeStyle, TextStyle,
+};
+
+use super::Coord3D;
+
+pub(crate) struct KeyPoints3d<X: Ranged, Y: Ranged, Z: Ranged> {
+ pub(crate) x_points: Vec<X::ValueType>,
+ pub(crate) y_points: Vec<Y::ValueType>,
+ pub(crate) z_points: Vec<Z::ValueType>,
+}
+
+impl<'a, DB, X: Ranged, Y: Ranged, Z: Ranged> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
+where
+ DB: DrawingBackend,
+ X::ValueType: Clone,
+ Y::ValueType: Clone,
+ Z::ValueType: Clone,
+{
+ pub(crate) fn get_key_points<XH: KeyPointHint, YH: KeyPointHint, ZH: KeyPointHint>(
+ &self,
+ x_hint: XH,
+ y_hint: YH,
+ z_hint: ZH,
+ ) -> KeyPoints3d<X, Y, Z> {
+ let coord = self.plotting_area().as_coord_spec();
+ let x_points = coord.logic_x.key_points(x_hint);
+ let y_points = coord.logic_y.key_points(y_hint);
+ let z_points = coord.logic_z.key_points(z_hint);
+ KeyPoints3d {
+ x_points,
+ y_points,
+ z_points,
+ }
+ }
+ #[allow(clippy::type_complexity)]
+ pub(crate) fn draw_axis_ticks(
+ &mut self,
+ axis: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2],
+ labels: &[(
+ [Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3],
+ String,
+ )],
+ tick_size: i32,
+ style: ShapeStyle,
+ font: TextStyle,
+ ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
+ let coord = self.plotting_area().as_coord_spec();
+ let begin = coord.translate(&Coord3D::build_coord([
+ &axis[0][0],
+ &axis[0][1],
+ &axis[0][2],
+ ]));
+ let end = coord.translate(&Coord3D::build_coord([
+ &axis[1][0],
+ &axis[1][1],
+ &axis[1][2],
+ ]));
+ let axis_dir = (end.0 - begin.0, end.1 - begin.1);
+ let (x_range, y_range) = self.plotting_area().get_pixel_range();
+ let x_mid = (x_range.start + x_range.end) / 2;
+ let y_mid = (y_range.start + y_range.end) / 2;
+
+ let x_dir = if begin.0 < x_mid {
+ (-tick_size, 0)
+ } else {
+ (tick_size, 0)
+ };
+
+ let y_dir = if begin.1 < y_mid {
+ (0, -tick_size)
+ } else {
+ (0, tick_size)
+ };
+
+ let x_score = (x_dir.0 * axis_dir.0 + x_dir.1 * axis_dir.1).abs();
+ let y_score = (y_dir.0 * axis_dir.0 + y_dir.1 * axis_dir.1).abs();
+
+ let dir = if x_score < y_score { x_dir } else { y_dir };
+
+ for (pos, text) in labels {
+ let logic_pos = Coord3D::build_coord([&pos[0], &pos[1], &pos[2]]);
+ let mut font = font.clone();
+
+ match dir.0.cmp(&0) {
+ Ordering::Less => font.pos = Pos::new(HPos::Right, VPos::Center),
+ Ordering::Greater => font.pos = Pos::new(HPos::Left, VPos::Center),
+ _ => (),
+ }
+
+ match dir.1.cmp(&0) {
+ Ordering::Less => font.pos = Pos::new(HPos::Center, VPos::Bottom),
+ Ordering::Greater => font.pos = Pos::new(HPos::Center, VPos::Top),
+ _ => (),
+ }
+
+ let element = EmptyElement::at(logic_pos)
+ + PathElement::new(vec![(0, 0), dir], style)
+ + Text::new(text.to_string(), (dir.0 * 2, dir.1 * 2), font);
+ self.plotting_area().draw(&element)?;
+ }
+ Ok(())
+ }
+ #[allow(clippy::type_complexity)]
+ pub(crate) fn draw_axis(
+ &mut self,
+ idx: usize,
+ panels: &[[[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2]; 3],
+ style: ShapeStyle,
+ ) -> Result<
+ [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2],
+ DrawingAreaErrorKind<DB::ErrorType>,
+ > {
+ let coord = self.plotting_area().as_coord_spec();
+ let x_range = coord.logic_x.range();
+ let y_range = coord.logic_y.range();
+ let z_range = coord.logic_z.range();
+
+ let ranges: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 2]; 3] = [
+ [Coord3D::X(x_range.start), Coord3D::X(x_range.end)],
+ [Coord3D::Y(y_range.start), Coord3D::Y(y_range.end)],
+ [Coord3D::Z(z_range.start), Coord3D::Z(z_range.end)],
+ ];
+
+ let (start, end) = {
+ let mut start = [&ranges[0][0], &ranges[1][0], &ranges[2][0]];
+ let mut end = [&ranges[0][1], &ranges[1][1], &ranges[2][1]];
+
+ let mut plan = vec![];
+
+ for i in 0..3 {
+ if i == idx {
+ continue;
+ }
+ start[i] = &panels[i][0][i];
+ end[i] = &panels[i][0][i];
+ for j in 0..3 {
+ if i != idx && i != j && j != idx {
+ for k in 0..2 {
+ start[j] = &panels[i][k][j];
+ end[j] = &panels[i][k][j];
+ plan.push((start, end));
+ }
+ }
+ }
+ }
+ plan.into_iter()
+ .min_by_key(|&(s, e)| {
+ let d = coord.projected_depth(s[0].get_x(), s[1].get_y(), s[2].get_z());
+ let d = d + coord.projected_depth(e[0].get_x(), e[1].get_y(), e[2].get_z());
+ let (_, y1) = coord.translate(&Coord3D::build_coord(s));
+ let (_, y2) = coord.translate(&Coord3D::build_coord(e));
+ let y = y1 + y2;
+ (d, y)
+ })
+ .unwrap()
+ };
+
+ self.plotting_area().draw(&PathElement::new(
+ vec![Coord3D::build_coord(start), Coord3D::build_coord(end)],
+ style,
+ ))?;
+
+ Ok([
+ [start[0].clone(), start[1].clone(), start[2].clone()],
+ [end[0].clone(), end[1].clone(), end[2].clone()],
+ ])
+ }
+
+ #[allow(clippy::type_complexity)]
+ pub(crate) fn draw_axis_panels(
+ &mut self,
+ bold_points: &KeyPoints3d<X, Y, Z>,
+ light_points: &KeyPoints3d<X, Y, Z>,
+ panel_style: ShapeStyle,
+ bold_grid_style: ShapeStyle,
+ light_grid_style: ShapeStyle,
+ ) -> Result<
+ [[[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2]; 3],
+ DrawingAreaErrorKind<DB::ErrorType>,
+ > {
+ let mut r_iter = (0..3).map(|idx| {
+ self.draw_axis_panel(
+ idx,
+ bold_points,
+ light_points,
+ panel_style,
+ bold_grid_style,
+ light_grid_style,
+ )
+ });
+ Ok([
+ r_iter.next().unwrap()?,
+ r_iter.next().unwrap()?,
+ r_iter.next().unwrap()?,
+ ])
+ }
+ #[allow(clippy::type_complexity)]
+ fn draw_axis_panel(
+ &mut self,
+ idx: usize,
+ bold_points: &KeyPoints3d<X, Y, Z>,
+ light_points: &KeyPoints3d<X, Y, Z>,
+ panel_style: ShapeStyle,
+ bold_grid_style: ShapeStyle,
+ light_grid_style: ShapeStyle,
+ ) -> Result<
+ [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2],
+ DrawingAreaErrorKind<DB::ErrorType>,
+ > {
+ let coord = self.plotting_area().as_coord_spec();
+ let x_range = coord.logic_x.range();
+ let y_range = coord.logic_y.range();
+ let z_range = coord.logic_z.range();
+
+ let ranges: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 2]; 3] = [
+ [Coord3D::X(x_range.start), Coord3D::X(x_range.end)],
+ [Coord3D::Y(y_range.start), Coord3D::Y(y_range.end)],
+ [Coord3D::Z(z_range.start), Coord3D::Z(z_range.end)],
+ ];
+
+ let (mut panel, start, end) = {
+ let vert_a = [&ranges[0][0], &ranges[1][0], &ranges[2][0]];
+ let mut vert_b = [&ranges[0][1], &ranges[1][1], &ranges[2][1]];
+ let mut vert_c = vert_a;
+ let vert_d = vert_b;
+
+ vert_b[idx] = &ranges[idx][0];
+ vert_c[idx] = &ranges[idx][1];
+
+ let (vert_a, vert_b) =
+ if coord.projected_depth(vert_a[0].get_x(), vert_a[1].get_y(), vert_a[2].get_z())
+ >= coord.projected_depth(
+ vert_c[0].get_x(),
+ vert_c[1].get_y(),
+ vert_c[2].get_z(),
+ )
+ {
+ (vert_a, vert_b)
+ } else {
+ (vert_c, vert_d)
+ };
+
+ let mut m = vert_a;
+ m[(idx + 1) % 3] = vert_b[(idx + 1) % 3];
+ let mut n = vert_a;
+ n[(idx + 2) % 3] = vert_b[(idx + 2) % 3];
+
+ (
+ vec![
+ Coord3D::build_coord(vert_a),
+ Coord3D::build_coord(m),
+ Coord3D::build_coord(vert_b),
+ Coord3D::build_coord(n),
+ ],
+ vert_a,
+ vert_b,
+ )
+ };
+ self.plotting_area()
+ .draw(&Polygon::new(panel.clone(), panel_style))?;
+ panel.push(panel[0].clone());
+ self.plotting_area()
+ .draw(&PathElement::new(panel, bold_grid_style))?;
+
+ for (kps, style) in vec![
+ (light_points, light_grid_style),
+ (bold_points, bold_grid_style),
+ ]
+ .into_iter()
+ {
+ for idx in (0..3).filter(|&i| i != idx) {
+ let kps: Vec<_> = match idx {
+ 0 => kps.x_points.iter().map(|x| Coord3D::X(x.clone())).collect(),
+ 1 => kps.y_points.iter().map(|y| Coord3D::Y(y.clone())).collect(),
+ _ => kps.z_points.iter().map(|z| Coord3D::Z(z.clone())).collect(),
+ };
+ for kp in kps.iter() {
+ let mut kp_start = start;
+ let mut kp_end = end;
+ kp_start[idx] = kp;
+ kp_end[idx] = kp;
+ self.plotting_area().draw(&PathElement::new(
+ vec![Coord3D::build_coord(kp_start), Coord3D::build_coord(kp_end)],
+ style,
+ ))?;
+ }
+ }
+ }
+
+ Ok([
+ [start[0].clone(), start[1].clone(), start[2].clone()],
+ [end[0].clone(), end[1].clone(), end[2].clone()],
+ ])
+ }
+}
diff --git a/vendor/plotters/src/chart/context/cartesian3d/mod.rs b/vendor/plotters/src/chart/context/cartesian3d/mod.rs
new file mode 100644
index 000000000..ff28adf4f
--- /dev/null
+++ b/vendor/plotters/src/chart/context/cartesian3d/mod.rs
@@ -0,0 +1,130 @@
+use crate::chart::{axes3d::Axes3dStyle, ChartContext};
+use crate::coord::{
+ cartesian::Cartesian3d,
+ ranged1d::{Ranged, ValueFormatter},
+ ranged3d::{ProjectionMatrix, ProjectionMatrixBuilder},
+};
+use plotters_backend::DrawingBackend;
+
+mod draw_impl;
+
+#[derive(Clone, Debug)]
+pub(crate) enum Coord3D<X, Y, Z> {
+ X(X),
+ Y(Y),
+ Z(Z),
+}
+
+impl<X, Y, Z> Coord3D<X, Y, Z> {
+ fn get_x(&self) -> &X {
+ match self {
+ Coord3D::X(ret) => ret,
+ _ => panic!("Invalid call!"),
+ }
+ }
+ fn get_y(&self) -> &Y {
+ match self {
+ Coord3D::Y(ret) => ret,
+ _ => panic!("Invalid call!"),
+ }
+ }
+ fn get_z(&self) -> &Z {
+ match self {
+ Coord3D::Z(ret) => ret,
+ _ => panic!("Invalid call!"),
+ }
+ }
+
+ fn build_coord([x, y, z]: [&Self; 3]) -> (X, Y, Z)
+ where
+ X: Clone,
+ Y: Clone,
+ Z: Clone,
+ {
+ (x.get_x().clone(), y.get_y().clone(), z.get_z().clone())
+ }
+}
+
+impl<'a, DB, X, Y, Z, XT, YT, ZT> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
+where
+ DB: DrawingBackend,
+ X: Ranged<ValueType = XT> + ValueFormatter<XT>,
+ Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
+ Z: Ranged<ValueType = ZT> + ValueFormatter<ZT>,
+{
+ /**
+ Create an axis configuration object, to set line styles, labels, sizes, etc.
+
+ Default values for axis configuration are set by function `Axes3dStyle::new()`.
+
+ # Example
+
+ ```
+ use plotters::prelude::*;
+ let drawing_area = SVGBackend::new("configure_axes.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_bottom(30).build_cartesian_3d(0.0..4.0, 0.0..3.0, 0.0..2.7).unwrap();
+ chart_context.configure_axes().tick_size(8).x_labels(4).y_labels(3).z_labels(2)
+ .max_light_lines(5).axis_panel_style(GREEN.mix(0.1)).bold_grid_style(BLUE.mix(0.3))
+ .light_grid_style(BLUE.mix(0.2)).label_style(("Calibri", 10))
+ .x_formatter(&|x| format!("x={x}")).draw().unwrap();
+ ```
+
+ The resulting chart reflects the customizations specified through `configure_axes()`:
+
+ ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@4c3cef4/apidoc/configure_axes.svg)
+
+ All these customizations are `Axes3dStyle` methods.
+
+ In the chart, `tick_size(8)` produces tick marks 8 pixels long. You can use
+ `(5u32).percent().max(5).in_pixels(chart.plotting_area()` to tell Plotters to calculate the tick mark
+ size as a percentage of the dimensions of the figure. See [`crate::style::RelativeSize`] and
+ [`crate::style::SizeDesc`] for more information.
+
+ `x_labels(4)` specifies a maximum of 4
+ tick marks and labels in the X axis. `max_light_lines(5)` specifies a maximum of 5 minor grid lines
+ between any two tick marks. `axis_panel_style(GREEN.mix(0.1))` specifies the style of the panels in
+ the background, a light green color. `bold_grid_style(BLUE.mix(0.3))` and `light_grid_style(BLUE.mix(0.2))`
+ specify the style of the major and minor grid lines, respectively. `label_style()` specifies the text
+ style of the axis labels, and `x_formatter(|x| format!("x={x}"))` specifies the string format of the X
+ axis labels.
+
+ # See also
+
+ [`ChartContext::configure_mesh()`], a similar function for 2D plots
+ */
+ pub fn configure_axes(&mut self) -> Axes3dStyle<'a, '_, X, Y, Z, DB> {
+ Axes3dStyle::new(self)
+ }
+}
+
+impl<'a, DB, X: Ranged, Y: Ranged, Z: Ranged> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
+where
+ DB: DrawingBackend,
+{
+ /// Override the 3D projection matrix. This function allows to override the default projection
+ /// matrix.
+ /// - `pf`: A function that takes the default projection matrix configuration and returns the
+ /// projection matrix. This function will allow you to adjust the pitch, yaw angle and the
+ /// centeral point of the projection, etc. You can also build a projection matrix which is not
+ /// relies on the default configuration as well.
+ pub fn with_projection<P: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix>(
+ &mut self,
+ pf: P,
+ ) -> &mut Self {
+ let (actual_x, actual_y) = self.drawing_area.get_pixel_range();
+ self.drawing_area
+ .as_coord_spec_mut()
+ .set_projection(actual_x, actual_y, pf);
+ self
+ }
+ /// Sets the 3d coordinate pixel range.
+ pub fn set_3d_pixel_range(&mut self, size: (i32, i32, i32)) -> &mut Self {
+ let (actual_x, actual_y) = self.drawing_area.get_pixel_range();
+ self.drawing_area
+ .as_coord_spec_mut()
+ .set_coord_pixel_range(actual_x, actual_y, size);
+ self
+ }
+}
diff --git a/vendor/plotters/src/chart/dual_coord.rs b/vendor/plotters/src/chart/dual_coord.rs
new file mode 100644
index 000000000..d5960e030
--- /dev/null
+++ b/vendor/plotters/src/chart/dual_coord.rs
@@ -0,0 +1,242 @@
+/// The dual coordinate system support
+use std::borrow::{Borrow, BorrowMut};
+use std::ops::{Deref, DerefMut};
+use std::sync::Arc;
+
+use super::mesh::SecondaryMeshStyle;
+use super::{ChartContext, ChartState, SeriesAnno};
+
+use crate::coord::cartesian::Cartesian2d;
+use crate::coord::ranged1d::{Ranged, ValueFormatter};
+use crate::coord::{CoordTranslate, ReverseCoordTranslate, Shift};
+
+use crate::drawing::DrawingArea;
+use crate::drawing::DrawingAreaErrorKind;
+use crate::element::{Drawable, PointCollection};
+
+use plotters_backend::{BackendCoord, DrawingBackend};
+
+/// The chart context that has two coordinate system attached.
+/// This situation is quite common, for example, we with two different coodinate system.
+/// For instance this example <img src="https://plotters-rs.github.io/plotters-doc-data/twoscale.png"></img>
+/// This is done by attaching a second coordinate system to ChartContext by method [ChartContext::set_secondary_coord](struct.ChartContext.html#method.set_secondary_coord).
+/// For instance of dual coordinate charts, see [this example](https://github.com/38/plotters/blob/master/examples/two-scales.rs#L15).
+/// Note: `DualCoordChartContext` is always deref to the chart context.
+/// - If you want to configure the secondary axis, method [DualCoordChartContext::configure_secondary_axes](struct.DualCoordChartContext.html#method.configure_secondary_axes)
+/// - If you want to draw a series using secondary coordinate system, use [DualCoordChartContext::draw_secondary_series](struct.DualCoordChartContext.html#method.draw_secondary_series). And method [ChartContext::draw_series](struct.ChartContext.html#method.draw_series) will always use primary coordinate spec.
+pub struct DualCoordChartContext<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> {
+ pub(super) primary: ChartContext<'a, DB, CT1>,
+ pub(super) secondary: ChartContext<'a, DB, CT2>,
+}
+
+/// The chart state for a dual coord chart, see the detailed description for `ChartState` for more
+/// information about the purpose of a chart state.
+/// Similar to [ChartState](struct.ChartState.html), but used for the dual coordinate charts.
+#[derive(Clone)]
+pub struct DualCoordChartState<CT1: CoordTranslate, CT2: CoordTranslate> {
+ primary: ChartState<CT1>,
+ secondary: ChartState<CT2>,
+}
+
+impl<DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
+ DualCoordChartContext<'_, DB, CT1, CT2>
+{
+ /// Convert the chart context into a chart state, similar to [ChartContext::into_chart_state](struct.ChartContext.html#method.into_chart_state)
+ pub fn into_chart_state(self) -> DualCoordChartState<CT1, CT2> {
+ DualCoordChartState {
+ primary: self.primary.into(),
+ secondary: self.secondary.into(),
+ }
+ }
+
+ /// Convert the chart context into a sharable chart state.
+ pub fn into_shared_chart_state(self) -> DualCoordChartState<Arc<CT1>, Arc<CT2>> {
+ DualCoordChartState {
+ primary: self.primary.into_shared_chart_state(),
+ secondary: self.secondary.into_shared_chart_state(),
+ }
+ }
+
+ /// Copy the coordinate specs and make a chart state
+ pub fn to_chart_state(&self) -> DualCoordChartState<CT1, CT2>
+ where
+ CT1: Clone,
+ CT2: Clone,
+ {
+ DualCoordChartState {
+ primary: self.primary.to_chart_state(),
+ secondary: self.secondary.to_chart_state(),
+ }
+ }
+}
+
+impl<CT1: CoordTranslate, CT2: CoordTranslate> DualCoordChartState<CT1, CT2> {
+ /// Restore the chart state on the given drawing area
+ pub fn restore<DB: DrawingBackend>(
+ self,
+ area: &DrawingArea<DB, Shift>,
+ ) -> DualCoordChartContext<'_, DB, CT1, CT2> {
+ let primary = self.primary.restore(area);
+ let secondary = self
+ .secondary
+ .restore(&primary.plotting_area().strip_coord_spec());
+ DualCoordChartContext { primary, secondary }
+ }
+}
+
+impl<DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
+ From<DualCoordChartContext<'_, DB, CT1, CT2>> for DualCoordChartState<CT1, CT2>
+{
+ fn from(chart: DualCoordChartContext<'_, DB, CT1, CT2>) -> DualCoordChartState<CT1, CT2> {
+ chart.into_chart_state()
+ }
+}
+
+impl<'b, DB: DrawingBackend, CT1: CoordTranslate + Clone, CT2: CoordTranslate + Clone>
+ From<&'b DualCoordChartContext<'_, DB, CT1, CT2>> for DualCoordChartState<CT1, CT2>
+{
+ fn from(chart: &'b DualCoordChartContext<'_, DB, CT1, CT2>) -> DualCoordChartState<CT1, CT2> {
+ chart.to_chart_state()
+ }
+}
+
+impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
+ DualCoordChartContext<'a, DB, CT1, CT2>
+{
+ pub(super) fn new(mut primary: ChartContext<'a, DB, CT1>, secondary_coord: CT2) -> Self {
+ let secondary_drawing_area = primary
+ .drawing_area
+ .strip_coord_spec()
+ .apply_coord_spec(secondary_coord);
+ let mut secondary_x_label_area = [None, None];
+ let mut secondary_y_label_area = [None, None];
+
+ std::mem::swap(&mut primary.x_label_area[0], &mut secondary_x_label_area[0]);
+ std::mem::swap(&mut primary.y_label_area[1], &mut secondary_y_label_area[1]);
+
+ Self {
+ primary,
+ secondary: ChartContext {
+ x_label_area: secondary_x_label_area,
+ y_label_area: secondary_y_label_area,
+ drawing_area: secondary_drawing_area,
+ series_anno: vec![],
+ drawing_area_pos: (0, 0),
+ },
+ }
+ }
+
+ /// Get a reference to the drawing area that uses the secondary coordinate system
+ pub fn secondary_plotting_area(&self) -> &DrawingArea<DB, CT2> {
+ &self.secondary.drawing_area
+ }
+
+ /// Borrow a mutable reference to the chart context that uses the secondary
+ /// coordinate system
+ pub fn borrow_secondary(&self) -> &ChartContext<'a, DB, CT2> {
+ &self.secondary
+ }
+}
+
+impl<DB: DrawingBackend, CT1: CoordTranslate, CT2: ReverseCoordTranslate>
+ DualCoordChartContext<'_, DB, CT1, CT2>
+{
+ /// Convert the chart context into the secondary coordinate translation function
+ pub fn into_secondary_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT2::From> {
+ let coord_spec = self.secondary.drawing_area.into_coord_spec();
+ move |coord| coord_spec.reverse_translate(coord)
+ }
+}
+
+impl<DB: DrawingBackend, CT1: ReverseCoordTranslate, CT2: ReverseCoordTranslate>
+ DualCoordChartContext<'_, DB, CT1, CT2>
+{
+ /// Convert the chart context into a pair of closures that maps the pixel coordinate into the
+ /// logical coordinate for both primary coordinate system and secondary coordinate system.
+ pub fn into_coord_trans_pair(
+ self,
+ ) -> (
+ impl Fn(BackendCoord) -> Option<CT1::From>,
+ impl Fn(BackendCoord) -> Option<CT2::From>,
+ ) {
+ let coord_spec_1 = self.primary.drawing_area.into_coord_spec();
+ let coord_spec_2 = self.secondary.drawing_area.into_coord_spec();
+ (
+ move |coord| coord_spec_1.reverse_translate(coord),
+ move |coord| coord_spec_2.reverse_translate(coord),
+ )
+ }
+}
+
+impl<
+ 'a,
+ DB: DrawingBackend,
+ CT1: CoordTranslate,
+ XT,
+ YT,
+ SX: Ranged<ValueType = XT>,
+ SY: Ranged<ValueType = YT>,
+ > DualCoordChartContext<'a, DB, CT1, Cartesian2d<SX, SY>>
+where
+ SX: ValueFormatter<XT>,
+ SY: ValueFormatter<YT>,
+{
+ /// Start configure the style for the secondary axes
+ pub fn configure_secondary_axes<'b>(&'b mut self) -> SecondaryMeshStyle<'a, 'b, SX, SY, DB> {
+ SecondaryMeshStyle::new(&mut self.secondary)
+ }
+}
+
+impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged, SX: Ranged, SY: Ranged>
+ DualCoordChartContext<'a, DB, Cartesian2d<X, Y>, Cartesian2d<SX, SY>>
+{
+ /// Draw a series use the secondary coordinate system.
+ /// - `series`: The series to draw
+ /// - `Returns` the series annotation object or error code
+ pub fn draw_secondary_series<E, R, S>(
+ &mut self,
+ series: S,
+ ) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>>
+ where
+ for<'b> &'b E: PointCollection<'b, (SX::ValueType, SY::ValueType)>,
+ E: Drawable<DB>,
+ R: Borrow<E>,
+ S: IntoIterator<Item = R>,
+ {
+ self.secondary.draw_series_impl(series)?;
+ Ok(self.primary.alloc_series_anno())
+ }
+}
+
+impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
+ Borrow<ChartContext<'a, DB, CT1>> for DualCoordChartContext<'a, DB, CT1, CT2>
+{
+ fn borrow(&self) -> &ChartContext<'a, DB, CT1> {
+ &self.primary
+ }
+}
+
+impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
+ BorrowMut<ChartContext<'a, DB, CT1>> for DualCoordChartContext<'a, DB, CT1, CT2>
+{
+ fn borrow_mut(&mut self) -> &mut ChartContext<'a, DB, CT1> {
+ &mut self.primary
+ }
+}
+
+impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> Deref
+ for DualCoordChartContext<'a, DB, CT1, CT2>
+{
+ type Target = ChartContext<'a, DB, CT1>;
+ fn deref(&self) -> &Self::Target {
+ self.borrow()
+ }
+}
+
+impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> DerefMut
+ for DualCoordChartContext<'a, DB, CT1, CT2>
+{
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.borrow_mut()
+ }
+}
diff --git a/vendor/plotters/src/chart/mesh.rs b/vendor/plotters/src/chart/mesh.rs
new file mode 100644
index 000000000..c2b7a9577
--- /dev/null
+++ b/vendor/plotters/src/chart/mesh.rs
@@ -0,0 +1,533 @@
+use std::marker::PhantomData;
+
+use super::builder::LabelAreaPosition;
+use super::context::ChartContext;
+use crate::coord::cartesian::{Cartesian2d, MeshLine};
+use crate::coord::ranged1d::{BoldPoints, LightPoints, Ranged, ValueFormatter};
+use crate::drawing::DrawingAreaErrorKind;
+use crate::style::{
+ AsRelative, Color, FontDesc, FontFamily, FontStyle, IntoTextStyle, RGBColor, ShapeStyle,
+ SizeDesc, TextStyle,
+};
+
+use plotters_backend::DrawingBackend;
+
+/// The style used to describe the mesh and axis for a secondary coordinate system.
+pub struct SecondaryMeshStyle<'a, 'b, X: Ranged, Y: Ranged, DB: DrawingBackend> {
+ style: MeshStyle<'a, 'b, X, Y, DB>,
+}
+
+impl<'a, 'b, XT, YT, X: Ranged<ValueType = XT>, Y: Ranged<ValueType = YT>, DB: DrawingBackend>
+ SecondaryMeshStyle<'a, 'b, X, Y, DB>
+where
+ X: ValueFormatter<XT>,
+ Y: ValueFormatter<YT>,
+{
+ pub(super) fn new(target: &'b mut ChartContext<'a, DB, Cartesian2d<X, Y>>) -> Self {
+ let mut style = target.configure_mesh();
+ style.draw_x_mesh = false;
+ style.draw_y_mesh = false;
+ Self { style }
+ }
+
+ /// Set the style definition for the axis
+ /// - `style`: The style for the axis
+ pub fn axis_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self {
+ self.style.axis_style(style);
+ self
+ }
+
+ /// The offset of x labels. This is used when we want to place the label in the middle of
+ /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this
+ /// use case is deprecated, see [SegmentedCoord coord decorator](../coord/ranged1d/trait.IntoSegmentedCoord.html) for more details
+ /// - `value`: The offset in pixel
+ pub fn x_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self {
+ self.style.x_label_offset(value);
+ self
+ }
+
+ /// The offset of y labels. This is used when we want to place the label in the middle of
+ /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this
+ /// use case is deprecated, see [SegmentedCoord coord decorator](../coord/ranged1d/trait.IntoSegmentedCoord.html) for more details
+ /// - `value`: The offset in pixel
+ pub fn y_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self {
+ self.style.y_label_offset(value);
+ self
+ }
+
+ /// Set how many labels for the X axis at most
+ /// - `value`: The maximum desired number of labels in the X axis
+ pub fn x_labels(&mut self, value: usize) -> &mut Self {
+ self.style.x_labels(value);
+ self
+ }
+
+ /// Set how many label for the Y axis at most
+ /// - `value`: The maximum desired number of labels in the Y axis
+ pub fn y_labels(&mut self, value: usize) -> &mut Self {
+ self.style.y_labels(value);
+ self
+ }
+
+ /// Set the formatter function for the X label text
+ /// - `fmt`: The formatter function
+ pub fn x_label_formatter(&mut self, fmt: &'b dyn Fn(&X::ValueType) -> String) -> &mut Self {
+ self.style.x_label_formatter(fmt);
+ self
+ }
+
+ /// Set the formatter function for the Y label text
+ /// - `fmt`: The formatter function
+ pub fn y_label_formatter(&mut self, fmt: &'b dyn Fn(&Y::ValueType) -> String) -> &mut Self {
+ self.style.y_label_formatter(fmt);
+ self
+ }
+
+ /// Set the axis description's style. If not given, use label style instead.
+ /// - `style`: The text style that would be applied to descriptions
+ pub fn axis_desc_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
+ self.style
+ .axis_desc_style(style.into_text_style(&self.style.parent_size));
+ self
+ }
+
+ /// Set the X axis's description
+ /// - `desc`: The description of the X axis
+ pub fn x_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self {
+ self.style.x_desc(desc);
+ self
+ }
+
+ /// Set the Y axis's description
+ /// - `desc`: The description of the Y axis
+ pub fn y_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self {
+ self.style.y_desc(desc);
+ self
+ }
+
+ /// Draw the axes for the secondary coordinate system
+ pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
+ self.style.draw()
+ }
+
+ /// Set the label style for the secondary axis
+ pub fn label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
+ self.style.label_style(style);
+ self
+ }
+
+ /// Set all the tick marks to the same size
+ /// `value`: The new size
+ pub fn set_all_tick_mark_size<S: SizeDesc>(&mut self, value: S) -> &mut Self {
+ let size = value.in_pixels(&self.style.parent_size);
+ self.style.x_tick_size = [size, size];
+ self.style.y_tick_size = [size, size];
+ self
+ }
+ /// Sets the tick mark size for a given label area position.
+ /// `value`: The new size
+ pub fn set_tick_mark_size<S: SizeDesc>(
+ &mut self,
+ pos: LabelAreaPosition,
+ value: S,
+ ) -> &mut Self {
+ *match pos {
+ LabelAreaPosition::Top => &mut self.style.x_tick_size[0],
+ LabelAreaPosition::Bottom => &mut self.style.x_tick_size[1],
+ LabelAreaPosition::Left => &mut self.style.y_tick_size[0],
+ LabelAreaPosition::Right => &mut self.style.y_tick_size[1],
+ } = value.in_pixels(&self.style.parent_size);
+ self
+ }
+}
+
+/// The struct that is used for tracking the configuration of a mesh of any chart
+pub struct MeshStyle<'a, 'b, X: Ranged, Y: Ranged, DB: DrawingBackend> {
+ pub(super) parent_size: (u32, u32),
+ pub(super) draw_x_mesh: bool,
+ pub(super) draw_y_mesh: bool,
+ pub(super) draw_x_axis: bool,
+ pub(super) draw_y_axis: bool,
+ pub(super) x_label_offset: i32,
+ pub(super) y_label_offset: i32,
+ pub(super) x_light_lines_limit: usize,
+ pub(super) y_light_lines_limit: usize,
+ pub(super) n_x_labels: usize,
+ pub(super) n_y_labels: usize,
+ pub(super) axis_desc_style: Option<TextStyle<'b>>,
+ pub(super) x_desc: Option<String>,
+ pub(super) y_desc: Option<String>,
+ pub(super) bold_line_style: Option<ShapeStyle>,
+ pub(super) light_line_style: Option<ShapeStyle>,
+ pub(super) axis_style: Option<ShapeStyle>,
+ pub(super) x_label_style: Option<TextStyle<'b>>,
+ pub(super) y_label_style: Option<TextStyle<'b>>,
+ pub(super) format_x: Option<&'b dyn Fn(&X::ValueType) -> String>,
+ pub(super) format_y: Option<&'b dyn Fn(&Y::ValueType) -> String>,
+ pub(super) target: Option<&'b mut ChartContext<'a, DB, Cartesian2d<X, Y>>>,
+ pub(super) _phantom_data: PhantomData<(X, Y)>,
+ pub(super) x_tick_size: [i32; 2],
+ pub(super) y_tick_size: [i32; 2],
+}
+
+impl<'a, 'b, X, Y, XT, YT, DB> MeshStyle<'a, 'b, X, Y, DB>
+where
+ X: Ranged<ValueType = XT> + ValueFormatter<XT>,
+ Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
+ DB: DrawingBackend,
+{
+ pub(crate) fn new(chart: &'b mut ChartContext<'a, DB, Cartesian2d<X, Y>>) -> Self {
+ let base_tick_size = (5u32).percent().max(5).in_pixels(chart.plotting_area());
+
+ let mut x_tick_size = [base_tick_size, base_tick_size];
+ let mut y_tick_size = [base_tick_size, base_tick_size];
+
+ for idx in 0..2 {
+ if chart.is_overlapping_drawing_area(chart.x_label_area[idx].as_ref()) {
+ x_tick_size[idx] = -x_tick_size[idx];
+ }
+ if chart.is_overlapping_drawing_area(chart.y_label_area[idx].as_ref()) {
+ y_tick_size[idx] = -y_tick_size[idx];
+ }
+ }
+
+ MeshStyle {
+ parent_size: chart.drawing_area.dim_in_pixel(),
+ axis_style: None,
+ x_label_offset: 0,
+ y_label_offset: 0,
+ draw_x_mesh: true,
+ draw_y_mesh: true,
+ draw_x_axis: true,
+ draw_y_axis: true,
+ x_light_lines_limit: 10,
+ y_light_lines_limit: 10,
+ n_x_labels: 11,
+ n_y_labels: 11,
+ bold_line_style: None,
+ light_line_style: None,
+ x_label_style: None,
+ y_label_style: None,
+ format_x: None,
+ format_y: None,
+ target: Some(chart),
+ _phantom_data: PhantomData,
+ x_desc: None,
+ y_desc: None,
+ axis_desc_style: None,
+ x_tick_size,
+ y_tick_size,
+ }
+ }
+}
+
+impl<'a, 'b, X, Y, DB> MeshStyle<'a, 'b, X, Y, DB>
+where
+ X: Ranged,
+ Y: Ranged,
+ DB: DrawingBackend,
+{
+ /// Set all the tick mark to the same size
+ /// `value`: The new size
+ pub fn set_all_tick_mark_size<S: SizeDesc>(&mut self, value: S) -> &mut Self {
+ let size = value.in_pixels(&self.parent_size);
+ self.x_tick_size = [size, size];
+ self.y_tick_size = [size, size];
+ self
+ }
+
+ /// Set the tick mark size on the axes. When this is set to negative, the axis value label will
+ /// become inward.
+ ///
+ /// - `pos`: The which label area we want to set
+ /// - `value`: The size specification
+ pub fn set_tick_mark_size<S: SizeDesc>(
+ &mut self,
+ pos: LabelAreaPosition,
+ value: S,
+ ) -> &mut Self {
+ *match pos {
+ LabelAreaPosition::Top => &mut self.x_tick_size[0],
+ LabelAreaPosition::Bottom => &mut self.x_tick_size[1],
+ LabelAreaPosition::Left => &mut self.y_tick_size[0],
+ LabelAreaPosition::Right => &mut self.y_tick_size[1],
+ } = value.in_pixels(&self.parent_size);
+ self
+ }
+
+ /// The offset of x labels. This is used when we want to place the label in the middle of
+ /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this
+ /// use case is deprecated, see [SegmentedCoord coord decorator](../coord/ranged1d/trait.IntoSegmentedCoord.html) for more details
+ /// - `value`: The offset in pixel
+ pub fn x_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self {
+ self.x_label_offset = value.in_pixels(&self.parent_size);
+ self
+ }
+
+ /// The offset of y labels. This is used when we want to place the label in the middle of
+ /// the grid. This is used to adjust label position for histograms, but since plotters 0.3, this
+ /// use case is deprecated, see [SegmentedCoord coord decorator](../coord/ranged1d/trait.IntoSegmentedCoord.html) for more details
+ /// - `value`: The offset in pixel
+ pub fn y_label_offset<S: SizeDesc>(&mut self, value: S) -> &mut Self {
+ self.y_label_offset = value.in_pixels(&self.parent_size);
+ self
+ }
+
+ /// Disable the mesh for the x axis.
+ pub fn disable_x_mesh(&mut self) -> &mut Self {
+ self.draw_x_mesh = false;
+ self
+ }
+
+ /// Disable the mesh for the y axis
+ pub fn disable_y_mesh(&mut self) -> &mut Self {
+ self.draw_y_mesh = false;
+ self
+ }
+
+ /// Disable drawing the X axis
+ pub fn disable_x_axis(&mut self) -> &mut Self {
+ self.draw_x_axis = false;
+ self
+ }
+
+ /// Disable drawing the Y axis
+ pub fn disable_y_axis(&mut self) -> &mut Self {
+ self.draw_y_axis = false;
+ self
+ }
+
+ /// Disable drawing all meshes
+ pub fn disable_mesh(&mut self) -> &mut Self {
+ self.disable_x_mesh().disable_y_mesh()
+ }
+
+ /// Disable drawing all axes
+ pub fn disable_axes(&mut self) -> &mut Self {
+ self.disable_x_axis().disable_y_axis()
+ }
+
+ /// Set the style definition for the axis
+ /// - `style`: The style for the axis
+ pub fn axis_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self {
+ self.axis_style = Some(style.into());
+ self
+ }
+
+ /// Set the maximum number of divisions for the minor grid
+ /// - `value`: Maximum desired divisions between two consecutive X labels
+ pub fn x_max_light_lines(&mut self, value: usize) -> &mut Self {
+ self.x_light_lines_limit = value;
+ self
+ }
+
+ /// Set the maximum number of divisions for the minor grid
+ /// - `value`: Maximum desired divisions between two consecutive Y labels
+ pub fn y_max_light_lines(&mut self, value: usize) -> &mut Self {
+ self.y_light_lines_limit = value;
+ self
+ }
+
+ /// Set the maximum number of divisions for the minor grid
+ /// - `value`: Maximum desired divisions between two consecutive labels in X and Y
+ pub fn max_light_lines(&mut self, value: usize) -> &mut Self {
+ self.x_light_lines_limit = value;
+ self.y_light_lines_limit = value;
+ self
+ }
+
+ /// Set how many labels for the X axis at most
+ /// - `value`: The maximum desired number of labels in the X axis
+ pub fn x_labels(&mut self, value: usize) -> &mut Self {
+ self.n_x_labels = value;
+ self
+ }
+
+ /// Set how many label for the Y axis at most
+ /// - `value`: The maximum desired number of labels in the Y axis
+ pub fn y_labels(&mut self, value: usize) -> &mut Self {
+ self.n_y_labels = value;
+ self
+ }
+
+ /// Set the style for the coarse grind grid
+ /// - `style`: This is the coarse grind grid style
+ pub fn bold_line_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self {
+ self.bold_line_style = Some(style.into());
+ self
+ }
+
+ /// Set the style for the fine grind grid
+ /// - `style`: The fine grind grid style
+ pub fn light_line_style<T: Into<ShapeStyle>>(&mut self, style: T) -> &mut Self {
+ self.light_line_style = Some(style.into());
+ self
+ }
+
+ /// Set the style of the label text
+ /// - `style`: The text style that would be applied to the labels
+ pub fn label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
+ let style = style.into_text_style(&self.parent_size);
+ self.x_label_style = Some(style.clone());
+ self.y_label_style = Some(style);
+ self
+ }
+
+ /// Set the style of the label X axis text
+ /// - `style`: The text style that would be applied to the labels
+ pub fn x_label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
+ self.x_label_style = Some(style.into_text_style(&self.parent_size));
+ self
+ }
+
+ /// Set the style of the label Y axis text
+ /// - `style`: The text style that would be applied to the labels
+ pub fn y_label_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
+ self.y_label_style = Some(style.into_text_style(&self.parent_size));
+ self
+ }
+
+ /// Set the formatter function for the X label text
+ /// - `fmt`: The formatter function
+ pub fn x_label_formatter(&mut self, fmt: &'b dyn Fn(&X::ValueType) -> String) -> &mut Self {
+ self.format_x = Some(fmt);
+ self
+ }
+
+ /// Set the formatter function for the Y label text
+ /// - `fmt`: The formatter function
+ pub fn y_label_formatter(&mut self, fmt: &'b dyn Fn(&Y::ValueType) -> String) -> &mut Self {
+ self.format_y = Some(fmt);
+ self
+ }
+
+ /// Set the axis description's style. If not given, use label style instead.
+ /// - `style`: The text style that would be applied to descriptions
+ pub fn axis_desc_style<T: IntoTextStyle<'b>>(&mut self, style: T) -> &mut Self {
+ self.axis_desc_style = Some(style.into_text_style(&self.parent_size));
+ self
+ }
+
+ /// Set the X axis's description
+ /// - `desc`: The description of the X axis
+ pub fn x_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self {
+ self.x_desc = Some(desc.into());
+ self
+ }
+
+ /// Set the Y axis's description
+ /// - `desc`: The description of the Y axis
+ pub fn y_desc<T: Into<String>>(&mut self, desc: T) -> &mut Self {
+ self.y_desc = Some(desc.into());
+ self
+ }
+
+ /// Draw the configured mesh on the target plot
+ pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
+ where
+ X: ValueFormatter<<X as Ranged>::ValueType>,
+ Y: ValueFormatter<<Y as Ranged>::ValueType>,
+ {
+ let target = self.target.take().unwrap();
+
+ let default_mesh_color_1 = RGBColor(0, 0, 0).mix(0.2);
+ let default_mesh_color_2 = RGBColor(0, 0, 0).mix(0.1);
+ let default_axis_color = RGBColor(0, 0, 0);
+ let default_label_font = FontDesc::new(
+ FontFamily::SansSerif,
+ f64::from((12i32).percent().max(12).in_pixels(&self.parent_size)),
+ FontStyle::Normal,
+ );
+
+ let bold_style = self
+ .bold_line_style
+ .unwrap_or_else(|| (&default_mesh_color_1).into());
+ let light_style = self
+ .light_line_style
+ .unwrap_or_else(|| (&default_mesh_color_2).into());
+ let axis_style = self
+ .axis_style
+ .unwrap_or_else(|| (&default_axis_color).into());
+
+ let x_label_style = self
+ .x_label_style
+ .clone()
+ .unwrap_or_else(|| default_label_font.clone().into());
+
+ let y_label_style = self
+ .y_label_style
+ .clone()
+ .unwrap_or_else(|| default_label_font.into());
+
+ let axis_desc_style = self
+ .axis_desc_style
+ .clone()
+ .unwrap_or_else(|| x_label_style.clone());
+
+ target.draw_mesh(
+ (
+ LightPoints::new(self.n_y_labels, self.n_y_labels * self.y_light_lines_limit),
+ LightPoints::new(self.n_x_labels, self.n_x_labels * self.x_light_lines_limit),
+ ),
+ &light_style,
+ &x_label_style,
+ &y_label_style,
+ |_, _, _| None,
+ self.draw_x_mesh,
+ self.draw_y_mesh,
+ self.x_label_offset,
+ self.y_label_offset,
+ false,
+ false,
+ &axis_style,
+ &axis_desc_style,
+ self.x_desc.clone(),
+ self.y_desc.clone(),
+ self.x_tick_size,
+ self.y_tick_size,
+ )?;
+
+ target.draw_mesh(
+ (BoldPoints(self.n_y_labels), BoldPoints(self.n_x_labels)),
+ &bold_style,
+ &x_label_style,
+ &y_label_style,
+ |xr, yr, m| match m {
+ MeshLine::XMesh(_, _, v) => {
+ if self.draw_x_axis {
+ if let Some(fmt_func) = self.format_x {
+ Some(fmt_func(v))
+ } else {
+ Some(xr.format_ext(v))
+ }
+ } else {
+ None
+ }
+ }
+ MeshLine::YMesh(_, _, v) => {
+ if self.draw_y_axis {
+ if let Some(fmt_func) = self.format_y {
+ Some(fmt_func(v))
+ } else {
+ Some(yr.format_ext(v))
+ }
+ } else {
+ None
+ }
+ }
+ },
+ self.draw_x_mesh,
+ self.draw_y_mesh,
+ self.x_label_offset,
+ self.y_label_offset,
+ self.draw_x_axis,
+ self.draw_y_axis,
+ &axis_style,
+ &axis_desc_style,
+ None,
+ None,
+ self.x_tick_size,
+ self.y_tick_size,
+ )
+ }
+}
diff --git a/vendor/plotters/src/chart/mod.rs b/vendor/plotters/src/chart/mod.rs
new file mode 100644
index 000000000..4a8802963
--- /dev/null
+++ b/vendor/plotters/src/chart/mod.rs
@@ -0,0 +1,30 @@
+/*!
+The high-level plotting abstractions.
+
+Plotters uses `ChartContext`, a thin layer on the top of `DrawingArea`, to provide
+high-level chart specific drawing functionalities, like, mesh line, coordinate label
+and other common components for the data chart.
+
+To draw a series, `ChartContext::draw_series` is used to draw a series on the chart.
+In Plotters, a series is abstracted as an iterator of elements.
+
+`ChartBuilder` is used to construct a chart. To learn more detailed information, check the
+detailed description for each struct.
+*/
+
+mod axes3d;
+mod builder;
+mod context;
+mod dual_coord;
+mod mesh;
+mod series;
+mod state;
+
+pub use builder::{ChartBuilder, LabelAreaPosition};
+pub use context::ChartContext;
+pub use dual_coord::{DualCoordChartContext, DualCoordChartState};
+pub use mesh::{MeshStyle, SecondaryMeshStyle};
+pub use series::{SeriesAnno, SeriesLabelPosition, SeriesLabelStyle};
+pub use state::ChartState;
+
+use context::Coord3D;
diff --git a/vendor/plotters/src/chart/series.rs b/vendor/plotters/src/chart/series.rs
new file mode 100644
index 000000000..8c430cbef
--- /dev/null
+++ b/vendor/plotters/src/chart/series.rs
@@ -0,0 +1,301 @@
+use super::ChartContext;
+use crate::coord::CoordTranslate;
+use crate::drawing::DrawingAreaErrorKind;
+use crate::element::{DynElement, EmptyElement, IntoDynElement, MultiLineText, Rectangle};
+use crate::style::{IntoFont, IntoTextStyle, ShapeStyle, SizeDesc, TextStyle, TRANSPARENT};
+
+use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
+
+type SeriesAnnoDrawFn<'a, DB> = dyn Fn(BackendCoord) -> DynElement<'a, DB, BackendCoord> + 'a;
+
+/// The annotations (such as the label of the series, the legend element, etc)
+/// When a series is drawn onto a drawing area, an series annotation object
+/// is created and a mutable reference is returned.
+pub struct SeriesAnno<'a, DB: DrawingBackend> {
+ label: Option<String>,
+ draw_func: Option<Box<SeriesAnnoDrawFn<'a, DB>>>,
+}
+
+impl<'a, DB: DrawingBackend> SeriesAnno<'a, DB> {
+ #[allow(clippy::option_as_ref_deref)]
+ pub(crate) fn get_label(&self) -> &str {
+ // TODO: Change this when we bump the MSRV
+ self.label.as_ref().map(|x| x.as_str()).unwrap_or("")
+ }
+
+ pub(crate) fn get_draw_func(&self) -> Option<&SeriesAnnoDrawFn<'a, DB>> {
+ self.draw_func.as_ref().map(|x| x.as_ref())
+ }
+
+ pub(crate) fn new() -> Self {
+ Self {
+ label: None,
+ draw_func: None,
+ }
+ }
+
+ /**
+ Sets the series label for the current series.
+
+ See [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn label<L: Into<String>>(&mut self, label: L) -> &mut Self {
+ self.label = Some(label.into());
+ self
+ }
+
+ /**
+ Sets the legend element creator function.
+
+ - `func`: The function use to create the element
+
+ # Note
+
+ The creation function uses a shifted pixel-based coordinate system, where the
+ point (0,0) is defined to the mid-right point of the shape.
+
+ # See also
+
+ See [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn legend<E: IntoDynElement<'a, DB, BackendCoord>, T: Fn(BackendCoord) -> E + 'a>(
+ &mut self,
+ func: T,
+ ) -> &mut Self {
+ self.draw_func = Some(Box::new(move |p| func(p).into_dyn()));
+ self
+ }
+}
+
+/**
+Useful to specify the position of the series label.
+
+See [`ChartContext::configure_series_labels()`] for more information and examples.
+*/
+pub enum SeriesLabelPosition {
+ /// Places the series label at the upper left
+ UpperLeft,
+ /// Places the series label at the middle left
+ MiddleLeft,
+ /// Places the series label at the lower left
+ LowerLeft,
+ /// Places the series label at the upper middle
+ UpperMiddle,
+ /// Places the series label at the middle middle
+ MiddleMiddle,
+ /// Places the series label at the lower middle
+ LowerMiddle,
+ /// Places the series label at the upper right
+ UpperRight,
+ /// Places the series label at the middle right
+ MiddleRight,
+ /// Places the series label at the lower right
+ LowerRight,
+ /// Places the series label at the specific location in backend coordinates
+ Coordinate(i32, i32),
+}
+
+impl SeriesLabelPosition {
+ fn layout_label_area(&self, label_dim: (i32, i32), area_dim: (u32, u32)) -> (i32, i32) {
+ use SeriesLabelPosition::*;
+ (
+ match self {
+ UpperLeft | MiddleLeft | LowerLeft => 5,
+ UpperMiddle | MiddleMiddle | LowerMiddle => {
+ (area_dim.0 as i32 - label_dim.0 as i32) / 2
+ }
+ UpperRight | MiddleRight | LowerRight => area_dim.0 as i32 - label_dim.0 as i32 - 5,
+ Coordinate(x, _) => *x,
+ },
+ match self {
+ UpperLeft | UpperMiddle | UpperRight => 5,
+ MiddleLeft | MiddleMiddle | MiddleRight => {
+ (area_dim.1 as i32 - label_dim.1 as i32) / 2
+ }
+ LowerLeft | LowerMiddle | LowerRight => area_dim.1 as i32 - label_dim.1 as i32 - 5,
+ Coordinate(_, y) => *y,
+ },
+ )
+ }
+}
+
+/// The struct to specify the series label of a target chart context
+pub struct SeriesLabelStyle<'a, 'b, DB: DrawingBackend, CT: CoordTranslate> {
+ target: &'b mut ChartContext<'a, DB, CT>,
+ position: SeriesLabelPosition,
+ legend_area_size: u32,
+ border_style: ShapeStyle,
+ background: ShapeStyle,
+ label_font: Option<TextStyle<'b>>,
+ margin: u32,
+}
+
+impl<'a, 'b, DB: DrawingBackend + 'a, CT: CoordTranslate> SeriesLabelStyle<'a, 'b, DB, CT> {
+ pub(super) fn new(target: &'b mut ChartContext<'a, DB, CT>) -> Self {
+ Self {
+ target,
+ position: SeriesLabelPosition::MiddleRight,
+ legend_area_size: 30,
+ border_style: (&TRANSPARENT).into(),
+ background: (&TRANSPARENT).into(),
+ label_font: None,
+ margin: 10,
+ }
+ }
+
+ /**
+ Sets the series label positioning style
+
+ `pos` - The positioning style
+
+ See [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn position(&mut self, pos: SeriesLabelPosition) -> &mut Self {
+ self.position = pos;
+ self
+ }
+
+ /**
+ Sets the margin of the series label drawing area.
+
+ - `value`: The size specification in backend units (pixels)
+
+ See [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn margin<S: SizeDesc>(&mut self, value: S) -> &mut Self {
+ self.margin = value
+ .in_pixels(&self.target.plotting_area().dim_in_pixel())
+ .max(0) as u32;
+ self
+ }
+
+ /**
+ Sets the size of the legend area.
+
+ `size` - The size of legend area in backend units (pixels)
+
+ See [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn legend_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
+ let size = size
+ .in_pixels(&self.target.plotting_area().dim_in_pixel())
+ .max(0) as u32;
+ self.legend_area_size = size;
+ self
+ }
+
+ /**
+ Sets the style of the label series area.
+
+ `style` - The style of the border
+
+ See [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn border_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
+ self.border_style = style.into();
+ self
+ }
+
+ /**
+ Sets the background style of the label series area.
+
+ `style` - The style of the border
+
+ See [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn background_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
+ self.background = style.into();
+ self
+ }
+
+ /**
+ Sets the font for series labels.
+
+ `font` - Desired font
+
+ See [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn label_font<F: IntoTextStyle<'b>>(&mut self, font: F) -> &mut Self {
+ self.label_font = Some(font.into_text_style(&self.target.plotting_area().dim_in_pixel()));
+ self
+ }
+
+ /**
+ Draws the series label area.
+
+ See [`ChartContext::configure_series_labels()`] for more information and examples.
+ */
+ pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> {
+ let drawing_area = self.target.plotting_area().strip_coord_spec();
+
+ // TODO: Issue #68 Currently generic font family doesn't load on OSX, change this after the issue
+ // resolved
+ let default_font = ("sans-serif", 12).into_font();
+ let default_style: TextStyle = default_font.into();
+
+ let font = {
+ let mut temp = None;
+ std::mem::swap(&mut self.label_font, &mut temp);
+ temp.unwrap_or(default_style)
+ };
+
+ let mut label_element = MultiLineText::<_, &str>::new((0, 0), &font);
+ let mut funcs = vec![];
+
+ for anno in self.target.series_anno.iter() {
+ let label_text = anno.get_label();
+ let draw_func = anno.get_draw_func();
+
+ if label_text.is_empty() && draw_func.is_none() {
+ continue;
+ }
+
+ funcs.push(
+ draw_func.unwrap_or(&|p: BackendCoord| EmptyElement::at(p).into_dyn()),
+ );
+ label_element.push_line(label_text);
+ }
+
+ let (mut w, mut h) = label_element.estimate_dimension().map_err(|e| {
+ DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(Box::new(e)))
+ })?;
+
+ let margin = self.margin as i32;
+
+ w += self.legend_area_size as i32 + margin * 2;
+ h += margin * 2;
+
+ let (area_w, area_h) = drawing_area.dim_in_pixel();
+
+ let (label_x, label_y) = self.position.layout_label_area((w, h), (area_w, area_h));
+
+ label_element.relocate((
+ label_x + self.legend_area_size as i32 + margin,
+ label_y + margin,
+ ));
+
+ drawing_area.draw(&Rectangle::new(
+ [(label_x, label_y), (label_x + w, label_y + h)],
+ self.background.filled(),
+ ))?;
+ drawing_area.draw(&Rectangle::new(
+ [(label_x, label_y), (label_x + w, label_y + h)],
+ self.border_style,
+ ))?;
+ drawing_area.draw(&label_element)?;
+
+ for (((_, y0), (_, y1)), make_elem) in label_element
+ .compute_line_layout()
+ .map_err(|e| {
+ DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(Box::new(e)))
+ })?
+ .into_iter()
+ .zip(funcs.into_iter())
+ {
+ let legend_element = make_elem((label_x + margin, (y0 + y1) / 2));
+ drawing_area.draw(&legend_element)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/vendor/plotters/src/chart/state.rs b/vendor/plotters/src/chart/state.rs
new file mode 100644
index 000000000..1ce2f8285
--- /dev/null
+++ b/vendor/plotters/src/chart/state.rs
@@ -0,0 +1,112 @@
+use std::sync::Arc;
+
+use super::ChartContext;
+use crate::coord::{CoordTranslate, Shift};
+use crate::drawing::DrawingArea;
+use plotters_backend::DrawingBackend;
+
+/// A chart context state - This is the data that is needed to reconstruct the chart context
+/// without actually drawing the chart. This is useful when we want to do realtime rendering and
+/// want to incrementally update the chart.
+///
+/// For each frame, instead of updating the entire backend, we are able to keep the keep the figure
+/// component like axis, labels untouched and make updates only in the plotting drawing area.
+/// This is very useful for incremental render.
+/// ```rust
+/// use plotters::prelude::*;
+/// let mut buffer = vec![0u8;1024*768*3];
+/// let area = BitMapBackend::with_buffer(&mut buffer[..], (1024, 768))
+/// .into_drawing_area()
+/// .split_evenly((1,2));
+/// let chart = ChartBuilder::on(&area[0])
+/// .caption("Incremental Example", ("sans-serif", 20))
+/// .set_all_label_area_size(30)
+/// .build_cartesian_2d(0..10, 0..10)
+/// .expect("Unable to build ChartContext");
+/// // Draw the first frame at this point
+/// area[0].present().expect("Present");
+/// let state = chart.into_chart_state();
+/// // Let's draw the second frame
+/// let chart = state.restore(&area[0]);
+/// chart.plotting_area().fill(&WHITE).unwrap(); // Clear the previously drawn graph
+/// // At this point, you are able to draw next frame
+///```
+#[derive(Clone)]
+pub struct ChartState<CT: CoordTranslate> {
+ drawing_area_pos: (i32, i32),
+ drawing_area_size: (u32, u32),
+ coord: CT,
+}
+
+impl<'a, DB: DrawingBackend, CT: CoordTranslate> From<ChartContext<'a, DB, CT>> for ChartState<CT> {
+ fn from(chart: ChartContext<'a, DB, CT>) -> ChartState<CT> {
+ ChartState {
+ drawing_area_pos: chart.drawing_area_pos,
+ drawing_area_size: chart.drawing_area.dim_in_pixel(),
+ coord: chart.drawing_area.into_coord_spec(),
+ }
+ }
+}
+
+impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> {
+ /// Convert a chart context into a chart state, by doing so, the chart context is consumed and
+ /// a saved chart state is created for later use. This is typically used in incrmental rendering. See documentation of `ChartState` for more detailed example.
+ pub fn into_chart_state(self) -> ChartState<CT> {
+ self.into()
+ }
+
+ /// Convert the chart context into a sharable chart state.
+ /// Normally a chart state can not be clone, since the coordinate spec may not be able to be
+ /// cloned. In this case, we can use an `Arc` get the coordinate wrapped thus the state can be
+ /// cloned and shared by multiple chart context
+ pub fn into_shared_chart_state(self) -> ChartState<Arc<CT>> {
+ ChartState {
+ drawing_area_pos: self.drawing_area_pos,
+ drawing_area_size: self.drawing_area.dim_in_pixel(),
+ coord: Arc::new(self.drawing_area.into_coord_spec()),
+ }
+ }
+}
+
+impl<'a, DB, CT> From<&ChartContext<'a, DB, CT>> for ChartState<CT>
+where
+ DB: DrawingBackend,
+ CT: CoordTranslate + Clone,
+{
+ fn from(chart: &ChartContext<'a, DB, CT>) -> ChartState<CT> {
+ ChartState {
+ drawing_area_pos: chart.drawing_area_pos,
+ drawing_area_size: chart.drawing_area.dim_in_pixel(),
+ coord: chart.drawing_area.as_coord_spec().clone(),
+ }
+ }
+}
+
+impl<'a, DB: DrawingBackend, CT: CoordTranslate + Clone> ChartContext<'a, DB, CT> {
+ /// Make the chart context, do not consume the chart context and clone the coordinate spec
+ pub fn to_chart_state(&self) -> ChartState<CT> {
+ self.into()
+ }
+}
+
+impl<CT: CoordTranslate> ChartState<CT> {
+ /// Restore the chart context on the given drawing area
+ ///
+ /// - `area`: The given drawing area where we want to restore the chart context
+ /// - **returns** The newly created chart context
+ pub fn restore<'a, DB: DrawingBackend>(
+ self,
+ area: &DrawingArea<DB, Shift>,
+ ) -> ChartContext<'a, DB, CT> {
+ let area = area
+ .clone()
+ .shrink(self.drawing_area_pos, self.drawing_area_size);
+ ChartContext {
+ x_label_area: [None, None],
+ y_label_area: [None, None],
+ drawing_area: area.apply_coord_spec(self.coord),
+ series_anno: vec![],
+ drawing_area_pos: self.drawing_area_pos,
+ }
+ }
+}