Skip to content

visualdynamics.gui.stage_drag

stage_drag

Dragging the stage's averaging span and shock windows.

The handles are spheres viz.marks puts on each slab's floor edge — left edge, centre, right edge: resize, move, resize, the 2-D region's own grammar. This class turns interactor events into those drags and commits them through the same rules the 2-D overlays use (core.averaging.from_span, core.shocks.drag_settled), so the two editors cannot disagree about what a drag means.

Split on purpose into a semantic layer (begin / drag_to / finish) and the VTK plumbing that feeds it: the semantics are what the tests drive, because a synthetic mouse cannot reach an offscreen interactor. While a drag is live the interactor style is stood down, so the camera does not orbit under the handle; the press is also aborted at our observer so the style never begins a rotation.

Dragging previews by redrawing the marks (their actors replace by name, a few hundred points) and commits on release — the modal fit's lesson stands: the expensive recompute happens once, at the end.

Classes:

Name Description
StageMarksDragger

One per stage plotter, armed with whatever the marks mean now.

Classes

StageMarksDragger

StageMarksDragger(plotter: Any)

One per stage plotter, armed with whatever the marks mean now.

Methods:

Name Description
arm_averaging

preview(averaging) redraws the marks; commit(averaging)

arm_truncation

preview(truncation) redraws the marks; commit(truncation)

arm_shocks

preview(shocks) redraws; commit(shocks) stores the

begin

Grab the named handle; returns whether it was one of ours.

drag_to

The pointer moved: redraw the marks where it asks.

finish

The pointer let go: settle and commit through the shared

Source code in src/visualdynamics/gui/stage_drag.py
def __init__(self, plotter: Any) -> None:
    self.plotter: Any = plotter
    self._context: dict[str, Any] | None = None
    self._grab: dict[str, Any] | None = None
    self._style = None
    iren = getattr(plotter, 'iren', None)
    self._iren = getattr(iren, 'interactor', None)
    if self._iren is not None:
        # priority above the interactor style's, so a grabbed press
        # can be aborted before the style starts a camera rotation
        self._tags = [
            self._iren.AddObserver('LeftButtonPressEvent',
                                   self._pressed, 10.0),
            self._iren.AddObserver('MouseMoveEvent',
                                   self._moved, 10.0),
            self._iren.AddObserver('LeftButtonReleaseEvent',
                                   self._released, 10.0)]
Methods:
arm_averaging
arm_averaging(
    averaging: Averaging,
    sample_rate: float,
    samples: int,
    extents,
    preview,
    commit,
) -> None

preview(averaging) redraws the marks; commit(averaging) is the window's own drag handler.

Source code in src/visualdynamics/gui/stage_drag.py
def arm_averaging(self, averaging: Averaging, sample_rate: float,
                  samples: int, extents, preview, commit) -> None:
    """`preview(averaging)` redraws the marks; `commit(averaging)`
    is the window's own drag handler."""
    self._context = {
        'kind': 'averaging', 'averaging': averaging,
        'rate': float(sample_rate), 'samples': int(samples),
        'extents': tuple(extents), 'preview': preview,
        'commit': commit}
arm_truncation
arm_truncation(
    truncation,
    first: float,
    last: float,
    extents,
    preview,
    commit,
) -> None

preview(truncation) redraws the marks; commit(truncation) is the window's own drag handler. first/last are the record's walls, which a drag cannot leave.

Source code in src/visualdynamics/gui/stage_drag.py
def arm_truncation(self, truncation, first: float, last: float,
                   extents, preview, commit) -> None:
    """`preview(truncation)` redraws the marks; `commit(truncation)`
    is the window's own drag handler. `first`/`last` are the
    record's walls, which a drag cannot leave."""
    self._context = {
        'kind': 'truncation', 'truncation': truncation,
        'first': float(first), 'last': float(last),
        'extents': tuple(extents), 'preview': preview,
        'commit': commit}
arm_shocks
arm_shocks(
    shocks, common: bool, limit, extents, preview, commit
) -> None

preview(shocks) redraws; commit(shocks) stores the settled series.

Source code in src/visualdynamics/gui/stage_drag.py
def arm_shocks(self, shocks, common: bool, limit, extents,
               preview, commit) -> None:
    """`preview(shocks)` redraws; `commit(shocks)` stores the
    settled series."""
    self._context = {
        'kind': 'shocks', 'shocks': tuple(shocks),
        'common': bool(common), 'limit': limit,
        'extents': tuple(extents), 'preview': preview,
        'commit': commit}
begin
begin(name: str, at_seconds: float | None = None) -> bool

Grab the named handle; returns whether it was one of ours.

Source code in src/visualdynamics/gui/stage_drag.py
def begin(self, name: str, at_seconds: float | None = None) -> bool:
    """Grab the named handle; returns whether it was one of ours."""
    context = self._context
    if context is None:
        return False
    matched = _HANDLE.match(name or '')
    if matched is None:
        return False
    averaging_kind, truncation_kind, index, role = matched.groups()
    handle_kind = ('averaging' if averaging_kind else
                   'truncation' if truncation_kind else 'shocks')
    if handle_kind != context['kind']:
        return False        # a stale handle from another view
    if context['kind'] == 'averaging':
        averaging = context['averaging']
        rate = context['rate']
        low = averaging.start_sample(rate) / rate
        high = averaging.stop(rate)
    elif context['kind'] == 'truncation':
        truncation = context['truncation']
        low, high = truncation.start, truncation.stop
    else:
        index = int(index)
        if index >= len(context['shocks']):
            return False
        shock = context['shocks'][index]
        low, high = shock.start, shock.stop
    self._grab = {
        'role': role, 'index': None if index is None else int(index),
        'low': low, 'high': high,
        'anchor': (low + high) / 2.0 if at_seconds is None
        else float(at_seconds)}
    return True
drag_to
drag_to(seconds: float) -> None

The pointer moved: redraw the marks where it asks.

Source code in src/visualdynamics/gui/stage_drag.py
def drag_to(self, seconds: float) -> None:
    """The pointer moved: redraw the marks where it asks."""
    if self._grab is None or self._context is None:
        return
    self._context['preview'](self._proposal(seconds))
finish
finish(seconds: float) -> None

The pointer let go: settle and commit through the shared rules — or snap back when the drag asked for the impossible.

Source code in src/visualdynamics/gui/stage_drag.py
def finish(self, seconds: float) -> None:
    """The pointer let go: settle and commit through the shared
    rules — or snap back when the drag asked for the impossible."""
    grab, context = self._grab, self._context
    if grab is None or context is None:
        self._grab = None
        return
    try:
        if context['kind'] in ('averaging', 'truncation'):
            context['commit'](self._proposal(seconds))
            return
        low, high = self._span_for(seconds)
        settled = drag_settled(context['shocks'], grab['index'],
                               low, high, context['common'],
                               context['limit'])
        if settled is None:
            # nothing changed, or no room: the preview may have
            # moved the slab, so the marks go back to the stored
            context['preview'](context['shocks'])
        else:
            context['commit'](settled)
    finally:
        # the commit re-renders, which re-arms through
        # _stage_marks; released after so a half-finished drag
        # can never read a cleared grab
        self._grab = None

Functions: