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