use std::borrow::Borrow;
use std::collections::HashMap;
pub trait Scaleable<D, R> {
fn scale(&self, value: &D) -> R;
}
pub trait LinearRangeable<R> {
fn get_range(&self) -> (R, R);
}
pub trait Tickable<D> {
fn ticks(&self, count: Option<usize>) -> Vec<D>;
}
#[derive(Debug, Clone)]
pub struct ScaleLinear {
domain: (f64, f64),
range: (f64, f64),
clamp: bool,
}
impl Default for ScaleLinear {
fn default() -> Self {
Self {
domain: (0.0, 1.0),
range: (0.0, 1.0),
clamp: false,
}
}
}
impl Scaleable<f64, f64> for ScaleLinear {
fn scale(&self, x: &f64) -> f64 {
let (d0, d1) = self.domain;
let (r0, r1) = self.range;
let x_clamped = if self.clamp {
if d0 > d1 {
x.max(d1).min(d0)
} else {
x.max(d0).min(d1)
}
} else {
*x
};
if d1 < d0 {
let normalize = normalize(d1, d0);
let interpolate = interpolate_number(r1, r0);
interpolate(normalize(x_clamped))
} else {
let normalize = normalize(d0, d1);
let interpolate = interpolate_number(r0, r1);
interpolate(normalize(x_clamped))
}
}
}
impl LinearRangeable<f64> for ScaleLinear {
fn get_range(&self) -> (f64, f64) {
self.range
}
}
impl Tickable<f64> for ScaleLinear {
fn ticks(&self, count: Option<usize>) -> Vec<f64> {
let count = count.unwrap_or(10);
let (start, stop) = self.get_domain();
ticks(start, stop, count)
}
}
impl ScaleLinear {
pub fn new() -> Self {
Self::default()
}
pub fn get_domain(&self) -> (f64, f64) {
self.domain
}
pub fn set_domain(&mut self, domain: (f64, f64)) {
self.domain = domain;
}
pub fn set_range(&mut self, range: (f64, f64)) {
self.range = range;
}
pub fn get_clamp(&self) -> bool {
self.clamp
}
pub fn set_clamp(&mut self, clamp: bool) {
self.clamp = clamp;
}
pub fn nice(&mut self, count: Option<usize>) {
let count = count.unwrap_or(10);
let (start, stop) = self.get_domain();
let (new_start, new_stop) = nice(start, stop, count);
self.set_domain((new_start, new_stop));
}
}
#[derive(Debug, Clone)]
pub struct ScaleBand {
domain: Vec<String>,
range: (f64, f64),
range_map: HashMap<String, f64>,
step: f64,
bandwidth: f64,
round: bool,
padding_inner: f64,
padding_outer: f64,
align: f64,
}
impl Default for ScaleBand {
fn default() -> Self {
let mut scale = Self {
domain: Vec::new(),
range: (0.0, 1.0),
range_map: HashMap::new(),
step: 0.0,
bandwidth: 0.0,
round: false,
padding_inner: 0.0,
padding_outer: 0.0,
align: 0.5,
};
scale.rescale();
scale
}
}
impl LinearRangeable<f64> for ScaleBand {
fn get_range(&self) -> (f64, f64) {
self.range
}
}
impl Tickable<String> for ScaleBand {
fn ticks(&self, count: Option<usize>) -> Vec<String> {
self.domain.clone()
}
}
impl ScaleBand {
pub fn new() -> Self {
Self::default()
}
pub fn get_domain(&self) -> &[String] {
&self.domain
}
pub fn set_domain(&mut self, domain: Vec<String>) {
self.domain = domain;
self.rescale();
}
pub fn set_range(&mut self, range: (f64, f64)) {
self.range = range;
self.rescale();
}
pub fn set_range_round(&mut self, range: (f64, f64)) {
self.range = range;
self.round = true;
self.rescale();
}
pub fn bandwidth(&self) -> f64 {
self.bandwidth
}
pub fn step(&self) -> f64 {
self.step
}
pub fn get_round(&self) -> bool {
self.round
}
pub fn set_round(&mut self, round: bool) {
self.round = round;
self.rescale();
}
pub fn set_padding(&mut self, padding: f64) {
self.padding_inner = padding.min(1.0);
self.padding_outer = padding;
self.rescale();
}
pub fn get_padding_inner(&self) -> f64 {
self.padding_inner
}
pub fn set_padding_inner(&mut self, padding: f64) {
self.padding_inner = padding.min(1.0);
self.rescale();
}
pub fn get_padding_outer(&self) -> f64 {
self.padding_outer
}
pub fn set_padding_outer(&mut self, padding: f64) {
self.padding_outer = padding;
self.rescale();
}
pub fn get_align(&self) -> f64 {
self.align
}
pub fn set_align(&mut self, align: f64) {
self.align = align.max(0.0).min(1.0);
self.rescale();
}
fn rescale(&mut self) {
let n = self.domain.len();
let (r0, r1) = self.range;
let reverse = r1 < r0;
let (start, stop) = if reverse { (r1, r0) } else { (r0, r1) };
let divisor = (n as f64 - self.padding_inner + self.padding_outer * 2.0).max(1.0);
self.step = (stop - start) / divisor;
if self.round {
self.step = self.step.floor();
}
let mut adjusted_start =
start + (stop - start - self.step * (n as f64 - self.padding_inner)) * self.align;
self.bandwidth = self.step * (1.0 - self.padding_inner);
if self.round {
adjusted_start = adjusted_start.round();
self.bandwidth = self.bandwidth.round();
}
self.range_map.clear();
for (i, key) in self.domain.iter().enumerate() {
let value = adjusted_start + self.step * i as f64;
let final_value = if reverse {
stop - (value - start)
} else {
value
};
self.range_map.insert(key.clone(), final_value);
}
}
}
impl Scaleable<String, f64> for ScaleBand {
fn scale(&self, value: &String) -> f64 {
*self.range_map.get(value).unwrap()
}
}
pub fn ticks(start: f64, stop: f64, count: usize) -> Vec<f64> {
let step = tick_step(start, stop, count);
let start_ceil = (start / step).ceil();
let stop_floor = (stop / step).floor();
let n = (stop_floor - start_ceil + 1.0).ceil() as usize;
let mut ticks = Vec::with_capacity(n);
for i in 0..n {
ticks.push((start_ceil + i as f64) * step);
}
ticks
}
pub fn tick_step(start: f64, stop: f64, count: usize) -> f64 {
let step0 = (stop - start).abs() / (count as f64).max(0.0);
let mut step1 = 10.0_f64.powf((step0.log10()).floor());
let error = step0 / step1;
let E10: f64 = 50.0_f64.sqrt();
let E5: f64 = 10.0_f64.sqrt();
let E2: f64 = 2.0_f64.sqrt();
if error >= E10 {
step1 *= 10.0;
} else if error >= E5 {
step1 *= 5.0;
} else if error >= E2 {
step1 *= 2.0;
}
if stop < start {
-step1
} else {
step1
}
}
pub fn tick_increment(start: f64, stop: f64, count: usize) -> f64 {
let step = (stop - start) / (count as f64).max(0.0);
let power = (step.log10()).floor();
let error = step / 10.0_f64.powf(power);
let E10: f64 = 50.0_f64.sqrt();
let E5: f64 = 10.0_f64.sqrt();
let E2: f64 = 2.0_f64.sqrt();
let increment = if power >= 0.0 {
let factor = if error >= E10 {
10.0
} else if error >= E5 {
5.0
} else if error >= E2 {
2.0
} else {
1.0
};
factor * 10.0_f64.powf(power)
} else {
-10.0_f64.powf(-power)
/ if error >= E10 {
10.0
} else if error >= E5 {
5.0
} else if error >= E2 {
2.0
} else {
1.0
}
};
increment
}
pub fn nice(mut start: f64, mut stop: f64, count: usize) -> (f64, f64) {
if start == stop {
return (start, stop);
}
if stop < start {
std::mem::swap(&mut start, &mut stop);
}
let mut pre_step = 0.0;
for _ in 0..10 {
let step = tick_increment(start, stop, count);
if step == pre_step {
break;
}
if step > 0.0 {
start = (start / step).floor() * step;
stop = (stop / step).ceil() * step;
} else if step < 0.0 {
start = (start * step).ceil() / step;
stop = (stop * step).floor() / step;
} else {
break;
}
pre_step = step;
}
(start, stop)
}
pub fn tick_format(
start: f64,
stop: f64,
count: usize,
specifier: Option<&str>,
) -> impl Fn(f64) -> String {
let step = tick_step(start, stop, count);
let specifier = specifier.unwrap_or(",f");
let precision = if let Some(idx) = specifier.find('.') {
if let Some(end) = specifier[idx + 1..].chars().find(|c| !c.is_ascii_digit()) {
specifier[idx + 1..specifier.len() - end.len_utf8()]
.parse::<usize>()
.unwrap_or(0)
} else {
specifier[idx + 1..].parse::<usize>().unwrap_or(0)
}
} else {
(step.abs().log10().floor().max(0.0) * -1.0) as usize
};
move |d| format!("{:.prec$}", d, prec = precision)
}
fn normalize(a: f64, b: f64) -> impl Fn(f64) -> f64 {
let diff = b - a;
move |x| {
if diff.abs() > 1e-6 {
(x - a) / diff
} else {
0.5
}
}
}
fn interpolate_number(a: f64, b: f64) -> impl Fn(f64) -> f64 {
let diff = b - a;
move |t| a + diff * t
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scale_linear_defaults() {
let s = ScaleLinear::new();
assert_eq!(s.get_domain(), (0.0, 1.0));
assert_eq!(s.get_range(), (0.0, 1.0));
assert_eq!(s.get_clamp(), false);
}
#[test]
fn test_scale_linear_range_sets_range() {
let mut s = ScaleLinear::new();
s.set_range((1.0, 2.0));
assert_eq!(s.get_domain(), (0.0, 1.0));
assert_eq!(s.get_range(), (1.0, 2.0));
assert_eq!(s.scale(&0.5), 1.5);
}
#[test]
fn test_scale_linear_domain_range_sets_domain_and_range() {
let mut s = ScaleLinear::new();
s.set_domain((1.0, 2.0));
s.set_range((3.0, 4.0));
assert_eq!(s.get_domain(), (1.0, 2.0));
assert_eq!(s.get_range(), (3.0, 4.0));
assert_eq!(s.scale(&1.5), 3.5);
}
#[test]
fn test_linear_maps_domain_to_range() {
let mut s = ScaleLinear::new();
s.set_range((1.0, 2.0));
assert_eq!(s.scale(&0.5), 1.5);
}
#[test]
fn test_linear_clamp_true_restricts_output_to_range() {
let mut s = ScaleLinear::new();
s.set_clamp(true);
s.set_range((10.0, 20.0));
assert_eq!(s.scale(&2.0), 20.0);
assert_eq!(s.scale(&-1.0), 10.0);
}
#[test]
fn test_linear_nice_extends_domain() {
let mut s = ScaleLinear::new();
s.set_domain((0.0, 0.96));
s.nice(None);
assert_eq!(s.get_domain(), (0.0, 1.0));
let mut s = ScaleLinear::new();
s.set_domain((0.0, 96.0));
s.nice(None);
assert_eq!(s.get_domain(), (0.0, 100.0));
}
#[test]
fn test_linear_ticks_returns_expected_ticks() {
let s = ScaleLinear::new();
let ticks = s.ticks(Some(10));
let expected: Vec<f64> = vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
assert_eq!(ticks.len(), expected.len());
for (a, b) in ticks.iter().zip(expected.iter()) {
assert!((a - b).abs() < 1e-9);
}
let mut s = ScaleLinear::new();
s.set_domain((-100.0, 100.0));
let ticks = s.ticks(Some(10));
let expected: Vec<f64> = vec![
-100.0, -80.0, -60.0, -40.0, -20.0, 0.0, 20.0, 40.0, 60.0, 80.0, 100.0,
];
assert_eq!(ticks, expected);
}
#[test]
fn test_linear_copy_isolates_domain_changes() {
let mut x = ScaleLinear::new();
let mut y = x.clone();
x.set_domain((1.0, 2.0));
assert_eq!(y.get_domain(), (0.0, 1.0));
assert_eq!(x.scale(&1.0), 0.0);
assert_eq!(y.scale(&1.0), 1.0);
y.set_domain((2.0, 3.0));
assert_eq!(x.scale(&2.0), 1.0);
assert_eq!(y.scale(&2.0), 0.0);
assert_eq!(x.get_domain(), (1.0, 2.0));
assert_eq!(y.get_domain(), (2.0, 3.0));
}
#[test]
fn test_linear_copy_isolates_range_changes() {
let mut x = ScaleLinear::new();
let mut y = x.clone();
x.set_range((1.0, 2.0));
assert_eq!(y.get_range(), (0.0, 1.0));
y.set_range((2.0, 3.0));
assert_eq!(x.get_range(), (1.0, 2.0));
assert_eq!(y.get_range(), (2.0, 3.0));
}
#[test]
fn test_linear_copy_isolates_clamp_changes() {
let mut x = ScaleLinear::new();
x.set_clamp(true);
let mut y = x.clone();
x.set_clamp(false);
assert_eq!(x.scale(&2.0), 2.0);
assert_eq!(y.scale(&2.0), 1.0);
assert_eq!(y.get_clamp(), true);
y.set_clamp(false);
assert_eq!(x.scale(&2.0), 2.0);
assert_eq!(y.scale(&2.0), 2.0);
assert_eq!(x.get_clamp(), false);
}
#[test]
fn test_scale_band_defaults() {
let s = ScaleBand::new();
assert_eq!(s.get_domain().len(), 0);
assert_eq!(s.get_range(), (0.0, 1.0));
assert_eq!(s.get_round(), false);
assert_eq!(s.get_padding_inner(), 0.0);
assert_eq!(s.get_padding_outer(), 0.0);
assert_eq!(s.get_align(), 0.5);
}
#[test]
fn test_scale_band_domain_sets_domain() {
let mut s = ScaleBand::new();
s.set_domain(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
assert_eq!(s.get_domain(), &["a", "b", "c"]);
}
#[test]
fn test_scale_band_maps_domain_to_range() {
let mut s = ScaleBand::new();
s.set_domain(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
s.set_range((0.0, 960.0));
assert_eq!(s.scale(&"a".to_string()), 0.0);
assert_eq!(s.scale(&"b".to_string()), 320.0);
assert_eq!(s.scale(&"c".to_string()), 640.0);
assert_eq!(s.bandwidth(), 320.0);
}
#[test]
fn test_scale_band_with_padding() {
let mut s = ScaleBand::new();
s.set_domain(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
s.set_range((0.0, 960.0));
s.set_padding_inner(0.1);
s.set_padding_outer(0.2);
let step = s.step();
let bandwidth = s.bandwidth();
assert!((step - 290.909090909).abs() < 1e-6);
assert!((bandwidth - step * 0.9).abs() < 1e-6);
}
#[test]
fn test_scale_band_with_round() {
let mut s = ScaleBand::new();
s.set_domain(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
s.set_range_round((0.0, 100.0));
let step = s.step();
assert_eq!(step, step.floor());
let a_pos = s.scale(&"a".to_string());
assert_eq!(a_pos, a_pos.round());
}
#[test]
fn test_scale_band_unknown_value() {
let mut s = ScaleBand::new();
s.set_domain(vec!["a".to_string(), "b".to_string()]);
assert_eq!(s.scale(&"a".to_string()), 0.0);
}
#[test]
fn test_scale_band_ticks() {
let mut s = ScaleBand::new();
s.set_domain(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
let ticks = s.ticks(None);
assert_eq!(ticks, vec!["a", "b", "c"]);
}
#[test]
fn test_scale_band_copy_isolates_changes() {
let mut x = ScaleBand::new();
x.set_domain(vec!["a".to_string(), "b".to_string()]);
let mut y = x.clone();
y.set_domain(vec!["c".to_string(), "d".to_string()]);
assert_eq!(x.get_domain(), &["a", "b"]);
assert_eq!(y.get_domain(), &["c", "d"]);
}
#[test]
fn test_scale_band_align() {
let mut s = ScaleBand::new();
s.set_domain(vec!["a".to_string(), "b".to_string()]);
s.set_range((0.0, 100.0));
s.set_padding(0.2);
let default_a = s.scale(&"a".to_string());
s.set_align(0.0);
let start_a = s.scale(&"a".to_string());
assert!(start_a < default_a);
s.set_align(1.0);
let end_a = s.scale(&"a".to_string());
assert!(end_a > default_a);
}
}