Skip to content

visualdynamics.gui.stage_drag

stage_drag

Dragging the averaging span, the truncation span and the shock windows on the 3-D stage.

The handles are spheres viz.marks puts on each slab's floor edge: the left edge, the center and the right edge, which resize, move and resize, as in the 2-D region. This class turns interactor events into those drags. The averaging and shock drags are settled 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. A truncation drag is kept inside the record's first and last times.

The class is split into a layer that says what a drag means (begin, drag_to, finish) and the VTK code that feeds it. The tests drive the first layer, because a synthetic mouse cannot reach an offscreen interactor. While a drag is live the interactor style is removed, so the camera does not orbit under the handle, and the press is aborted in this class's observer so the style never starts a rotation.

During a drag only the marks are redrawn (their actors are replaced by name, a few hundred points), and the change is committed on release, so the expensive recompute happens once, at the end.

Classes:

Name Description
StageMarksDragger

One per stage plotter, armed with whatever the marks currently

Classes

StageMarksDragger

StageMarksDragger(plotter: Any)

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

Methods:

Name Description
arm_averaging

Arm for an averaging span. preview(averaging) redraws the

arm_truncation

Arm for a truncation span. preview(truncation) redraws the

arm_shocks

Arm for shock windows. preview(shocks) redraws the marks,

begin

Grab the named handle. Returns True when name is a handle

drag_to

The pointer moved: redraw the marks where it asks.

finish

The pointer was released: settle the drag and commit it

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, origin: float = 0.0) -> None

Arm for an averaging span. preview(averaging) redraws the marks, and commit(averaging) is the window's own drag handler. origin is the record's first time: the handles are on the record's clock, and the averaging counts from its beginning.

Source code in src/visualdynamics/gui/stage_drag.py
def arm_averaging(self, averaging: Averaging, sample_rate: float,
                  samples: int, extents, preview, commit,
                  origin: float = 0.0) -> None:
    """Arm for an averaging span. `preview(averaging)` redraws the
    marks, and `commit(averaging)` is the window's own drag handler.
    `origin` is the record's first time: the handles are on the
    record's clock, and the averaging counts from its beginning."""
    self._context = {
        'kind': 'averaging', 'averaging': averaging,
        'rate': float(sample_rate), 'samples': int(samples),
        'extents': tuple(extents), 'preview': preview,
        'commit': commit, 'origin': float(origin)}
arm_truncation
arm_truncation(truncation, first: float, last: float, extents, preview, commit) -> None

Arm for a truncation span. preview(truncation) redraws the marks, and commit(truncation) is the window's own drag handler. first and last are the record's first and last times, which a drag cannot go past.

Source code in src/visualdynamics/gui/stage_drag.py
def arm_truncation(self, truncation, first: float, last: float,
                   extents, preview, commit) -> None:
    """Arm for a truncation span. `preview(truncation)` redraws the
    marks, and `commit(truncation)` is the window's own drag
    handler. `first` and `last` are the record's first and last
    times, which a drag cannot go past."""
    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, origin: float = 0.0) -> None

Arm for shock windows. preview(shocks) redraws the marks, and commit(shocks) stores the settled series. common and limit are passed to core.shocks.drag_settled, and origin is as for arm_averaging.

Source code in src/visualdynamics/gui/stage_drag.py
def arm_shocks(self, shocks, common: bool, limit, extents,
               preview, commit, origin: float = 0.0) -> None:
    """Arm for shock windows. `preview(shocks)` redraws the marks,
    and `commit(shocks)` stores the settled series. `common` and
    `limit` are passed to `core.shocks.drag_settled`, and `origin`
    is as for `arm_averaging`."""
    self._context = {
        'kind': 'shocks', 'shocks': tuple(shocks),
        'common': bool(common), 'limit': limit,
        'extents': tuple(extents), 'preview': preview,
        'commit': commit, 'origin': float(origin)}
begin
begin(name: str, at_seconds: float | None = None) -> bool

Grab the named handle. Returns True when name is a handle of what the dragger is armed for. at_seconds is where the pointer took hold; it defaults to the middle of the span.

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 True when `name` is a handle
    of what the dragger is armed for. `at_seconds` is where the
    pointer took hold; it defaults to the middle of the span."""
    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
    origin = context.get('origin', 0.0)
    if context['kind'] == 'averaging':
        averaging = context['averaging']
        rate = context['rate']
        low = origin + averaging.start_sample(rate) / rate
        high = averaging.stop(rate, origin)
    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 = origin + shock.start, origin + 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 was released: settle the drag and commit it through the shared rules, or put the marks back when the drag asked for something impossible.

Source code in src/visualdynamics/gui/stage_drag.py
def finish(self, seconds: float) -> None:
    """The pointer was released: settle the drag and commit it
    through the shared rules, or put the marks back when the drag
    asked for something 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: