gpui_component/plot/scale/
linear.rs

1// @reference: https://d3js.org/d3-scale/linear
2
3use itertools::Itertools;
4use num_traits::{Num, ToPrimitive};
5
6use super::{sealed::Sealed, Scale};
7
8#[derive(Clone)]
9pub struct ScaleLinear<T> {
10    domain_len: usize,
11    domain_start: T,
12    domain_diff: T,
13    range_start: f32,
14    range_diff: f32,
15}
16
17impl<T> ScaleLinear<T>
18where
19    T: Copy + PartialOrd + Num + ToPrimitive + Sealed,
20{
21    pub fn new(domain: Vec<T>, range: Vec<f32>) -> Self {
22        let (domain_start, domain_end) = domain
23            .iter()
24            .minmax()
25            .into_option()
26            .map_or((T::zero(), T::zero()), |(min, max)| (*min, *max));
27
28        let (range_start, range_end) =
29            range
30                .iter()
31                .minmax()
32                .into_option()
33                .map_or((0., 0.), |(min, max)| {
34                    let min_pos = range.iter().position(|&x| x == *min).unwrap_or(0);
35                    let max_pos = range.iter().position(|&x| x == *max).unwrap_or(0);
36
37                    if min_pos <= max_pos {
38                        (*min, *max)
39                    } else {
40                        (*max, *min)
41                    }
42                });
43
44        Self {
45            domain_len: domain.len(),
46            domain_start,
47            domain_diff: domain_end - domain_start,
48            range_start,
49            range_diff: range_end - range_start,
50        }
51    }
52}
53
54impl<T> Scale<T> for ScaleLinear<T>
55where
56    T: Copy + PartialOrd + Num + ToPrimitive + Sealed,
57{
58    fn tick(&self, value: &T) -> Option<f32> {
59        if self.domain_diff.is_zero() {
60            return None;
61        }
62
63        let ratio = ((*value - self.domain_start) / self.domain_diff).to_f32()?;
64
65        Some(ratio * self.range_diff + self.range_start)
66    }
67
68    fn least_index_with_domain(&self, tick: f32, domain: &[T]) -> (usize, f32) {
69        if self.domain_len == 0 || domain.is_empty() {
70            return (0, 0.);
71        }
72
73        domain
74            .iter()
75            .flat_map(|v| self.tick(v))
76            .enumerate()
77            .min_by(|(_, a), (_, b)| {
78                ((*a) - tick)
79                    .abs()
80                    .partial_cmp(&((*b) - tick).abs())
81                    .unwrap_or(std::cmp::Ordering::Equal)
82            })
83            .unwrap_or((0, 0.))
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_scale_linear() {
93        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![0., 100.]);
94        assert_eq!(scale.tick(&1.), Some(0.));
95        assert_eq!(scale.tick(&2.), Some(50.));
96        assert_eq!(scale.tick(&3.), Some(100.));
97
98        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![100., 0.]);
99        assert_eq!(scale.tick(&1.), Some(100.));
100        assert_eq!(scale.tick(&2.), Some(50.));
101        assert_eq!(scale.tick(&3.), Some(0.));
102    }
103
104    #[test]
105    fn test_scale_linear_multiple_range() {
106        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![0., 50., 100.]);
107        assert_eq!(scale.tick(&1.), Some(0.));
108        assert_eq!(scale.tick(&2.), Some(50.));
109        assert_eq!(scale.tick(&3.), Some(100.));
110
111        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![100., 50., 0.]);
112        assert_eq!(scale.tick(&1.), Some(100.));
113        assert_eq!(scale.tick(&2.), Some(50.));
114        assert_eq!(scale.tick(&3.), Some(0.));
115
116        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![100., 0., 100.]);
117        assert_eq!(scale.tick(&1.), Some(100.));
118        assert_eq!(scale.tick(&2.), Some(50.));
119        assert_eq!(scale.tick(&3.), Some(0.));
120
121        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![0., 100., 0.]);
122        assert_eq!(scale.tick(&1.), Some(0.));
123        assert_eq!(scale.tick(&2.), Some(50.));
124        assert_eq!(scale.tick(&3.), Some(100.));
125    }
126
127    #[test]
128    fn test_scale_linear_empty() {
129        let scale = ScaleLinear::new(vec![], vec![0., 100.]);
130        assert_eq!(scale.tick(&1.), None);
131        assert_eq!(scale.tick(&2.), None);
132        assert_eq!(scale.tick(&3.), None);
133
134        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![]);
135        assert_eq!(scale.tick(&1.), Some(0.));
136        assert_eq!(scale.tick(&2.), Some(0.));
137        assert_eq!(scale.tick(&3.), Some(0.));
138    }
139
140    #[test]
141    fn test_scale_linear_least_index_with_domain() {
142        let scale = ScaleLinear::new(vec![1., 2., 3.], vec![0., 100.]);
143        assert_eq!(scale.least_index_with_domain(0., &[1., 2., 3.]), (0, 0.));
144        assert_eq!(scale.least_index_with_domain(50., &[1., 2., 3.]), (1, 50.));
145        assert_eq!(
146            scale.least_index_with_domain(100., &[1., 2., 3.]),
147            (2, 100.)
148        );
149    }
150}