rate_ui/packages/masterpiece/
scale.rs

1use derive_more::{From, Into};
2use std::f64::consts;
3
4#[derive(Debug, Clone, PartialEq)]
5pub struct Range {
6    start: f64,
7    stop: f64,
8    min: f64,
9    max: f64,
10    diff: f64,
11    reverse: bool,
12}
13
14impl Range {
15    pub fn new(start: f64, stop: f64) -> Self {
16        if start <= stop {
17            Self {
18                start,
19                stop,
20                min: start,
21                max: stop,
22                diff: stop - start,
23                reverse: false,
24            }
25        } else {
26            Self {
27                start,
28                stop,
29                min: stop,
30                max: start,
31                diff: start - stop,
32                reverse: true,
33            }
34        }
35    }
36
37    pub fn min(&self) -> f64 {
38        self.min
39    }
40
41    pub fn max(&self) -> f64 {
42        self.max
43    }
44
45    /*
46    pub fn start(&self) -> f64 {
47        self.start
48    }
49
50    pub fn stop(&self) -> f64 {
51        self.stop
52    }
53
54    pub fn diff(&self) -> f64 {
55        self.diff
56    }
57
58    pub fn reverse(&self) -> bool {
59        self.reverse
60    }
61    */
62
63    pub fn with_padding(&mut self, mut pad: f64) {
64        if self.reverse {
65            pad = -pad;
66        }
67        *self = Self::new(self.start + pad, self.stop - pad);
68    }
69
70    // TODO: Fix. It doesn't work on `0`
71    // TODO: Implement `nice`?
72    pub fn spread(&mut self, mut spread: f64) {
73        if self.reverse {
74            spread = -spread;
75        }
76        *self = Self::new(self.start * (1.0 - spread), self.stop * (1.0 + spread));
77    }
78
79    pub fn is_flat(&self) -> bool {
80        self.diff == 0.0
81    }
82}
83
84impl From<(f64, f64)> for Range {
85    fn from((min, max): (f64, f64)) -> Self {
86        Self::new(min, max)
87    }
88}
89
90pub struct LinearScale {
91    domain: Range,
92    range: Range,
93}
94
95impl LinearScale {
96    pub fn new(domain: Range, range: Range) -> Self {
97        Self { domain, range }
98    }
99
100    pub fn rescale(&self, value: f64) -> f64 {
101        self.range.interpolate(self.domain.normalize(value))
102    }
103}
104
105impl Range {
106    fn tick_increment(&self, count: f64) -> f64 {
107        let start = self.min;
108        let stop = self.max;
109
110        let e10 = 50_f64.sqrt();
111        let e5 = 10_f64.sqrt();
112        let e2 = 2_f64.sqrt();
113
114        let step = (stop - start) / count.max(0.0);
115        let power = (step.ln() / consts::LN_10).floor();
116        let error = step / 10_f64.powf(power);
117
118        let factor;
119        if error >= e10 {
120            factor = 10.0;
121        } else if error >= e5 {
122            factor = 5.0;
123        } else if error >= e2 {
124            factor = 2.0;
125        } else {
126            factor = 1.0;
127        }
128
129        if power >= 0.0 {
130            factor * 10_f64.powf(power)
131        } else {
132            -(10_f64.powf(-power)) / factor
133        }
134    }
135
136    pub fn ticks(&self, count: u16) -> Vec<f64> {
137        let start = self.min;
138        let stop = self.max;
139        let mut ticks = Vec::with_capacity(count as usize);
140        if self.diff == 0.0 && count > 0 {
141            ticks.push(start);
142        } else {
143            let step = self.tick_increment(count as f64);
144            if step.is_finite() {
145                if step > 0.0 {
146                    let mut r_start = (start / step).round();
147                    let mut r_stop = (stop / step).round();
148                    if r_start * step < start {
149                        r_start += 1.0;
150                    }
151                    if r_stop * step > stop {
152                        r_stop -= 1.0;
153                    }
154                    let n = (r_stop - r_start + 1.0) as usize;
155                    let iter = (0..n).map(|i| (r_start + i as f64) * step);
156                    if !self.reverse {
157                        ticks.extend(iter);
158                    } else {
159                        ticks.extend(iter.rev());
160                    }
161                } else if step < 0.0 {
162                    let step = -step;
163                    let mut r_start = (start * step).round();
164                    let mut r_stop = (stop * step).round();
165                    if r_start / step < start {
166                        r_start += 1.0;
167                    }
168                    if r_stop / step > stop {
169                        r_stop -= 1.0;
170                    }
171                    let n = (r_stop - r_start + 1.0) as usize;
172                    let iter = (0..n).map(|i| (r_start + i as f64) / step);
173                    if !self.reverse {
174                        ticks.extend(iter);
175                    } else {
176                        ticks.extend(iter.rev());
177                    }
178                } else {
179                    // and skip if `step == 0.0`
180                }
181            }
182        }
183        ticks
184    }
185
186    fn interpolate(&self, norm: Norm) -> f64 {
187        let value = f64::from(norm);
188        if !self.reverse {
189            self.min * (1.0 - value) + self.max * value
190        } else {
191            self.min * value + self.max * (1.0 - value)
192        }
193    }
194
195    fn normalize(&self, value: f64) -> Norm {
196        if !self.reverse {
197            ((value - self.min) / self.diff).into()
198        } else {
199            ((self.max - value) / self.diff).into()
200        }
201    }
202}
203
204// Normalized value (in range [0;1])
205#[derive(From, Into, Debug, PartialEq, PartialOrd)]
206struct Norm(pub f64);
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use approx::assert_abs_diff_eq;
212
213    #[test]
214    fn test_tick_increment() {
215        let range = Range::new(0.03310319276564422, 0.9859442826901874);
216        assert_abs_diff_eq!(range.tick_increment(5.0), -5.0);
217
218        let range = Range::new(0.12, 500.0);
219        assert_abs_diff_eq!(range.tick_increment(29.0), 20.0);
220    }
221
222    #[test]
223    fn test_range_ticks() {
224        let range = Range::new(1.0, 10.0);
225        assert_eq!(range.ticks(5), vec![2.0, 4.0, 6.0, 8.0, 10.0]);
226
227        let range = Range::new(0.03310319276564422, 0.9859442826901874);
228        assert_eq!(range.ticks(5), vec![0.2, 0.4, 0.6, 0.8]);
229    }
230
231    #[test]
232    fn test_linear_rescale() {
233        let domain = Range::new(10.0, 20.0);
234        assert_eq!(domain.normalize(11.0), Norm(0.1));
235        let range = Range::new(0.0, 600.0);
236        let linear = LinearScale::new(domain, range);
237        assert_abs_diff_eq!(linear.rescale(11.0), 60.0);
238    }
239}