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 |
10 | fn as_f64(&self) -> f64; |
11 | /// Convert a floating point number to the scale |
12 | 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 |
67 | fn log_scale(self) -> LogRangeExt<Self::ValueType>; |
68 | } |
69 | |
70 | impl<T: LogScalable> IntoLogRange for Range<T> { |
71 | type ValueType = 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 |
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 |
107 | 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> { |
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> { |
165 | fn value_to_f64(&self, value: &V) -> f64 { |
166 | let fv: f64 = value.as_f64() - self.zero_point; |
167 | if self.negative { |
168 | -fv |
169 | } else { |
170 | fv |
171 | } |
172 | } |
173 | |
174 | fn f64_to_value(&self, fv: f64) -> V { |
175 | let fv: f64 = if self.negative { -fv } else { fv }; |
176 | V::from_f64(fv + self.zero_point) |
177 | } |
178 | |
179 | fn is_inf(&self, fv: f64) -> bool { |
180 | let fv: f64 = if self.negative { -fv } else { fv }; |
181 | let a: V = V::from_f64(fv + self.zero_point); |
182 | let b: V = 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 | |
192 | 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 | |
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 | |
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> { |
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 ] |
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 | |