summaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/main_window.py2
-rw-r--r--ui/strip_chart.py54
2 files changed, 46 insertions, 10 deletions
diff --git a/ui/main_window.py b/ui/main_window.py
index 8f7a3be..616f16d 100644
--- a/ui/main_window.py
+++ b/ui/main_window.py
@@ -304,7 +304,7 @@ class MainWindow(QMainWindow):
self._ctrl_dock.raise_()
def _connect_signals(self):
- self.engine.new_data.connect(self.processor.on_raw_data)
+ self.engine.new_data.connect(self.processor.on_raw_batch)
self.processor.processed_data.connect(self._chart.on_new_data)
self.engine.log_started.connect(lambda p: self._log_lbl.setText(f"● {p}"))
self.engine.log_stopped.connect(lambda p: self._log_lbl.setText(f"✓ {p}"))
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)