blob: 84b64bf5de392a073b8a3246a13d4e6779234438 [file] [log] [blame]
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 [LogRangeExt](struct.LogRangeExt.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!(i, i8);
impl_log_scalable!(i, i16);
impl_log_scalable!(i, i32);
impl_log_scalable!(i, i64);
impl_log_scalable!(f, f32);
impl_log_scalable!(f, f64);
/// Convert a range to a log scale coordinate spec
pub trait IntoLogRange {
/// The type of the value
type ValueType: LogScalable;
/// Make the log scale coordinate
fn log_scale(self) -> LogRangeExt<Self::ValueType>;
}
impl<T: LogScalable> IntoLogRange for Range<T> {
type ValueType = T;
fn log_scale(self) -> LogRangeExt<T> {
LogRangeExt {
range: self,
zero: 0.0,
base: 10.0,
}
}
}
/// The logarithmic coodinate decorator.
/// This decorator is used to make the axis rendered as logarithmically.
#[derive(Clone)]
pub struct LogRangeExt<V: LogScalable> {
range: Range<V>,
zero: f64,
base: f64,
}
impl<V: LogScalable> LogRangeExt<V> {
/// Set the zero point of the log scale coordinate. Zero point is the point where we map -inf
/// of the axis to the coordinate
pub fn zero_point(mut self, value: V) -> Self
where
V: PartialEq,
{
self.zero = if V::from_f64(0.0) == value {
0.0
} else {
value.as_f64()
};
self
}
/// Set the base multipler
pub fn base(mut self, base: f64) -> Self {
if self.base > 1.0 {
self.base = base;
}
self
}
}
impl<V: LogScalable> From<LogRangeExt<V>> for LogCoord<V> {
fn from(spec: LogRangeExt<V>) -> LogCoord<V> {
let zero_point = spec.zero;
let mut start = spec.range.start.as_f64() - zero_point;
let mut end = spec.range.end.as_f64() - zero_point;
let negative = if start < 0.0 || end < 0.0 {
start = -start;
end = -end;
true
} else {
false
};
if start < end {
if start == 0.0 {
start = start.max(end * 1e-5);
}
} else {
if end == 0.0 {
end = end.max(start * 1e-5);
}
}
LogCoord {
linear: (start.ln()..end.ln()).into(),
logic: spec.range,
normalized: start..end,
base: spec.base,
zero_point,
negative,
marker: PhantomData,
}
}
}
impl<V: LogScalable> AsRangedCoord for LogRangeExt<V> {
type CoordDescType = LogCoord<V>;
type Value = V;
}
/// A log scaled coordinate axis
pub struct LogCoord<V: LogScalable> {
linear: RangedCoordf64,
logic: Range<V>,
normalized: Range<f64>,
base: f64,
zero_point: f64,
negative: bool,
marker: PhantomData<V>,
}
impl<V: LogScalable> LogCoord<V> {
fn value_to_f64(&self, value: &V) -> f64 {
let fv = value.as_f64() - self.zero_point;
if self.negative {
-fv
} else {
fv
}
}
fn f64_to_value(&self, fv: f64) -> V {
let fv = if self.negative { -fv } else { fv };
V::from_f64(fv + self.zero_point)
}
fn is_inf(&self, fv: f64) -> bool {
let fv = if self.negative { -fv } else { fv };
let a = V::from_f64(fv + self.zero_point);
let b = V::from_f64(self.zero_point);
V::as_f64(&a) == V::as_f64(&b)
}
}
impl<V: LogScalable> Ranged for LogCoord<V> {
type FormatOption = DefaultFormatting;
type ValueType = V;
fn map(&self, value: &V, limit: (i32, i32)) -> i32 {
let fv = self.value_to_f64(value);
let value_ln = fv.ln();
self.linear.map(&value_ln, limit)
}
fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<Self::ValueType> {
let max_points = hint.max_num_points();
let base = self.base;
let base_ln = base.ln();
let Range { mut start, mut end } = self.normalized;
if start > end {
std::mem::swap(&mut start, &mut end);
}
let bold_count = ((end / start).ln().abs() / base_ln).floor().max(1.0) as usize;
let light_density = if max_points < bold_count {
0
} else {
let density = 1 + (max_points - bold_count) / bold_count;
let mut exp = 1;
while exp * 10 <= density {
exp *= 10;
}
exp - 1
};
let mut multiplier = base;
let mut cnt = 1;
while max_points < bold_count / cnt {
multiplier *= base;
cnt += 1;
}
let mut ret = vec![];
let mut val = (base).powf((start.ln() / base_ln).ceil());
while val <= end {
if !self.is_inf(val) {
ret.push(self.f64_to_value(val));
}
for i in 1..=light_density {
let v = val
* (1.0
+ multiplier / f64::from(light_density as u32 + 1) * f64::from(i as u32));
if v > end {
break;
}
if !self.is_inf(val) {
ret.push(self.f64_to_value(v));
}
}
val *= multiplier;
}
ret
}
fn range(&self) -> Range<V> {
self.logic.clone()
}
}
/// The logarithmic coodinate decorator.
/// This decorator is used to make the axis rendered as logarithmically.
#[deprecated(note = "LogRange is deprecated, use IntoLogRange trait method instead")]
#[derive(Clone)]
pub struct LogRange<V: LogScalable>(pub Range<V>);
#[allow(deprecated)]
impl<V: LogScalable> AsRangedCoord for LogRange<V> {
type CoordDescType = LogCoord<V>;
type Value = V;
}
#[allow(deprecated)]
impl<V: LogScalable> From<LogRange<V>> for LogCoord<V> {
fn from(range: LogRange<V>) -> LogCoord<V> {
range.0.log_scale().into()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn regression_test_issue_143() {
let range: LogCoord<f64> = (1.0..5.0).log_scale().into();
range.key_points(100);
}
}