Skip to content

visualdynamics.gui.truncate_panel

truncate_panel

The truncation parameters, as a table beside the time history.

Two instants are set here — where the kept stretch starts and where it stops — and the record on the plot shows the cut as it is made: the discarded ends grayed over, the kept span between two draggable edges (plot.truncation), the same span-editing grammar as the averaging region because it is the same kind of gesture.

The panel says what the settings are, reports when they move, and its Apply Truncation button makes the truncated record with whatever is set at that moment — the project verb a script calls, the same division of labor as the averaging and filter panels, because it is the same kind of thing: parameters that ride the record.

Classes:

Name Description
TruncatePanel

The parameter table. Edits arrive as a whole Truncation.

Classes

TruncatePanel

TruncatePanel(parent: QWidget | None = None)

Bases: QWidget

The parameter table. Edits arrive as a whole Truncation.

Methods:

Name Description
show_history

Point the panel at a time history and the span set on it.

show_span

Point the panel at a span of time with no record behind it.

set_truncation

Restate the panel from a truncation, without re-emitting.

truncation

The truncation the editors currently describe.

Source code in src/visualdynamics/gui/truncate_panel.py
def __init__(self, parent: QWidget | None = None) -> None:
    super().__init__(parent)
    #: the record the parameters describe, for counting samples
    self._history = None
    #: the record's own first and last instants — the walls the
    #: span cannot leave
    self._first = 0.0
    self._last = 1.0
    #: the least daylight between start and stop: one sample
    #: period, or the boxes' own resolution where that is coarser
    self._gap = 1e-4
    #: the sample period, when the span has no record behind it
    #: to count samples off (an import window read off a preview)
    self._period = 0.0
    #: set while the panel is writing to its own editors, so that
    #: restating a clamped value does not read as a fresh edit
    self._loading = False

    self.title: QLabel = QLabel('Truncate')
    grid = panel_grid(self, self.title)

    self.start_box: DoubleSpinBox = DoubleSpinBox()
    self.start_box.setToolTip(
        'Where the kept stretch starts, on the record’s own clock. '
        'Drag the left edge of the span on the plot to set it by '
        'hand')
    self.stop_box: DoubleSpinBox = DoubleSpinBox()
    self.stop_box.setToolTip(
        'Where the kept stretch stops. Drag the right edge of the '
        'span on the plot to set it by hand')
    for box in (self.start_box, self.stop_box):
        box.setDecimals(4)
        box.setSuffix(' s')

    rows = (('Start', self.start_box), ('Stop', self.stop_box))
    for row, (label, editor) in enumerate(rows, start=1):
        grid.addWidget(QLabel(label), row, 0)
        grid.addWidget(editor, row, 1)

    rule = QFrame()
    rule.setFrameShape(QFrame.Shape.HLine)
    rule.setFrameShadow(QFrame.Shadow.Sunken)
    grid.addWidget(rule, len(rows) + 1, 0, 1, 2)

    # what follows from the two, shown so the cost of the cut is
    # visible while it is being made rather than after the record
    # comes out
    self.derived: dict[str, QLabel] = {}
    derived = (('record', 'Record'), ('kept', 'Kept'),
               ('samples', 'Samples'))
    for key, (_name, value) in add_derived(grid, derived,
                                           len(rows) + 2).items():
        self.derived[key] = value

    # the act itself, where its settings are set (Brandon,
    # 2026-08-28). Disabled while the span covers the whole
    # record — the cut of everything is a copy, and a button that
    # makes one is a button that does nothing. There is no
    # whole-record reset beside it (Brandon, 2026-08-28): the
    # boxes and the drag reach every span, this one included.
    self.apply_button: QPushButton = QPushButton('Apply Truncation')
    self.apply_button.clicked.connect(self.apply_asked.emit)
    grid.addWidget(self.apply_button, len(rows) + 2 + len(derived),
                   0, 1, 2)
    grid.setRowStretch(len(rows) + 3 + len(derived), 1)

    commit_on_enter(self.start_box, self.stop_box)
    self.start_box.valueChanged.connect(self._edited)
    self.stop_box.valueChanged.connect(self._edited)
Methods:
show_history
show_history(history: TimeHistory, truncation: Truncation) -> None

Point the panel at a time history and the span set on it.

The walls come from the history rather than being guessed from the numbers already in the boxes.

Source code in src/visualdynamics/gui/truncate_panel.py
def show_history(self, history: TimeHistory,
                 truncation: Truncation) -> None:
    """Point the panel at a time history and the span set on it.

    The walls come from the history rather than being guessed from
    the numbers already in the boxes.
    """
    import numpy as np

    self._history = history
    abscissa = np.asarray(history.abscissa, dtype=float)
    self._first = float(abscissa[0])
    self._last = float(abscissa[-1])
    if len(abscissa) > 1:
        self._gap = max(float(abscissa[1] - abscissa[0]), 1e-4)
    self.set_truncation(truncation)
show_span
show_span(first: float, last: float, period: float, truncation: Truncation) -> None

Point the panel at a span of time with no record behind it.

The import window: the run is still in its file, so the walls and the sample count come from its clock — first and last instant, and the period between samples — rather than from an abscissa nobody has read yet.

Source code in src/visualdynamics/gui/truncate_panel.py
def show_span(self, first: float, last: float, period: float,
              truncation: Truncation) -> None:
    """Point the panel at a span of time with no record behind it.

    The import window: the run is still in its file, so the walls
    and the sample count come from its clock — first and last
    instant, and the period between samples — rather than from an
    abscissa nobody has read yet.
    """
    self._history = None
    self._period = float(period)
    self._first, self._last = float(first), float(last)
    self._gap = max(self._period, 1e-4)
    self.set_truncation(truncation)
set_truncation
set_truncation(truncation: Truncation) -> None

Restate the panel from a truncation, without re-emitting.

Source code in src/visualdynamics/gui/truncate_panel.py
def set_truncation(self, truncation: Truncation) -> None:
    """Restate the panel from a truncation, without re-emitting."""
    self._loading = True
    try:
        # the walls first, for the same reason every panel here
        # sets its limits first: a spin box clamps to the range it
        # has at the moment it is written
        self.start_box.setRange(self._first, self._last - self._gap)
        self.stop_box.setRange(self._first + self._gap, self._last)
        for box in (self.start_box, self.stop_box):
            box.setSingleStep(
                max((self._last - self._first) / 100.0, self._gap))
        self.start_box.setValue(truncation.start)
        self.stop_box.setValue(truncation.stop)
        self._constrain()
    finally:
        self._loading = False
    self._restate(self.truncation())
truncation
truncation() -> Truncation

The truncation the editors currently describe.

Source code in src/visualdynamics/gui/truncate_panel.py
def truncation(self) -> Truncation:
    """The truncation the editors currently describe."""
    return Truncation(self.start_box.value(), self.stop_box.value())

Functions: