use super::funejson::Value; /// Widen a domain to multiples of a round tick step (1, 2 or 5 times a power /// of ten) chosen for about `count` ticks. Widening can change the best step, /// so it repeats until the step settles, as d3's nice() does. /// /// # Panics /// Panics if the domain does not have exactly two values or `count` is less /// than 1. pub fn nice_domain(domain: &[f64], count: i64) -> NiceDomain { if domain.len() != 2 { panic!("domain must have exactly 2 values, [from, to]; got {}", domain.len()); } if count < 1 { panic!("count must be a whole number of at least 1, got {}", count); } let reversed = domain[1] < domain[0]; let (mut lo, mut hi) = if reversed { (domain[1], domain[0]) } else { (domain[0], domain[1]) }; if lo == hi { return NiceDomain { domain: vec![domain[0], domain[1]], step: 0.0 }; } let mut step = 0.0; for _ in 0..10 { let next = tick_step(lo, hi, count as f64); if next == step { break; } step = next; if step >= 1.0 { lo = (lo / step).floor() * step; hi = (hi / step).ceil() * step; } else { // A fractional step divides badly (0.3 / 0.1 is 2.9999999999999996), // so multiply by its whole-number inverse instead. let inverse = (1.0 / step + 0.5).floor(); lo = (lo * inverse).floor() / inverse; hi = (hi * inverse).ceil() / inverse; } } let ends = if reversed { vec![round6(hi), round6(lo)] } else { vec![round6(lo), round6(hi)] }; NiceDomain { domain: ends, step: round6(step) } } fn tick_step(lo: f64, hi: f64, count: f64) -> f64 { let raw = (hi - lo) / count; // Powers of ten by repeated multiplication rather than log10, whose last // digit is not guaranteed to agree between languages. let mut power = 1.0; while power * 10.0 <= raw { power *= 10.0; } while power > raw { power /= 10.0; } let error = raw / power; let factor = if error >= 50f64.sqrt() { 10.0 } else if error >= 10f64.sqrt() { 5.0 } else if error >= 2f64.sqrt() { 2.0 } else { 1.0 }; factor * power } fn round6(x: f64) -> f64 { (x * 1e6 + 0.5).floor() / 1e6 } pub fn nice_domain_to_value(nice: &NiceDomain) -> Value { Value::obj(vec![ ("domain", Value::Arr(nice.domain.iter().map(|x| Value::Float(*x)).collect())), ("step", Value::Float(nice.step)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let domain: Vec = args[0].as_arr().iter().map(|v| v.as_f64()).collect(); nice_domain_to_value(&nice_domain(&domain, args[1].as_i64())) }