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/context | |
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/context')
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 + } +} |