1#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct Axis {
9 pub lo: f64,
10 pub hi: f64,
11 pub log: bool,
12 pub shift: f64,
15}
16
17impl Axis {
18 pub fn linear(lo: f64, hi: f64) -> Self {
19 Self {
20 lo,
21 hi,
22 log: false,
23 shift: 0.0,
24 }
25 }
26
27 pub fn log1p(lo: f64, hi: f64) -> Self {
29 Self {
30 lo,
31 hi,
32 log: true,
33 shift: 1.0,
34 }
35 }
36
37 pub fn fit(values: &[f32], log: bool) -> Self {
41 let (mut lo, mut hi) = values
42 .iter()
43 .filter(|v| v.is_finite())
44 .fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), &v| {
45 (lo.min(v as f64), hi.max(v as f64))
46 });
47 if !lo.is_finite() {
48 (lo, hi) = (0.0, 1.0);
49 }
50 if log {
51 lo = lo.max(0.0);
52 }
53 if hi <= lo {
54 hi = lo + if log { lo.max(1.0) } else { 1.0 };
55 }
56 let shift = if !log || hi >= 20.0 {
57 1.0
58 } else {
59 let least = values
60 .iter()
61 .filter(|v| v.is_finite() && **v > 0.0)
62 .fold(f64::INFINITY, |m, &v| m.min(v as f64));
63 (least / 2.0).clamp(hi * 1e-4, 1.0)
64 };
65 Self { lo, hi, log, shift }
66 }
67
68 pub fn including(mut self, v: f64) -> Self {
70 if v.is_finite() {
71 self.lo = self.lo.min(if self.log { v.max(0.0) } else { v });
72 self.hi = self.hi.max(v);
73 }
74 self
75 }
76
77 fn t(&self, v: f64) -> f64 {
78 if self.log {
79 (v.max(0.0) + self.shift).ln()
80 } else {
81 v
82 }
83 }
84
85 pub fn frac(&self, v: f64) -> f64 {
87 let (a, b) = (self.t(self.lo), self.t(self.hi));
88 if b > a {
89 (self.t(v) - a) / (b - a)
90 } else {
91 0.0
92 }
93 }
94
95 pub fn at(&self, f: f64) -> f64 {
97 let (a, b) = (self.t(self.lo), self.t(self.hi));
98 let t = a + f * (b - a);
99 if self.log {
100 (t.exp() - self.shift).max(0.0)
101 } else {
102 t
103 }
104 }
105
106 pub fn bin(&self, v: f64, n: usize) -> usize {
108 ((self.frac(v) * n as f64).floor().max(0.0) as usize).min(n.saturating_sub(1))
109 }
110
111 pub fn counts(&self, values: &[f32], n: usize, pick: impl Fn(usize) -> bool) -> Vec<usize> {
113 let mut out = vec![0; n];
114 for (i, &v) in values.iter().enumerate() {
115 if v.is_finite() && pick(i) {
116 out[self.bin(v as f64, n)] += 1;
117 }
118 }
119 out
120 }
121
122 pub fn ticks(&self) -> Vec<f64> {
125 if self.log {
126 let mut t: Vec<f64> = (self.lo <= 0.0).then_some(0.0).into_iter().collect();
127 for e in -3..10 {
128 for m in [1.0, 2.0, 5.0] {
129 let v = m * 10f64.powi(e);
130 if v >= self.lo && v <= self.hi {
131 t.push(v);
132 }
133 }
134 }
135 let mut kept: Vec<f64> = vec![];
138 for v in t {
139 if kept
140 .last()
141 .is_none_or(|&l| self.frac(v) - self.frac(l) >= 0.05)
142 {
143 kept.push(v);
144 }
145 }
146 let mut t = kept;
147 while t.len() > 8 {
149 let decade = |v: &f64| *v == 0.0 || (v.log10() - v.log10().round()).abs() < 1e-9;
150 if t.iter().all(decade) {
151 t = t.into_iter().step_by(2).collect();
152 } else {
153 t.retain(decade);
154 }
155 }
156 t
157 } else {
158 let span = self.hi - self.lo;
159 let raw = span / 5.0;
160 let mag = 10f64.powf(raw.log10().floor());
161 let step = [1.0, 2.0, 2.5, 5.0, 10.0]
162 .iter()
163 .map(|m| m * mag)
164 .find(|s| span / s <= 6.0)
165 .unwrap_or(10.0 * mag);
166 if !step.is_finite() || step <= 0.0 {
170 let one = self.lo == self.hi && self.lo.is_finite();
171 return one.then_some(self.lo).into_iter().collect();
172 }
173 let places = 10f64.powi((2 - step.log10().floor() as i32).max(0));
176 let first = (self.lo / step).ceil() as i64;
177 (first..)
178 .map(|k| (k as f64 * step * places).round() / places)
179 .take_while(|v| *v <= self.hi + 1e-9 * step)
180 .map(|v| if v == 0.0 { 0.0 } else { v })
181 .collect()
182 }
183 }
184}