diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
commit | 9835e2ae736235810b4ea1c162ca5e65c547e770 (patch) | |
tree | 3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/plotters/src/chart | |
parent | Releasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff) | |
download | rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip |
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/plotters/src/chart')
-rw-r--r-- | vendor/plotters/src/chart/axes3d.rs | 317 | ||||
-rw-r--r-- | vendor/plotters/src/chart/builder.rs | 568 | ||||
-rw-r--r-- | vendor/plotters/src/chart/context.rs | 221 | ||||
-rw-r--r-- | vendor/plotters/src/chart/context/cartesian2d/draw_impl.rs | 372 | ||||
-rw-r--r-- | vendor/plotters/src/chart/context/cartesian2d/mod.rs | 90 | ||||
-rw-r--r-- | vendor/plotters/src/chart/context/cartesian3d/draw_impl.rs | 309 | ||||
-rw-r--r-- | vendor/plotters/src/chart/context/cartesian3d/mod.rs | 130 | ||||
-rw-r--r-- | vendor/plotters/src/chart/dual_coord.rs | 242 | ||||
-rw-r--r-- | vendor/plotters/src/chart/mesh.rs | 533 | ||||
-rw-r--r-- | vendor/plotters/src/chart/mod.rs | 30 | ||||
-rw-r--r-- | vendor/plotters/src/chart/series.rs | 301 | ||||
-rw-r--r-- | vendor/plotters/src/chart/state.rs | 112 |
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, + } + } +} |