1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use crate::coord::ranged1d::{
    AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, Ranged,
};
use std::ops::Range;

/// This axis decorator will make the axis partially display on the axis.
/// At some time, we want the axis only covers some part of the value.
/// This decorator will have an additional display range defined.
#[derive(Clone)]
pub struct PartialAxis<R: Ranged>(R, Range<R::ValueType>);

/// The trait for the types that can be converted into a partial axis
pub trait IntoPartialAxis: AsRangedCoord {
    /// Make the partial axis
    ///
    /// - `axis_range`: The range of the axis to be displayed
    /// - **returns**: The converted range specification
    fn partial_axis(
        self,
        axis_range: Range<<Self::CoordDescType as Ranged>::ValueType>,
    ) -> PartialAxis<Self::CoordDescType> {
        PartialAxis(self.into(), axis_range)
    }
}

impl<R: AsRangedCoord> IntoPartialAxis for R {}

impl<R: Ranged> Ranged for PartialAxis<R>
where
    R::ValueType: Clone,
{
    type FormatOption = DefaultFormatting;
    type ValueType = R::ValueType;

    fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
        self.0.map(value, limit)
    }

    fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> {
        self.0.key_points(hint)
    }

    fn range(&self) -> Range<Self::ValueType> {
        self.0.range()
    }

    fn axis_pixel_range(&self, limit: (i32, i32)) -> Range<i32> {
        let left = self.map(&self.1.start, limit);
        let right = self.map(&self.1.end, limit);

        left.min(right)..left.max(right)
    }
}

impl<R: DiscreteRanged> DiscreteRanged for PartialAxis<R>
where
    R: Ranged,
    <R as Ranged>::ValueType: Eq + Clone,
{
    fn size(&self) -> usize {
        self.0.size()
    }

    fn index_of(&self, value: &R::ValueType) -> Option<usize> {
        self.0.index_of(value)
    }

    fn from_index(&self, index: usize) -> Option<Self::ValueType> {
        self.0.from_index(index)
    }
}

/// Make a partial axis based on the percentage of visible portion.
/// We can use `into_partial_axis` to create a partial axis range specification.
/// But sometimes, we want to directly specify the percentage visible to the user.
///
/// - `axis_range`: The range specification
/// - `part`: The visible part of the axis. Each value is from [0.0, 1.0]
/// - **returns**: The partial axis created from the input, or `None` when not possible
pub fn 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,
{
    let left: f64 = num_traits::cast(axis_range.start.clone())?;
    let right: f64 = num_traits::cast(axis_range.end.clone())?;

    let full_range_size = (right - left) / (part.end - part.start);

    let full_left = left - full_range_size * part.start;
    let full_right = right + full_range_size * (1.0 - part.end);

    let full_range: Range<T> = num_traits::cast(full_left)?..num_traits::cast(full_right)?;

    let axis_range: <Range<T> as AsRangedCoord>::CoordDescType = axis_range.into();

    Some(PartialAxis(full_range.into(), axis_range.range()))
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_make_partial_axis() {
        let r = make_partial_axis(20..80, 0.2..0.8).unwrap();
        assert_eq!(r.size(), 101);
        assert_eq!(r.range(), 0..100);
        assert_eq!(r.axis_pixel_range((0, 100)), 20..80);
    }
}