Track a _needs_autozoom flag per plot. Set True at startup, on
reconnect (clear_data), and when a trace is added or removed. In
_redraw, when the flag is set and visible data exists, fit the Y range
to the data (with 10% margin) then clear the flag — subsequent live
updates leave the user's manual Y zoom undisturbed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add PlotTooltip — a frameless child widget that appears next to the
mouse cursor whenever it moves over a plot. It lists each active trace
with a colored bullet, short name, and the nearest y-value (unit
appended). The tooltip is repositioned on every mouse-move event and
clamped to stay within the plot widget bounds. Hidden on mouse-exit and
on clear_data (reconnect).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace pg.DateAxisItem with a custom TimeAxisItem that formats ticks
as HH:MM:SS, eliminating locale-dependent day/date prefixes on the axis.
Add a per-plot date label (right-aligned in the control bar) that shows
the date range of the visible window — single date when the view stays
within one day, "YYYY-MM-DD – YYYY-MM-DD" when it spans midnight.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server (network.py): send every record in diff mode starting from
prev={}, including the first — fixes history drop on new client connect.
Collect (collect.py): remove payload["_diff"] = True from live broadcast.
GUI client (gui_client.py): always call jay_merge_full in diff mode;
drop the _diff key check that gated reconstruction.
CLI client (client.py): fix `args.diff or True` guard that made --diff
irrelevant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jay_merge_full/jay_merge_update mutates _state nested dicts in place.
The previously emitted dict(_state) was a shallow copy sharing those
nested dicts, so background-thread merges retroactively corrupted
already-emitted snapshots: timestamps were correct (strings are
immutable) but Y values all converged to the latest record's values.
Fix: emit copy.deepcopy(_state) so each snapshot is fully independent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server (collect.py --diff):
- History burst: first record sent full, subsequent as jay_diff_full diffs
- Live broadcast: each snapshot diffed against the previous one
- Diff payloads tagged with "_diff": true
Client (client.py --diff):
- Reconstructs full state via jay_merge_full before printing
GUI (Connector tab "Diff mode" checkbox):
- TcpReader.connect_to() accepts diff_mode; maintains _state dict
- Merges incoming diffs before emitting to Dashboard/Plot
- Checkbox state persisted to plot_settings.json
Both sides default to off; must be enabled consistently on server and client.
utils/__init__.py added so utils.jay_diff is importable as a package.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace direct _redraw() calls in PlotTab.update() with a 100 ms
single-shot QTimer. During a burst of incoming records (e.g. 500
history records on connect) only the data is accumulated; a single
redraw fires 100 ms after the last record arrives instead of once
per record (~4000 pyqtgraph redraws avoided).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add PlotTab.clear_data() which resets _series and _units, then redraws.
Called in MainWindow._connect() before opening the socket so stale data
from a previous session never mingles with fresh history from the server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Track the earliest timestamp across plotted traces (starting from None,
not cutoff) so setXRange uses the first real data point as the left
bound — no empty gap when data doesn't fill the full time window.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Save host/port to plot_settings.json on connect; restore on startup
- Raise MAX_PLOT_POINTS from 200 to 2880 so 24 h of data is retained
even at 30 s polling intervals
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the single global toolbar spinbox with one QSpinBox per plot,
placed between the '+' button and the trace chips. Each plot has an
independent 1–24 h window; values are persisted in plot_settings.json
under 'time_windows'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Color palette taken directly from nmea_client/gui/plot_widget.py:
- Background #1a1a2e / #12121e, borders #2a2a4a/#3a3a5e,
text #aaaacc/#ddddee, connected #4CAF50, error #F44336
- Trace colors: Material palette (blue, red, green, orange, …)
- Plot widget background overridden from "white" to #12121e
- pyqtgraph foreground set to #aaaacc
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
InfiniteLine was included in auto-range calculations, causing the view
to rescale on every cursor move. Add it with ignoreBounds=True in both
the initial setup and after each redraw.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace static combo grid with a control bar above each plot:
add-trace dropdown + "+" button; active traces shown as colored chips with
"×" to remove (max 4 per plot)
- Save trace selection to ~/.config/we_monitor/plot_settings.json on every
change; restore automatically once matching signals arrive from the server
- Remove tiny tick-font overrides so axis labels are readable at default size
- Show received record count in the status bar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed the QScrollArea wrapper and fixed figure-height calculation.
FigureCanvasQTAgg now fills the tab with an Expanding size policy so
all 8 subplots fit in the visible area at 1920x1200 without scrolling.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
create_connection(timeout=5) left a 5-second read timeout on the
socket. Any collection interval longer than 5 s caused recv() to
raise socket.timeout, which was caught as OSError and reported as
a lost connection. Switch to blocking mode with settimeout(None)
immediately after the connect handshake.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Connector tab: host/port fields, connect/disconnect button, status
- Dashboard tab: live QTreeWidget of the full snapshot hierarchy;
physical values show value and unit in separate columns;
expand state preserved across refreshes
- Plot tab: matplotlib time-series subplots (2 columns) for every
physical {value, unit} field, scrollable, auto-layout on new keys
- TcpReader runs in a daemon thread and delivers messages via
Qt signals so the UI never blocks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>