| 1 | /*! |
| 2 | The 2-dimensional cartesian coordinate system. |
| 3 | |
| 4 | This module provides the 2D cartesian coordinate system, which is composed by two independent |
| 5 | ranged 1D coordinate specification. |
| 6 | |
| 7 | This type of coordinate system is used by the chart constructed with [ChartBuilder::build_cartesian_2d](../../chart/ChartBuilder.html#method.build_cartesian_2d). |
| 8 | */ |
| 9 | |
| 10 | use crate::coord::ranged1d::{KeyPointHint, Ranged, ReversibleRanged}; |
| 11 | use crate::coord::{CoordTranslate, ReverseCoordTranslate}; |
| 12 | |
| 13 | use crate::style::ShapeStyle; |
| 14 | use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; |
| 15 | |
| 16 | use std::ops::Range; |
| 17 | |
| 18 | /// A 2D Cartesian coordinate system described by two 1D ranged coordinate specs. |
| 19 | #[derive (Clone)] |
| 20 | pub struct Cartesian2d<X: Ranged, Y: Ranged> { |
| 21 | logic_x: X, |
| 22 | logic_y: Y, |
| 23 | back_x: (i32, i32), |
| 24 | back_y: (i32, i32), |
| 25 | } |
| 26 | |
| 27 | impl<X: Ranged, Y: Ranged> Cartesian2d<X, Y> { |
| 28 | /// Create a new 2D cartesian coordinate system |
| 29 | /// - `logic_x` and `logic_y` : The description for the 1D coordinate system |
| 30 | /// - `actual`: The pixel range on the screen for this coordinate system |
| 31 | pub fn new<IntoX: Into<X>, IntoY: Into<Y>>( |
| 32 | logic_x: IntoX, |
| 33 | logic_y: IntoY, |
| 34 | actual: (Range<i32>, Range<i32>), |
| 35 | ) -> Self { |
| 36 | Self { |
| 37 | logic_x: logic_x.into(), |
| 38 | logic_y: logic_y.into(), |
| 39 | back_x: (actual.0.start, actual.0.end), |
| 40 | back_y: (actual.1.start, actual.1.end), |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | /// Draw the mesh for the coordinate system |
| 45 | pub fn draw_mesh< |
| 46 | E, |
| 47 | DrawMesh: FnMut(MeshLine<X, Y>) -> Result<(), E>, |
| 48 | XH: KeyPointHint, |
| 49 | YH: KeyPointHint, |
| 50 | >( |
| 51 | &self, |
| 52 | h_limit: YH, |
| 53 | v_limit: XH, |
| 54 | mut draw_mesh: DrawMesh, |
| 55 | ) -> Result<(), E> { |
| 56 | let (xkp, ykp) = ( |
| 57 | self.logic_x.key_points(v_limit), |
| 58 | self.logic_y.key_points(h_limit), |
| 59 | ); |
| 60 | |
| 61 | for logic_x in xkp { |
| 62 | let x = self.logic_x.map(&logic_x, self.back_x); |
| 63 | draw_mesh(MeshLine::XMesh( |
| 64 | (x, self.back_y.0), |
| 65 | (x, self.back_y.1), |
| 66 | &logic_x, |
| 67 | ))?; |
| 68 | } |
| 69 | |
| 70 | for logic_y in ykp { |
| 71 | let y = self.logic_y.map(&logic_y, self.back_y); |
| 72 | draw_mesh(MeshLine::YMesh( |
| 73 | (self.back_x.0, y), |
| 74 | (self.back_x.1, y), |
| 75 | &logic_y, |
| 76 | ))?; |
| 77 | } |
| 78 | |
| 79 | Ok(()) |
| 80 | } |
| 81 | |
| 82 | /// Get the range of X axis |
| 83 | pub fn get_x_range(&self) -> Range<X::ValueType> { |
| 84 | self.logic_x.range() |
| 85 | } |
| 86 | |
| 87 | /// Get the range of Y axis |
| 88 | pub fn get_y_range(&self) -> Range<Y::ValueType> { |
| 89 | self.logic_y.range() |
| 90 | } |
| 91 | |
| 92 | /// Get the horizental backend coordinate range where X axis should be drawn |
| 93 | pub fn get_x_axis_pixel_range(&self) -> Range<i32> { |
| 94 | self.logic_x.axis_pixel_range(self.back_x) |
| 95 | } |
| 96 | |
| 97 | /// Get the vertical backend coordinate range where Y axis should be drawn |
| 98 | pub fn get_y_axis_pixel_range(&self) -> Range<i32> { |
| 99 | self.logic_y.axis_pixel_range(self.back_y) |
| 100 | } |
| 101 | |
| 102 | /// Get the 1D coordinate spec for X axis |
| 103 | pub fn x_spec(&self) -> &X { |
| 104 | &self.logic_x |
| 105 | } |
| 106 | |
| 107 | /// Get the 1D coordinate spec for Y axis |
| 108 | pub fn y_spec(&self) -> &Y { |
| 109 | &self.logic_y |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | impl<X: Ranged, Y: Ranged> CoordTranslate for Cartesian2d<X, Y> { |
| 114 | type From = (X::ValueType, Y::ValueType); |
| 115 | |
| 116 | fn translate(&self, from: &Self::From) -> BackendCoord { |
| 117 | ( |
| 118 | self.logic_x.map(&from.0, self.back_x), |
| 119 | self.logic_y.map(&from.1, self.back_y), |
| 120 | ) |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | impl<X: ReversibleRanged, Y: ReversibleRanged> ReverseCoordTranslate for Cartesian2d<X, Y> { |
| 125 | fn reverse_translate(&self, input: BackendCoord) -> Option<Self::From> { |
| 126 | Some(( |
| 127 | self.logic_x.unmap(input.0, self.back_x)?, |
| 128 | self.logic_y.unmap(input.1, self.back_y)?, |
| 129 | )) |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /// Represent a coordinate mesh for the two ranged value coordinate system |
| 134 | pub enum MeshLine<'a, X: Ranged, Y: Ranged> { |
| 135 | /// Used to plot the horizontal lines of the mesh |
| 136 | XMesh(BackendCoord, BackendCoord, &'a X::ValueType), |
| 137 | /// Used to plot the vertical lines of the mesh |
| 138 | YMesh(BackendCoord, BackendCoord, &'a Y::ValueType), |
| 139 | } |
| 140 | |
| 141 | impl<'a, X: Ranged, Y: Ranged> MeshLine<'a, X, Y> { |
| 142 | /// Draw a single mesh line onto the backend |
| 143 | pub fn draw<DB: DrawingBackend>( |
| 144 | &self, |
| 145 | backend: &mut DB, |
| 146 | style: &ShapeStyle, |
| 147 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { |
| 148 | let (&left: (i32, i32), &right: (i32, i32)) = match self { |
| 149 | MeshLine::XMesh(a: &(i32, i32), b: &(i32, i32), _) => (a, b), |
| 150 | MeshLine::YMesh(a: &(i32, i32), b: &(i32, i32), _) => (a, b), |
| 151 | }; |
| 152 | backend.draw_line(from:left, to:right, style) |
| 153 | } |
| 154 | } |
| 155 | |