lc_render/scale/
linear.rs

1use crate::math::linear::{interpolate, normalize, range};
2use crate::{Scale, ScaleKind};
3
4const DEFAULT_TICK_COUNT: usize = 11;
5
6/// LinearScale represents axis scale with numerical values.
7#[derive(Clone)]
8pub struct LinearScale {
9    /// Start of the scale domain.
10    domain_start: f32,
11
12    /// End of the scale domain.
13    domain_end: f32,
14
15    /// Start of the scale range.
16    range_start: i32,
17
18    /// End of the scale range.
19    range_end: i32,
20
21    /// Count of ticks on scale axis.
22    tick_count: usize,
23}
24
25impl LinearScale {
26    /// Create a new LinearScale.
27    pub fn new(domain_start: f32, domain_end: f32, range_start: i32, range_end: i32) -> Self {
28        Self {
29            domain_start,
30            domain_end,
31            range_start,
32            range_end,
33            tick_count: DEFAULT_TICK_COUNT,
34        }
35    }
36
37    /// Get scale range start.
38    pub fn range_start(&self) -> i32 {
39        self.range_start
40    }
41
42    /// Get scale range end.
43    pub fn range_end(&self) -> i32 {
44        self.range_end
45    }
46
47    // Compute the step for each tick.
48    fn compute_tick_step(&self, start: f32, end: f32) -> f32 {
49        let mut step_denominator = 0_f32;
50        if self.tick_count as f32 > step_denominator {
51            step_denominator = self.tick_count as f32;
52        }
53
54        let step = range(start, end) / step_denominator;
55        let power = (step.ln() / 10_f32.ln()).trunc() as i32;
56        let error = step / 10_f32.powi(power);
57
58        let mut dynamic = 1;
59        if error >= 50_f32.sqrt() {
60            dynamic = 10;
61        } else if error >= 10_f32.sqrt() {
62            dynamic = 5;
63        } else if error >= 2_f32.sqrt() {
64            dynamic = 2;
65        };
66
67        if power < 0 {
68            return -(10_f32.powi(-power)) / dynamic as f32;
69        }
70
71        dynamic as f32 * 10_f32.powi(power)
72    }
73
74    // Calculate vector of ticks for positive step.
75    fn ticks_positive_step(&self, step: f32) -> Vec<f32> {
76        let mut res = Vec::new();
77
78        let start = (self.domain_start / step).ceil();
79        let end = (self.domain_end / step).floor();
80        let ticks_count = (range(start, end) + 1_f32).ceil() as i32;
81        for i in 0..ticks_count {
82            res.push((start + i as f32) * step);
83        }
84
85        res
86    }
87
88    // Calculate vector of ticks for negative step.
89    fn ticks_negative_step(&self, step: f32) -> Vec<f32> {
90        let mut res = Vec::new();
91
92        let start = (self.domain_start as f32 * step).floor();
93        let end = (self.domain_end as f32 * step).ceil();
94        let ticks_count = (range(end, start) + 1_f32).ceil() as i32;
95        for i in 0..ticks_count {
96            res.push((start - i as f32) / step);
97        }
98
99        res
100    }
101}
102
103impl Scale<f32> for LinearScale {
104    fn scale(&self, domain: &f32) -> f32 {
105        let normalized = normalize(self.domain_start, self.domain_end, *domain);
106        interpolate(self.range_start as f32, self.range_end as f32, normalized)
107    }
108
109    fn ticks(&self) -> Vec<f32> {
110        if (self.domain_end - self.domain_start).abs() < f32::EPSILON && self.tick_count > 0 {
111            return vec![self.domain_start as f32];
112        }
113
114        let step = self.compute_tick_step(self.domain_start, self.domain_end);
115        if step > 0_f32 {
116            return self.ticks_positive_step(step);
117        }
118
119        self.ticks_negative_step(step)
120    }
121
122    fn kind(&self) -> ScaleKind {
123        ScaleKind::Linear
124    }
125
126    fn bandwidth(&self) -> f32 {
127        0_f32
128    }
129
130    fn is_range_reversed(&self) -> bool {
131        self.range_start > self.range_end
132    }
133
134    fn tick_offset(&self) -> f32 {
135        0_f32
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn linear_scale_basic() {
145        let linear_scale = LinearScale::new(0_f32, 200_f32, 540, 0);
146
147        assert_eq!(linear_scale.range_start(), 540);
148        assert_eq!(linear_scale.range_end(), 0);
149        assert_eq!(
150            *linear_scale.ticks(),
151            vec![
152                0_f32, 20_f32, 40_f32, 60_f32, 80_f32, 100_f32, 120_f32, 140_f32, 160_f32, 180_f32,
153                200_f32
154            ]
155        );
156        assert!((linear_scale.scale(&24_f32) - 475.2_f32).abs() < f32::EPSILON);
157        assert_eq!(linear_scale.kind(), ScaleKind::Linear);
158        assert!((linear_scale.bandwidth() - 0_f32).abs() < f32::EPSILON);
159        assert!(linear_scale.is_range_reversed());
160        assert!((linear_scale.tick_offset() - 0_f32).abs() < f32::EPSILON);
161    }
162}