From ba94976bf2f6df21746bec74b2715b99b42d2667 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:15:36 +0200 Subject: [PATCH] Fix scatter X axis ticks looping forever on data near the f64 maximum (#1707) --- crates/engine/src/cmd/graph.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/graph.rs b/crates/engine/src/cmd/graph.rs index fd5fbee7..929504aa 100644 --- a/crates/engine/src/cmd/graph.rs +++ b/crates/engine/src/cmd/graph.rs @@ -890,13 +890,14 @@ fn generate(d: &mut Document, g: &GraphSpec) -> Vec> { } } else { // Scatter: a horizontal value axis too. - let (xl, xh, xs) = nice_axis(if xlo == f64::MAX { 0.0 } else { xlo.min(0.0) }, if xhi == f64::MIN { 1.0 } else { xhi }, g.ticks); - let mut x = xl; - while x <= xh + xs * 1e-6 { + // Like the value axis: finite, and a counted (capped) list of ticks, so data near f64::MAX can't loop forever. + let x_lo = if xlo == f64::MAX { 0.0 } else { xlo.min(0.0) }; + let x_hi = if xhi == f64::MIN { 1.0 } else { xhi }; + let (xl, xh, xs) = value_axis(g.ticks, None, None, x_lo, x_hi); + for x in tick_values(xl, xh, xs) { let q = r.x0 + (x - xl) / (xh - xl) * r.width(); axes.push(b.line(Point::new(q, r.y1), Point::new(q, r.y1 + 4.0))); axes.push(b.text(Role::Value, Point::new(q, r.y1 + 4.0 + vs * 1.1), &fmt_value(x), Justify::Center)); - x += xs; } for (si, s) in (0..nser).step_by(2).enumerate() { let pts: Vec> = (0..ncat) @@ -2043,6 +2044,21 @@ mod tests { assert!((tallest(&s) - 74.0 * 10488.0 / 12000.0).abs() > 1.0); } + #[test] + fn a_scatter_x_axis_that_overflows_does_not_hang() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 800, "height": 800})).unwrap(); + // Finite x values whose rounded axis end is infinite: this used to add ticks forever. + let csv = ",y1,x1\nP,1,1.7e308\nQ,2,1.5e308"; + let axes = |s: &Session, id: NodeId| group(s.doc().unwrap().doc.node(id).unwrap(), "Axes").children().unwrap().len(); + let id = make(&mut s, "scatter", csv); + assert!(axes(&s, id) < 100, "{}", axes(&s, id)); + // The same data through Graph Data, on an ordinary scatter graph. + let id = make(&mut s, "scatter", ",y1,x1\nP,1,10\nQ,2,20"); + s.execute("graph.setData", &json!({"id": id.0, "csv": csv})).unwrap(); + assert!(axes(&s, id) < 100, "{}", axes(&s, id)); + } + #[test] fn tick_values_end_on_the_maximum_and_never_run_away() { // Counted, not accumulated: an offset range keeps its last tick.