plotters_unsable/coord/
logarithmic.rs

1use super::{AsRangedCoord, Ranged, RangedCoordf64};
2use std::marker::PhantomData;
3use std::ops::Range;
4
5/// The trait for the type that is able to be presented in the log scale
6pub trait LogScalable: Clone {
7    /// Make the conversion from the type to the floating point number
8    fn as_f64(&self) -> f64;
9    /// Convert a floating point number to the scale
10    fn from_f64(f: f64) -> Self;
11}
12
13macro_rules! impl_log_scalable {
14    (i, $t:ty) => {
15        impl LogScalable for $t {
16            fn as_f64(&self) -> f64 {
17                if *self != 0 {
18                    return *self as f64;
19                }
20                // If this is an integer, we should allow zero point to be shown
21                // on the chart, thus we can't map the zero point to inf.
22                // So we just assigning a value smaller than 1 as the alternative
23                // of the zero point.
24                return 0.5;
25            }
26            fn from_f64(f: f64) -> $t {
27                f.round() as $t
28            }
29        }
30    };
31    (f, $t:ty) => {
32        impl LogScalable for $t {
33            fn as_f64(&self) -> f64 {
34                *self as f64
35            }
36            fn from_f64(f: f64) -> $t {
37                f as $t
38            }
39        }
40    };
41}
42
43impl_log_scalable!(i, u8);
44impl_log_scalable!(i, u16);
45impl_log_scalable!(i, u32);
46impl_log_scalable!(i, u64);
47impl_log_scalable!(f, f32);
48impl_log_scalable!(f, f64);
49
50/// The wrapper type for a range of a log-scaled value
51pub struct LogRange<V: LogScalable>(pub Range<V>);
52
53impl<V: LogScalable> From<LogRange<V>> for LogCoord<V> {
54    fn from(range: LogRange<V>) -> LogCoord<V> {
55        LogCoord {
56            linear: (range.0.start.as_f64().ln()..range.0.end.as_f64().ln()).into(),
57            logic: range.0,
58            marker: PhantomData,
59        }
60    }
61}
62
63impl<V: LogScalable> AsRangedCoord for LogRange<V> {
64    type CoordDescType = LogCoord<V>;
65    type Value = V;
66}
67
68/// A log scaled coordinate axis
69pub struct LogCoord<V: LogScalable> {
70    linear: RangedCoordf64,
71    logic: Range<V>,
72    marker: PhantomData<V>,
73}
74
75impl<V: LogScalable> Ranged for LogCoord<V> {
76    type ValueType = V;
77
78    fn map(&self, value: &V, limit: (i32, i32)) -> i32 {
79        let value = value.as_f64();
80        let value = value.max(self.logic.start.as_f64()).ln();
81        self.linear.map(&value, limit)
82    }
83
84    fn key_points(&self, max_points: usize) -> Vec<Self::ValueType> {
85        let tier_1 = (self.logic.end.as_f64() / self.logic.start.as_f64())
86            .log10()
87            .abs()
88            .floor() as usize;
89        let tier_2_density = if max_points < tier_1 {
90            0
91        } else {
92            let density = 1 + (max_points - tier_1) / tier_1;
93            let mut exp = 1;
94            while exp * 10 <= density {
95                exp *= 10;
96            }
97            exp - 1
98        };
99
100        let mut multiplier = 10.0;
101        let mut cnt = 1;
102        while max_points < tier_1 / cnt {
103            multiplier *= 10.0;
104            cnt += 1;
105        }
106
107        let mut ret = vec![];
108        let mut val = (10f64).powf(self.logic.start.as_f64().log10().ceil());
109
110        while val <= self.logic.end.as_f64() {
111            ret.push(V::from_f64(val));
112            for i in 1..=tier_2_density {
113                let v = val
114                    * (1.0
115                        + multiplier / f64::from(tier_2_density as u32 + 1) * f64::from(i as u32));
116                if v > self.logic.end.as_f64() {
117                    break;
118                }
119                ret.push(V::from_f64(v));
120            }
121            val *= multiplier;
122        }
123
124        ret
125    }
126
127    fn range(&self) -> Range<V> {
128        self.logic.clone()
129    }
130}