fix: the t0 caption is chrome anchored to the axis, not an annotation in the data
The caption that names the relative x labels' anchor was an `Annotation`, whose anchor is a data coordinate -- so it crawled across the plot under a pan and drifted under a zoom, which is how it was reported. It is now `Plot::set_x_note()`: the bottom row's caption, drawn by the renderer in the axes' bottom-left corner in pixels, on the legend's own backplate. The text stays the host's, because only the host knows that label + caption has to be the exact absolute time (`%.6g`, where `format_time()` would round to three figures). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
e2985aa159
commit
39f30d913f
6 files changed
+47
-45
No files matched your search
+17
-26
@@ -30,8 +30,6 @@ constexpr plot::u32 kRulerBColor = 0x8FA8C8u;
|
||||
/// The measurements' own colour, distinct from both cursor rulers so a readout mark cannot be
|
||||
/// mistaken for something the user placed.
|
||||
constexpr plot::u32 kMeasureColor = 0x9BE8A0u;
|
||||
/// The window-start caption's colour: quiet chrome, brighter than a grid line.
|
||||
constexpr plot::u32 kTimeNoteColor = 0xC8C8C8u;
|
||||
constexpr plot::u32 kTriggerColor = 0xFF5B5Bu;
|
||||
|
||||
/// The smallest difference a `TickFormat::Time` label can *write* at the magnitude `t` -- what
|
||||
@@ -170,11 +168,6 @@ void RowSet::install(Row& r, int /*row_index*/, plot::Plot& p) {
|
||||
r.plot->set_cursor_stroke(r.measure_v[mi], kMeasureStrokeWidth,
|
||||
plot::Vec2{kMeasureDashOn, kMeasureDashOff});
|
||||
}
|
||||
// The window-start caption for the relative x labels: an annotation, hidden until the
|
||||
// labels are relative. Inside the axes on purpose -- its text changes with the window, and
|
||||
// an axis title of a changing width would relayout the plot every frame.
|
||||
r.time_origin_note = r.plot->add_annotation(
|
||||
plot::Annotation{0.0, 0.0, "", plot::Color::hex(kTimeNoteColor), r.axis_x, r.axis_y, false});
|
||||
r.trigger_marker = r.plot->add_cursor(kParked, plot::Color::hex(kTriggerColor));
|
||||
// A marker, not an instrument: it must sit exactly on the edge's time, so its
|
||||
// snap is Free and the controller never rounds it onto a sample.
|
||||
@@ -581,27 +574,25 @@ bool RowSet::update_time_labels() {
|
||||
r.plot->set_x_axis_origin_from_range(r.axis_x, relative_x_);
|
||||
if (!relative_x_) r.plot->set_x_axis_origin(r.axis_x, 0.0);
|
||||
// The caption belongs with the labels, which a figure draws once, on the bottom row (the
|
||||
// one whose x axis is the figure's). Every row gets it parked.
|
||||
// one whose x axis is the figure's). Every other row gets an empty one, which draws nothing.
|
||||
const bool bottom = i == rows_ - 1;
|
||||
if (r.time_origin_note >= r.plot->annotation_count()) continue;
|
||||
plot::Annotation note = r.plot->annotation(r.time_origin_note);
|
||||
note.visible = relative_x_ && bottom;
|
||||
if (note.visible) {
|
||||
// Inside the axes, just above the axis line, at their left edge: the reader adds this
|
||||
// to a tick, so it sits beside the ticks.
|
||||
const plot::Range yr = r.plot->y_range(r.axis_y);
|
||||
note.x = view.min;
|
||||
note.y = yr.min + 0.04 * (yr.span() > 0.0 ? yr.span() : 1.0);
|
||||
char text[64];
|
||||
// The anchor the *drawn* labels were built with: the axis' own origin, which the last
|
||||
// layout set from its range. Reading it back (rather than keeping our own copy) is what
|
||||
// makes the reader's addition exact -- label plus caption is that tick's absolute time.
|
||||
const double shown = r.plot->x_axis_origin(r.axis_x);
|
||||
x_origin_ = shown;
|
||||
std::snprintf(text, sizeof(text), "t0 = %.6g s", shown);
|
||||
note.text = text;
|
||||
if (!relative_x_ || !bottom) {
|
||||
r.plot->set_x_note("");
|
||||
continue;
|
||||
}
|
||||
r.plot->set_annotation(r.time_origin_note, note);
|
||||
char text[64];
|
||||
// The anchor the *drawn* labels were built with: the axis' own origin, which the last
|
||||
// layout set from its range. Reading it back (rather than keeping our own copy) is what
|
||||
// makes the reader's addition exact -- label plus caption is that tick's absolute time.
|
||||
const double shown = r.plot->x_axis_origin(r.axis_x);
|
||||
x_origin_ = shown;
|
||||
std::snprintf(text, sizeof(text), "t0 = %.6g s", shown);
|
||||
// Chrome, not an annotation: an annotation is anchored to a *data* coordinate, so the caption
|
||||
// crawled across the plot as the view panned and drifted as it zoomed -- and it came with the
|
||||
// annotation's own pin cross, which pointed at nothing. `set_x_note()` places it in the axes'
|
||||
// bottom-left corner in pixels, on the legend's backplate, which is where the labels it
|
||||
// qualifies are.
|
||||
r.plot->set_x_note(text);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -106,11 +106,6 @@ class RowSet {
|
||||
/// it keeps its position whatever the cursor toggle says.
|
||||
plot::u32 trigger_marker = 0;
|
||||
|
||||
/// The caption naming the window's start while the x labels are relative: an annotation
|
||||
/// inside the axes, so its text can change every frame without moving the layout (an axis
|
||||
/// *title* would, and it would be a different width each time).
|
||||
plot::u32 time_origin_note = 0;
|
||||
|
||||
/// The measurements' readout marks: a pool created with the plot (a `Plot` cannot add or
|
||||
/// drop a cursor after the fact) and parked unless a measurement is using the slot.
|
||||
std::array<plot::u32, kMeasureMarks> measure_h{};
|
||||
|
||||
+5
-10
@@ -7264,10 +7264,7 @@ gboolean timelabels_probe_step(gpointer user) {
|
||||
ticks += t.label;
|
||||
}
|
||||
}
|
||||
if (last.time_origin_note < last.plot->annotation_count()) {
|
||||
const plot::Annotation& an = last.plot->annotation(last.time_origin_note);
|
||||
if (an.visible) note = an.text;
|
||||
}
|
||||
note = last.plot->x_note();
|
||||
}
|
||||
double first_value = 0.0;
|
||||
plot::Range shown{};
|
||||
@@ -8317,14 +8314,12 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
}
|
||||
{
|
||||
// The x label form: relative to the window's own start (with the caption that names
|
||||
// that start) while the span is sub-second, absolute otherwise. `x_origin_set` is read
|
||||
// back out of the plot, so this is what the axis is actually formatting with.
|
||||
// that start) when the labels could not carry the tick step absolutely, absolute
|
||||
// otherwise. `x_origin_set` is read back out of the plot, so this is what the axis is
|
||||
// actually formatting with, and `note` is the plot's own caption string.
|
||||
const RowSet::Row& rlt = rows.row(rows.row_count() - 1);
|
||||
std::string note;
|
||||
if (rlt.plot != nullptr && rlt.time_origin_note < rlt.plot->annotation_count()) {
|
||||
const plot::Annotation& a = rlt.plot->annotation(rlt.time_origin_note);
|
||||
if (a.visible) note = a.text;
|
||||
}
|
||||
if (rlt.plot != nullptr) note = rlt.plot->x_note();
|
||||
// The labels themselves, from the axis the reader looks at: the assertion is what the
|
||||
// ticks *say*, not only which mode is on.
|
||||
std::string ticks;
|
||||
|
||||
Reference in new issue
Block a user