1use crate::format;
4
5#[derive(Clone, Copy, Debug, PartialEq)]
28#[must_use = "builder methods return a new value; it does nothing until it is rendered or used"]
29pub struct Scale {
30 min: f64,
31 max: f64,
32}
33
34impl Default for Scale {
35 fn default() -> Self {
36 Scale { min: 0.0, max: 1.0 }
37 }
38}
39
40impl Scale {
41 pub fn new(a: f64, b: f64) -> Self {
45 if !a.is_finite() || !b.is_finite() {
46 return Scale::default();
47 }
48 let (min, max) = if a <= b { (a, b) } else { (b, a) };
49 if min == max {
50 return Self::degenerate(min);
51 }
52 Scale { min, max }
53 }
54
55 fn degenerate(v: f64) -> Self {
56 if v == 0.0 {
57 Scale { min: 0.0, max: 1.0 }
58 } else {
59 Scale {
60 min: v - v.abs(),
61 max: v + v.abs(),
62 }
63 }
64 }
65
66 pub fn from_values(values: impl IntoIterator<Item = f64>) -> Self {
68 let mut range: Option<(f64, f64)> = None;
69 for v in values.into_iter().filter(|v| v.is_finite()) {
70 range = Some(match range {
71 None => (v, v),
72 Some((lo, hi)) => (lo.min(v), hi.max(v)),
73 });
74 }
75 match range {
76 None => Scale::default(),
77 Some((lo, hi)) => Scale::new(lo, hi),
78 }
79 }
80
81 pub fn bounds(self, min: Option<f64>, max: Option<f64>) -> Self {
84 let min = min.filter(|v| v.is_finite());
85 let max = max.filter(|v| v.is_finite());
86 let width = self.span().abs().max(1.0);
87 match (min, max) {
88 (Some(lo), None) if lo > self.max => Scale::new(lo, lo + width),
91 (None, Some(hi)) if hi < self.min => Scale::new(hi - width, hi),
92 (lo, hi) => Scale::new(lo.unwrap_or(self.min), hi.unwrap_or(self.max)),
93 }
94 }
95
96 pub fn include_zero(self) -> Self {
98 Scale::new(self.min.min(0.0), self.max.max(0.0))
99 }
100
101 pub fn nice(self, count: usize) -> Self {
104 let step = self.step(count);
105 let min = (self.min / step).floor() * step;
106 let max = (self.max / step).ceil() * step;
107 Scale::new(clean(min), clean(max))
108 }
109
110 pub fn min(&self) -> f64 {
112 self.min
113 }
114
115 pub fn max(&self) -> f64 {
117 self.max
118 }
119
120 pub fn span(&self) -> f64 {
123 let span = self.max - self.min;
124 if span.is_finite() { span } else { f64::MAX }
125 }
126
127 pub fn normalize(&self, value: f64) -> Option<f64> {
130 if !value.is_finite() {
131 return None;
132 }
133 let span = self.max - self.min;
134 let at = if span.is_finite() {
135 (value - self.min) / span
136 } else {
137 (value * 0.5 - self.min * 0.5) / (self.max * 0.5 - self.min * 0.5)
139 };
140 Some(at.clamp(0.0, 1.0))
141 }
142
143 pub(crate) fn lerp(&self, t: f64) -> f64 {
146 let span = self.max - self.min;
147 if span.is_finite() {
148 self.min + t * span
149 } else {
150 self.min * (1.0 - t) + self.max * t
151 }
152 }
153
154 pub fn step(&self, count: usize) -> f64 {
156 let intervals = count.max(2) - 1;
157 nice_number(self.span() / intervals as f64, true)
158 }
159
160 pub fn ticks(&self, count: usize) -> Vec<f64> {
165 self.ticks_every(self.step(count))
166 }
167
168 pub fn ticks_every(&self, step: f64) -> Vec<f64> {
173 if !(step.is_finite() && step > 0.0) || self.span() / step > 10_000.0 {
174 return vec![self.min, self.max];
175 }
176 let first = (self.min / step - 1e-9).ceil() as i64;
177 let last = (self.max / step + 1e-9).floor() as i64;
178 (first..=last).map(|k| clean(k as f64 * step)).collect()
179 }
180}
181
182fn nice_number(x: f64, round: bool) -> f64 {
184 if !(x.is_finite() && x > 0.0) {
185 return 1.0;
186 }
187 let exponent = x.log10().floor();
188 let power = 10f64.powf(exponent);
189 let fraction = x / power;
190 let nice = if round {
191 match fraction {
192 f if f < 1.5 => 1.0,
193 f if f < 3.0 => 2.0,
194 f if f < 7.0 => 5.0,
195 _ => 10.0,
196 }
197 } else {
198 match fraction {
199 f if f <= 1.0 => 1.0,
200 f if f <= 2.0 => 2.0,
201 f if f <= 5.0 => 5.0,
202 _ => 10.0,
203 }
204 };
205 nice * power
206}
207
208pub(crate) fn along(lo: f64, k: f64, unit: f64) -> f64 {
211 let offset = k * unit;
212 let value = lo + offset;
213 if value.is_finite() || !lo.is_finite() || !unit.is_finite() {
214 value
215 } else {
216 (lo * 0.5 + k * (unit * 0.5)) * 2.0
217 }
218}
219
220pub(crate) fn units_past(value: f64, lo: f64, unit: f64) -> f64 {
223 let diff = value - lo;
224 if diff.is_finite() {
225 diff / unit
226 } else {
227 (value * 0.5 - lo * 0.5) / unit * 2.0
228 }
229}
230
231pub(crate) fn clean(v: f64) -> f64 {
234 if !v.is_finite() {
235 return v;
236 }
237 let rounded: f64 = format!("{v:.11e}").parse().unwrap_or(v);
238 if rounded == 0.0 { 0.0 } else { rounded }
239}
240
241#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
252pub enum ValueFormat {
253 #[default]
259 Compact,
260 Fixed(usize),
263}
264
265impl ValueFormat {
266 pub const MAX_DECIMALS: usize = 17;
269
270 pub fn format(self, value: f64) -> String {
272 if !value.is_finite() {
273 return "-".to_string();
274 }
275 let value = if value == 0.0 { 0.0 } else { value };
276 match self {
277 ValueFormat::Fixed(decimals) => {
278 let decimals = decimals.min(Self::MAX_DECIMALS);
279 let text = format!("{value:.decimals$}");
280 if text
282 .trim_start_matches('-')
283 .chars()
284 .all(|c| c == '0' || c == '.')
285 {
286 text.trim_start_matches('-').to_string()
287 } else {
288 text
289 }
290 }
291 ValueFormat::Compact => {
292 if value.abs() >= 1e15 {
293 return format!("{value:.1e}");
295 }
296 if value.abs() >= 1000.0 {
297 return format::compact(value);
298 }
299 let text = format!("{value:.2}");
300 let text = text.trim_end_matches('0').trim_end_matches('.');
301 if text == "-0" {
302 "0".to_string()
303 } else {
304 text.to_string()
305 }
306 }
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn ticks_are_round_numbers_inside_the_scale() {
317 let s = Scale::new(0.0, 100.0);
318 assert_eq!(s.ticks(5), vec![0.0, 20.0, 40.0, 60.0, 80.0, 100.0]);
320 assert_eq!(s.ticks(6), vec![0.0, 20.0, 40.0, 60.0, 80.0, 100.0]);
321 assert_eq!(s.ticks(3), vec![0.0, 50.0, 100.0]);
322 assert_eq!(
323 Scale::new(0.0, 1.0).ticks(5),
324 vec![0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
325 );
326 assert_eq!(Scale::new(3.0, 17.0).ticks(4), vec![5.0, 10.0, 15.0]);
327 assert_eq!(Scale::new(0.1, 0.35).ticks(3), vec![0.1, 0.2, 0.3]);
328 }
329
330 #[test]
331 fn nice_widens_to_whole_steps() {
332 let s = Scale::new(3.0, 17.0).nice(5);
333 assert_eq!((s.min(), s.max()), (0.0, 20.0));
334 assert_eq!(s.ticks(5), vec![0.0, 5.0, 10.0, 15.0, 20.0]);
335 let data = Scale::new(1.0, 5.0);
337 let wide = data.nice(3);
338 assert_eq!((wide.min(), wide.max()), (0.0, 6.0));
339 assert_eq!(wide.ticks_every(data.step(3)), vec![0.0, 2.0, 4.0, 6.0]);
340 assert_eq!(wide.ticks(3), vec![0.0, 5.0]);
341 assert_eq!(wide.ticks_every(0.0), vec![0.0, 6.0]);
342 let s = Scale::new(-7.0, 42.0).nice(5);
343 assert_eq!((s.min(), s.max()), (-10.0, 50.0));
344 let s = Scale::new(0.0, 100.0).nice(5);
346 assert_eq!((s.min(), s.max()), (0.0, 100.0));
347 }
348
349 #[test]
350 fn a_single_bound_past_the_data_is_kept() {
351 let data = Scale::from_values([150.0, 200.0]);
352 let s = data.bounds(None, Some(100.0));
353 assert_eq!((s.min(), s.max()), (50.0, 100.0));
354 let s = data.bounds(Some(300.0), None);
355 assert_eq!((s.min(), s.max()), (300.0, 350.0));
356 let s = data.bounds(Some(10.0), Some(0.0));
358 assert_eq!((s.min(), s.max()), (0.0, 10.0));
359 }
360
361 #[test]
362 fn tiny_scales_keep_their_magnitude() {
363 let s = Scale::new(1e-12, 5e-12).nice(5);
364 assert_eq!((s.min(), s.max()), (1e-12, 5e-12));
365 assert_eq!(s.ticks(5), vec![1e-12, 2e-12, 3e-12, 4e-12, 5e-12]);
366 assert_eq!(clean(0.1 + 0.2), 0.3);
367 assert_eq!(clean(-0.0).to_bits(), 0.0f64.to_bits());
368 }
369
370 #[test]
371 fn degenerate_ranges_never_collapse() {
372 let zero = Scale::from_values([0.0, 0.0, 0.0]);
373 assert_eq!((zero.min(), zero.max()), (0.0, 1.0));
374 let five = Scale::from_values([5.0; 4]);
375 assert_eq!((five.min(), five.max()), (0.0, 10.0));
376 assert_eq!(five.normalize(5.0), Some(0.5));
377 let neg = Scale::from_values([-4.0]);
378 assert_eq!((neg.min(), neg.max()), (-8.0, 0.0));
379 let empty = Scale::from_values(std::iter::empty());
380 assert_eq!((empty.min(), empty.max()), (0.0, 1.0));
381 assert_eq!(Scale::new(2.0, 2.0).span(), 4.0);
382 assert!(Scale::new(f64::NAN, 3.0) == Scale::default());
383 for s in [zero, five, neg, empty] {
384 assert!(s.span() > 0.0);
385 assert!(!s.ticks(5).is_empty());
386 }
387 }
388
389 #[test]
390 fn negative_values_and_zero() {
391 let s = Scale::from_values([-30.0, -5.0, 20.0]);
392 assert_eq!((s.min(), s.max()), (-30.0, 20.0));
393 assert_eq!(s.ticks(6), vec![-30.0, -20.0, -10.0, 0.0, 10.0, 20.0]);
394 assert_eq!(s.normalize(-30.0), Some(0.0));
395 assert_eq!(s.normalize(-5.0), Some(0.5));
396 let pos = Scale::from_values([4.0, 9.0]).include_zero();
397 assert_eq!((pos.min(), pos.max()), (0.0, 9.0));
398 let neg = Scale::from_values([-4.0, -9.0]).include_zero();
399 assert_eq!((neg.min(), neg.max()), (-9.0, 0.0));
400 assert!(
402 Scale::new(-1.0, 1.0)
403 .ticks(5)
404 .iter()
405 .all(|t| t.to_string() != "-0")
406 );
407 }
408
409 #[test]
410 fn nan_and_infinities_are_skipped() {
411 let s = Scale::from_values([f64::NAN, 1.0, f64::INFINITY, 3.0, f64::NEG_INFINITY]);
412 assert_eq!((s.min(), s.max()), (1.0, 3.0));
413 assert_eq!(s.normalize(f64::NAN), None);
414 assert_eq!(s.normalize(f64::INFINITY), None);
415 assert_eq!(Scale::from_values([f64::NAN]), Scale::default());
416 }
417
418 #[test]
419 fn explicit_bounds_override_and_clamp() {
420 let s = Scale::from_values([3.0, 7.0]).bounds(Some(0.0), Some(10.0));
421 assert_eq!((s.min(), s.max()), (0.0, 10.0));
422 assert_eq!(s.normalize(15.0), Some(1.0));
423 assert_eq!(s.normalize(-5.0), Some(0.0));
424 let only_max = Scale::from_values([3.0, 7.0]).bounds(None, Some(100.0));
425 assert_eq!((only_max.min(), only_max.max()), (3.0, 100.0));
426 let past = Scale::from_values([3.0, 7.0]).bounds(Some(50.0), None);
428 assert_eq!(past.min(), 50.0);
429 assert!(past.span() > 0.0);
430 }
431
432 #[test]
433 fn value_formats() {
434 let c = ValueFormat::Compact;
435 assert_eq!(c.format(0.0), "0");
436 assert_eq!(c.format(-0.0), "0");
437 assert_eq!(c.format(42.0), "42");
438 assert_eq!(c.format(12.5), "12.5");
439 assert_eq!(c.format(0.126), "0.13");
440 assert_eq!(c.format(-3.0), "-3");
441 assert_eq!(c.format(1_250.0), "1.2k");
442 assert_eq!(c.format(-3_400_000.0), "-3.4M");
443 assert_eq!(c.format(f64::NAN), "-");
444 assert_eq!(c.format(-0.001), "0");
445 let f = ValueFormat::Fixed(2);
446 assert_eq!(f.format(1.23456), "1.23");
447 assert_eq!(f.format(-0.001), "0.00");
448 assert_eq!(ValueFormat::Fixed(0).format(2.6), "3");
449 }
450}