fix: a fit undoes an x zoom instead of redefining the time window

The previous commit took "fit x sets the window properly" to mean a fit should
frame the data and propagate that as the new time scale. It should not: a fit is
a plot interaction, and the window is also the trigger's event span and how the
samples are stored, so a fit that framed the data could re-plan the rings and
drop the history on a menu click. What a fit means here is the undo of a zoom --
a 2 s window zoomed into reads 2 s again.

`fit_x()` therefore reads `window_s_` and never writes it. Live it takes the
window-control path with the window already set, which re-arms the follow and
parks the newest span at the live edge. Pinned -- a held event, a filling capture
or a paused view -- it restores the pin's full range, which is the snapshot's own
span and may be narrower than the requested window, and it writes the per-row
member axes as well as the link group: `hold_pinned_window()` prefers a member
that differs from `pinned_stated_`, so writing the group alone is reverted to the
zoomed range one frame later. `apply_window_scale()` is folded back into
`apply_time_window()`, which the window control is again the only caller of.

The probe now zooms in before fitting, since otherwise there is nothing to undo,
and reports `pinned=`. `window` and `event_span` must be unchanged on every line.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-10-05 16:26:37 +02:00
1 parent 9888fd9977
commit 3d7e214295
7 files changed
+193 -150

No files matched your search

+24 -23
View File
@@ -657,24 +657,27 @@ void RowSet::unpin_pause_window() {
set_time_window(window_s_);
}
bool RowSet::fit_x(double& span) {
Row& first = row(0);
if (!first.plot || !figure_) return false;
first.plot->auto_fit_x();
const plot::Range fitted = first.plot->x_range(first.axis_x);
const double fitted_span = fitted.span();
if (!std::isfinite(fitted_span) || !(fitted_span > 0.0)) return false;
if (pinned_) {
// Fitting a captured or paused view: the window is the snapshot's own span, and
// `set_time_window()` would re-arm the follow and re-frame on a live edge that a frozen
// view has nothing bound to.
window_s_ = fitted_span;
figure_->set_x_link_range(x_group_, fitted);
} else {
set_time_window(fitted_span);
void RowSet::fit_x() {
if (!figure_) return;
if (!pinned_) {
// Live: the same path the window control and a resume take, with the window it already has.
// That re-arms the follow and parks the newest `window_s_` at the live edge, which is the
// whole of what a live view may show.
set_time_window(window_s_);
return;
}
span = window_s_;
return true;
// Pinned (a held event or a paused view): the pin's full range, and the pin stays. The
// *members* have to be written as well as the group, because `hold_pinned_window()` prefers a
// member that has moved since our own last statement -- that is how it tells a gesture from its
// own echo -- so writing the group alone would be reverted to the zoomed-in member next frame.
const plot::Range pin{-pinned_pre_, pinned_post_};
if (!(pin.span() > 0.0) || !std::isfinite(pin.min) || !std::isfinite(pin.max)) return;
for (int i = 0; i < rows_; ++i) {
Row& r = row(i);
if (r.plot) r.plot->set_x_range(r.axis_x, pin);
}
pinned_stated_ = pin;
figure_->set_x_link_range(x_group_, pin);
}
void RowSet::sync_cursors(int drag_row) {
@@ -788,16 +791,14 @@ void RowSet::pin_event_window(double pre_s, double post_s) {
figure_->set_x_link_range(x_group_, {-pre_s, post_s});
}
bool RowSet::fit_all(double& span) {
void RowSet::fit_all() {
for (int i = 0; i < rows_; ++i) {
Row& r = row(i);
if (r.plot) r.plot->auto_fit_y(r.axis_y);
}
// The x half is `fit_x()`'s exactly -- row 0's data span, stated through the link group so
// every row lands on it -- and sharing it is what keeps the two fits from drifting apart.
// The y fit above has already happened either way: a row 0 that holds nothing still gets
// every row's y scaled, it just has no span to report.
return fit_x(span);
// The x half is `fit_x()`'s exactly -- the zoom undone, the time window untouched -- and
// sharing it is what keeps the two fits from drifting apart.
fit_x();
}
void RowSet::note_y_gutter(double need_px) {