1use super::Scale;
4
5#[derive(Clone)]
10pub struct ScalePoint<T> {
11 domain: Vec<T>,
12 range_start: f32,
13 range_tick: f32,
14}
15
16impl<T> ScalePoint<T>
17where
18 T: PartialEq,
19{
20 pub fn new(domain: impl IntoIterator<Item = T>, range: [f32; 2]) -> Self {
32 let domain: Vec<T> = domain.into_iter().collect();
33 let len = domain.len();
34 let range_diff = range[1] - range[0];
35 let (range_start, range_tick) = match len {
36 0 => (0., 0.),
37 1 => (range[0], range_diff),
38 _ => (range[0], range_diff / (len - 1) as f32),
39 };
40
41 Self {
42 domain,
43 range_start,
44 range_tick,
45 }
46 }
47
48 pub fn tick_at(&self, index: usize) -> Option<f32> {
54 let len = self.domain.len();
55 if index >= len {
56 return None;
57 }
58
59 if len == 1 {
60 Some(self.range_start + self.range_tick / 2.)
61 } else {
62 Some(self.range_start + index as f32 * self.range_tick)
63 }
64 }
65}
66
67impl<T> Scale<T> for ScalePoint<T>
68where
69 T: PartialEq,
70{
71 fn tick(&self, value: &T) -> Option<f32> {
72 let index = self.domain.iter().position(|v| v == value)?;
73 self.tick_at(index)
74 }
75
76 fn nearest_index(&self, tick: f32) -> usize {
77 if self.domain.is_empty() {
78 return 0;
79 }
80
81 if self.range_tick == 0. {
82 return 0;
83 }
84
85 let normalized_tick = tick - self.range_start;
86 let index = (normalized_tick / self.range_tick).round() as usize;
87 index.min(self.domain.len() - 1)
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn test_scale_point() {
97 let scale = ScalePoint::new(vec![1, 2, 3], [0., 100.]);
98 assert_eq!(scale.tick(&1), Some(0.));
99 assert_eq!(scale.tick(&2), Some(50.));
100 assert_eq!(scale.tick(&3), Some(100.));
101 }
102
103 #[test]
104 fn test_scale_point_range() {
105 let scale = ScalePoint::new(vec![1, 2, 3], [40., 80.]);
106 assert_eq!(scale.tick(&1), Some(40.));
107 assert_eq!(scale.tick(&2), Some(60.));
108 assert_eq!(scale.tick(&3), Some(80.));
109 }
110
111 #[test]
112 fn test_scale_point_empty() {
113 let scale = ScalePoint::new(vec![], [0., 100.]);
114 assert_eq!(scale.tick(&1), None);
115 assert_eq!(scale.tick(&2), None);
116 assert_eq!(scale.tick(&3), None);
117
118 let scale = ScalePoint::new(vec![1, 2, 3], [0., 0.]);
119 assert_eq!(scale.tick(&1), Some(0.));
120 assert_eq!(scale.tick(&2), Some(0.));
121 assert_eq!(scale.tick(&3), Some(0.));
122 }
123
124 #[test]
125 fn test_scale_point_single() {
126 let scale = ScalePoint::new(vec![1], [0., 100.]);
127 assert_eq!(scale.tick(&1), Some(50.));
128 }
129
130 #[test]
131 fn test_tick_at_matches_tick() {
132 for domain in [vec![], vec![1], vec![1, 2, 3], vec![1, 2, 3, 4, 5]] {
133 let scale = ScalePoint::new(domain.clone(), [40., 80.]);
134 for (i, value) in domain.iter().enumerate() {
135 assert_eq!(scale.tick_at(i), scale.tick(value));
136 }
137 assert_eq!(scale.tick_at(domain.len()), None);
138 }
139 }
140
141 #[test]
142 fn test_nearest_index_basic() {
143 let scale = ScalePoint::new(vec![1, 2, 3], [0., 100.]);
144
145 assert_eq!(scale.nearest_index(0.), 0);
147 assert_eq!(scale.nearest_index(50.), 1);
148 assert_eq!(scale.nearest_index(100.), 2);
149
150 assert_eq!(scale.nearest_index(24.), 0); assert_eq!(scale.nearest_index(25.), 1); assert_eq!(scale.nearest_index(26.), 1); assert_eq!(scale.nearest_index(74.), 1); assert_eq!(scale.nearest_index(75.), 2); assert_eq!(scale.nearest_index(76.), 2); assert_eq!(scale.nearest_index(-10.), 0); assert_eq!(scale.nearest_index(150.), 2); }
162
163 #[test]
164 fn test_nearest_index_with_offset() {
165 let scale = ScalePoint::new(vec![1, 2, 3], [40., 80.]);
166
167 assert_eq!(scale.nearest_index(40.), 0);
169 assert_eq!(scale.nearest_index(60.), 1);
170 assert_eq!(scale.nearest_index(80.), 2);
171
172 assert_eq!(scale.nearest_index(49.), 0); assert_eq!(scale.nearest_index(50.), 1); assert_eq!(scale.nearest_index(51.), 1); assert_eq!(scale.nearest_index(69.), 1); assert_eq!(scale.nearest_index(70.), 2); assert_eq!(scale.nearest_index(71.), 2); assert_eq!(scale.nearest_index(30.), 0); assert_eq!(scale.nearest_index(100.), 2); }
184
185 #[test]
186 fn test_nearest_index_empty() {
187 let scale = ScalePoint::new(Vec::<i32>::new(), [0., 100.]);
188 assert_eq!(scale.nearest_index(0.), 0);
189 assert_eq!(scale.nearest_index(50.), 0);
190 assert_eq!(scale.nearest_index(100.), 0);
191 }
192
193 #[test]
194 fn test_nearest_index_single() {
195 let scale = ScalePoint::new(vec![1], [0., 100.]);
196 assert_eq!(scale.nearest_index(0.), 0);
197 assert_eq!(scale.nearest_index(50.), 0);
198 assert_eq!(scale.nearest_index(100.), 0);
199 }
200
201 #[test]
202 fn test_nearest_index_empty_range() {
203 let scale = ScalePoint::new(vec![1, 2, 3], [0., 0.]);
204 assert_eq!(scale.nearest_index(0.), 0);
205 assert_eq!(scale.nearest_index(50.), 0);
206 assert_eq!(scale.nearest_index(100.), 0);
207 }
208
209 #[test]
210 fn test_reversed_range() {
211 let scale = ScalePoint::new([1, 2, 3], [100., 0.]);
212 assert_eq!(scale.tick(&1), Some(100.));
213 assert_eq!(scale.tick(&3), Some(0.));
214 assert_eq!(scale.nearest_index(90.), 0);
215 assert_eq!(scale.nearest_index(10.), 2);
216 }
217}