docs: spec and plan for figure y-gutter alignment, crosshair line and style, and cursor dragging
This commit is contained in:
1 parent
a14f333dbe
commit
663b57c679
2 files changed
+171
No files matched your search
@@ -324,3 +324,44 @@ void clear_crosshair();
|
||||
**Pointer leaving the axes clears it**, and a middle-drag or a wheel keeps it active but re-anchored — the crosshair follows the pointer, so any interaction that does not move the pointer leaves it where the pointer is.
|
||||
|
||||
The controller owns translating motion into `set_crosshair`; the plot owns the data; the renderer owns only painting. Tests: the crosshair x tracks motion and clears on leave; the interpolated value matches a hand-computed interpolation at a non-sample x; the readout lists one row per visible trace including unnamed ones; a `Figure` applies one x to every row; no crosshair ink outside `axes`.
|
||||
|
||||
---
|
||||
|
||||
## 16. Figure rows share one y gutter (defect)
|
||||
|
||||
Reported from use: **"multi plot example, the y axis are not vertically aligned."** The cause is that a `Figure` does nothing to align its rows: each row's `Plot` sizes its own `y_label_area` from its own widest tick label, so rows whose magnitudes differ ("671m" vs "90" vs "2.51") get different strip widths and their `axes` rects start at different x.
|
||||
|
||||
A figure must give its rows a **common left gutter**:
|
||||
|
||||
- Lay each row out once to learn the strip width its own labels need.
|
||||
- Take the maximum across rows, plus the title columns, and re-lay every row out with that width imposed.
|
||||
- All rows' `axes.x` and `axes.w` are then identical, so a vertical line through the figure is a real column of the same x and traces in different rows line up.
|
||||
|
||||
This is the figure's decision, not the plot's: a `Plot` standing alone keeps sizing its own strip from its own data. So the imposition is a figure-level override — for example `Plot::set_min_y_gutter(f32)` consulted by `layout()` — and it composes with the existing title columns (the gutter covers labels plus titles, so §3's column rule still holds). The equality of `axes.x`/`axes.w` across rows is the invariant, and it is asserted rather than eyeballed.
|
||||
|
||||
## 17. Crosshair: a horizontal line where it means something, and a configurable style
|
||||
|
||||
Two reported items.
|
||||
|
||||
**The missing horizontal line.** In a `Figure` the sync path calls the single-coordinate `set_crosshair(x, active)`, which by design draws only the vertical line — and the existing comment's reasoning is sound: rows carry different y scales, so one figure-wide `y` would be a value meaning something different in each row. The resolution is not to invent a figure-wide y but to draw the horizontal line **in the row under the pointer**, where a y is genuine. So the figure's sync learns which row the pointer is in and that row's local y, applies the shared x to every row (vertical lines everywhere, as now) and additionally the y to the hovered row only — one horizontal line, in the row being examined. No row ever shows a y belonging to another row's scale.
|
||||
|
||||
**The style.** The crosshair is a passive hover instrument drawn over the data, so its default must recede: a **thinner** stroke (~0.75 logical px rather than a trace's line width) and a **dashed** stroke, so it reads as chrome rather than as data. Both are configurable:
|
||||
|
||||
```cpp
|
||||
// in Style, alongside the crosshair colour:
|
||||
f32 crosshair_width = 0.75f; // logical px
|
||||
f32 crosshair_dash_on = 4.0f; // logical px of ink
|
||||
f32 crosshair_dash_off = 4.0f; // logical px of gap; 0 disables dashing (solid)
|
||||
```
|
||||
|
||||
Dashing is a **fragment-shader** effect, not new geometry: `kLineVS` already passes `v_along` (distance travelled along the segment in pixels), so the fragment stage discards fragments where `mod(v_along, on + off) >= on`. Vertices stay in data space, no CPU work is added, and the pattern is a uniform — per-draw configurable and free to change per frame. A `Crosshair` may override the style's values so one plot differs from another without touching global style.
|
||||
|
||||
## 18. Cursor dragging is hard and snaps oddly (defect)
|
||||
|
||||
Reported from use: **"moving cursors horizontally is very difficult and they stick to data in a wired way."** Two mechanisms, both real:
|
||||
|
||||
**Snapping during the drag.** `set_cursor_x` applies `NearestSample` snapping on **every** assignment, so a drag does not follow the pointer — it hops between sample positions. On a dense row that quantum is sub-pixel and invisible; on a sparse row, or zoomed in, samples can be tens of pixels apart, so the cursor jumps and appears to stick to the data. The fix is the convention every editor uses: **during a drag the cursor follows the pointer freely, and the snap is applied when the drag ends**, so the drop commits the snapped position. Snapping therefore becomes a property applied at the end of a gesture rather than inside the setter, with the setter keeping a documented free path for programmatic placement. A host that wants snapped motion can still ask for it; the default becomes "smooth while moving, snapped when dropped".
|
||||
|
||||
**Grabbing.** The hit radius is a small logical constant (5 logical px) and a cursor can lose the grab to a region whose edge or body is nearby. Make the grab reliable: a cursor within the radius takes precedence over a region's edge or body at the same point (the region still wins where no cursor is close), and the radius is large enough to hit a 1 px marker comfortably — 6 to 8 logical px, with hover and grab using the same value so the affordance the host sees is the one it gets.
|
||||
|
||||
Both changes are measured, not asserted: the report states the pixel distance at which a grab succeeds, and the cursor's per-move x during a drag on a sparse row before and after.
|
||||
Reference in new issue
Block a user