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

+17 -13
View File
@@ -1305,9 +1305,10 @@ void Plot::build_crosshair() {
row.label_box = Rect{};
row.value_box = Rect{};
row.label.assign(trace.name.empty() ? crosshair_unnamed_label(i) : trace.name);
row.value.clear();
// The value, and with it the marker: a trace with no sample at this x writes no number
// and gets no marker, rather than a value clamped to the end of its series.
// The value: the interpolated number where the trace has a sample at this x, and the em dash
// where it does not. The row is listed either way, so the absence has to be *said* -- a blank
// value cell reads as a value that has not arrived -- and the dash is measured into the panel's
// width like any other value, so the row's boxes are placed by one rule.
if (crosshair_value(i, crosshair_.x, row.y)) {
row.valid = true;
char value[32];
@@ -1319,6 +1320,8 @@ void Plot::build_crosshair() {
// actually drawn however its axes are scaled.
row.marker = Vec2{static_cast<f32>(x_transform(trace.axis_x).to_screen_x(crosshair_.x)),
static_cast<f32>(y_transform(trace.axis_y).to_screen_y(row.y))};
} else {
row.value.assign(kCrosshairNoValue);
}
}
crosshair.rows.resize(count);
@@ -1328,13 +1331,13 @@ void Plot::build_crosshair() {
if (count == 0u || !crosshair_.show_readout || text_ == nullptr) return;
const ReadoutChrome chrome = readout_chrome(legend_, style_, *text_);
// Each row's content: its label, the gutter, and its value when it has one -- an invalid
// row is narrower, and the panel is sized by the widest row that will be listed, which the
// prefix-max reads in O(1) after a row was dropped from the end.
// Each row's content: its label, the gutter, and its value -- which is a number or the em dash,
// so every row is the same two runs and the panel is sized by the widest *listed* one, read in
// O(1) by the prefix-max after a row was dropped from the end.
crosshair_content_px_.clear();
for (const CrosshairRow& row : crosshair.rows) {
f32 content = text_->measure(row.label.c_str());
if (row.valid) content += chrome.pad + text_->measure(row.value.c_str());
const f32 content =
text_->measure(row.label.c_str()) + chrome.pad + text_->measure(row.value.c_str());
crosshair_content_px_.push_back(crosshair_content_px_.empty()
? content
: std::max(crosshair_content_px_.back(), content));
@@ -1393,11 +1396,12 @@ void Plot::build_crosshair() {
row.swatch = readout_swatch(chrome, crosshair.panel, i);
const Vec2 pen = readout_text_pen(chrome, crosshair.panel, i);
row.label_box = Rect{pen.x, pen.y, text_->measure(row.label.c_str()), chrome.line_h};
if (row.valid) {
const f32 width = text_->measure(row.value.c_str());
row.value_box =
Rect{crosshair.panel.right() - chrome.pad - width, pen.y, width, chrome.line_h};
}
// Every listed row stages a value run: the number, or `kCrosshairNoValue` where the trace has
// no sample here. A blank cell would read as a value still to come, which is a different thing
// from "nothing at this x".
const f32 width = text_->measure(row.value.c_str());
row.value_box =
Rect{crosshair.panel.right() - chrome.pad - width, pen.y, width, chrome.line_h};
}
}