plotters_unstable/coord/ranged1d/combinators/
logarithmic.rs

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