gpui_component/plot/scale/
linear.rs1use 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}