From 02814782bf05f6eb20ac06f0824ea42dec033d95 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Mon, 25 May 2026 23:40:41 +0200 Subject: [PATCH] =?UTF-8?q?Add=20global=20time=20window=20control=20to=20P?= =?UTF-8?q?lot=20tab=20(1=E2=80=9324=20h)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- gui_client.py | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/gui_client.py b/gui_client.py index 97cfa34..a01afe4 100644 --- a/gui_client.py +++ b/gui_client.py @@ -15,6 +15,7 @@ import json import socket import sys import threading +import time from collections import deque from datetime import datetime from pathlib import Path @@ -348,6 +349,22 @@ class PlotTab(QWidget): main_layout.setContentsMargins(4, 4, 4, 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 ────────────────────────────────────────────────────────────── self._traces: list[list[str]] = [[] for _ in range(self.NUM_PLOTS)] self._saved_traces: list[list[str]] = self._load_settings() @@ -540,25 +557,37 @@ class PlotTab(QWidget): def _save_settings(self): try: _SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True) - _SETTINGS_PATH.write_text( - json.dumps({"traces": [list(t) for t in self._traces]}, indent=2) - ) + _SETTINGS_PATH.write_text(json.dumps({ + "traces": [list(t) for t in self._traces], + "time_window": self._tw_spin.value(), + }, indent=2)) except OSError: pass def _load_settings(self) -> list[list[str]]: try: - data = json.loads(_SETTINGS_PATH.read_text()) - saved = data.get("traces", []) + data = json.loads(_SETTINGS_PATH.read_text()) + saved = data.get("traces", []) while len(saved) < self.NUM_PLOTS: 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]] - except (OSError, json.JSONDecodeError, KeyError): + except (OSError, json.JSONDecodeError, KeyError, AttributeError): return [[] for _ in range(self.NUM_PLOTS)] # ── drawing ─────────────────────────────────────────────────────────────── + def _on_time_window_changed(self): + self._save_settings() + self._redraw() + def _redraw(self): + now = time.time() + cutoff = now - self._tw_spin.value() * 3600 + for i, pw in enumerate(self._plot_widgets): pi = pw.getPlotItem() pi.clear() @@ -569,7 +598,7 @@ class PlotTab(QWidget): for j, key in enumerate(self._traces[i]): if key not in self._series: continue - series = self._series[key] + series = [(t, v) for t, v in self._series[key] if t >= cutoff] if not series: continue times, values = zip(*series) @@ -580,6 +609,8 @@ class PlotTab(QWidget): name=label, ) + pw.setXRange(cutoff, now, padding=0.02) + # ── cursor tracing ──────────────────────────────────────────────────────── def _on_mouse_move(self, evt, plot_idx: int):