1 use super::{CoordTranslate, ReverseCoordTranslate};
2 use crate::drawing::backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
3 use crate::style::ShapeStyle;
4
5 use std::ops::Range;
6
7 /// The trait that indicates we have a ordered and ranged value
8 /// Which is used to describe the axis
9 pub trait Ranged {
10 /// The type of this value
11 type ValueType;
12
13 /// This function maps the value to i32, which is the drawing coordinate
map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i3214 fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32;
15
16 /// This function gives the key points that we can draw a grid based on this
key_points(&self, max_points: usize) -> Vec<Self::ValueType>17 fn key_points(&self, max_points: usize) -> Vec<Self::ValueType>;
18
19 /// Get the range of this value
range(&self) -> Range<Self::ValueType>20 fn range(&self) -> Range<Self::ValueType>;
21
22 /// This function provides the on-axis part of its range
23 #[allow(clippy::range_plus_one)]
axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32>24 fn axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32> {
25 if limit.0 < limit.1 {
26 limit.0..limit.1
27 } else {
28 (limit.1 + 1)..(limit.0 + 1)
29 }
30 }
31 }
32
33 /// The trait indicates the ranged value can be map reversely, which means
34 /// an pixel-based coordinate is given, it's possible to figure out the underlying
35 /// logic value.
36 pub trait ReversibleRanged: Ranged {
unmap(&self, input: i32, limit: (i32, i32)) -> Option<Self::ValueType>37 fn unmap(&self, input: i32, limit: (i32, i32)) -> Option<Self::ValueType>;
38 }
39
40 /// The coordinate described by two ranged value
41 pub struct RangedCoord<X: Ranged, Y: Ranged> {
42 logic_x: X,
43 logic_y: Y,
44 back_x: (i32, i32),
45 back_y: (i32, i32),
46 }
47
48 impl<X: Ranged + Clone, Y: Ranged + Clone> Clone for RangedCoord<X, Y> {
clone(&self) -> Self49 fn clone(&self) -> Self {
50 Self {
51 logic_x: self.logic_x.clone(),
52 logic_y: self.logic_y.clone(),
53 back_x: self.back_x,
54 back_y: self.back_y,
55 }
56 }
57 }
58
59 impl<X: Ranged, Y: Ranged> RangedCoord<X, Y> {
60 /// Create a new ranged value coordinate system
new<IntoX: Into<X>, IntoY: Into<Y>>( logic_x: IntoX, logic_y: IntoY, actual: (Range<i32>, Range<i32>), ) -> Self61 pub fn new<IntoX: Into<X>, IntoY: Into<Y>>(
62 logic_x: IntoX,
63 logic_y: IntoY,
64 actual: (Range<i32>, Range<i32>),
65 ) -> Self {
66 Self {
67 logic_x: logic_x.into(),
68 logic_y: logic_y.into(),
69 back_x: (actual.0.start, actual.0.end),
70 back_y: (actual.1.start, actual.1.end),
71 }
72 }
73
74 /// Draw the mesh for the coordinate system
draw_mesh<E, DrawMesh: FnMut(MeshLine<X, Y>) -> Result<(), E>>( &self, h_limit: usize, v_limit: usize, mut draw_mesh: DrawMesh, ) -> Result<(), E>75 pub fn draw_mesh<E, DrawMesh: FnMut(MeshLine<X, Y>) -> Result<(), E>>(
76 &self,
77 h_limit: usize,
78 v_limit: usize,
79 mut draw_mesh: DrawMesh,
80 ) -> Result<(), E> {
81 let (xkp, ykp) = (
82 self.logic_x.key_points(v_limit),
83 self.logic_y.key_points(h_limit),
84 );
85
86 for logic_x in xkp {
87 let x = self.logic_x.map(&logic_x, self.back_x);
88 draw_mesh(MeshLine::XMesh(
89 (x, self.back_y.0),
90 (x, self.back_y.1),
91 &logic_x,
92 ))?;
93 }
94
95 for logic_y in ykp {
96 let y = self.logic_y.map(&logic_y, self.back_y);
97 draw_mesh(MeshLine::YMesh(
98 (self.back_x.0, y),
99 (self.back_x.1, y),
100 &logic_y,
101 ))?;
102 }
103
104 Ok(())
105 }
106
107 /// Get the range of X axis
get_x_range(&self) -> Range<X::ValueType>108 pub fn get_x_range(&self) -> Range<X::ValueType> {
109 self.logic_x.range()
110 }
111
112 /// Get the range of Y axis
get_y_range(&self) -> Range<Y::ValueType>113 pub fn get_y_range(&self) -> Range<Y::ValueType> {
114 self.logic_y.range()
115 }
116
get_x_axis_pixel_range(&self) -> Range<i32>117 pub fn get_x_axis_pixel_range(&self) -> Range<i32> {
118 self.logic_x.axis_pixel_range(self.back_x)
119 }
120
get_y_axis_pixel_range(&self) -> Range<i32>121 pub fn get_y_axis_pixel_range(&self) -> Range<i32> {
122 self.logic_y.axis_pixel_range(self.back_y)
123 }
124
x_spec(&self) -> &X125 pub fn x_spec(&self) -> &X {
126 &self.logic_x
127 }
128
y_spec(&self) -> &Y129 pub fn y_spec(&self) -> &Y {
130 &self.logic_y
131 }
132 }
133
134 impl<X: Ranged, Y: Ranged> CoordTranslate for RangedCoord<X, Y> {
135 type From = (X::ValueType, Y::ValueType);
136
translate(&self, from: &Self::From) -> BackendCoord137 fn translate(&self, from: &Self::From) -> BackendCoord {
138 (
139 self.logic_x.map(&from.0, self.back_x),
140 self.logic_y.map(&from.1, self.back_y),
141 )
142 }
143 }
144
145 impl<X: ReversibleRanged, Y: ReversibleRanged> ReverseCoordTranslate for RangedCoord<X, Y> {
reverse_translate(&self, input: BackendCoord) -> Option<Self::From>146 fn reverse_translate(&self, input: BackendCoord) -> Option<Self::From> {
147 Some((
148 self.logic_x.unmap(input.0, self.back_x)?,
149 self.logic_y.unmap(input.1, self.back_y)?,
150 ))
151 }
152 }
153
154 /// Represent a coordinate mesh for the two ranged value coordinate system
155 pub enum MeshLine<'a, X: Ranged, Y: Ranged> {
156 XMesh(BackendCoord, BackendCoord, &'a X::ValueType),
157 YMesh(BackendCoord, BackendCoord, &'a Y::ValueType),
158 }
159
160 impl<'a, X: Ranged, Y: Ranged> MeshLine<'a, X, Y> {
161 /// Draw a single mesh line onto the backend
draw<DB: DrawingBackend>( &self, backend: &mut DB, style: &ShapeStyle, ) -> Result<(), DrawingErrorKind<DB::ErrorType>>162 pub fn draw<DB: DrawingBackend>(
163 &self,
164 backend: &mut DB,
165 style: &ShapeStyle,
166 ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
167 let (&left, &right) = match self {
168 MeshLine::XMesh(a, b, _) => (a, b),
169 MeshLine::YMesh(a, b, _) => (a, b),
170 };
171 backend.draw_line(left, right, &style.color)
172 }
173 }
174
175 /// The trait indicates the coordinate is discrete, so that we can draw histogram on it
176 pub trait DiscreteRanged
177 where
178 Self: Ranged,
179 {
180 type RangeParameter;
181
get_range_parameter(&self) -> Self::RangeParameter182 fn get_range_parameter(&self) -> Self::RangeParameter;
183
184 /// Get the smallest value that is larger than the `this` value
next_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType185 fn next_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType;
186
187 /// Get the largest value that is smaller than `this` value
previous_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType188 fn previous_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType;
189 }
190
191 /// The trait for the type that can be converted into a ranged coordinate axis
192 pub trait AsRangedCoord: Sized {
193 type CoordDescType: Ranged<ValueType = Self::Value> + From<Self>;
194 type Value;
195 }
196
197 impl<T> AsRangedCoord for T
198 where
199 T: Ranged,
200 Range<T::ValueType>: Into<T>,
201 {
202 type CoordDescType = T;
203 type Value = T::ValueType;
204 }
205
206 /// The axis decorator that makes key-point in the center of the value range
207 /// This is useful when we draw a histogram, since we want the axis value label
208 /// to be shown in the middle of the range rather than exactly the location where
209 /// the value mapped to.
210 pub struct CentricDiscreteRange<D: DiscreteRanged>(D)
211 where
212 <D as Ranged>::ValueType: Eq;
213
214 /// The trait for types that can decorated by `CentricDiscreteRange` decorator
215 pub trait IntoCentric: AsRangedCoord
216 where
217 Self::CoordDescType: DiscreteRanged,
218 <Self::CoordDescType as Ranged>::ValueType: Eq,
219 {
220 /// Convert current ranged value into a centric ranged value
into_centric(self) -> CentricDiscreteRange<Self::CoordDescType>221 fn into_centric(self) -> CentricDiscreteRange<Self::CoordDescType> {
222 CentricDiscreteRange(self.into())
223 }
224 }
225
226 impl<T: AsRangedCoord> IntoCentric for T
227 where
228 T::CoordDescType: DiscreteRanged,
229 <Self::CoordDescType as Ranged>::ValueType: Eq,
230 {
231 }
232
233 impl<D: DiscreteRanged + Clone> Clone for CentricDiscreteRange<D>
234 where
235 <D as Ranged>::ValueType: Eq,
236 {
clone(&self) -> Self237 fn clone(&self) -> Self {
238 Self(self.0.clone())
239 }
240 }
241
242 impl<D: DiscreteRanged> Ranged for CentricDiscreteRange<D>
243 where
244 <D as Ranged>::ValueType: Eq,
245 {
246 type ValueType = <D as Ranged>::ValueType;
247
map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32248 fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
249 let prev = <D as DiscreteRanged>::previous_value(&value, &self.0.get_range_parameter());
250 (self.0.map(&prev, limit) + self.0.map(value, limit)) / 2
251 }
252
key_points(&self, max_points: usize) -> Vec<Self::ValueType>253 fn key_points(&self, max_points: usize) -> Vec<Self::ValueType> {
254 self.0.key_points(max_points)
255 }
256
range(&self) -> Range<Self::ValueType>257 fn range(&self) -> Range<Self::ValueType> {
258 self.0.range()
259 }
260 }
261
262 impl<D: DiscreteRanged> DiscreteRanged for CentricDiscreteRange<D>
263 where
264 <D as Ranged>::ValueType: Eq,
265 {
266 type RangeParameter = <D as DiscreteRanged>::RangeParameter;
get_range_parameter(&self) -> Self::RangeParameter267 fn get_range_parameter(&self) -> Self::RangeParameter {
268 self.0.get_range_parameter()
269 }
next_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType270 fn next_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType {
271 <D as DiscreteRanged>::next_value(this, param)
272 }
273
previous_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType274 fn previous_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType {
275 <D as DiscreteRanged>::previous_value(this, param)
276 }
277 }
278
279 impl<D: DiscreteRanged> AsRangedCoord for CentricDiscreteRange<D>
280 where
281 <D as Ranged>::ValueType: Eq,
282 {
283 type CoordDescType = Self;
284 type Value = <Self as Ranged>::ValueType;
285 }
286
287 /// This axis decorator will make the axis partially display on the axis.
288 /// At some time, we want the axis only covers some part of the value.
289 /// This decorator will have an additional display range defined.
290 pub struct PartialAxis<R: Ranged>(R, Range<R::ValueType>);
291
292 /// The trait for the types that can be converted into a partial axis
293 pub trait IntoPartialAxis: AsRangedCoord {
294 /// Make the partial axis
295 ///
296 /// - `axis_range`: The range of the axis to be displayed
297 /// - **returns**: The converted range specification
partial_axis( self, axis_range: Range<<Self::CoordDescType as Ranged>::ValueType>, ) -> PartialAxis<Self::CoordDescType>298 fn partial_axis(
299 self,
300 axis_range: Range<<Self::CoordDescType as Ranged>::ValueType>,
301 ) -> PartialAxis<Self::CoordDescType> {
302 PartialAxis(self.into(), axis_range)
303 }
304 }
305
306 impl<R: AsRangedCoord> IntoPartialAxis for R {}
307
308 impl<R: Ranged + Clone> Clone for PartialAxis<R>
309 where
310 <R as Ranged>::ValueType: Clone,
311 {
clone(&self) -> Self312 fn clone(&self) -> Self {
313 PartialAxis(self.0.clone(), self.1.clone())
314 }
315 }
316
317 impl<R: Ranged> Ranged for PartialAxis<R>
318 where
319 R::ValueType: Clone,
320 {
321 type ValueType = R::ValueType;
322
map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32323 fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
324 self.0.map(value, limit)
325 }
326
key_points(&self, max_points: usize) -> Vec<Self::ValueType>327 fn key_points(&self, max_points: usize) -> Vec<Self::ValueType> {
328 self.0.key_points(max_points)
329 }
330
range(&self) -> Range<Self::ValueType>331 fn range(&self) -> Range<Self::ValueType> {
332 self.0.range()
333 }
334
axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32>335 fn axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32> {
336 let left = self.map(&self.1.start, limit);
337 let right = self.map(&self.1.end, limit);
338
339 left.min(right)..left.max(right)
340 }
341 }
342
343 impl<R: DiscreteRanged> DiscreteRanged for PartialAxis<R>
344 where
345 R: Ranged,
346 <R as Ranged>::ValueType: Eq + Clone,
347 {
348 type RangeParameter = <R as DiscreteRanged>::RangeParameter;
get_range_parameter(&self) -> Self::RangeParameter349 fn get_range_parameter(&self) -> Self::RangeParameter {
350 self.0.get_range_parameter()
351 }
next_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType352 fn next_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType {
353 <R as DiscreteRanged>::next_value(this, param)
354 }
355
previous_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType356 fn previous_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType {
357 <R as DiscreteRanged>::previous_value(this, param)
358 }
359 }
360
361 impl<R: Ranged> AsRangedCoord for PartialAxis<R>
362 where
363 <R as Ranged>::ValueType: Clone,
364 {
365 type CoordDescType = Self;
366 type Value = <Self as Ranged>::ValueType;
367 }
368
369 /// Make a partial axis based on the percentage of visible portion.
370 /// We can use `into_partial_axis` to create a partial axis range specification.
371 /// But sometimes, we want to directly specify the percentage visible to the user.
372 ///
373 /// - `axis_range`: The range specification
374 /// - `part`: The visible part of the axis. Each value is from [0.0, 1.0]
375 /// - **returns**: The partial axis created from the input, or `None` when not possible
make_partial_axis<T>( axis_range: Range<T>, part: Range<f64>, ) -> Option<PartialAxis<<Range<T> as AsRangedCoord>::CoordDescType>> where Range<T>: AsRangedCoord, T: num_traits::NumCast + Clone,376 pub fn make_partial_axis<T>(
377 axis_range: Range<T>,
378 part: Range<f64>,
379 ) -> Option<PartialAxis<<Range<T> as AsRangedCoord>::CoordDescType>>
380 where
381 Range<T>: AsRangedCoord,
382 T: num_traits::NumCast + Clone,
383 {
384 let left: f64 = num_traits::cast(axis_range.start.clone())?;
385 let right: f64 = num_traits::cast(axis_range.end.clone())?;
386
387 let full_range_size = (right - left) / (part.end - part.start);
388
389 let full_left = left - full_range_size * part.start;
390 let full_right = right + full_range_size * (1.0 - part.end);
391
392 let full_range: Range<T> = num_traits::cast(full_left)?..num_traits::cast(full_right)?;
393
394 let axis_range: <Range<T> as AsRangedCoord>::CoordDescType = axis_range.into();
395
396 Some(PartialAxis(full_range.into(), axis_range.range()))
397 }
398