Make chart axis flatness thresholds relative to data magnitude

Found while testing crates/model/src/chart.rs:775-794 (axis_scale); same pattern in nice_scale :817

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-22 21:07:43 +00:00
co-authored by Claude Haiku 5.5
parent b47195feec
commit 53972cf37c
+21 -6
View File
@@ -772,7 +772,7 @@ pub fn axis_scale(
// Office's automatic limits ("How chart axis limits are determined", as Jon Peltier
// summarises it): 5 % of the data range past the data, and zero when the values don't
// sit within the top sixth of their largest magnitude. A single value is measured from zero.
let flat = hi - lo < 1e-12;
let flat = hi - lo <= 1e-12 * lo.abs().max(hi.abs());
let pad = if !headroom {
0.0
} else if flat {
@@ -784,12 +784,15 @@ pub fn axis_scale(
let auto_hi = if hi <= 0.0 && (flat || hi > lo * 5.0 / 6.0) { 0.0 } else { hi + pad };
let mut lo = fmin.unwrap_or(auto_lo);
let mut hi = fmax.unwrap_or(auto_hi);
if hi - lo < 1e-12 {
let mag = lo.abs().max(hi.abs());
// Relative to the data's magnitude, so tiny values don't count as flat; all-zero falls back to 1.
let unit = if mag == 0.0 { 1.0 } else { mag };
if hi - lo <= 1e-12 * mag {
// A fixed limit on the far side of the data: the automatic end gives way.
match (fmin, fmax) {
(None, Some(_)) => lo = if hi > 0.0 { 0.0 } else { hi - hi.abs().max(1.0) },
(None, Some(_)) => lo = if hi > 0.0 { 0.0 } else { hi - hi.abs().max(unit) },
(Some(_), None) if lo < 0.0 => hi = 0.0,
_ => hi = lo + lo.abs().max(1.0),
_ => hi = lo + unit,
}
}
let n = max_intervals.clamp(1, 50) as f64;
@@ -814,7 +817,9 @@ pub fn axis_scale(
/// "Nice" axis scale: (min, max, major step) covering [lo, hi].
pub fn nice_scale(lo: f64, hi: f64) -> (f64, f64, f64) {
let (lo, hi) = if lo.is_finite() && hi.is_finite() { (lo.min(hi), lo.max(hi)) } else { (0.0, 1.0) };
let span = if hi - lo < 1e-12 { hi.abs().max(1.0) } else { hi - lo };
let data_mag = lo.abs().max(hi.abs());
let span = if hi - lo <= 1e-12 * data_mag { data_mag } else { hi - lo };
let span = if span == 0.0 { 1.0 } else { span };
let raw = span / 5.0;
let mag = 10f64.powf(raw.log10().floor());
let norm = raw / mag;
@@ -831,7 +836,7 @@ pub fn nice_scale(lo: f64, hi: f64) -> (f64, f64, f64) {
} * mag;
let min = (lo / step).floor() * step;
let max = (hi / step).ceil() * step;
let max = if (max - min).abs() < 1e-12 { min + step } else { max };
let max = if (max - min).abs() <= 1e-12 * data_mag { min + step } else { max };
(min, max, step)
}
@@ -839,6 +844,16 @@ pub fn nice_scale(lo: f64, hi: f64) -> (f64, f64, f64) {
mod tests {
use super::*;
#[test]
fn tiny_data_keeps_its_own_scale() {
let (min, max, step) = axis_scale(1e-13, 5e-13, None, None, None, 8, true);
assert!(min == 0.0 && (5e-13..1e-11).contains(&max), "{min} {max} {step}");
assert!(step > 0.0 && step < 1e-11);
let (min, max, step) = nice_scale(1e-13, 5e-13);
assert!(min <= 1e-13 && (5e-13..1e-11).contains(&max) && step < 1e-11, "{min} {max} {step}");
assert_eq!(axis_scale(0.0, 0.0, None, None, None, 8, true).1, 1.0);
}
#[test]
fn log_axes_run_over_whole_powers() {
let mut c = Chart::sample(ChartType::Line);