docs: split rows share one y axis

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-04 01:04:21 +01:00
1 parent b8177cc25a
commit fa9f0c6da9
8 files changed
+86 -40

No files matched your search

+27 -14
View File
@@ -123,27 +123,40 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
y), and the **vertical cursor pair is one shared time drawn in every cell** (`cursors_aligned`).
A split is an arrangement of more plots, not a second time scale: nothing about the time axis changes
(see [invariants-time-axis-and-window.md](invariants-time-axis-and-window.md)).
- **Per-cell y.** Each cell has its own y axis (auto or fixed), its own horizontal rulers and its own
fit; a wheel or axis drag acts on the cell under the pointer, an x gesture on any bottom-row cell
moves the shared window. The cursors pane's ruler label is `plot 2a` for a plot of a split row and
`row N` (the plot index) for one of an unsplit row, so the pane reads the same as before until a row
is split.
- **One y per figure row.** The cells of a figure row share one y range (the plotter's
`Figure::set_share_row_y(true)`, which `rebuild_all()` sets before the cells are populated): one y
axis (auto or fixed), one pair of horizontal rulers, one fit. A wheel or axis drag in any cell moves
the row, an x gesture on any bottom-row cell moves the shared window. The host therefore acts on the
**figure row** of a plot it is given (`set_y_auto`, `set_y_range`, `fit_all`, the Auto/Fixed dialog,
`row_unit`, which joins the units of every cell and reads "" if they differ). The rulers are stored on
the row's first plot index, copied to the other cells by `sync_cursors()` / `insert_cell()` /
`rebuild_all()` and placed in every cell by `place_cursors()`. The cursors pane lists **one ruler line
per figure row**: `row N` with N the 1-based figure row for a split row, and the 0-based plot index
for an unsplit row (the pane read that before splitting existed). Readouts and measurements stay per
trace and per plot index; only the axis is shared.
- **Bottom-row x labels only.** The plotter gives every plot that is not in the last figure row
`XStrip::None`; the cells of the bottom row each keep their strip, so a middle-drag on the axis
works in any of them. The `t0` caption goes on those cells too.
- **One left gutter.** The y gutter is one maximum for the whole figure with the same reserved floor
as before (`Figure::set_min_y_gutter()`), **not a per-column one**: a per-column gutter would make a
cell's width depend on its labels, which "layout must not depend on content" forbids. A cell whose y
labels are narrower than the gutter simply has a wider margin.
- **Widths are weights with a floor.** The plotter keeps 64 px logical per cell and a column gap
(`kColGap`, 6 px); the editor's drag is stricter (below). A weight is a share of what is
left *after* the floors, so cells of `{3, 1}` read 533:220 in a 756 px figure, not 3:1, and the
editor's 96 px drag floor is a floor on the *width*, not on the share. Because a cell's width is
- **One left gutter, on the first cell only.** The y gutter is one maximum for the whole figure with the
same reserved floor as before (`Figure::set_min_y_gutter()`). It is carried by the first cell of each
row alone: `Figure::layout` gives every other cell `YStrip::None` from its column index (structurally,
never from what it holds), which forces its y label width, title width and reserved floor to 0. A
`None` cell contributes 0 to the common maximum, so a split row's non-first cells cannot widen the
gutter and a row's gutter does not depend on how many cells it has. A per-column gutter sized by the
labels would make a cell's width depend on its content, which "layout must not depend on content"
forbids. A row whose labels are narrower than the gutter simply has a wider margin. The weights apply
to **plot widths**, not cell rects (below).
- **Widths are weights with a floor.** The plotter keeps 64 px logical per plot and a column gap
(`kColGap`, 6 px); the editor's drag is stricter (below). The floors and the weights act on **plot widths**: the row's y gutter (carried by the first
cell) is taken off the row width first, then the cells share the rest, so equal weights give equal plot
areas and the first cell's rect is its gutter plus its plot width. A weight is a share of what is
left *after* the gutter and the floors, so cells of `{3, 1}` read 546:207 (rects) in a 756 px
figure, not 3:1, and the editor's 96 px drag floor is a floor on the plot *width*, not on the share. Because a cell's width is
`64 + extra * weight / total` and not proportional to its weight, `layout_col_apply()` does not scale
the pair's weights by the width ratio: it keeps the pair's weight sum `S` from the drag's *start*
(`layout_col_drag_start`) and gives the left cell `S * (want_left - 64) / (pair - 128)`, clamped to
`[0.001, 0.999]` (the plotter refuses non-positive weights; `0.5` if the pair is not wider than 128).
`want_left` is the pointer's width clamped to `[96, pair - 96]` logical px. The divider therefore
`want_left` and the pair are plot widths (`layout_col_drag_begin` subtracts the first cell's `y_axis_width + y_title_width` from its rect); `want_left` is the pointer's width clamped to `[96, pair - 96]` logical px. The divider therefore
follows the pointer, the shrinking cell reads 96 px at the extreme and the pair keeps its total width
(the split probe's `col-drag` line asserts all three). `kPlotterMinCellPx` (64) mirrors the plotter's
`kMinCellWidthLogical`; both are logical px (the plotter scales it by `ui_scale`, which is