1
/*!
2

3
A module for managing axes
4

5
*/
6

7
#[derive(Debug, Clone)]
8
pub struct Range {
9
    pub lower: f64,
10
    pub upper: f64,
11
}
12

13
impl Range {
14 1
    pub fn new(lower: f64, upper: f64) -> Range {
15
        Range { lower, upper }
16
    }
17

18 1
    pub(crate) fn is_valid(&self) -> bool {
19 1
        self.lower < self.upper
20
    }
21
}
22

23
#[derive(Debug)]
24
pub struct ContinuousAxis {
25
    range: Range,
26
    ticks: Vec<f64>,
27
    label: String,
28
}
29

30
impl ContinuousAxis {
31
    /// Constructs a new ContinuousAxis
32 1
    pub fn new(lower: f64, upper: f64, max_ticks: usize) -> ContinuousAxis {
33
        ContinuousAxis {
34 1
            range: Range::new(lower, upper),
35 1
            ticks: calculate_ticks(lower, upper, max_ticks),
36 1
            label: "".into(),
37
        }
38
    }
39

40 1
    pub fn max(&self) -> f64 {
41 1
        self.range.upper
42
    }
43

44 1
    pub fn min(&self) -> f64 {
45 1
        self.range.lower
46
    }
47

48 1
    pub fn label<S>(mut self, l: S) -> Self
49
    where
50
        S: Into<String>,
51
    {
52 1
        self.label = l.into();
53 1
        self
54
    }
55

56 1
    pub fn get_label(&self) -> &str {
57 1
        self.label.as_ref()
58
    }
59

60
    /// Get the positions of the ticks on the axis
61 1
    pub fn ticks(&self) -> &Vec<f64> {
62 1
        &self.ticks
63
    }
64
}
65

66
#[derive(Debug)]
67
pub struct CategoricalAxis {
68
    ticks: Vec<String>,
69
    label: String,
70
}
71

72
impl CategoricalAxis {
73
    /// Constructs a new ContinuousAxis
74 0
    pub fn new(ticks: &[String]) -> CategoricalAxis {
75
        CategoricalAxis {
76 0
            ticks: ticks.into(),
77 0
            label: "".into(),
78
        }
79
    }
80

81 0
    pub fn label<S>(mut self, l: S) -> Self
82
    where
83
        S: Into<String>,
84
    {
85 0
        self.label = l.into();
86 0
        self
87
    }
88

89 0
    pub fn get_label(&self) -> &str {
90 0
        self.label.as_ref()
91
    }
92

93
    /// Get the positions of the ticks on the axis
94 0
    pub fn ticks(&self) -> &Vec<String> {
95 0
        &self.ticks
96
    }
97
}
98

99
/// The base units for the step sizes
100
/// They should be within one order of magnitude, e.g. [1,10)
101
const BASE_STEPS: [u32; 4] = [1, 2, 4, 5];
102

103
#[derive(Debug, Clone)]
104
struct TickSteps {
105
    next: f64,
106
}
107

108
impl TickSteps {
109 1
    fn start_at(start: f64) -> TickSteps {
110 1
        let start_options = TickSteps::scaled_steps(start);
111 1
        let overflow = start_options[0] * 10.0;
112 1
        let curr = start_options.iter().find(|&step| step >= &start);
113

114
        TickSteps {
115 1
            next: *curr.unwrap_or(&overflow),
116
        }
117
    }
118

119 1
    fn scaled_steps(curr: f64) -> Vec<f64> {
120 1
        let power = curr.log10().floor();
121 1
        let base_step_scale = 10f64.powf(power);
122 1
        BASE_STEPS
123
            .iter()
124 1
            .map(|&s| (f64::from(s) * base_step_scale))
125
            .collect()
126
    }
127
}
128

129
impl Iterator for TickSteps {
130
    type Item = f64;
131

132 1
    fn next(&mut self) -> Option<f64> {
133 1
        let curr = self.next; // cache the value we're currently on
134 1
        let curr_steps = TickSteps::scaled_steps(self.next);
135 1
        let overflow = curr_steps[0] * 10.0;
136 1
        self.next = *curr_steps.iter().find(|&s| s > &curr).unwrap_or(&overflow);
137 1
        Some(curr)
138
    }
139
}
140

