1use crate::coord::ranged1d::{
2 AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, Ranged,
3};
4use 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)]
10pub struct PartialAxis<R: Ranged>(R, Range<R::ValueType>);
11
12/// The trait for the types that can be converted into a partial axis
13pub 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
26impl<R: AsRangedCoord> IntoPartialAxis for R {}
27
28impl<R: Ranged> Ranged for PartialAxis<R>
29where
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
55impl<R: DiscreteRanged> DiscreteRanged for PartialAxis<R>
56where
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
80pub fn make_partial_axis<T>(
81 axis_range: Range<T>,
82 part: Range<f64>,
83) -> Option<PartialAxis<<Range<T> as AsRangedCoord>::CoordDescType>>
84where
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)]
104mod 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