diff options
Diffstat (limited to 'ui/strip_chart.py')
| -rw-r--r-- | ui/strip_chart.py | 54 |
1 files changed, 45 insertions, 9 deletions
diff --git a/ui/strip_chart.py b/ui/strip_chart.py index 5a1d1e9..a212e5c 100644 --- a/ui/strip_chart.py +++ b/ui/strip_chart.py @@ -14,7 +14,7 @@ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QDoubleSpinBox, QPushButton, QSizePolicy, ) -from PyQt6.QtCore import Qt, pyqtSlot +from PyQt6.QtCore import Qt, pyqtSlot, QTimer from PyQt6.QtGui import QFont try: @@ -52,6 +52,22 @@ _STYLES = { } +def _windowed(times_dq, values_dq, seconds: float): + """Last `seconds` of a (times, values) deque pair, walking in from the + newest sample only — same trick as ChannelBuffer.window(), needed here + for derived-channel buffers which aren't wrapped in ChannelBuffer.""" + if not times_dq: + return [], [] + cutoff = times_dq[-1] - seconds + out_t, out_v = [], [] + for t, v in zip(reversed(times_dq), reversed(values_dq)): + if t < cutoff: + break + out_t.append(t); out_v.append(v) + out_t.reverse(); out_v.reverse() + return out_t, out_v + + class StripChartWidget(QWidget): def __init__(self, engine: AcquisitionEngine, registry: DeviceRegistry, processor: SignalProcessor): @@ -63,9 +79,18 @@ class StripChartWidget(QWidget): self._cfg: Optional[LayoutConfig] = None # (dev_id, ch_id) → [{curve, plot, spec}] self._curves: Dict[Tuple, List[dict]] = {} + # Time-axis plots, scrolled continuously by _scroll_timer rather than + # only when a new sample lands — decouples the visible scroll rate + # from the (much slower) poll rate so it doesn't look stepped. + self._time_plots: List = [] self._build() self.apply_layout(build_default_layout(registry, processor)) + self._scroll_timer = QTimer(self) + self._scroll_timer.setInterval(33) # ~30 fps + self._scroll_timer.timeout.connect(self._on_scroll_tick) + self._scroll_timer.start() + def _build(self): lay = QVBoxLayout(self); lay.setContentsMargins(6,6,6,4); lay.setSpacing(4) ctrl = QHBoxLayout() @@ -89,7 +114,7 @@ class StripChartWidget(QWidget): self._cfg = cfg self._win.setValue(cfg.time_window_s) if not _HAS_PG: return - self._gw.clear(); self._curves.clear() + self._gw.clear(); self._curves.clear(); self._time_plots.clear() if not cfg.panes: return mf = QFont("IBM Plex Mono", 8) @@ -130,6 +155,7 @@ class StripChartWidget(QWidget): plot.setXLink(ref_plot) if spec.x_source == "time": ref_plot = plot + self._time_plots.append(plot) if cfg.show_legend: t = _current_theme @@ -182,7 +208,8 @@ class StripChartWidget(QWidget): if device_id == "derived": bufs = self.processor.get_derived_buffer(channel_id) if bufs is None or len(bufs[0]) < 2: return - ts_l = list(bufs[0]); vs_l = list(bufs[1]) + ts_l, vs_l = _windowed(bufs[0], bufs[1], window) + if not ts_l: return else: buf = self.engine.get_buffer(device_id, channel_id) if buf is None or len(buf) < 2: return @@ -193,13 +220,11 @@ class StripChartWidget(QWidget): spec = e["pane"] # Determine X axis data if spec.x_source == "time": - cutoff = ts_l[-1] - window if ts_l else 0 - idx = next((i for i,t in enumerate(ts_l) if t >= cutoff), 0) - x_arr = np.array(ts_l[idx:], dtype=np.float64) - y_arr = np.array(vs_l[idx:], dtype=np.float64) + x_arr = np.array(ts_l, dtype=np.float64) + y_arr = np.array(vs_l, dtype=np.float64) e["curve"].setData(x_arr, y_arr) - if x_arr.size: - e["plot"].setXRange(x_arr[-1]-window, x_arr[-1], padding=0) + # X-range scrolling is handled by _scroll_timer, continuously — + # not here, so the window doesn't jump only on data arrival. else: # X = another channel's processed values parts = spec.x_source.split("/") @@ -227,3 +252,14 @@ class StripChartWidget(QWidget): def _on_pause(self, c: bool): self._paused = c self._pause.setText("▶ Resume" if c else "⏸ Pause") + + def _on_scroll_tick(self): + """Scroll time-axis plots at a steady UI rate, independent of the + (much slower) poll rate — keeps the window sliding smoothly instead + of jumping once per data tick.""" + if self._paused or not _HAS_PG or not self._cfg or not self.engine.is_running(): + return + now = self.engine.elapsed_now() + window = self._cfg.time_window_s + for plot in self._time_plots: + plot.setXRange(now - window, now, padding=0) |
