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