summaryrefslogtreecommitdiffstats
path: root/vendor/plotters/src/chart/context
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/plotters/src/chart/context')
-rw-r--r--vendor/plotters/src/chart/context/cartesian2d/draw_impl.rs372
-rw-r--r--vendor/plotters/src/chart/context/cartesian2d/mod.rs90
-rw-r--r--vendor/plotters/src/chart/context/cartesian3d/draw_impl.rs309
-rw-r--r--vendor/plotters/src/chart/context/cartesian3d/mod.rs130
4 files changed, 901 insertions, 0 deletions
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
+ }
+}