#[derive(Debug, Clone, Copy)]
pub enum ScaleKind {
Linear,
Log,
}
#[derive(Debug, Clone, Copy)]
pub struct Scale {
pub kind: ScaleKind,
pub d0: f64,
pub d1: f64,
pub r0: f64,
pub r1: f64,
pub base: f64,
}
impl Scale {
pub fn linear(domain: (f64, f64), range: (f64, f64)) -> Self {
Self {
kind: ScaleKind::Linear,
d0: domain.0,
d1: domain.1,
r0: range.0,
r1: range.1,
base: 10.0,
}
}
pub fn log(domain: (f64, f64), range: (f64, f64), base: f64) -> Self {
Self {
kind: ScaleKind::Log,
d0: domain.0,
d1: domain.1,
r0: range.0,
r1: range.1,
base,
}
}
pub fn project(&self, v: f64) -> f64 {
match self.kind {
ScaleKind::Linear => self.r0 + (v - self.d0) / (self.d1 - self.d0) * (self.r1 - self.r0),
ScaleKind::Log => {
let lb = self.base.ln();
let lv = v.ln() / lb;
let l0 = self.d0.ln() / lb;
let l1 = self.d1.ln() / lb;
self.r0 + (lv - l0) / (l1 - l0) * (self.r1 - self.r0)
}
}
}
pub fn domain(&self) -> (f64, f64) {
(self.d0, self.d1)
}
}
pub fn auto_domain(values: &[f64], padding: f64) -> (f64, f64) {
let mut iter = values.iter().copied().filter(|v| v.is_finite());
let first = match iter.next() {
Some(v) => v,
None => return (0.0, 1.0),
};
let mut lo = first;
let mut hi = first;
for v in iter {
if v < lo {
lo = v;
}
if v > hi {
hi = v;
}
}
if lo == hi {
if lo == 0.0 {
return (-1.0, 1.0);
}
let pad = lo.abs() * 0.1;
return (lo - pad, hi + pad);
}
let pad = (hi - lo) * padding;
(lo - pad, hi + pad)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linear_projection_is_proportional() {
let s = Scale::linear((0.0, 10.0), (40.0, 440.0));
assert_eq!(s.project(0.0), 40.0);
assert_eq!(s.project(5.0), 240.0);
assert_eq!(s.project(10.0), 440.0);
}
#[test]
fn log_projection_is_proportional_in_log_space() {
let s = Scale::log((1.0, 100.0), (0.0, 200.0), 10.0);
assert!((s.project(10.0) - 100.0).abs() < 1e-9);
}
#[test]
fn zero_span_gets_unit_pad() {
assert_eq!(auto_domain(&[0.0, 0.0, 0.0], 0.05), (-1.0, 1.0));
}
}