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