| 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 | |
| 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 |
| 64 | fn log_scale(self) -> LogRangeExt<Self::ValueType>; |
| 65 | } |
| 66 | |
| 67 | impl<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)] |
| 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 |
| 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 | |
| 112 | impl<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 | |
| 145 | impl<V: LogScalable> AsRangedCoord for LogRangeExt<V> { |
| 146 | type CoordDescType = LogCoord<V>; |
| 147 | type Value = V; |
| 148 | } |
| 149 | |
| 150 | /// A log scaled coordinate axis |
| 151 | pub 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 | |
| 161 | impl<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 | |
| 185 | impl<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)] |
| 260 | pub struct LogRange<V: LogScalable>(pub Range<V>); |
| 261 | |
| 262 | #[allow (deprecated)] |
| 263 | impl<V: LogScalable> AsRangedCoord for LogRange<V> { |
| 264 | type CoordDescType = LogCoord<V>; |
| 265 | type Value = V; |
| 266 | } |
| 267 | |
| 268 | #[allow (deprecated)] |
| 269 | impl<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)] |
| 276 | mod 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 | |