fix: the x labels go relative on what a label can write, not on the span

A `t0 = ...` caption appeared on every sub-second window, including a
triggered one whose axis is already relative to the edge -- so the ticks
were re-anchored away from the one position a triggered axis exists to
show, and the caption had nothing to explain.

The form is now decided by the tick step against `TickFormat::Time`'s own
resolution at the axis' magnitude (three significant figures below a
minute, whole seconds through `m:ss` to a day, three figures of days
above). The span said nothing about that and was wrong both ways: a 2 s
window at twenty minutes of uptime reads `20:00` on every tick and stayed
absolute.

The step is read back from the last layout's majors (a tick value depends
on the range and the axis width, not on the label form); with no ticks yet
the form is left alone rather than guessed, since the hysteresis would
latch a guess. Entering the relative form carries a ULP of slack, because
`step == res` is readable and a bare comparison of two differently
computed doubles flipped the form between frames.

`[timelabels-probe]` and the dump gain `distinct=`, which asserts that no
two major labels read the same -- the thing the relative form is for, and
the only reading that holds whatever the stream's uptime is.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-10-05 18:01:41 +02:00
1 parent b505706d92
commit e2985aa159
4 files changed
+145 -32

No files matched your search

+29 -13
View File
@@ -7246,13 +7246,23 @@ gboolean timelabels_probe_step(gpointer user) {
const RowSet::Row& last = a->rows->row(a->rows->row_count() - 1);
std::string ticks;
std::string note;
// Whether *every* major label on the axis is a different string. That, and not a span
// threshold, is what the relative form exists to buy: two adjacent ticks reading `12.3s` are
// not labels. It is therefore the one assertion that holds whatever the stream's uptime is --
// which decides where the switch actually falls, since the label resolution is a function of
// the axis' magnitude.
bool distinct = true;
if (last.plot != nullptr) {
int n = 0;
std::vector<std::string> seen;
for (const plot::Tick& t : last.plot->x_axis(last.axis_x).ticks) {
if (!t.major) continue;
if (n++ != 0) ticks += ",";
ticks += t.label;
if (n >= 4) break;
if (std::find(seen.begin(), seen.end(), t.label) != seen.end()) distinct = false;
seen.emplace_back(t.label);
if (n++ < 4) {
if (n != 1) ticks += ",";
ticks += t.label;
}
}
if (last.time_origin_note < last.plot->annotation_count()) {
const plot::Annotation& an = last.plot->annotation(last.time_origin_note);
@@ -7271,9 +7281,10 @@ gboolean timelabels_probe_step(gpointer user) {
shown = last.plot->x_range(last.axis_x);
}
std::fprintf(stderr,
"[timelabels-probe] %-12s relative=%d window=%.6g origin=%.6f note=[%s] "
"ticks=[%s] first_value=%.6f first_rel=%.6f range=[%.6f..%.6f]\n",
what, a->rows->time_labels_relative() ? 1 : 0, a->rows->time_window(),
"[timelabels-probe] %-12s relative=%d distinct=%d window=%.6g origin=%.6f "
"note=[%s] ticks=[%s] first_value=%.6f first_rel=%.6f range=[%.6f..%.6f]\n",
what, a->rows->time_labels_relative() ? 1 : 0, distinct ? 1 : 0,
a->rows->time_window(),
a->rows->time_labels_origin(), note.c_str(), ticks.c_str(), first_value,
first_value - (last.plot != nullptr ? last.plot->x_axis_origin(last.axis_x) : 0.0),
shown.min, shown.max);
@@ -8317,13 +8328,18 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
// 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;
bool distinct = true;
if (rlt.plot != nullptr) {
int shown = 0;
std::vector<std::string> seen;
for (const plot::Tick& t : rlt.plot->x_axis(rlt.axis_x).ticks) {
if (!t.major) continue;
if (shown++ != 0) ticks += ",";
ticks += t.label;
if (shown >= 4) break;
if (std::find(seen.begin(), seen.end(), t.label) != seen.end()) distinct = false;
seen.emplace_back(t.label);
if (shown++ < 4) {
if (shown != 1) ticks += ",";
ticks += t.label;
}
}
}
// The labels against the anchor they were built with: the dump runs after the last
@@ -8340,10 +8356,10 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
}
}
}
std::printf("[udpscope] time labels: relative=%d origin=%.6f note=[%s] x_origin_set=%.6f "
"ticks=[%s] first_rel=%.9f\n",
rows.time_labels_relative() ? 1 : 0, rows.time_labels_origin(), note.c_str(),
origin_set, ticks.c_str(), first_value - origin_set);
std::printf("[udpscope] time labels: relative=%d distinct=%d origin=%.6f note=[%s] "
"x_origin_set=%.6f ticks=[%s] first_rel=%.9f\n",
rows.time_labels_relative() ? 1 : 0, distinct ? 1 : 0, rows.time_labels_origin(),
note.c_str(), origin_set, ticks.c_str(), first_value - origin_set);
}
{
std::lock_guard<std::mutex> lock(model.mutex());