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 |
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:input.0, self.back_x)?, |
128 | self.logic_y.unmap(input: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 | |