141 1
fn generate_ticks(min: f64, max: f64, step_size: f64) -> Vec<f64> {
142
    // "fix" just makes sure there are no floating-point errors
143 1
    fn fix(x: f64) -> f64 {
144
        const PRECISION: f64 = 100_000_f64;
145 1
        (x * PRECISION).round() / PRECISION
146
    }
147

148 1
    let mut ticks: Vec<f64> = vec![];
149 1
    if min <= 0.0 {
150 1
        if max >= 0.0 {
151
            // standard spanning axis
152 1
            ticks.extend(
153 1
                (1..)
154 1
                    .map(|n| -1.0 * fix(f64::from(n) * step_size))
155 1
                    .take_while(|&v| v >= min)
156
                    .collect::<Vec<f64>>()
157
                    .iter()
158
                    .rev(),
159
            );
160 1
            ticks.push(0.0);
161 1
            ticks.extend(
162 1
                (1..)
163 1
                    .map(|n| fix(f64::from(n) * step_size))
164 1
                    .take_while(|&v| v <= max),
165
            );
166
        } else {
167
            // entirely negative axis
168 1
            ticks.extend(
169 1
                (0..)
170 1
                    .map(|n| -1.0 * fix((f64::from(n) * step_size) - max))
171 1
                    .take_while(|&v| v >= min)
172
                    .collect::<Vec<f64>>()
173
                    .iter()
174
                    .rev(),
175
            );
176
        }
177
    } else {
178
        // entirely positive axis
179 1
        ticks.extend(
180 1
            (0..)
181 1
                .map(|n| fix((f64::from(n) * step_size) + min))
182 1
                .take_while(|&v| v <= max),
183
        );
184
    }
185
    ticks
186
}
187

188
/// Given a range and a step size, work out how many ticks will be displayed
189 1
fn number_of_ticks(min: f64, max: f64, step_size: f64) -> usize {
190 1
    generate_ticks(min, max, step_size).len()
191
}
192

193
/// Given a range of values, and a maximum number of ticks, calulate the step between the ticks
194 1
fn calculate_tick_step_for_range(min: f64, max: f64, max_ticks: usize) -> f64 {
195 1
    let range = max - min;
196 1
    let min_tick_step = range / max_ticks as f64;
197
    // Get the first entry which is our smallest possible tick step size
198 1
    let smallest_valid_step = TickSteps::start_at(min_tick_step)
199 1
        .find(|&s| number_of_ticks(min, max, s) <= max_ticks)
200
        .expect("ERROR: We've somehow run out of tick step options!");
201
    // Count how many ticks that relates to
202 1
    let actual_num_ticks = number_of_ticks(min, max, smallest_valid_step);
203

204
    // Create a new TickStep iterator, starting at the correct lower bound
205 1
    let tick_steps = TickSteps::start_at(smallest_valid_step);
206
    // Get all the possible tick step sizes that give just as many ticks
207 1
    let step_options = tick_steps.take_while(|&s| number_of_ticks(min, max, s) == actual_num_ticks);
208
    // Get the largest tick step size from the list
209 1
    step_options.fold(-1. / 0., f64::max)
210
}
211

212
/// Given an axis range, calculate the sensible places to place the ticks
213 1
fn calculate_ticks(min: f64, max: f64, max_ticks: usize) -> Vec<f64> {
214 1
    let tick_step = calculate_tick_step_for_range(min, max, max_ticks);
215 1
    generate_ticks(min, max, tick_step)
216
}
217

