1 | use crate::coord::ranged1d::{ |
2 | AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, Ranged, |
3 | }; |
4 | use std::ops::Range; |
5 | |
6 | /// This axis decorator will make the axis partially display on the axis. |
7 | /// At some time, we want the axis only covers some part of the value. |
8 | /// This decorator will have an additional display range defined. |
9 | #[derive (Clone)] |
10 | pub struct PartialAxis<R: Ranged>(R, Range<R::ValueType>); |
11 | |
12 | /// The trait for the types that can be converted into a partial axis |
13 | pub trait IntoPartialAxis: AsRangedCoord { |
14 | /// Make the partial axis |
15 | /// |
16 | /// - `axis_range`: The range of the axis to be displayed |
17 | /// - **returns**: The converted range specification |
18 | fn partial_axis( |
19 | self, |
20 | axis_range: Range<<Self::CoordDescType as Ranged>::ValueType>, |
21 | ) -> PartialAxis<Self::CoordDescType> { |
22 | PartialAxis(self.into(), axis_range) |
23 | } |
24 | } |
25 | |
26 | impl<R: AsRangedCoord> IntoPartialAxis for R {} |
27 | |
28 | impl<R: Ranged> Ranged for PartialAxis<R> |
29 | where |
30 | R::ValueType: Clone, |
31 | { |
32 | type FormatOption = DefaultFormatting; |
33 | type ValueType = R::ValueType; |
34 | |
35 | fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { |
36 | self.0.map(value, limit) |
37 | } |
38 | |
39 | fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> { |
40 | self.0.key_points(hint) |
41 | } |
42 | |
43 | fn range(&self) -> Range<Self::ValueType> { |
44 | self.0.range() |
45 | } |
46 | |
47 | fn axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32> { |
48 | let left: i32 = self.map(&self.1.start, limit); |
49 | let right: i32 = self.map(&self.1.end, limit); |
50 | |
51 | left.min(right)..left.max(right) |
52 | } |
53 | } |
54 | |
55 | impl<R: DiscreteRanged> DiscreteRanged for PartialAxis<R> |
56 | where |
57 | R: Ranged, |
58 | <R as Ranged>::ValueType: Eq + Clone, |
59 | { |
60 | fn size(&self) -> usize { |
61 | self.0.size() |
62 | } |
63 | |
64 | fn index_of(&self, value: &R::ValueType) -> Option<usize> { |
65 | self.0.index_of(value) |
66 | } |
67 | |
68 | fn from_index(&self, index: usize) -> Option<Self::ValueType> { |
69 | self.0.from_index(index) |
70 | } |
71 | } |
72 | |
73 | /// Make a partial axis based on the percentage of visible portion. |
74 | /// We can use `into_partial_axis` to create a partial axis range specification. |
75 | /// But sometimes, we want to directly specify the percentage visible to the user. |
76 | /// |
77 | /// - `axis_range`: The range specification |
78 | /// - `part`: The visible part of the axis. Each value is from [0.0, 1.0] |
79 | /// - **returns**: The partial axis created from the input, or `None` when not possible |
80 | pub fn make_partial_axis<T>( |
81 | axis_range: Range<T>, |
82 | part: Range<f64>, |
83 | ) -> Option<PartialAxis<<Range<T> as AsRangedCoord>::CoordDescType>> |
84 | where |
85 | Range<T>: AsRangedCoord, |
86 | T: num_traits::NumCast + Clone, |
87 | { |
88 | let left: f64 = num_traits::cast(axis_range.start.clone())?; |
89 | let right: f64 = num_traits::cast(axis_range.end.clone())?; |
90 | |
91 | let full_range_size: f64 = (right - left) / (part.end - part.start); |
92 | |
93 | let full_left: f64 = left - full_range_size * part.start; |
94 | let full_right: f64 = right + full_range_size * (1.0 - part.end); |
95 | |
96 | let full_range: Range<T> = num_traits::cast(full_left)?..num_traits::cast(full_right)?; |
97 | |
98 | let axis_range: <Range<T> as AsRangedCoord>::CoordDescType = axis_range.into(); |
99 | |
100 | Some(PartialAxis(full_range.into(), axis_range.range())) |
101 | } |
102 | |
103 | #[cfg (test)] |
104 | mod test { |
105 | use super::*; |
106 | #[test ] |
107 | fn test_make_partial_axis() { |
108 | let r = make_partial_axis(20..80, 0.2..0.8).unwrap(); |
109 | assert_eq!(r.size(), 101); |
110 | assert_eq!(r.range(), 0..100); |
111 | assert_eq!(r.axis_pixel_range((0, 100)), 20..80); |
112 | } |
113 | } |
114 | |