fix(plot,render,glfw): a hue-neutral crosshair, an em-dash placeholder, one layout per figure event, and a usable readout switch

This commit is contained in:
Martino Ferrari committed 2026-09-28 19:38:30 +02:00
1 parent 10d5129fbf
commit a14f333dbe
10 files changed
+175 -70

No files matched your search

+19 -4
View File
@@ -130,8 +130,11 @@ struct CrosshairRow {
f64 y = 0.0;
/// The trace's name, or the positional placeholder of an unnamed one.
std::string label;
/// The formatted value, written in the trace's y axis' own tick format (`Axis::format_value()`,
/// so a readout reads exactly like the labels beside it), and empty when `!valid`.
/// What the row's value column shows. For a `valid` row it is the formatted number, written in
/// the trace's y axis' own tick format (`Axis::format_value()`, so a readout reads exactly like
/// the labels beside it); for a row whose trace has **no sample at the crosshair's x** it is
/// `kCrosshairNoValue`, the em dash. The absence is therefore *legible* -- a blank cell reads as
/// "still loading" rather than as "nothing here" -- and nothing numeric is fabricated for it.
std::string value;
/// The trace's intersection with the crosshair, in framebuffer pixels: the pen the
/// renderer draws the marker at. Placed through the *trace's own* axis pair, so the
@@ -143,8 +146,10 @@ struct CrosshairRow {
/// The label's line box: its top-left is the pen the run is staged at and its height one
/// text line. Empty when `!listed`.
Rect label_box{};
/// The value's line box, one gutter (the legend's padding) after the label's. Empty when
/// `!listed` or `!valid`: a row with no value stages no run.
/// The value's line box, right-aligned on the panel's inner edge and one gutter's worth of
/// measurement away from the label's own box. Empty when `!listed`: a row that is not in the panel
/// stages neither run, while a listed row always stages something -- the number, or
/// `kCrosshairNoValue`.
Rect value_box{};
};
@@ -185,6 +190,16 @@ struct CrosshairLayout {
bool empty() const noexcept { return !active; }
};
/// What a listed readout row shows in its value column when its trace has **no sample at the
/// crosshair's x**: an em dash, not a blank.
///
/// The row is listed either way -- the trace *is* under the pointer, and a panel that grew and
/// shrank with the pointer would be worse to read than one that says "nothing here" -- so the
/// absence has to be *said*: a blank value cell reads as a value that has not arrived yet. The em
/// dash is in the embedded font's coverage (spec §2 covers U+2013/U+2014), it is not a number, and
/// it cannot be mistaken for one.
inline constexpr const char* kCrosshairNoValue = "\u2014";
/// The label an **unnamed** trace's readout row carries: `#` and the trace's position, e.g.
/// `#2`.
///
+6
View File
@@ -567,6 +567,12 @@ class Plot {
/// calls this when the pointer leaves the axes.
void clear_crosshair() noexcept { crosshair_.active = false; }
/// Switches the readout panel off -- or back on -- without moving the crosshair: the lines and
/// the per-trace markers stay, the panel is not measured and no row is listed. A host that wants
/// the pointer's column marked but not a panel over its data sets this once; it is deliberately
/// **not** part of `set_crosshair()`, so placing a hover cannot silently change it.
void set_crosshair_readout(bool on) noexcept { crosshair_.show_readout = on; }
/// The crosshair's state: where it is, which lines it draws and whether the readout is
/// on. Read-only -- `set_crosshair()` and `clear_crosshair()` are how it moves -- so the
/// coordinates it holds and the geometry `layout()` places from them cannot disagree.
+12 -7
View File
@@ -41,13 +41,18 @@ struct Style {
Color axis_text = Color::hex(0xC8D0DC);
Color cursor = Color::hex(0xFFB020);
Color measure_fill = Color::hex(0xFFB020, 0.15f);
/// The crosshair's colour: its two lines, its markers' own colour excepted, and the
/// readout panel's border (spec §15). Deliberately **not** `cursor`: the cursor accent is
/// an orange that a caller's own cursors, a region's bounds and its fill all already use,
/// while the crosshair is a passive hover instrument that has to read as *chrome* over a
/// dense trace rather than as another handle to grab. A pale cyan does that against the
/// dark default theme, and it is a colour no default element of a frame shares.
Color crosshair = Color::hex(0xB8E8FF);
/// The crosshair's colour: its two lines and the readout panel's border (spec §15). Markers are
/// deliberately *not* this colour -- they carry each trace's own, because a marker says *which*
/// trace and the line says *where*.
///
/// **Hue-neutral by design**, and that is the whole choice: a saturated colour here would be
/// confusable with a trace, and the library's own default trace colour is a saturated cyan
/// (`Trace`'s default, 0x40C0FF) that a hue-based reading cannot tell from a cyan crosshair --
/// only lightness would separate them, which no reader can rely on. A near-white can never be a
/// saturated trace of *any* hue, and it is still plainly not `cursor`'s orange, which is what
/// the spec asks for. The 1 px stroke is kept: form (a full-height thin line over a polyline) is
/// already distinct, so no dashes.
Color crosshair = Color::hex(0xE6EBF2);
// --- the scale ----------------------------------------------------------