yew_chart/
linear_axis_scale.rs1use std::{ops::Range, rc::Rc};
4
5use crate::axis::{NormalisedValue, Scale, Tick};
6
7pub trait Labeller: Fn(f32) -> String {}
9
10impl<T: Fn(f32) -> String> Labeller for T {}
11
12fn labeller() -> impl Labeller {
13 |v| (v as i32).to_string()
14}
15
16#[derive(Clone)]
17pub struct LinearScale {
18 range: Range<f32>,
19 step: f32,
20 scale: f32,
21 labeller: Option<Rc<dyn Labeller>>,
22}
23
24impl LinearScale {
25 pub fn new(range: Range<f32>, step: f32) -> LinearScale {
27 Self::with_labeller(range, step, Some(Rc::from(labeller())))
28 }
29
30 pub fn with_labeller(
32 range: Range<f32>,
33 step: f32,
34 labeller: Option<Rc<dyn Labeller>>,
35 ) -> LinearScale {
36 let delta = range.end - range.start;
37 let scale = if delta != 0.0 { 1.0 / delta } else { 1.0 };
38 LinearScale {
39 range,
40 step,
41 scale,
42 labeller,
43 }
44 }
45}
46
47impl Scale for LinearScale {
48 type Scalar = f32;
49
50 fn ticks(&self) -> Vec<Tick> {
51 LinearScaleInclusiveIter {
52 from: self.range.start,
53 to: self.range.end,
54 step: self.step,
55 first_time: true,
56 last_time: false,
57 }
58 .map(move |v| {
59 let location = (v - self.range.start) * self.scale;
60 Tick {
61 location: NormalisedValue(location),
62 label: self.labeller.as_ref().map(|l| (l)(v)),
63 }
64 })
65 .collect()
66 }
67
68 fn normalise(&self, value: Self::Scalar) -> NormalisedValue {
69 NormalisedValue((value - self.range.start) * self.scale)
70 }
71}
72
73struct LinearScaleInclusiveIter {
74 pub from: f32,
75 pub to: f32,
76 pub step: f32,
77 pub first_time: bool,
78 pub last_time: bool,
79}
80
81impl Iterator for LinearScaleInclusiveIter {
82 type Item = f32;
83
84 fn next(&mut self) -> Option<Self::Item> {
85 if !self.first_time {
86 self.from += self.step;
87 } else {
88 self.first_time = false;
89 };
90 if (self.step >= 0.0 && self.from < self.to) || (self.step < 0.0 && self.from > self.to) {
91 Some(self.from)
92 } else if !self.last_time {
93 self.last_time = true;
94 Some(self.to)
95 } else {
96 None
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_scale() {
107 let scale = LinearScale::new(0.0..100.0, 25.0);
108
109 assert_eq!(
110 scale.ticks(),
111 vec![
112 Tick {
113 location: NormalisedValue(0.0),
114 label: Some("0".to_string())
115 },
116 Tick {
117 location: NormalisedValue(0.25),
118 label: Some("25".to_string())
119 },
120 Tick {
121 location: NormalisedValue(0.5),
122 label: Some("50".to_string())
123 },
124 Tick {
125 location: NormalisedValue(0.75),
126 label: Some("75".to_string())
127 },
128 Tick {
129 location: NormalisedValue(1.0),
130 label: Some("100".to_string())
131 }
132 ]
133 );
134
135 assert_eq!(scale.normalise(50.0), NormalisedValue(0.5));
136 }
137
138 #[test]
139 fn test_backward_scale() {
140 let scale = LinearScale::new(100.0..0.0, -25.0);
141
142 assert_eq!(
143 scale.ticks(),
144 vec![
145 Tick {
146 location: NormalisedValue(-0.0),
147 label: Some("100".to_string())
148 },
149 Tick {
150 location: NormalisedValue(0.25),
151 label: Some("75".to_string())
152 },
153 Tick {
154 location: NormalisedValue(0.5),
155 label: Some("50".to_string())
156 },
157 Tick {
158 location: NormalisedValue(0.75),
159 label: Some("25".to_string())
160 },
161 Tick {
162 location: NormalisedValue(1.0),
163 label: Some("0".to_string())
164 },
165 ]
166 );
167
168 assert_eq!(scale.normalise(50.0), NormalisedValue(0.5));
169 }
170
171 #[test]
172 fn test_precise_scale() {
173 fn float_labeller() -> impl Labeller {
174 |v| format!("{:3.2}", v)
175 }
176
177 let scale = LinearScale::with_labeller(0.0..1.0, 0.25, Some(Rc::from(float_labeller())));
178
179 assert_eq!(
180 scale.ticks(),
181 vec![
182 Tick {
183 location: NormalisedValue(0.0),
184 label: Some("0.00".to_string())
185 },
186 Tick {
187 location: NormalisedValue(0.25),
188 label: Some("0.25".to_string())
189 },
190 Tick {
191 location: NormalisedValue(0.5),
192 label: Some("0.50".to_string())
193 },
194 Tick {
195 location: NormalisedValue(0.75),
196 label: Some("0.75".to_string())
197 },
198 Tick {
199 location: NormalisedValue(1.0),
200 label: Some("1.00".to_string())
201 }
202 ]
203 );
204
205 assert_eq!(scale.normalise(0.5), NormalisedValue(0.5));
206 }
207
208 #[test]
209 fn test_zero_range() {
210 let scale = LinearScale::new(1.0..1.0, 0.25);
211
212 assert_eq!(
213 scale.ticks(),
214 vec![Tick {
215 location: NormalisedValue(0.0),
216 label: Some("1".to_string())
217 },]
218 );
219
220 assert_eq!(scale.normalise(1.0), NormalisedValue(0.0));
221 }
222
223 #[test]
224 fn test_zero_duration() {
225 let scale = LinearScale::new(1.0..1.0, 0.0);
226
227 assert_eq!(
228 scale.ticks(),
229 vec![Tick {
230 location: NormalisedValue(0.0),
231 label: Some("1".to_string())
232 },]
233 );
234
235 assert_eq!(scale.normalise(1.0), NormalisedValue(0.0));
236 }
237}