diff options
Diffstat (limited to 'src/coord/ranged1d/combinators/logarithmic.rs')
-rw-r--r-- | src/coord/ranged1d/combinators/logarithmic.rs | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/src/coord/ranged1d/combinators/logarithmic.rs b/src/coord/ranged1d/combinators/logarithmic.rs new file mode 100644 index 0000000..d29c73e --- /dev/null +++ b/src/coord/ranged1d/combinators/logarithmic.rs @@ -0,0 +1,160 @@ +use crate::coord::ranged1d::types::RangedCoordf64; +use crate::coord::ranged1d::{AsRangedCoord, DefaultFormatting, KeyPointHint, Ranged}; +use std::marker::PhantomData; +use std::ops::Range; + +/// The trait for the type that is able to be presented in the log scale. +/// This trait is primarily used by [LogRange](struct.LogRange.html). +pub trait LogScalable: Clone { + /// Make the conversion from the type to the floating point number + fn as_f64(&self) -> f64; + /// Convert a floating point number to the scale + fn from_f64(f: f64) -> Self; +} + +macro_rules! impl_log_scalable { + (i, $t:ty) => { + impl LogScalable for $t { + fn as_f64(&self) -> f64 { + if *self != 0 { + return *self as f64; + } + // If this is an integer, we should allow zero point to be shown + // on the chart, thus we can't map the zero point to inf. + // So we just assigning a value smaller than 1 as the alternative + // of the zero point. + return 0.5; + } + fn from_f64(f: f64) -> $t { + f.round() as $t + } + } + }; + (f, $t:ty) => { + impl LogScalable for $t { + fn as_f64(&self) -> f64 { + *self as f64 + } + fn from_f64(f: f64) -> $t { + f as $t + } + } + }; +} + +impl_log_scalable!(i, u8); +impl_log_scalable!(i, u16); +impl_log_scalable!(i, u32); +impl_log_scalable!(i, u64); +impl_log_scalable!(f, f32); +impl_log_scalable!(f, f64); + +pub trait IntoLogRange { + type ValueType: LogScalable; + fn log_scale(self) -> LogRange<Self::ValueType>; +} + +impl<T: LogScalable> IntoLogRange for Range<T> { + type ValueType = T; + fn log_scale(self) -> LogRange<T> { + LogRange(self) + } +} + +/// The logarithmic coodinate decorator. +/// This decorator is used to make the axis rendered as logarithmically. +#[derive(Clone)] +pub struct LogRange<V: LogScalable>(pub Range<V>); + +impl<V: LogScalable> From<LogRange<V>> for LogCoord<V> { + fn from(range: LogRange<V>) -> LogCoord<V> { + LogCoord { + linear: (range.0.start.as_f64().ln()..range.0.end.as_f64().ln()).into(), + logic: range.0, + marker: PhantomData, + } + } +} + +impl<V: LogScalable> AsRangedCoord for LogRange<V> { + type CoordDescType = LogCoord<V>; + type Value = V; +} + +/// A log scaled coordinate axis +pub struct LogCoord<V: LogScalable> { + linear: RangedCoordf64, + logic: Range<V>, + marker: PhantomData<V>, +} + +impl<V: LogScalable> Ranged for LogCoord<V> { + type FormatOption = DefaultFormatting; + type ValueType = V; + + fn map(&self, value: &V, limit: (i32, i32)) -> i32 { + let value = value.as_f64(); + let value = value.max(self.logic.start.as_f64()).ln(); + self.linear.map(&value, limit) + } + + fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> { + let max_points = hint.max_num_points(); + let tier_1 = (self.logic.end.as_f64() / self.logic.start.as_f64()) + .log10() + .abs() + .floor() + .max(1.0) as usize; + + let tier_2_density = if max_points < tier_1 { + 0 + } else { + let density = 1 + (max_points - tier_1) / tier_1; + let mut exp = 1; + while exp * 10 <= density { + exp *= 10; + } + exp - 1 + }; + + let mut multiplier = 10.0; + let mut cnt = 1; + while max_points < tier_1 / cnt { + multiplier *= 10.0; + cnt += 1; + } + + let mut ret = vec![]; + let mut val = (10f64).powf(self.logic.start.as_f64().log10().ceil()); + + while val <= self.logic.end.as_f64() { + ret.push(V::from_f64(val)); + for i in 1..=tier_2_density { + let v = val + * (1.0 + + multiplier / f64::from(tier_2_density as u32 + 1) * f64::from(i as u32)); + if v > self.logic.end.as_f64() { + break; + } + ret.push(V::from_f64(v)); + } + val *= multiplier; + } + + ret + } + + fn range(&self) -> Range<V> { + self.logic.clone() + } +} +#[cfg(test)] +mod test { + use super::*; + #[test] + fn regression_test_issue_143() { + let range: LogCoord<f64> = LogRange(1.0..5.0).into(); + + range.key_points(100); + } +} |