218
#[cfg(test)]
219
mod tests {
220
    use super::*;
221

222
    #[test]
223 1
    fn test_tick_step_generator() {
224 1
        let t = TickSteps::start_at(1.0);
225 1
        let ts: Vec<_> = t.take(7).collect();
226 1
        assert_eq!(ts, [1.0, 2.0, 4.0, 5.0, 10.0, 20.0, 40.0]);
227

228 1
        let t = TickSteps::start_at(100.0);
229 1
        let ts: Vec<_> = t.take(5).collect();
230 1
        assert_eq!(ts, [100.0, 200.0, 400.0, 500.0, 1000.0]);
231

232 1
        let t = TickSteps::start_at(3.0);
233 1
        let ts: Vec<_> = t.take(5).collect();
234 1
        assert_eq!(ts, [4.0, 5.0, 10.0, 20.0, 40.0]);
235

236 1
        let t = TickSteps::start_at(8.0);
237 1
        let ts: Vec<_> = t.take(3).collect();
238 1
        assert_eq!(ts, [10.0, 20.0, 40.0]);
239
    }
240

241
    #[test]
242 1
    fn test_number_of_ticks() {
243 1
        assert_eq!(number_of_ticks(-7.93, 15.58, 4.0), 5);
244 1
        assert_eq!(number_of_ticks(-7.93, 15.58, 5.0), 5);
245 1
        assert_eq!(number_of_ticks(0.0, 15.0, 4.0), 4);
246 1
        assert_eq!(number_of_ticks(0.0, 15.0, 5.0), 4);
247 1
        assert_eq!(number_of_ticks(5.0, 21.0, 4.0), 5);
248 1
        assert_eq!(number_of_ticks(5.0, 21.0, 5.0), 4);
249 1
        assert_eq!(number_of_ticks(-8.0, 15.58, 4.0), 6);
250 1
        assert_eq!(number_of_ticks(-8.0, 15.58, 5.0), 5);
251
    }
252

253
    #[test]
254 1
    fn test_calculate_tick_step_for_range() {
255 1
        assert_eq!(calculate_tick_step_for_range(0.0, 3.0, 6), 1.0);
256 1
        assert_eq!(calculate_tick_step_for_range(0.0, 6.0, 6), 2.0);
257 1
        assert_eq!(calculate_tick_step_for_range(0.0, 11.0, 6), 2.0);
258 1
        assert_eq!(calculate_tick_step_for_range(0.0, 14.0, 6), 4.0);
259 1
        assert_eq!(calculate_tick_step_for_range(0.0, 15.0, 6), 5.0);
260 1
        assert_eq!(calculate_tick_step_for_range(-1.0, 5.0, 6), 2.0);
261 1
        assert_eq!(calculate_tick_step_for_range(-7.93, 15.58, 6), 5.0);
262 1
        assert_eq!(calculate_tick_step_for_range(0.0, 0.06, 6), 0.02);
263
    }
264

265
    #[test]
266 1
    fn test_calculate_ticks() {
267
        macro_rules! assert_approx_eq {
268
            ($a:expr, $b:expr) => {{
269
                let (a, b) = (&$a, &$b);
270
                assert!(
271
                    (*a - *b).abs() < 1.0e-6,
272
                    "{} is not approximately equal to {}",
273
                    *a,
274
                    *b
275
                );
276
            }};
277
        }
278

279 1
        for (prod, want) in calculate_ticks(0.0, 1.0, 6)
280
            .iter()
281 1
            .zip([0.0, 0.2, 0.4, 0.6, 0.8, 1.0].iter())
282
        {
283 1
            assert_approx_eq!(prod, want);
284
        }
285 1
        for (prod, want) in calculate_ticks(0.0, 2.0, 6)
286
            .iter()
287 1
            .zip([0.0, 0.4, 0.8, 1.2, 1.6, 2.0].iter())
288
        {
289 1
            assert_approx_eq!(prod, want);
290
        }
291 1
        assert_eq!(calculate_ticks(0.0, 3.0, 6), [0.0, 1.0, 2.0, 3.0]);
292 1
        assert_eq!(calculate_ticks(0.0, 4.0, 6), [0.0, 1.0, 2.0, 3.0, 4.0]);
293 1
        assert_eq!(calculate_ticks(0.0, 5.0, 6), [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]);
294 1
        assert_eq!(calculate_ticks(0.0, 6.0, 6), [0.0, 2.0, 4.0, 6.0]);
295 1
        assert_eq!(calculate_ticks(0.0, 7.0, 6), [0.0, 2.0, 4.0, 6.0]);
296 1
        assert_eq!(calculate_ticks(0.0, 8.0, 6), [0.0, 2.0, 4.0, 6.0, 8.0]);
297 1
        assert_eq!(calculate_ticks(0.0, 9.0, 6), [0.0, 2.0, 4.0, 6.0, 8.0]);
298 1
        assert_eq!(
299 1
            calculate_ticks(0.0, 10.0, 6),
300
            [0.0, 2.0, 4.0, 6.0, 8.0, 10.0]
301
        );
302 1
        assert_eq!(
303 1
            calculate_ticks(0.0, 11.0, 6),
304
            [0.0, 2.0, 4.0, 6.0, 8.0, 10.0]
305
        );
306 1
        assert_eq!(calculate_ticks(0.0, 12.0, 6), [0.0, 4.0, 8.0, 12.0]);
307 1
        assert_eq!(calculate_ticks(0.0, 13.0, 6), [0.0, 4.0, 8.0, 12.0]);
308 1
        assert_eq!(calculate_ticks(0.0, 14.0, 6), [0.0, 4.0, 8.0, 12.0]);
309 1
        assert_eq!(calculate_ticks(0.0, 15.0, 6), [0.0, 5.0, 10.0, 15.0]);
310 1
        assert_eq!(calculate_ticks(0.0, 16.0, 6), [0.0, 4.0, 8.0, 12.0, 16.0]);
311 1
        assert_eq!(calculate_ticks(0.0, 17.0, 6), [0.0, 4.0, 8.0, 12.0, 16.0]);
312 1
        assert_eq!(calculate_ticks(0.0, 18.0, 6), [0.0, 4.0, 8.0, 12.0, 16.0]);
313 1
        assert_eq!(calculate_ticks(0.0, 19.0, 6), [0.0, 4.0, 8.0, 12.0, 16.0]);
314 1
        assert_eq!(
315 1
            calculate_ticks(0.0, 20.0, 6),
316
            [0.0, 4.0, 8.0, 12.0, 16.0, 20.0]
317
        );
318 1
        assert_eq!(
319 1
            calculate_ticks(0.0, 21.0, 6),
320
            [0.0, 4.0, 8.0, 12.0, 16.0, 20.0]
321
        );
322 1
        assert_eq!(
323 1
            calculate_ticks(0.0, 22.0, 6),
324
            [0.0, 4.0, 8.0, 12.0, 16.0, 20.0]
325
        );
326 1
        assert_eq!(
327 1
            calculate_ticks(0.0, 23.0, 6),
328
            [0.0, 4.0, 8.0, 12.0, 16.0, 20.0]
329
        );
330 1
        assert_eq!(calculate_ticks(0.0, 24.0, 6), [0.0, 5.0, 10.0, 15.0, 20.0]);
331 1
        assert_eq!(
332 1
            calculate_ticks(0.0, 25.0, 6),
333
            [0.0, 5.0, 10.0, 15.0, 20.0, 25.0]
334
        );
335 1
        assert_eq!(
336 1
            calculate_ticks(0.0, 26.0, 6),
337
            [0.0, 5.0, 10.0, 15.0, 20.0, 25.0]
338
        );
339 1
        assert_eq!(
340 1
            calculate_ticks(0.0, 27.0, 6),
341
            [0.0, 5.0, 10.0, 15.0, 20.0, 25.0]
342
        );
343 1
        assert_eq!(
344 1
            calculate_ticks(0.0, 28.0, 6),
345
            [0.0, 5.0, 10.0, 15.0, 20.0, 25.0]
346
        );
347 1
        assert_eq!(
348 1
            calculate_ticks(0.0, 29.0, 6),
349
            [0.0, 5.0, 10.0, 15.0, 20.0, 25.0]
350
        );
351 1
        assert_eq!(calculate_ticks(0.0, 30.0, 6), [0.0, 10.0, 20.0, 30.0]);
352 1
        assert_eq!(calculate_ticks(0.0, 31.0, 6), [0.0, 10.0, 20.0, 30.0]);
353
        //...
354 1
        assert_eq!(calculate_ticks(0.0, 40.0, 6), [0.0, 10.0, 20.0, 30.0, 40.0]);
355 1
        assert_eq!(
356 1
            calculate_ticks(0.0, 50.0, 6),
357
            [0.0, 10.0, 20.0, 30.0, 40.0, 50.0]
358
        );
359 1
        assert_eq!(calculate_ticks(0.0, 60.0, 6), [0.0, 20.0, 40.0, 60.0]);
360 1
        assert_eq!(calculate_ticks(0.0, 70.0, 6), [0.0, 20.0, 40.0, 60.0]);
361 1
        assert_eq!(calculate_ticks(0.0, 80.0, 6), [0.0, 20.0, 40.0, 60.0, 80.0]);
362 1
        assert_eq!(calculate_ticks(0.0, 90.0, 6), [0.0, 20.0, 40.0, 60.0, 80.0]);
363 1
        assert_eq!(
364 1
            calculate_ticks(0.0, 100.0, 6),
365
            [0.0, 20.0, 40.0, 60.0, 80.0, 100.0]
366
        );
367 1
        assert_eq!(
368 1
            calculate_ticks(0.0, 110.0, 6),
369
            [0.0, 20.0, 40.0, 60.0, 80.0, 100.0]
370
        );
371 1
        assert_eq!(calculate_ticks(0.0, 120.0, 6), [0.0, 40.0, 80.0, 120.0]);
372 1
        assert_eq!(calculate_ticks(0.0, 130.0, 6), [0.0, 40.0, 80.0, 120.0]);
373 1
        assert_eq!(calculate_ticks(0.0, 140.0, 6), [0.0, 40.0, 80.0, 120.0]);
374 1
        assert_eq!(calculate_ticks(0.0, 150.0, 6), [0.0, 50.0, 100.0, 150.0]);
375
        //...
376 1
        assert_eq!(
377 1
            calculate_ticks(0.0, 3475.0, 6),
378
            [0.0, 1000.0, 2000.0, 3000.0]
379
        );
380

381 1
        assert_eq!(calculate_ticks(-11.0, -4.0, 6), [-10.0, -8.0, -6.0, -4.0]);
382

383
        // test rounding
384 1
        assert_eq!(calculate_ticks(1.0, 1.5, 6), [1.0, 1.1, 1.2, 1.3, 1.4, 1.5]);
385 1
        assert_eq!(calculate_ticks(0.0, 1.0, 6), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]);
386 1
        assert_eq!(calculate_ticks(0.0, 0.3, 4), [0.0, 0.1, 0.2, 0.3]);
387
    }
388
}

Read our documentation on viewing source code .

Loading