1use num_traits::{
16 cast::FromPrimitive,
17 float::{Float, FloatConst},
18};
19
20pub fn ticks<T: Float + FloatConst + FromPrimitive>(start: T, stop: T, count: usize) -> Vec<T> {
21 if start == stop && count > 0 {
22 return vec![start];
23 }
24
25 let reverse = stop < start;
26 let (start, stop) = if reverse {
27 (stop, start)
28 } else {
29 (start, stop)
30 };
31
32 let step = tick_increment(start, stop, count);
33 if step.is_zero() || !step.is_finite() {
34 return vec![];
35 }
36
37 let mut ticks = if step.is_sign_positive() {
38 let start: T = (start / step).ceil();
39 let stop: T = (stop / step).floor();
40 let n = (stop - start + T::from_f64(1.0).unwrap())
41 .ceil()
42 .to_usize()
43 .unwrap();
44 let mut ticks = vec![T::from_f64(0.0).unwrap(); n];
45 for i in 0..n {
46 ticks[i] = (start + T::from_usize(i).unwrap()) * step;
47 }
48 ticks
49 } else {
50 let step = step * T::from_f64(-1.0).unwrap();
51 let start = (start * step).floor();
52 let stop = (stop * step).ceil();
53 let n = (stop - start + T::from_f64(1.0).unwrap())
54 .ceil()
55 .to_usize()
56 .unwrap();
57 let mut ticks = vec![T::from_f64(0.0).unwrap(); n];
58 for i in 0..n {
59 ticks[i] = (start + T::from_usize(i).unwrap()) / step;
60 }
61 ticks
62 };
63
64 if reverse {
65 ticks.reverse()
66 }
67
68 ticks
69}
70
71fn tick_increment<T: Float + FloatConst + FromPrimitive>(start: T, stop: T, count: usize) -> T {
72 let step = (stop - start) / T::from_usize(count).unwrap();
73 let power = (step.ln() / T::LN_10()).floor();
74 let error = step / T::from_f64(10.0).unwrap().powf(power);
75
76 let v = if error >= T::from_f64(50.0).unwrap().sqrt() {
77 T::from_f64(10.0).unwrap()
78 } else if error >= T::from_f64(10.0).unwrap().sqrt() {
79 T::from_f64(5.0).unwrap()
80 } else if error >= T::from_f64(2.0).unwrap().sqrt() {
81 T::from_f64(2.0).unwrap()
82 } else {
83 T::from_f64(1.0).unwrap()
84 };
85
86 if power >= T::from_f64(0.0).unwrap() {
87 v * T::from_f64(10.0).unwrap().powf(power)
88 } else {
89 (T::from_f64(-1.0).unwrap()
90 * T::from_f64(10.0)
91 .unwrap()
92 .powf(power * T::from_f64(-1.0).unwrap()))
93 / v
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn returns_empty_vec_if_any_argument_is_nan() {
103 assert_eq!(ticks(f32::NAN, 1.0, 1), []);
104 assert_eq!(ticks(0.0, f32::NAN, 1), []);
105 assert_eq!(ticks(f32::NAN, f32::NAN, 1), []);
106 }
107
108 #[test]
109 fn returns_the_empty_vec_if_start_equal_stop() {
110 assert_eq!(ticks(1.0, 1.0, 0), []);
111 }
112
113 #[test]
114 fn returns_the_empty_vec_if_count_is_not_positive() {
115 assert_eq!(ticks(0.0, 1.0, 0), []);
116 }
117
118 #[test]
119 fn returns_approximately_count_plus_1_ticks_when_start_less_than_stop() {
120 assert_eq!(
121 ticks(0.0f32, 1.0f32, 10),
122 [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
123 );
124 assert_eq!(
125 ticks(0.0f64, 1.0f64, 10),
126 [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
127 );
128 assert_eq!(
129 ticks(0.0, 1.0, 8),
130 [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
131 );
132 assert_eq!(ticks(0.0, 1.0, 7), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]);
133 assert_eq!(ticks(0.0, 1.0, 6), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]);
134 assert_eq!(ticks(0.0, 1.0, 5), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]);
135 assert_eq!(ticks(0.0, 1.0, 4), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]);
136 assert_eq!(ticks(0.0, 1.0, 3), [0.0, 0.5, 1.0]);
137 assert_eq!(ticks(0.0, 1.0, 2), [0.0, 0.5, 1.0]);
138 assert_eq!(ticks(0.0, 1.0, 1), [0.0, 1.0]);
139 assert_eq!(
140 ticks(0.0, 10.0, 10),
141 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
142 );
143 assert_eq!(
144 ticks(0.0, 10.0, 9),
145 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
146 );
147 assert_eq!(
148 ticks(0.0, 10.0, 8),
149 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
150 );
151 assert_eq!(ticks(0.0, 10.0, 7), [0.0, 2.0, 4.0, 6.0, 8.0, 10.0]);
152 assert_eq!(ticks(0.0, 10.0, 6), [0.0, 2.0, 4.0, 6.0, 8.0, 10.0]);
153 assert_eq!(ticks(0.0, 10.0, 5), [0.0, 2.0, 4.0, 6.0, 8.0, 10.0]);
154 assert_eq!(ticks(0.0, 10.0, 4), [0.0, 2.0, 4.0, 6.0, 8.0, 10.0]);
155 assert_eq!(ticks(0.0, 10.0, 3), [0.0, 5.0, 10.0]);
156 assert_eq!(ticks(0.0, 10.0, 2), [0.0, 5.0, 10.0]);
157 assert_eq!(ticks(0.0, 10.0, 1), [0.0, 10.0]);
158 assert_eq!(
159 ticks(-10.0, 10.0, 10),
160 [-10.0, -8.0, -6.0, -4.0, -2.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0]
161 );
162 assert_eq!(
163 ticks(-10.0, 10.0, 9),
164 [-10.0, -8.0, -6.0, -4.0, -2.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0]
165 );
166 assert_eq!(
167 ticks(-10.0, 10.0, 8),
168 [-10.0, -8.0, -6.0, -4.0, -2.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0]
169 );
170 assert_eq!(
171 ticks(-10.0, 10.0, 7),
172 [-10.0, -8.0, -6.0, -4.0, -2.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0]
173 );
174 assert_eq!(ticks(-10.0, 10.0, 6), [-10.0, -5.0, 0.0, 5.0, 10.0]);
175 assert_eq!(ticks(-10.0, 10.0, 5), [-10.0, -5.0, 0.0, 5.0, 10.0]);
176 assert_eq!(ticks(-10.0, 10.0, 4), [-10.0, -5.0, 0.0, 5.0, 10.0]);
177 assert_eq!(ticks(-10.0, 10.0, 3), [-10.0, -5.0, 0.0, 5.0, 10.0]);
178 assert_eq!(ticks(-10.0, 10.0, 2), [-10.0, 0.0, 10.0]);
179 assert_eq!(ticks(-10.0, 10.0, 1), [0.0]);
180 }
181
182 #[test]
183 fn some_more_complex_tests() {
184 assert_eq!(
185 ticks(0.0, 1.0, 20),
186 [
187 0.0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7,
188 0.75, 0.8, 0.85, 0.9, 0.95, 1.0
189 ]
190 );
191
192 assert_eq!(
193 ticks(0.125, 0.25, 5),
194 [0.12, 0.14, 0.16, 0.18, 0.2, 0.22, 0.24, 0.26]
195 );
196
197 assert_eq!(
198 ticks(0.125, 0.25, 10),
199 [0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2, 0.21, 0.22, 0.23, 0.24, 0.25]
200 );
201
202 assert_eq!(
203 ticks(-0.125, 0.25, 10),
204 [-0.15, -0.1, -0.05, 0.0, 0.05, 0.1, 0.15, 0.2, 0.25]
205 );
206 }
207}