Skip to main content

plotchart/coord/
ranged.rs

1use super::{CoordTranslate, ReverseCoordTranslate};
2use crate::drawing::backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
3use crate::style::ShapeStyle;
4
5use std::ops::Range;
6
7/// The trait that indicates we have a ordered and ranged value
8/// Which is used to describe the axis
9pub 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
14    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
17    fn key_points(&self, max_points: usize) -> Vec<Self::ValueType>;
18
19    /// Get the range of this value
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)]
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.
36pub trait ReversibleRanged: Ranged {
37    fn unmap(&self, input: i32, limit: (i32, i32)) -> Option<Self::ValueType>;
38}
39
40/// The coordinate described by two ranged value
41pub 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
48impl<X: Ranged + Clone, Y: Ranged + Clone> Clone for RangedCoord<X, Y> {
49    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
59impl<X: Ranged, Y: Ranged> RangedCoord<X, Y> {
60    /// Create a new ranged value coordinate system
61    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
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
108    pub fn get_x_range(&self) -> Range<X::ValueType> {
109        self.logic_x.range()
110    }
111
112    /// Get the range of Y axis
113    pub fn get_y_range(&self) -> Range<Y::ValueType> {
114        self.logic_y.range()
115    }
116
117    pub fn get_x_axis_pixel_range(&self) -> Range<i32> {
118        self.logic_x.axis_pixel_range(self.back_x)
119    }
120
121    pub fn get_y_axis_pixel_range(&self) -> Range<i32> {
122        self.logic_y.axis_pixel_range(self.back_y)
123    }
124
125    pub fn x_spec(&self) -> &X {
126        &self.logic_x
127    }
128
129    pub fn y_spec(&self) -> &Y {
130        &self.logic_y
131    }
132}
133
134impl<X: Ranged, Y: Ranged> CoordTranslate for RangedCoord<X, Y> {
135    type From = (X::ValueType, Y::ValueType);
136
137    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
145impl<X: ReversibleRanged, Y: ReversibleRanged> ReverseCoordTranslate for RangedCoord<X, Y> {
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
155pub enum MeshLine<'a, X: Ranged, Y: Ranged> {
156    XMesh(BackendCoord, BackendCoord, &'a X::ValueType),
157    YMesh(BackendCoord, BackendCoord, &'a Y::ValueType),
158}
159
160impl<'a, X: Ranged, Y: Ranged> MeshLine<'a, X, Y> {
161    /// Draw a single mesh line onto the backend
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
176pub trait DiscreteRanged
177where
178    Self: Ranged,
179{
180    type RangeParameter;
181
182    fn get_range_parameter(&self) -> Self::RangeParameter;
183
184    /// Get the smallest value that is larger than the `this` value
185    fn next_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType;
186
187    /// Get the largest value that is smaller than `this` value
188    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
192pub trait AsRangedCoord: Sized {
193    type CoordDescType: Ranged<ValueType = Self::Value> + From<Self>;
194    type Value;
195}
196
197impl<T> AsRangedCoord for T
198where
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.
210pub struct CentricDiscreteRange<D: DiscreteRanged>(D)
211where
212    <D as Ranged>::ValueType: Eq;
213
214/// The trait for types that can decorated by `CentricDiscreteRange` decorator
215pub trait IntoCentric: AsRangedCoord
216where
217    Self::CoordDescType: DiscreteRanged,
218    <Self::CoordDescType as Ranged>::ValueType: Eq,
219{
220    /// Convert current ranged value into a centric ranged value
221    fn into_centric(self) -> CentricDiscreteRange<Self::CoordDescType> {
222        CentricDiscreteRange(self.into())
223    }
224}
225
226impl<T: AsRangedCoord> IntoCentric for T
227where
228    T::CoordDescType: DiscreteRanged,
229    <Self::CoordDescType as Ranged>::ValueType: Eq,
230{
231}
232
233impl<D: DiscreteRanged + Clone> Clone for CentricDiscreteRange<D>
234where
235    <D as Ranged>::ValueType: Eq,
236{
237    fn clone(&self) -> Self {
238        Self(self.0.clone())
239    }
240}
241
242impl<D: DiscreteRanged> Ranged for CentricDiscreteRange<D>
243where
244    <D as Ranged>::ValueType: Eq,
245{
246    type ValueType = <D as Ranged>::ValueType;
247
248    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
253    fn key_points(&self, max_points: usize) -> Vec<Self::ValueType> {
254        self.0.key_points(max_points)
255    }
256
257    fn range(&self) -> Range<Self::ValueType> {
258        self.0.range()
259    }
260}
261
262impl<D: DiscreteRanged> DiscreteRanged for CentricDiscreteRange<D>
263where
264    <D as Ranged>::ValueType: Eq,
265{
266    type RangeParameter = <D as DiscreteRanged>::RangeParameter;
267    fn get_range_parameter(&self) -> Self::RangeParameter {
268        self.0.get_range_parameter()
269    }
270    fn next_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType {
271        <D as DiscreteRanged>::next_value(this, param)
272    }
273
274    fn previous_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType {
275        <D as DiscreteRanged>::previous_value(this, param)
276    }
277}
278
279impl<D: DiscreteRanged> AsRangedCoord for CentricDiscreteRange<D>
280where
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.
290pub struct PartialAxis<R: Ranged>(R, Range<R::ValueType>);
291
292/// The trait for the types that can be converted into a partial axis
293pub 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
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
306impl<R: AsRangedCoord> IntoPartialAxis for R {}
307
308impl<R: Ranged + Clone> Clone for PartialAxis<R>
309where
310    <R as Ranged>::ValueType: Clone,
311{
312    fn clone(&self) -> Self {
313        PartialAxis(self.0.clone(), self.1.clone())
314    }
315}
316
317impl<R: Ranged> Ranged for PartialAxis<R>
318where
319    R::ValueType: Clone,
320{
321    type ValueType = R::ValueType;
322
323    fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
324        self.0.map(value, limit)
325    }
326
327    fn key_points(&self, max_points: usize) -> Vec<Self::ValueType> {
328        self.0.key_points(max_points)
329    }
330
331    fn range(&self) -> Range<Self::ValueType> {
332        self.0.range()
333    }
334
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
343impl<R: DiscreteRanged> DiscreteRanged for PartialAxis<R>
344where
345    R: Ranged,
346    <R as Ranged>::ValueType: Eq + Clone,
347{
348    type RangeParameter = <R as DiscreteRanged>::RangeParameter;
349    fn get_range_parameter(&self) -> Self::RangeParameter {
350        self.0.get_range_parameter()
351    }
352    fn next_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType {
353        <R as DiscreteRanged>::next_value(this, param)
354    }
355
356    fn previous_value(this: &Self::ValueType, param: &Self::RangeParameter) -> Self::ValueType {
357        <R as DiscreteRanged>::previous_value(this, param)
358    }
359}
360
361impl<R: Ranged> AsRangedCoord for PartialAxis<R>
362where
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
376pub fn make_partial_axis<T>(
377    axis_range: Range<T>,
378    part: Range<f64>,
379) -> Option<PartialAxis<<Range<T> as AsRangedCoord>::CoordDescType>>
380where
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}