1use super::{ContinuousScale, tick_step, round_to_precision};
2
3pub struct ScaleLinear {
6 domain: (f64, f64),
7 range: (f64, f64),
8}
9
10impl ScaleLinear {
11 pub fn new(domain: (f64, f64), range: (f64, f64)) -> Self {
13 Self { domain, range }
14 }
15
16 pub fn map(&self, value: f64) -> f64 {
18 let (d0, d1) = self.domain;
19 let (r0, r1) = self.range;
20 let domain_span = d1 - d0;
21 if domain_span == 0.0 {
22 return (r0 + r1) / 2.0;
24 }
25 r0 + (value - d0) / domain_span * (r1 - r0)
26 }
27
28 pub fn invert(&self, value: f64) -> f64 {
30 let (d0, d1) = self.domain;
31 let (r0, r1) = self.range;
32 let range_span = r1 - r0;
33 if range_span == 0.0 {
34 return (d0 + d1) / 2.0;
35 }
36 d0 + (value - r0) / range_span * (d1 - d0)
37 }
38
39 pub fn ticks(&self, count: usize) -> Vec<f64> {
42 if count == 0 {
43 return vec![];
44 }
45 let (d0, d1) = self.domain;
46 let reversed = d0 > d1;
47 let min = d0.min(d1);
48 let max = d0.max(d1);
49 if min == max {
50 return vec![min];
51 }
52
53 let step = tick_step(min, max, count);
54 if step == 0.0 || !step.is_finite() {
55 return vec![];
56 }
57
58 let mut ticks = Vec::new();
59 let start = (min / step).ceil();
60 let stop = (max / step).floor();
61
62 let mut i = start;
63 while i <= stop {
64 let tick = i * step;
65 let tick = round_to_precision(tick, step);
66 ticks.push(tick);
67 i += 1.0;
68 }
69
70 if reversed {
71 ticks.reverse();
72 }
73
74 ticks
75 }
76
77 pub fn nice(self, count: usize) -> Self {
82 if count == 0 {
83 return self;
84 }
85 let (d0, d1) = self.domain;
86 let reversed = d0 > d1;
87 let mut start = d0.min(d1);
88 let mut stop = d0.max(d1);
89 if start == stop {
90 return self;
91 }
92
93 let mut prestep = f64::NAN;
94 let mut max_iter = 10i32;
95 while max_iter > 0 {
96 max_iter -= 1;
97 let step = tick_step(start, stop, count);
98 if step == 0.0 || !step.is_finite() {
99 break;
100 }
101 if step == prestep {
102 break;
104 }
105 start = (start / step).floor() * step;
108 stop = (stop / step).ceil() * step;
109 prestep = step;
110 }
111
112 let domain = if reversed {
113 (stop, start)
114 } else {
115 (start, stop)
116 };
117
118 Self {
119 domain,
120 range: self.range,
121 }
122 }
123
124 pub fn domain(&self) -> (f64, f64) {
126 self.domain
127 }
128
129 pub fn range(&self) -> (f64, f64) {
131 self.range
132 }
133}
134
135impl ContinuousScale for ScaleLinear {
136 fn map(&self, value: f64) -> f64 {
137 ScaleLinear::map(self, value)
138 }
139
140 fn domain(&self) -> (f64, f64) {
141 ScaleLinear::domain(self)
142 }
143
144 fn range(&self) -> (f64, f64) {
145 ScaleLinear::range(self)
146 }
147
148 fn ticks(&self, count: usize) -> Vec<f64> {
149 ScaleLinear::ticks(self, count)
150 }
151
152 fn clamp(&self, value: f64) -> f64 {
153 let (d0, d1) = self.domain;
154 let min = d0.min(d1);
155 let max = d0.max(d1);
156 value.clamp(min, max)
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn linear_scale_maps_midpoint() {
166 let scale = ScaleLinear::new((0.0, 100.0), (0.0, 500.0));
167 assert!((scale.map(50.0) - 250.0).abs() < 1e-10);
168 }
169
170 #[test]
171 fn linear_scale_maps_endpoints() {
172 let scale = ScaleLinear::new((0.0, 100.0), (0.0, 500.0));
173 assert!((scale.map(0.0) - 0.0).abs() < 1e-10);
174 assert!((scale.map(100.0) - 500.0).abs() < 1e-10);
175 }
176
177 #[test]
178 fn linear_scale_inverts() {
179 let scale = ScaleLinear::new((0.0, 100.0), (0.0, 500.0));
180 assert!((scale.invert(250.0) - 50.0).abs() < 1e-10);
181 }
182
183 #[test]
184 fn linear_scale_reversed_range() {
185 let scale = ScaleLinear::new((0.0, 100.0), (500.0, 0.0));
186 assert!((scale.map(0.0) - 500.0).abs() < 1e-10);
187 assert!((scale.map(100.0) - 0.0).abs() < 1e-10);
188 }
189
190 #[test]
191 fn linear_scale_ticks() {
192 let scale = ScaleLinear::new((0.0, 100.0), (0.0, 500.0));
193 let ticks = scale.ticks(5);
194 let expected = vec![0.0, 20.0, 40.0, 60.0, 80.0, 100.0];
195 assert_eq!(ticks.len(), expected.len(), "tick count mismatch: got {:?}", ticks);
196 for (a, b) in ticks.iter().zip(expected.iter()) {
197 assert!((a - b).abs() < 1e-10, "tick mismatch: {} vs {}", a, b);
198 }
199 }
200
201 #[test]
202 fn linear_scale_ticks_non_round() {
203 let scale = ScaleLinear::new((0.0, 1.0), (0.0, 500.0));
204 let ticks = scale.ticks(5);
205 let expected = vec![0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
206 assert_eq!(ticks.len(), expected.len(), "tick count mismatch: got {:?}", ticks);
207 for (a, b) in ticks.iter().zip(expected.iter()) {
208 assert!((a - b).abs() < 1e-10, "tick mismatch: {} vs {}", a, b);
209 }
210 }
211
212 #[test]
213 fn linear_scale_nice() {
214 let scale = ScaleLinear::new((0.5, 9.7), (0.0, 500.0)).nice(10);
215 let (d0, d1) = scale.domain();
216 assert!((d0 - 0.0).abs() < 1e-10, "nice min should be 0, got {}", d0);
217 assert!((d1 - 10.0).abs() < 1e-10, "nice max should be 10, got {}", d1);
218 }
219
220 #[test]
221 fn linear_scale_single_value_domain() {
222 let scale = ScaleLinear::new((5.0, 5.0), (0.0, 500.0));
223 assert!((scale.map(5.0) - 250.0).abs() < 1e-10);
224 }
225
226 #[test]
227 fn linear_scale_negative_domain() {
228 let scale = ScaleLinear::new((-100.0, 100.0), (0.0, 1000.0));
229 assert!((scale.map(0.0) - 500.0).abs() < 1e-10);
230 }
231
232 #[test]
233 fn linear_scale_reversed_domain_ticks() {
234 let scale = ScaleLinear::new((100.0, 0.0), (0.0, 500.0));
235 let ticks = scale.ticks(5);
236 assert!(ticks[0] > ticks[ticks.len() - 1]);
238 assert!((ticks[0] - 100.0).abs() < 1e-10);
239 assert!((ticks[ticks.len() - 1] - 0.0).abs() < 1e-10);
240 }
241
242 #[test]
243 fn linear_scale_reversed_domain_nice() {
244 let scale = ScaleLinear::new((9.7, 0.5), (0.0, 500.0)).nice(10);
245 let (d0, d1) = scale.domain();
246 assert!(d0 > d1, "reversed domain should stay reversed: ({}, {})", d0, d1);
248 assert!((d0 - 10.0).abs() < 1e-10);
249 assert!((d1 - 0.0).abs() < 1e-10);
250 }
251
252 #[test]
253 fn linear_scale_invert_reversed_range() {
254 let scale = ScaleLinear::new((0.0, 100.0), (500.0, 0.0));
255 assert!((scale.invert(250.0) - 50.0).abs() < 1e-10);
256 assert!((scale.invert(500.0) - 0.0).abs() < 1e-10);
257 }
258
259 #[test]
260 fn linear_scale_ticks_zero() {
261 let scale = ScaleLinear::new((0.0, 100.0), (0.0, 500.0));
262 assert!(scale.ticks(0).is_empty());
263 }
264
265 #[test]
266 fn linear_scale_ticks_one() {
267 let scale = ScaleLinear::new((0.0, 100.0), (0.0, 500.0));
268 let ticks = scale.ticks(1);
269 assert!(!ticks.is_empty());
271 }
272}