• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::coord::ranged1d::types::RangedCoordf64;
2 use crate::coord::ranged1d::{AsRangedCoord, DefaultFormatting, KeyPointHint, Ranged};
3 use std::marker::PhantomData;
4 use 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 [LogRangeExt](struct.LogRangeExt.html).
8 pub trait LogScalable: Clone {
9     /// Make the conversion from the type to the floating point number
as_f64(&self) -> f6410     fn as_f64(&self) -> f64;
11     /// Convert a floating point number to the scale
from_f64(f: f64) -> Self12     fn from_f64(f: f64) -> Self;
13 }
14 
15 macro_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 
45 impl_log_scalable!(i, u8);
46 impl_log_scalable!(i, u16);
47 impl_log_scalable!(i, u32);
48 impl_log_scalable!(i, u64);
49 impl_log_scalable!(i, usize);
50 
51 impl_log_scalable!(i, i8);
52 impl_log_scalable!(i, i16);
53 impl_log_scalable!(i, i32);
54 impl_log_scalable!(i, i64);
55 impl_log_scalable!(i, i128);
56 impl_log_scalable!(i, isize);
57 
58 impl_log_scalable!(f, f32);
59 impl_log_scalable!(f, f64);
60 
61 /// Convert a range to a log scale coordinate spec
62 pub trait IntoLogRange {
63     /// The type of the value
64     type ValueType: LogScalable;
65 
66     /// Make the log scale coordinate
log_scale(self) -> LogRangeExt<Self::ValueType>67     fn log_scale(self) -> LogRangeExt<Self::ValueType>;
68 }
69 
70 impl<T: LogScalable> IntoLogRange for Range<T> {
71     type ValueType = T;
log_scale(self) -> LogRangeExt<T>72     fn log_scale(self) -> LogRangeExt<T> {
73         LogRangeExt {
74             range: self,
75             zero: 0.0,
76             base: 10.0,
77         }
78     }
79 }
80 
81 /// The logarithmic coordinate decorator.
82 /// This decorator is used to make the axis rendered as logarithmically.
83 #[derive(Clone)]
84 pub struct LogRangeExt<V: LogScalable> {
85     range: Range<V>,
86     zero: f64,
87     base: f64,
88 }
89 
90 impl<V: LogScalable> LogRangeExt<V> {
91     /// Set the zero point of the log scale coordinate. Zero point is the point where we map -inf
92     /// of the axis to the coordinate
zero_point(mut self, value: V) -> Self where V: PartialEq,93     pub fn zero_point(mut self, value: V) -> Self
94     where
95         V: PartialEq,
96     {
97         self.zero = if V::from_f64(0.0) == value {
98             0.0
99         } else {
100             value.as_f64()
101         };
102 
103         self
104     }
105 
106     /// Set the base multiplier
base(mut self, base: f64) -> Self107     pub fn base(mut self, base: f64) -> Self {
108         if self.base > 1.0 {
109             self.base = base;
110         }
111         self
112     }
113 }
114 
115 impl<V: LogScalable> From<LogRangeExt<V>> for LogCoord<V> {
from(spec: LogRangeExt<V>) -> LogCoord<V>116     fn from(spec: LogRangeExt<V>) -> LogCoord<V> {
117         let zero_point = spec.zero;
118         let mut start = spec.range.start.as_f64() - zero_point;
119         let mut end = spec.range.end.as_f64() - zero_point;
120         let negative = if start < 0.0 || end < 0.0 {
121             start = -start;
122             end = -end;
123             true
124         } else {
125             false
126         };
127 
128         if start < end {
129             if start == 0.0 {
130                 start = start.max(end * 1e-5);
131             }
132         } else if end == 0.0 {
133             end = end.max(start * 1e-5);
134         }
135 
136         LogCoord {
137             linear: (start.ln()..end.ln()).into(),
138             logic: spec.range,
139             normalized: start..end,
140             base: spec.base,
141             zero_point,
142             negative,
143             marker: PhantomData,
144         }
145     }
146 }
147 
148 impl<V: LogScalable> AsRangedCoord for LogRangeExt<V> {
149     type CoordDescType = LogCoord<V>;
150     type Value = V;
151 }
152 
153 /// A log scaled coordinate axis
154 pub struct LogCoord<V: LogScalable> {
155     linear: RangedCoordf64,
156     logic: Range<V>,
157     normalized: Range<f64>,
158     base: f64,
159     zero_point: f64,
160     negative: bool,
161     marker: PhantomData<V>,
162 }
163 
164 impl<V: LogScalable> LogCoord<V> {
value_to_f64(&self, value: &V) -> f64165     fn value_to_f64(&self, value: &V) -> f64 {
166         let fv = value.as_f64() - self.zero_point;
167         if self.negative {
168             -fv
169         } else {
170             fv
171         }
172     }
173 
f64_to_value(&self, fv: f64) -> V174     fn f64_to_value(&self, fv: f64) -> V {
175         let fv = if self.negative { -fv } else { fv };
176         V::from_f64(fv + self.zero_point)
177     }
178 
is_inf(&self, fv: f64) -> bool179     fn is_inf(&self, fv: f64) -> bool {
180         let fv = if self.negative { -fv } else { fv };
181         let a = V::from_f64(fv + self.zero_point);
182         let b = V::from_f64(self.zero_point);
183 
184         (V::as_f64(&a) - V::as_f64(&b)).abs() < f64::EPSILON
185     }
186 }
187 
188 impl<V: LogScalable> Ranged for LogCoord<V> {
189     type FormatOption = DefaultFormatting;
190     type ValueType = V;
191 
map(&self, value: &V, limit: (i32, i32)) -> i32192     fn map(&self, value: &V, limit: (i32, i32)) -> i32 {
193         let fv = self.value_to_f64(value);
194         let value_ln = fv.ln();
195         self.linear.map(&value_ln, limit)
196     }
197 
key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType>198     fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> {
199         let max_points = hint.max_num_points();
200 
201         let base = self.base;
202         let base_ln = base.ln();
203 
204         let Range { mut start, mut end } = self.normalized;
205 
206         if start > end {
207             std::mem::swap(&mut start, &mut end);
208         }
209 
210         let bold_count = ((end / start).ln().abs() / base_ln).floor().max(1.0) as usize;
211 
212         let light_density = if max_points < bold_count {
213             0
214         } else {
215             let density = 1 + (max_points - bold_count) / bold_count;
216             let mut exp = 1;
217             while exp * 10 <= density {
218                 exp *= 10;
219             }
220             exp - 1
221         };
222 
223         let mut multiplier = base;
224         let mut cnt = 1;
225         while max_points < bold_count / cnt {
226             multiplier *= base;
227             cnt += 1;
228         }
229 
230         let mut ret = vec![];
231         let mut val = (base).powf((start.ln() / base_ln).ceil());
232 
233         while val <= end {
234             if !self.is_inf(val) {
235                 ret.push(self.f64_to_value(val));
236             }
237             for i in 1..=light_density {
238                 let v = val
239                     * (1.0
240                         + multiplier / f64::from(light_density as u32 + 1) * f64::from(i as u32));
241                 if v > end {
242                     break;
243                 }
244                 if !self.is_inf(val) {
245                     ret.push(self.f64_to_value(v));
246                 }
247             }
248             val *= multiplier;
249         }
250 
251         ret
252     }
253 
range(&self) -> Range<V>254     fn range(&self) -> Range<V> {
255         self.logic.clone()
256     }
257 }
258 
259 /// The logarithmic coordinate decorator.
260 /// This decorator is used to make the axis rendered as logarithmically.
261 #[deprecated(note = "LogRange is deprecated, use IntoLogRange trait method instead")]
262 #[derive(Clone)]
263 pub struct LogRange<V: LogScalable>(pub Range<V>);
264 
265 #[allow(deprecated)]
266 impl<V: LogScalable> AsRangedCoord for LogRange<V> {
267     type CoordDescType = LogCoord<V>;
268     type Value = V;
269 }
270 
271 #[allow(deprecated)]
272 impl<V: LogScalable> From<LogRange<V>> for LogCoord<V> {
from(range: LogRange<V>) -> LogCoord<V>273     fn from(range: LogRange<V>) -> LogCoord<V> {
274         range.0.log_scale().into()
275     }
276 }
277 
278 #[cfg(test)]
279 mod test {
280     use super::*;
281     #[test]
regression_test_issue_143()282     fn regression_test_issue_143() {
283         let range: LogCoord<f64> = (1.0..5.0).log_scale().into();
284 
285         range.key_points(100);
286     }
287 }
288