Add global time window control to Plot tab (1–24 h)

A spinbox above the plot grid sets the visible x range for all 8 plots.
Data outside the window is excluded from each redraw; x axis is fixed to
[now - window, now]. Setting is persisted in plot_settings.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 23:40:41 +02:00
co-authored by Claude Sonnet 4.6
parent dfdf6a550e
commit 02814782bf
+36 -5
View File
@@ -15,6 +15,7 @@ import json
import socket import socket
import sys import sys
import threading import threading
import time
from collections import deque from collections import deque
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@@ -348,6 +349,22 @@ class PlotTab(QWidget):
main_layout.setContentsMargins(4, 4, 4, 4) main_layout.setContentsMargins(4, 4, 4, 4)
main_layout.setSpacing(4) main_layout.setSpacing(4)
# ── toolbar ───────────────────────────────────────────────────────────
tb = QWidget()
tb.setMaximumHeight(32)
tb_row = QHBoxLayout(tb)
tb_row.setContentsMargins(0, 0, 0, 0)
tb_row.setSpacing(6)
tb_row.addWidget(QLabel("Time window:"))
self._tw_spin = QSpinBox()
self._tw_spin.setRange(1, 24)
self._tw_spin.setSuffix(" h")
self._tw_spin.setFixedWidth(72)
self._tw_spin.valueChanged.connect(self._on_time_window_changed)
tb_row.addWidget(self._tw_spin)
tb_row.addStretch()
main_layout.addWidget(tb)
# ── data ────────────────────────────────────────────────────────────── # ── data ──────────────────────────────────────────────────────────────
self._traces: list[list[str]] = [[] for _ in range(self.NUM_PLOTS)] self._traces: list[list[str]] = [[] for _ in range(self.NUM_PLOTS)]
self._saved_traces: list[list[str]] = self._load_settings() self._saved_traces: list[list[str]] = self._load_settings()
@@ -540,9 +557,10 @@ class PlotTab(QWidget):
def _save_settings(self): def _save_settings(self):
try: try:
_SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True) _SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True)
_SETTINGS_PATH.write_text( _SETTINGS_PATH.write_text(json.dumps({
json.dumps({"traces": [list(t) for t in self._traces]}, indent=2) "traces": [list(t) for t in self._traces],
) "time_window": self._tw_spin.value(),
}, indent=2))
except OSError: except OSError:
pass pass
@@ -552,13 +570,24 @@ class PlotTab(QWidget):
saved = data.get("traces", []) saved = data.get("traces", [])
while len(saved) < self.NUM_PLOTS: while len(saved) < self.NUM_PLOTS:
saved.append([]) saved.append([])
tw = int(data.get("time_window", 1))
self._tw_spin.blockSignals(True)
self._tw_spin.setValue(max(1, min(24, tw)))
self._tw_spin.blockSignals(False)
return [list(t) for t in saved[: self.NUM_PLOTS]] return [list(t) for t in saved[: self.NUM_PLOTS]]
except (OSError, json.JSONDecodeError, KeyError): except (OSError, json.JSONDecodeError, KeyError, AttributeError):
return [[] for _ in range(self.NUM_PLOTS)] return [[] for _ in range(self.NUM_PLOTS)]
# ── drawing ─────────────────────────────────────────────────────────────── # ── drawing ───────────────────────────────────────────────────────────────
def _on_time_window_changed(self):
self._save_settings()
self._redraw()
def _redraw(self): def _redraw(self):
now = time.time()
cutoff = now - self._tw_spin.value() * 3600
for i, pw in enumerate(self._plot_widgets): for i, pw in enumerate(self._plot_widgets):
pi = pw.getPlotItem() pi = pw.getPlotItem()
pi.clear() pi.clear()
@@ -569,7 +598,7 @@ class PlotTab(QWidget):
for j, key in enumerate(self._traces[i]): for j, key in enumerate(self._traces[i]):
if key not in self._series: if key not in self._series:
continue continue
series = self._series[key] series = [(t, v) for t, v in self._series[key] if t >= cutoff]
if not series: if not series:
continue continue
times, values = zip(*series) times, values = zip(*series)
@@ -580,6 +609,8 @@ class PlotTab(QWidget):
name=label, name=label,
) )
pw.setXRange(cutoff, now, padding=0.02)
# ── cursor tracing ──────────────────────────────────────────────────────── # ── cursor tracing ────────────────────────────────────────────────────────
def _on_mouse_move(self, evt, plot_idx: int): def _on_mouse_move(self, evt, plot_idx: int):