diff --git a/ROADMAP.md b/ROADMAP.md index 7a794e06e..f4d42d21e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -197,6 +197,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | 2026-10-13 | Conditional formatting: a rule gathers range statistics only when it reads them (top/bottom, above average, duplicates, scales, bars, icons) and borrows them instead of copying them per cell, so the per-frame lookup of an expression or cell-value rule no longer visits its whole range (#1098, part 1) | | 2026-10-12 | Next/previous sheet navigation stops at the first/last visible worksheet instead of activating a hidden boundary sheet; the current selection is retained | | 2026-10-21 | Macro engine ([macros.md](docs/macros.md)): typed, declarative macros saved in the workbook (a JSON field; hidden names in xlsx); declared inputs checked with messages that name the cells; tokens (`Input.col(2)`, `Row`, `Output`, `Name:Total`, `{{…}}` in formulas); one undo step with full rollback; a footprint dry run with ask, fail, overwrite, shift down or shift right; a policy that keeps macros away from files and the app, with approval for macros in opened files; Repeat, If and Stop steps; a debugger; a relative recorder; `macro.*` commands beside the unchanged Automate scripts | +| 2026-10-13 | Charts: one combo plot-kind rule for resolving and drawing; ticks of a scale off the step grid keep their own values (#1146) | | 2026-10-12 | Security: the `--control` server requires an auth token (random, kept in a 0600 file or chosen with `GRIDCRAFT_CONTROL_TOKEN`) on each connection's first line and closes bad connections (#777) | | 2026-10-12 | Rows auto-fit oversized fonts: opening a file and whole-row/column formatting grow non-custom rows to their tallest font (#833) | | 2026-10-12 | Number formats: parsing a format code is linear in its length (long runs of `[`, `,` or `/` no longer take quadratic time), and an unclosed `[` is a literal when splitting sections as well as when tokenizing (#810) | diff --git a/crates/chart/src/axis.rs b/crates/chart/src/axis.rs index 8686bf7d9..46fc43f99 100644 --- a/crates/chart/src/axis.rs +++ b/crates/chart/src/axis.rs @@ -25,9 +25,11 @@ impl Scale { (0..=n) .map(|i| { let v = self.min + i as f64 * self.step; - // Snap away accumulated float error. + // Snap away accumulated float error, but only onto a multiple of the step that is + // really that close: a `min` off the step grid must keep its own ticks. let snapped = (v / self.step).round() * self.step; - if snapped.abs() < self.step * 1e-9 { 0.0 } else { snapped } + let v = if (snapped - v).abs() < self.step * 1e-9 { snapped } else { v }; + if v.abs() < self.step * 1e-9 { 0.0 } else { v } }) .collect() } @@ -107,11 +109,6 @@ pub fn nice_scale(lo: f64, hi: f64, force_zero: bool) -> Scale { Scale { min, max, step } } -/// Exact fixed scale (100% stacked axes). -pub(crate) fn fixed_scale(min: f64, max: f64, step: f64) -> Scale { - Scale { min, max, step } -} - /// A number format prepared once and reused for many labels. pub(crate) struct Fmt { general: bool, diff --git a/crates/chart/src/render.rs b/crates/chart/src/render.rs index 2b46c5fbd..35ccf4551 100644 --- a/crates/chart/src/render.rs +++ b/crates/chart/src/render.rs @@ -4,7 +4,7 @@ use std::f32::consts::{PI, TAU}; use gridcraft_model::{Chart, ChartKind, LineDash, QuartileMethod, Theme, TrendlineKind}; -use crate::axis::{Fmt, Scale, clean, fixed_scale, nice_scale}; +use crate::axis::{Fmt, Scale, clean, nice_scale}; use crate::resolve::{MAX_POINTS, MAX_SERIES, MAX_TRENDLINES, series_color}; use crate::trend::{auto_name, fit_trendline, moving_average}; use crate::{ChartData, HAlign, Measure, Prim, Rgba, SeriesData, VAlign}; diff --git a/crates/chart/src/render/cartesian.rs b/crates/chart/src/render/cartesian.rs index 7fae90a9f..2d2afd366 100644 --- a/crates/chart/src/render/cartesian.rs +++ b/crates/chart/src/render/cartesian.rs @@ -4,7 +4,7 @@ use super::lines::{decimate_area, draw_markers, draw_runs}; use super::trend::{draw_trend_labels, draw_trendlines, max_backward, max_forward, max_forward_slots, supports_trendline}; use super::{ Chart, ChartData, ChartKind, Ctx, Fmt, HAlign, LABEL_SIZE, MAX_CATS, MAX_LABELS, MAX_LINE_PTS, MAX_MARKERS, Prim, Scale, SeriesData, TEXT, - VAlign, WHITE, clean, fixed_scale, nice_scale, + VAlign, WHITE, clean, nice_scale, }; // ---------------------------------------------------------------- cartesian (column/bar/line/area/combo) @@ -145,7 +145,7 @@ fn axis_scales(data: &ChartData, fams: &[Fam], sec: &[bool], spans: &[Vec