Skip to content

scale, coord, render: axis breaks and folds, marked or not drawn - #53

Merged
timzifer merged 1 commit into
mainfrom
axis-breaks
Sep 22, 2026
Merged

timzifer merged 1 commit into
mainfrom
axis-breaks

Conversation

@timzifer

Copy link
Copy Markdown
Owner

Summary

A linear or time axis can now leave intervals out, and the gap is always marked. See ADR 0083.

  • Breaks: scale.Break(lo, hi) and scale.TimeBreak(from, to). Use one for a chart where a single outlier would flatten every other bar.
  • Folds: scale.Fold(ivs...) and scale.TimeFold(spans...). Use them for many stretches the reader does not want to look at, such as a machine's idle periods. A fold leaves a narrow gap and gets a small slash, never a zigzag.
  • Table helpers: figure.SpansWhere(tbl, "start", "end", "state", "Inaktiv") reads the folds out of a table. SpansFunc, IntervalsWhere and IntervalsFunc cover other tests and numeric axes.
  • Theme: new fields AxisBreakGap, AxisBreakSize, AxisBreakMark, AxisFoldGap and AxisFoldSize, set through theme.AxisBreaks and theme.AxisFolds. BreakSlash (//) is the default and BreakZigzag is the alternative.
  • Dialect: a scale writes "cuts" and "folds". "breaks" is already the colour scale's class boundaries.

How it works

  • Map stays continuous. A value inside a cut maps across the gap and the panel's clip hides it. Invert stays exact, and a bar across the break is still one bar with its middle missing.
  • Render sets the gap. It does this once per render through scale.Breaker. Under any coord other than Cartesian, and on second axes, render switches breaks off, because a break without its mark lies about distances.
  • The Cartesian coord draws the break. It cuts the clip into one rectangle per kept piece and splits the axis and grid lines at the gaps. The marks are drawn after the data, so a bar crossing the break cannot cover them.
  • A cut at an end of the domain trims the axis instead of leaving a gap with nothing on its far side.
  • Ticks use one step for the length the axis still shows, e.g. 0, 5, 10 | 90, 95, 100.
  • Lookup is a binary search, so a fold costs nothing per row. BenchmarkFolded1k and BenchmarkFolded100k both come out at 102 allocs and are now in the allocation gate.
  • The a11y description names the omitted intervals.

What does not change

  • A chart without breaks draws exactly what it drew. Every existing golden file matches, and gallery -check reports the documentation figures up to date.
  • New gallery plates: axis-break, axis-break-zigzag and machine-folds.
  • New goldens: axis-break-slash, axis-break-zigzag, axis-break-time and axis-folds.

Known cost

A broken chart's clip is several rectangles, so gg masks it instead of scissoring it. Only charts with a break pay this.

Test plan

  • go test ./... in the core and in backend/gg, including the gallery tests
  • -race on scale, coord, render and the root package; a parallel facet with a break is byte-identical to the serial one
  • The core's dependency check prints nothing
  • The allocation gate is flat for BenchmarkFolded1k against BenchmarkFolded100k

🤖 Generated with Claude Code

A linear or time axis can now leave intervals out. scale.Break and
scale.TimeBreak take one interval out, for a chart where one value would
flatten the rest. scale.Fold and scale.TimeFold take many out, for
stretches the reader does not want to see, such as a machine's idle
periods. figure.SpansWhere reads those periods out of a table.

Map stays continuous. A value inside a cut maps across the gap and the
panel's clip hides it, so Invert is exact and a bar across a break is
still one bar with its middle missing. The gap is a device length that
render sets through scale.Breaker. It is switched off under any coord
that cannot mark a break, and on second axes, because a break without
its mark lies about distances. Only Cartesian marks one: it cuts the
clip into one rectangle per kept piece, splits axis and grid lines, and
draws a slash or a zigzag over the data. A cut that reaches an end of
the domain trims the axis.

Ticks use one step for the length the axis still shows. Lookup is a
binary search, so an axis with hundreds of folds allocates nothing per
row (BenchmarkFolded1k/100k are in the allocation gate). The dialect
spells cuts and folds as "cuts" and "folds". The accessible description
names them. Charts without breaks draw exactly what they drew; every
golden file and documentation figure is unchanged.

See ADR 0083.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@timzifer
timzifer merged commit 1e87b0b into main Sep 22, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant