Plot tab: abbreviated names in combo, 8 plots with scroll, remove toolbar
- Trace selection combos show short labels (_trace_label) with full key stored as item data; width auto-adjusts to content (min 120 px) - Extended plot grid from 4 to 8 (4×2) wrapped in QScrollArea - Removed reset-zoom toolbar and cursor readout area Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+41
-53
@@ -23,8 +23,8 @@ from PyQt5.QtCore import QObject, Qt, pyqtSignal
|
||||
from PyQt5.QtGui import QColor, QFont
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QComboBox, QFormLayout, QGridLayout, QGroupBox,
|
||||
QHBoxLayout, QLabel, QLineEdit, QMainWindow, QPushButton, QSizePolicy,
|
||||
QSpinBox, QStatusBar, QTabWidget, QTreeWidget, QTreeWidgetItem,
|
||||
QHBoxLayout, QLabel, QLineEdit, QMainWindow, QPushButton, QScrollArea,
|
||||
QSizePolicy, QSpinBox, QStatusBar, QTabWidget, QTreeWidget, QTreeWidgetItem,
|
||||
QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
@@ -36,6 +36,28 @@ pg.setConfigOption("foreground", "k")
|
||||
MAX_PLOT_POINTS = 200
|
||||
_SETTINGS_PATH = Path.home() / ".config" / "we_monitor" / "plot_settings.json"
|
||||
|
||||
# Maps full dotted trace key → short display label used in chips, legends and
|
||||
# cursor readout. Falls back to the last path component for unknown keys.
|
||||
_TRACE_LABELS: dict[str, str] = {
|
||||
"charging.chargingStatus.chargePower": "charge power",
|
||||
"charging.chargingStatus.remainingChargingTimeToComplete": "time to full",
|
||||
"charging.batteryStatus.currentSOC": "SOC",
|
||||
"charging.batteryStatus.cruisingRangeElectric": "range",
|
||||
"charging.chargingSettings.targetSOC": "target SOC",
|
||||
"climatisation.climatisationStatus.remainingClimatisationTime": "clim. time",
|
||||
"climatisation.climatisationSettings.targetTemperature": "target temp",
|
||||
"measurements.odometerStatus.odometer": "odometer",
|
||||
"measurements.rangeStatus.electricRange": "elec. range",
|
||||
"measurements.rangeStatus.totalRange": "total range",
|
||||
"measurements.temperatureBatteryStatus.temperatureHvBatteryMax": "batt. temp max",
|
||||
"measurements.temperatureBatteryStatus.temperatureHvBatteryMin": "batt. temp min",
|
||||
"measurements.temperatureOutsideStatus.temperatureOutside": "outside temp",
|
||||
}
|
||||
|
||||
|
||||
def _trace_label(key: str) -> str:
|
||||
return _TRACE_LABELS.get(key, key.split(".")[-1])
|
||||
|
||||
|
||||
# ── TCP reader ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -271,7 +293,7 @@ class DashboardTab(QWidget):
|
||||
# ── Plot tab ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class PlotTab(QWidget):
|
||||
NUM_PLOTS = 4
|
||||
NUM_PLOTS = 8
|
||||
MAX_TRACES = 4
|
||||
COLORS = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728"]
|
||||
|
||||
@@ -281,32 +303,19 @@ class PlotTab(QWidget):
|
||||
main_layout.setContentsMargins(4, 4, 4, 4)
|
||||
main_layout.setSpacing(4)
|
||||
|
||||
# ── toolbar ───────────────────────────────────────────────────────────
|
||||
tb = QWidget()
|
||||
tb_layout = QHBoxLayout(tb)
|
||||
tb_layout.setContentsMargins(0, 0, 0, 0)
|
||||
reset_btn = QPushButton("Reset zoom")
|
||||
reset_btn.setFixedWidth(100)
|
||||
reset_btn.clicked.connect(self._reset_zoom)
|
||||
tb_layout.addWidget(reset_btn)
|
||||
self._cursor_label = QLabel("")
|
||||
self._cursor_label.setTextFormat(Qt.RichText)
|
||||
self._cursor_label.setStyleSheet("font-size: 11px; color: #333;")
|
||||
tb_layout.addWidget(self._cursor_label)
|
||||
tb_layout.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()
|
||||
self._series: dict[str, deque] = {}
|
||||
self._units: dict[str, str] = {}
|
||||
|
||||
# ── 2×2 plot grid (each cell: control bar + plot) ─────────────────────
|
||||
# ── 4×2 scrollable plot grid (each cell: control bar + plot) ─────────
|
||||
plot_container = QWidget()
|
||||
plot_grid = QGridLayout(plot_container)
|
||||
plot_grid.setContentsMargins(0, 0, 0, 0)
|
||||
plot_grid.setSpacing(4)
|
||||
for row in range(self.NUM_PLOTS // 2):
|
||||
plot_grid.setRowMinimumHeight(row, 260)
|
||||
|
||||
self._plot_widgets: list[pg.PlotWidget] = []
|
||||
self._vlines: list[pg.InfiniteLine] = []
|
||||
@@ -328,7 +337,8 @@ class PlotTab(QWidget):
|
||||
ctrl_row.setSpacing(4)
|
||||
|
||||
add_combo = QComboBox()
|
||||
add_combo.setMinimumWidth(180)
|
||||
add_combo.setMinimumWidth(120)
|
||||
add_combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
|
||||
add_combo.addItem("— add trace —")
|
||||
add_combo.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
||||
self._add_combos.append(add_combo)
|
||||
@@ -377,14 +387,17 @@ class PlotTab(QWidget):
|
||||
self._plot_widgets.append(pw)
|
||||
plot_grid.addWidget(cell, i // 2, i % 2)
|
||||
|
||||
main_layout.addWidget(plot_container)
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setWidget(plot_container)
|
||||
main_layout.addWidget(scroll)
|
||||
|
||||
# ── trace management ──────────────────────────────────────────────────────
|
||||
|
||||
def _add_trace(self, plot_idx: int):
|
||||
key = self._add_combos[plot_idx].currentText()
|
||||
key = self._add_combos[plot_idx].currentData()
|
||||
traces = self._traces[plot_idx]
|
||||
if key.startswith("—") or key not in self._series:
|
||||
if key is None or key not in self._series:
|
||||
return
|
||||
if key in traces or len(traces) >= self.MAX_TRACES:
|
||||
return
|
||||
@@ -394,7 +407,7 @@ class PlotTab(QWidget):
|
||||
self._redraw()
|
||||
|
||||
def _add_chip(self, plot_idx: int, key: str, color_idx: int):
|
||||
short = key.split(".")[-1]
|
||||
short = _trace_label(key)
|
||||
color = self.COLORS[color_idx]
|
||||
btn = QPushButton(f"{short} ×")
|
||||
btn.setFixedHeight(22)
|
||||
@@ -457,13 +470,13 @@ class PlotTab(QWidget):
|
||||
def _refresh_combos(self):
|
||||
signals = sorted(self._series.keys())
|
||||
for combo in self._add_combos:
|
||||
current = combo.currentText()
|
||||
current = combo.currentData()
|
||||
combo.blockSignals(True)
|
||||
combo.clear()
|
||||
combo.addItem("— add trace —")
|
||||
for s in signals:
|
||||
combo.addItem(s)
|
||||
idx = combo.findText(current)
|
||||
combo.addItem(_trace_label(s), s)
|
||||
idx = combo.findData(current)
|
||||
combo.setCurrentIndex(idx if idx >= 0 else 0)
|
||||
combo.blockSignals(False)
|
||||
|
||||
@@ -515,19 +528,13 @@ class PlotTab(QWidget):
|
||||
if not series:
|
||||
continue
|
||||
times, values = zip(*series)
|
||||
label = f"{key.split('.')[-1]} [{self._units.get(key, '')}]"
|
||||
label = f"{_trace_label(key)} [{self._units.get(key, '')}]"
|
||||
pw.plot(
|
||||
list(times), list(values),
|
||||
pen=pg.mkPen(self.COLORS[j], width=1.5),
|
||||
name=label,
|
||||
)
|
||||
|
||||
# ── zoom reset ────────────────────────────────────────────────────────────
|
||||
|
||||
def _reset_zoom(self):
|
||||
for pw in self._plot_widgets:
|
||||
pw.getViewBox().autoRange()
|
||||
|
||||
# ── cursor tracing ────────────────────────────────────────────────────────
|
||||
|
||||
def _on_mouse_move(self, evt, plot_idx: int):
|
||||
@@ -537,31 +544,12 @@ class PlotTab(QWidget):
|
||||
|
||||
if not pw.sceneBoundingRect().contains(pos):
|
||||
vl.setVisible(False)
|
||||
self._cursor_label.setText("")
|
||||
return
|
||||
|
||||
x = pw.getPlotItem().vb.mapSceneToView(pos).x()
|
||||
vl.setPos(x)
|
||||
vl.setVisible(True)
|
||||
|
||||
parts = []
|
||||
for j, key in enumerate(self._traces[plot_idx]):
|
||||
if key not in self._series:
|
||||
continue
|
||||
series = list(self._series[key])
|
||||
if not series:
|
||||
continue
|
||||
_, nearest_v = min(series, key=lambda p: abs(p[0] - x))
|
||||
label = key.split(".")[-1]
|
||||
unit = self._units.get(key, "")
|
||||
color = self.COLORS[j]
|
||||
parts.append(
|
||||
f'<span style="color:{color}">■</span>'
|
||||
f' {label}: <b>{nearest_v:.2f}</b> {unit}'
|
||||
)
|
||||
|
||||
self._cursor_label.setText(" ".join(parts))
|
||||
|
||||
|
||||
# ── Main window ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user