use super::funejson::Value; use super::charts_ticks_tick_step::tick_step; use super::dates_add_days::epoch_day_from_iso; // d3-scale's calendar ladder, from a day up, with the lengths in days it // compares against: a month counts as 30, a quarter as 90, a year as 365. const LADDER: [(&str, i64, f64); 6] = [ ("day", 1, 1.0), ("day", 2, 2.0), ("week", 1, 7.0), ("month", 1, 30.0), ("quarter", 1, 90.0), ("year", 1, 365.0), ]; /// The calendar interval for about `count` ticks between two dates, as d3's /// time scale picks it: the nearest rung of the ladder by ratio, or whole years /// in steps of 1, 2 or 5 x 10^k beyond a year. /// /// # Panics /// Panics on a date that is not a real ISO date or `count` less than 1. pub fn time_tick_interval(start: &str, stop: &str, count: i64) -> TimeTickInterval { if count < 1 { panic!("count must be a whole number of at least 1, got {}", count); } let span = (epoch_day_from_iso(stop) - epoch_day_from_iso(start)).abs() as f64; let target = span / count as f64; let mut i = 0; while i < LADDER.len() && LADDER[i].2 <= target { i += 1; } if i == LADDER.len() { let step = tick_step(0.0, span / 365.0, count) as i64; return TimeTickInterval { interval: "year".to_string(), step: step.max(1) }; } if i == 0 { return TimeTickInterval { interval: "day".to_string(), step: 1 }; } let pick = if target / LADDER[i - 1].2 < LADDER[i].2 / target { LADDER[i - 1] } else { LADDER[i] }; TimeTickInterval { interval: pick.0.to_string(), step: pick.1 } } pub fn time_tick_interval_to_value(t: &TimeTickInterval) -> Value { Value::obj(vec![("interval", Value::str(&t.interval)), ("step", Value::Int(t.step))]) } pub fn fune_vector(args: &[Value]) -> Value { time_tick_interval_to_value(&time_tick_interval(args[0].as_str(), args[1].as_str(), args[2].as_i64())) }