| 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 | |