feat(input): live x follow with relative zoom and mouse panning
This commit is contained in:
1 parent
eaae07d8e0
commit
77e861f79e
10 files changed
+971
-28
No files matched your search
@@ -155,6 +155,17 @@ class Plot {
|
||||
/// Sets an explicit range. Choosing a range is a user decision, so it also
|
||||
/// clears that axis' auto_scale: a later layout() must not snap the range back.
|
||||
/// auto_fit_x()/auto_fit_y() re-arm it.
|
||||
///
|
||||
/// **Under `XFollow::Live` on an x axis that has a live edge, the argument is not
|
||||
/// an absolute window.** It is read as the *span* the window should have and the
|
||||
/// *offset* it implies -- `offset = newest - r.max`, clamped to what the traces
|
||||
/// still retain -- and the visible window follows from those at the live edge of
|
||||
/// the moment. A caller who expects `r` to stay where it was put is in the wrong
|
||||
/// mode: `XFollow::Off` stores the range verbatim, which is what every existing
|
||||
/// caller gets. The point of the reinterpretation is that every gesture reaches
|
||||
/// the x range through this one setter, so pan, wheel, box and axis zoom all keep
|
||||
/// following for free instead of each needing its own special case -- and none of
|
||||
/// them clears the follow state.
|
||||
void set_x_range(u32 axis, Range r);
|
||||
void set_y_range(u32 axis, Range r);
|
||||
|
||||
@@ -162,6 +173,57 @@ class Plot {
|
||||
void set_x_auto_scale(u32 axis, bool on);
|
||||
void set_y_auto_scale(u32 axis, bool on);
|
||||
|
||||
// --- live follow ------------------------------------------------------
|
||||
|
||||
/// Arms or disarms live following on one x axis; see XFollow for what `Live`
|
||||
/// makes the visible window. **Only this disarms it**: no gesture -- pan, wheel,
|
||||
/// box zoom, axis zoom -- and no programmatic `set_x_range` reaches the follow
|
||||
/// state, because "an interaction silently switched off following for good" is
|
||||
/// the defect this state exists to remove (spec §12).
|
||||
///
|
||||
/// Arming an axis that has a live edge places its window at the live edge at
|
||||
/// once, so `x_range()` reports the window the next layout will produce; the span
|
||||
/// it starts from is the span the axis already had, so a caller that wants the
|
||||
/// retained window framed calls `auto_fit_x()`. Turning `Live` off leaves the
|
||||
/// axis exactly where it is: the last live window becomes an ordinary stored
|
||||
/// range, and `auto_scale` is untouched.
|
||||
///
|
||||
/// While an axis is `Live` *and* has a live edge, this path owns its range, and
|
||||
/// that has two consequences worth knowing. `auto_scale` is inert on the axis:
|
||||
/// `layout()` does not fit it, because the fit would overwrite the span the
|
||||
/// follow window is built from -- the user's zoom -- on every frame, and the
|
||||
/// follow path wins because following is the more specific statement about that
|
||||
/// axis (`auto_fit_x()` is how such an axis is framed, and it does fit). And the
|
||||
/// window is recomputed every frame, so its position is a *distance behind the
|
||||
/// newest sample* rather than a pair of numbers. With no live edge -- no trace on
|
||||
/// the axis, or none of them streaming -- nothing is recomputed and the axis
|
||||
/// keeps its stored range, `auto_scale` included, exactly as an `Off` axis does.
|
||||
void set_x_follow(u32 axis, XFollow f);
|
||||
/// The follow state of one x axis. `Off` for an axis that does not exist.
|
||||
XFollow x_follow(u32 axis) const;
|
||||
|
||||
/// How far behind the live edge the visible window's right edge sits, in data
|
||||
/// units. Clamped to `[0, retained span]`: the upper limit is what the traces
|
||||
/// still hold, so the window's right edge never goes behind the oldest sample the
|
||||
/// ring has, and the empty region in front of it -- data the ring dropped -- is
|
||||
/// what the view is left showing rather than a window invented over samples that
|
||||
/// are gone. The lower limit is why dragging toward the live edge stops instead
|
||||
/// of turning the window inside out. On an axis that is not `Live` the value is
|
||||
/// simply stored: it applies when following is armed, and changes nothing before.
|
||||
///
|
||||
/// A value that is not a number is refused outright -- the window it would place
|
||||
/// is not a window -- and leaves the last offset in place.
|
||||
void set_x_offset(u32 axis, f64 units_behind_live);
|
||||
/// The offset in force, as of the last `set_x_offset()`, `set_x_range()` or
|
||||
/// `layout()`. 0.0 for an axis that does not exist.
|
||||
f64 x_offset(u32 axis) const;
|
||||
|
||||
/// The newest x any visible streaming trace bound to `axis` holds: the coordinate
|
||||
/// a `Live` window is anchored to, which the controller uses as the anchor of a
|
||||
/// zoom made from outside `axes`. False -- and `newest` untouched -- when the axis
|
||||
/// has no such trace, i.e. when there is no live edge to follow.
|
||||
bool x_live_edge(u32 axis, f64& newest) const;
|
||||
|
||||
/// Data -> pixels for one axis, built from the axis' range, log flag and the
|
||||
/// current layout. Always invertible: a collapsed range or a collapsed axes
|
||||
/// rect is widened by math::Transform, so to_data_*(to_screen_*(v)) == v.
|
||||
@@ -178,6 +240,11 @@ class Plot {
|
||||
/// auto-fit tries again -- so a stream that starts at zero begins fitting on its
|
||||
/// own once it goes positive. A log axis is therefore never left with `min >=
|
||||
/// max` or a non-positive bound by an auto-fit.
|
||||
///
|
||||
/// Under `XFollow::Live` the fit is also the way back to the live edge: the span
|
||||
/// becomes the retained window's (with the same 2% margin) and the offset returns
|
||||
/// to 0, so the window ends at the newest sample again. It does **not** turn
|
||||
/// following off -- a fit is not how a plot stops following; `set_x_follow()` is.
|
||||
void auto_fit_x();
|
||||
/// Same for one y axis, over that axis' traces' y bounds.
|
||||
void auto_fit_y(u32 axis);
|
||||
@@ -277,6 +344,22 @@ class Plot {
|
||||
/// selects x or y). False when there is no such trace; `out` then retains
|
||||
/// whatever it held.
|
||||
bool trace_bounds_union(bool x_side, u32 axis, Range& out) const;
|
||||
/// The retained window of the visible **streaming** traces bound to one x axis:
|
||||
/// `min` the oldest x they still hold, `max` the newest -- the live edge. False
|
||||
/// when the axis has no such trace, i.e. when there is nothing to follow, and
|
||||
/// `out` then retains whatever it held.
|
||||
bool live_x_bounds(u32 axis, Range& out) const;
|
||||
/// True when the follow path owns `axis`: it is `Live` *and* has a live edge.
|
||||
/// What `autoscale_axes()` asks before fitting, and the only place "a live axis"
|
||||
/// is decided.
|
||||
bool follows_live(u32 axis) const;
|
||||
/// Places a `Live` x axis' window at its live edge -- `[newest - offset - span,
|
||||
/// newest - offset]`, with `span` the span the axis holds and `offset` clamped
|
||||
/// against the retained window first, so the window can never be moved over
|
||||
/// samples the ring has dropped. O(1) and allocation-free: `layout()` runs it on
|
||||
/// every frame. False, and a no-op, when the axis is not `Live`, has no live edge,
|
||||
/// or holds no usable span.
|
||||
bool place_live_x_window(u32 axis);
|
||||
/// Refit every axis that is still on auto scale. layout() runs this, so a
|
||||
/// growing stream stays framed until a caller gesture turns auto scale off.
|
||||
void autoscale_axes();
|
||||
|
||||
Reference in new issue
Block a user