plotters/coord/ranged1d/types/
numeric.rs

1use std::convert::TryFrom;
2use std::ops::Range;
3
4use crate::coord::ranged1d::{
5    AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged,
6    ReversibleRanged, ValueFormatter,
7};
8
9macro_rules! impl_discrete_trait {
10    ($name:ident) => {
11        impl DiscreteRanged for $name {
12            fn size(&self) -> usize {
13                if &self.1 < &self.0 {
14                    return 0;
15                }
16                let values = self.1 - self.0;
17                (values + 1) as usize
18            }
19
20            fn index_of(&self, value: &Self::ValueType) -> Option<usize> {
21                if value < &self.0 {
22                    return None;
23                }
24                let ret = value - self.0;
25                Some(ret as usize)
26            }
27
28            fn from_index(&self, index: usize) -> Option<Self::ValueType> {
29                if let Ok(index) = Self::ValueType::try_from(index) {
30                    return Some(self.0 + index);
31                }
32                None
33            }
34        }
35    };
36}
37
38macro_rules! impl_ranged_type_trait {
39    ($value:ty, $coord:ident) => {
40        impl AsRangedCoord for Range<$value> {
41            type CoordDescType = $coord;
42            type Value = $value;
43        }
44    };
45}
46macro_rules! impl_reverse_mapping_trait {
47    ($type:ty, $name: ident) => {
48        impl ReversibleRanged for $name {
49            fn unmap(&self, p: i32, (min, max): (i32, i32)) -> Option<$type> {
50                if p < min.min(max) || p > max.max(min) || min == max {
51                    return None;
52                }
53
54                let logical_offset = f64::from(p - min) / f64::from(max - min);
55
56                return Some(((self.1 - self.0) as f64 * logical_offset + self.0 as f64) as $type);
57            }
58        }
59    };
60}
61macro_rules! make_numeric_coord {
62    ($type:ty, $name:ident, $key_points:ident, $doc: expr, $fmt: ident) => {
63        #[doc = $doc]
64        #[derive(Clone)]
65        pub struct $name($type, $type);
66        impl From<Range<$type>> for $name {
67            fn from(range: Range<$type>) -> Self {
68                return $name(range.start, range.end);
69            }
70        }
71        impl Ranged for $name {
72            type FormatOption = $fmt;
73            type ValueType = $type;
74            #[allow(clippy::float_cmp)]
75            fn map(&self, v: &$type, limit: (i32, i32)) -> i32 {
76                // Corner case: If we have a range that have only one value,
77                // then we just assign everything to the only point
78                if self.1 == self.0 {
79                    return (limit.1 - limit.0) / 2;
80                }
81
82                let logic_length = (*v as f64 - self.0 as f64) / (self.1 as f64 - self.0 as f64);
83
84                let actual_length = limit.1 - limit.0;
85
86                if actual_length == 0 {
87                    return limit.1;
88                }
89
90                if logic_length.is_infinite() {
91                    if logic_length.is_sign_positive() {
92                        return limit.1;
93                    } else {
94                        return limit.0;
95                    }
96                }
97
98                if actual_length > 0 {
99                    return limit.0 + (actual_length as f64 * logic_length + 1e-3).floor() as i32;
100                } else {
101                    return limit.0 + (actual_length as f64 * logic_length - 1e-3).ceil() as i32;
102                }
103            }
104            fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<$type> {
105                $key_points((self.0, self.1), hint.max_num_points())
106            }
107            fn range(&self) -> Range<$type> {
108                return self.0..self.1;
109            }
110        }
111    };
112    ($type:ty, $name:ident, $key_points:ident, $doc: expr) => {
113        make_numeric_coord!($type, $name, $key_points, $doc, DefaultFormatting);
114    };
115}
116
117macro_rules! gen_key_points_comp {
118    (float, $name:ident, $type:ty) => {
119        fn $name(range: ($type, $type), max_points: usize) -> Vec<$type> {
120            if max_points == 0 {
121                return vec![];
122            }
123
124            let range = (range.0.min(range.1) as f64, range.1.max(range.0) as f64);
125
126            assert!(!(range.0.is_nan() || range.1.is_nan()));
127
128            if (range.0 - range.1).abs() < f64::EPSILON {
129                return vec![range.0 as $type];
130            }
131
132            let mut scale = (10f64).powf((range.1 - range.0).log(10.0).floor());
133            // The value granularity controls how we round the values.
134            // To avoid generating key points like 1.00000000001, we round to the nearest multiple of the
135            // value granularity.
136            // By default, we make the granularity as the 1/10 of the scale.
137            let mut value_granularity = scale / 10.0;
138            fn rem_euclid(a: f64, b: f64) -> f64 {
139                let ret = if b > 0.0 {
140                    a - (a / b).floor() * b
141                } else {
142                    a - (a / b).ceil() * b
143                };
144                if (ret - b).abs() < f64::EPSILON {
145                    0.0
146                } else {
147                    ret
148                }
149            }
150
151            // At this point we need to make sure that the loop invariant:
152            // The scale must yield number of points than requested
153            if 1 + ((range.1 - range.0) / scale).floor() as usize > max_points {
154                scale *= 10.0;
155                value_granularity *= 10.0;
156            }
157
158            'outer: loop {
159                let old_scale = scale;
160                for nxt in [2.0, 5.0, 10.0].iter() {
161                    let mut new_left = range.0 - rem_euclid(range.0, old_scale / nxt);
162                    if new_left < range.0 {
163                        new_left += old_scale / nxt;
164                    }
165                    let new_right = range.1 - rem_euclid(range.1, old_scale / nxt);
166
167                    let npoints = 1.0 + ((new_right - new_left) / old_scale * nxt);
168
169                    if npoints.round() as usize > max_points {
170                        break 'outer;
171                    }
172
173                    scale = old_scale / nxt;
174                }
175                scale = old_scale / 10.0;
176                value_granularity /= 10.0;
177            }
178
179            let mut ret = vec![];
180            // In some extreme cases, left might be too big, so that (left + scale) - left == 0 due to
181            // floating point error.
182            // In this case, we may loop forever. To avoid this, we need to use two variables to store
183            // the current left value. So we need keep a left_base and a left_relative.
184            let left = {
185                let mut value = range.0 - rem_euclid(range.0, scale);
186                if value < range.0 {
187                    value += scale;
188                }
189                value
190            };
191            let left_base = (left / value_granularity).floor() * value_granularity;
192            let mut left_relative = left - left_base;
193            let right = range.1 - rem_euclid(range.1, scale);
194            while (right - left_relative - left_base) >= -f64::EPSILON {
195                let new_left_relative =
196                    (left_relative / value_granularity).round() * value_granularity;
197                if new_left_relative < 0.0 {
198                    left_relative += value_granularity;
199                }
200                ret.push((left_relative + left_base) as $type);
201                left_relative += scale;
202            }
203            return ret;
204        }
205    };
206    (integer, $name:ident, $type:ty) => {
207        fn $name(range: ($type, $type), max_points: usize) -> Vec<$type> {
208            let mut scale: $type = 1;
209            let range = (range.0.min(range.1), range.0.max(range.1));
210            let range_size = range.1 as f64 - range.0 as f64;
211            'outer: while (range_size / scale as f64).ceil() > max_points as f64 {
212                let next_scale = scale * 10;
213                for new_scale in [scale * 2, scale * 5, scale * 10].iter() {
214                    scale = *new_scale;
215                    if (range_size / *new_scale as f64).ceil() < max_points as f64 {
216                        break 'outer;
217                    }
218                }
219                scale = next_scale;
220            }
221
222            let (mut left, right) = (
223                range.0 + (scale - range.0 % scale) % scale,
224                range.1 - range.1 % scale,
225            );
226
227            let mut ret = vec![];
228            while left <= right {
229                ret.push(left as $type);
230                if left < right {
231                    left += scale;
232                } else {
233                    break;
234                }
235            }
236
237            return ret;
238        }
239    };
240}
241
242gen_key_points_comp!(float, compute_f32_key_points, f32);
243gen_key_points_comp!(float, compute_f64_key_points, f64);
244gen_key_points_comp!(integer, compute_i32_key_points, i32);
245gen_key_points_comp!(integer, compute_u32_key_points, u32);
246gen_key_points_comp!(integer, compute_i64_key_points, i64);
247gen_key_points_comp!(integer, compute_u64_key_points, u64);
248gen_key_points_comp!(integer, compute_i128_key_points, i128);
249gen_key_points_comp!(integer, compute_u128_key_points, u128);
250gen_key_points_comp!(integer, compute_isize_key_points, isize);
251gen_key_points_comp!(integer, compute_usize_key_points, usize);
252
253make_numeric_coord!(
254    f32,
255    RangedCoordf32,
256    compute_f32_key_points,
257    "The ranged coordinate for type f32",
258    NoDefaultFormatting
259);
260impl_reverse_mapping_trait!(f32, RangedCoordf32);
261impl ValueFormatter<f32> for RangedCoordf32 {
262    fn format(value: &f32) -> String {
263        crate::data::float::FloatPrettyPrinter {
264            allow_scientific: false,
265            min_decimal: 1,
266            max_decimal: 5,
267        }
268        .print(*value as f64)
269    }
270}
271make_numeric_coord!(
272    f64,
273    RangedCoordf64,
274    compute_f64_key_points,
275    "The ranged coordinate for type f64",
276    NoDefaultFormatting
277);
278impl_reverse_mapping_trait!(f64, RangedCoordf64);
279impl ValueFormatter<f64> for RangedCoordf64 {
280    fn format(value: &f64) -> String {
281        crate::data::float::FloatPrettyPrinter {
282            allow_scientific: false,
283            min_decimal: 1,
284            max_decimal: 5,
285        }
286        .print(*value)
287    }
288}
289make_numeric_coord!(
290    u32,
291    RangedCoordu32,
292    compute_u32_key_points,
293    "The ranged coordinate for type u32"
294);
295make_numeric_coord!(
296    i32,
297    RangedCoordi32,
298    compute_i32_key_points,
299    "The ranged coordinate for type i32"
300);
301make_numeric_coord!(
302    u64,
303    RangedCoordu64,
304    compute_u64_key_points,
305    "The ranged coordinate for type u64"
306);
307make_numeric_coord!(
308    i64,
309    RangedCoordi64,
310    compute_i64_key_points,
311    "The ranged coordinate for type i64"
312);
313make_numeric_coord!(
314    u128,
315    RangedCoordu128,
316    compute_u128_key_points,
317    "The ranged coordinate for type u128"
318);
319make_numeric_coord!(
320    i128,
321    RangedCoordi128,
322    compute_i128_key_points,
323    "The ranged coordinate for type i128"
324);
325make_numeric_coord!(
326    usize,
327    RangedCoordusize,
328    compute_usize_key_points,
329    "The ranged coordinate for type usize"
330);
331make_numeric_coord!(
332    isize,
333    RangedCoordisize,
334    compute_isize_key_points,
335    "The ranged coordinate for type isize"
336);
337
338impl_discrete_trait!(RangedCoordu32);
339impl_discrete_trait!(RangedCoordi32);
340impl_discrete_trait!(RangedCoordu64);
341impl_discrete_trait!(RangedCoordi64);
342impl_discrete_trait!(RangedCoordu128);
343impl_discrete_trait!(RangedCoordi128);
344impl_discrete_trait!(RangedCoordusize);
345impl_discrete_trait!(RangedCoordisize);
346
347impl_ranged_type_trait!(f32, RangedCoordf32);
348impl_ranged_type_trait!(f64, RangedCoordf64);
349impl_ranged_type_trait!(i32, RangedCoordi32);
350impl_ranged_type_trait!(u32, RangedCoordu32);
351impl_ranged_type_trait!(i64, RangedCoordi64);
352impl_ranged_type_trait!(u64, RangedCoordu64);
353impl_ranged_type_trait!(i128, RangedCoordi128);
354impl_ranged_type_trait!(u128, RangedCoordu128);
355impl_ranged_type_trait!(isize, RangedCoordisize);
356impl_ranged_type_trait!(usize, RangedCoordusize);
357
358#[cfg(test)]
359mod test {
360    use super::*;
361    #[test]
362    fn test_key_points() {
363        let kp = compute_i32_key_points((0, 999), 28);
364
365        assert!(!kp.is_empty());
366        assert!(kp.len() <= 28);
367
368        let kp = compute_f64_key_points((-1.2, 1.2), 1);
369        assert!(kp.len() == 1);
370
371        let kp = compute_f64_key_points((-1.2, 1.2), 0);
372        assert!(kp.is_empty());
373    }
374
375    #[test]
376    fn test_linear_coord_map() {
377        let coord: RangedCoordu32 = (0..20).into();
378        assert_eq!(coord.key_points(11).len(), 11);
379        assert_eq!(coord.key_points(11)[0], 0);
380        assert_eq!(coord.key_points(11)[10], 20);
381        assert_eq!(coord.map(&5, (0, 100)), 25);
382
383        let coord: RangedCoordf32 = (0f32..20f32).into();
384        assert_eq!(coord.map(&5.0, (0, 100)), 25);
385    }
386
387    #[test]
388    fn test_linear_coord_system() {
389        let _coord =
390            crate::coord::ranged2d::cartesian::Cartesian2d::<RangedCoordu32, RangedCoordu32>::new(
391                0..10,
392                0..10,
393                (0..1024, 0..768),
394            );
395    }
396
397    #[test]
398    fn test_coord_unmap() {
399        let coord: RangedCoordu32 = (0..20).into();
400        let pos = coord.map(&5, (1000, 2000));
401        let value = coord.unmap(pos, (1000, 2000));
402        assert_eq!(value, Some(5));
403    }
404
405    #[test]
406    fn regression_test_issue_253_zero_sized_coord_not_hang() {
407        let coord: RangedCoordf32 = (0.0..0.0).into();
408        let _points = coord.key_points(10);
409    }
410
411    #[test]
412    fn test_small_coord() {
413        let coord: RangedCoordf64 = (0.0..1e-25).into();
414        let points = coord.key_points(10);
415        assert!(!points.is_empty());
416    }
417
418    #[test]
419    fn regression_test_issue_255_reverse_f32_coord_no_hang() {
420        let coord: RangedCoordf32 = (10.0..0.0).into();
421        let _points = coord.key_points(10);
422    }
423
424    #[test]
425    fn regression_test_issue_358_key_points_no_hang() {
426        let coord: RangedCoordf64 = (-200.0..801.0).into();
427        let points = coord.key_points(500);
428        assert!(points.len() <= 500);
429    }
430
431    #[test]
432    fn regression_test_issue_358_key_points_no_hang_2() {
433        let coord: RangedCoordf64 = (10000000000001f64..10000000000002f64).into();
434        let points = coord.key_points(500);
435        assert!(points.len() <= 500);
436    }
437
438    #[test]
439    fn test_coord_follows_hint() {
440        let coord: RangedCoordf64 = (1.0..2.0).into();
441        let points = coord.key_points(6);
442        assert_eq!(points.len(), 6);
443        assert_eq!(points[0], 1.0);
444        let coord: RangedCoordf64 = (1.0..125.0).into();
445        let points = coord.key_points(12);
446        assert_eq!(points.len(), 12);
447        let coord: RangedCoordf64 = (0.9995..1.0005).into();
448        let points = coord.key_points(11);
449        assert_eq!(points.len(), 11);
450        let coord: RangedCoordf64 = (0.9995..1.0005).into();
451        let points = coord.key_points(2);
452        assert!(points.len() <= 2);
453    }
454
455    #[test]
456    fn regression_test_issue_304_intmax_keypoint_no_panic() {
457        let coord: RangedCoordu32 = (0..u32::MAX).into();
458        let p = coord.key_points(10);
459        assert!(!p.is_empty() && p.len() <= 10);
460    }
461}