Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.9 KiB
Split rows share one y axis -- design
Amends 2026-11-03-horizontal-split-design.md. Where the two disagree, this one governs.
Goal
The cells of a split row share one y range and draw one y axis, the way the rows of the figure share one x window and draw one x axis. A split row reads as one wide plot with dividers.
Decisions
- Always shared, no option. All cells of a figure row have one y range. An unsplit row is unaffected.
- One y axis per row. Only the leftmost cell has a y gutter and y labels. The other cells have none, as only the bottom row carries x labels.
- Rulers are per row. The two horizontal rulers (cursor pair) belong to the figure row and are drawn at the same y in every cell. A drag in any cell moves them.
- Approach. Extend the vendored plotter with a y link, symmetric to the x link. Rejected: syncing the ranges from the host every frame (the plotter's own per-plot autoscale and the controller's gestures would fight it, which is the problem the x link already solves).
Plotter (deps/plotter)
Figure::set_share_row_y(bool), default off so existing figures andtests/test_figure.cppare unchanged. When on, the cells of each figure row form an implicit y link group. The group is rebuilt with the cells.- A y link syncs like the x link: in
layout()a newsync_y_links()adopts a member's changedy_rangeas the group range and pushes it to the other members. Wheel, pan, box zoom and axis drag go throughset_y_range, so they need no controller change. - Auto scale: a member's own autoscale is suppressed (as
Axis::link_memberalready blocksLive). The group owns the auto flag and fits to the union of its members' visible traces; cells with no trace are ignored; a log axis takes the union in axis space.fit_alland double-click fit the group, not one cell. A manualset_y_rangeon a member clears the group's auto flag. YStrip {Both, None}andPlot::set_y_strip_mode, mirroringXStrip.Noneforces the y label width, title width and reserved floor to 0.Figure::layoutsetsNoneon every cell but the first of its row, structurally from the column index, never from content.Nonecells are skipped in the common-gutter maximum.- Cell widths: the row's y gutter is taken off the row width first, then the cells share the rest by
the existing
floor + extra*w/totalrule. Equal weights therefore give equal plot areas; the first cell's rect is gutter plus its plot width, and the cell rects still tile the row exactly. The floors apply to plot widths. - A y axis-drag can only start in the leftmost cell (the others have no label area).
- Documented in
docs/claude/build-and-run.mdwith the other vendored changes.
Host (src/plot_host.*, src/ui*.cpp)
set_y_auto,set_y_range,fit_all, and the Auto/Fixed dialog act on the figure row of the plot they are given. The dialog reads the row's range.- Rulers: one pair per figure row, stored on the row's first plot index and placed in every cell by
place_cursors;sync_cursorsreads the dragged one back;reset_cursorsand the measure level marks use the row's range. The cursors pane lists the pair once per figure row. row_unitjoins the units over all cells of the row, "" if mixed.sync_compare_axeswrites y once per figure row.rebuild_allturns the sharing on for the newFigurebefore the cells are populated.- Readouts and measurements stay per trace and per plot index; only the axis is shared.
- Session file: unchanged (y ranges were never persisted).
Layout editor
Divider drag maps the pointer to weights through plot widths, not cell widths: for the first cell,
subtract the row's gutter from the left rect before applying frac = (want_left-64)/(pair-128). The
96 px logical floor applies to the plot area. Nothing else changes.
Verification
tests/test_figure.cpp: with sharing on, a{2}row has equal y ranges after aset_y_rangeon either cell; a second cell has zero y gutter and the first the common maximum; widths tile exactly and equal weights give equal plot widths; group autoscale covers the union and ignores empty cells; sharing off reproduces today's layout;{1}rows unaffected.- Split probe gains: after a y gesture on one cell the other's range is identical; non-first cells have no y gutter; fit-all covers both cells' traces; both rulers line up across cells; a divider drag still tracks the pointer within 1 px.
- Regression: layout, cursors, measurements, readouts, E-1608 probes read as before; re-baseline only
what this feature explains.
gtk warnings: 0, noGtk-CRITICAL.
Docs
README.md, docs/USER-GUIDE.md, help/C/org.udpscope.app/ (layout page), docs/claude/probe-readings.md,
docs/claude/invariants-plot-and-layout.md (the "one gutter, not per column" rule and the per-cell y
rule), docs/claude/build-and-run.md, ui_context_help.cpp, CLAUDE.md if it mentions per-cell y.