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 sepcification.
6 
7  This types 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
new<IntoX: Into<X>, IntoY: Into<Y>>( logic_x: IntoX, logic_y: IntoY, actual: (Range<i32>, Range<i32>), ) -> Self31     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
draw_mesh< E, DrawMesh: FnMut(MeshLine<X, Y>) -> Result<(), E>, XH: KeyPointHint, YH: KeyPointHint, >( &self, h_limit: YH, v_limit: XH, mut draw_mesh: DrawMesh, ) -> Result<(), E>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
get_x_range(&self) -> Range<X::ValueType>83     pub fn get_x_range(&self) -> Range<X::ValueType> {
84         self.logic_x.range()
85     }
86 
87     /// Get the range of Y axis
get_y_range(&self) -> Range<Y::ValueType>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
get_x_axis_pixel_range(&self) -> Range<i32>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
get_y_axis_pixel_range(&self) -> Range<i32>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
x_spec(&self) -> &X103     pub fn x_spec(&self) -> &X {
104         &self.logic_x
105     }
106 
107     /// Get the 1D coordinate spec for Y axis
y_spec(&self) -> &Y108     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 
translate(&self, from: &Self::From) -> BackendCoord116     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> {
reverse_translate(&self, input: BackendCoord) -> Option<Self::From>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     XMesh(BackendCoord, BackendCoord, &'a X::ValueType),
136     YMesh(BackendCoord, BackendCoord, &'a Y::ValueType),
137 }
138 
139 impl<'a, X: Ranged, Y: Ranged> MeshLine<'a, X, Y> {
140     /// Draw a single mesh line onto the backend
draw<DB: DrawingBackend>( &self, backend: &mut DB, style: &ShapeStyle, ) -> Result<(), DrawingErrorKind<DB::ErrorType>>141     pub fn draw<DB: DrawingBackend>(
142         &self,
143         backend: &mut DB,
144         style: &ShapeStyle,
145     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
146         let (&left, &right) = match self {
147             MeshLine::XMesh(a, b, _) => (a, b),
148             MeshLine::YMesh(a, b, _) => (a, b),
149         };
150         backend.draw_line(left, right, style)
151     }
152 }
153