WebUI: per-signal vscale toolbar, active-signal highlighting, zoom fix

- Per-signal, per-plot vertical scale state (sigVScale keyed by plotId:signalKey)
  so the same signal in two plots has fully independent vscale config
- Active signal redrawn on top of all series with 2× line width for clear
  visual identification; badge click toggles selection and opens/closes the
  embedded vscale toolbar (click same badge again to deselect)
- Vscale configurator moved from floating popup to a slim toolbar strip
  anchored inside the plot card, with an × close button
- Trigger dropdown shows one entry per array signal with [0…N-1] label;
  opening it shows an index-picker dialog to choose the element
- Zoom resampling: when server returns no data for a zoomed range, fall
  back to the local circular buffer instead of returning empty arrays

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino Ferrari
2026-05-27 15:42:00 +02:00
parent b15e637f14
commit 3dd0d863fa
13 changed files with 1508 additions and 231 deletions
+27 -13
View File
@@ -82,9 +82,14 @@ func (c *wsClient) readPump() {
case "addSource":
label, _ := env["label"].(string)
addr, _ := env["addr"].(string)
mcastGroup, _ := env["multicastGroup"].(string)
dataPortF, _ := env["dataPort"].(float64)
if addr != "" {
select {
case c.hub.commandCh <- hubCmd{op: "wsAddSource", label: label, addr: addr}:
case c.hub.commandCh <- hubCmd{
op: "wsAddSource", label: label, addr: addr,
multicastGroup: mcastGroup, dataPort: int(dataPortF),
}:
default:
}
}
@@ -139,11 +144,13 @@ type taggedSample struct {
type hubCmd struct {
op string // "addSource","removeSource","setSourceState","updateConfig",
// "wsAddSource","wsRemoveSource","wsSaveSources"
sourceID string
label string
addr string
state string
sigs []SignalInfo
sourceID string
label string
addr string
state string
sigs []SignalInfo
multicastGroup string
dataPort int
}
// Hub is the central broker between UDP clients and WebSocket clients.
@@ -474,7 +481,9 @@ func (h *Hub) Run() {
case "wsAddSource":
if h.sm != nil {
go func(label, addr string) { h.sm.Add(label, addr, "", 0) }(cmd.label, cmd.addr)
go func(label, addr, mcastGroup string, dataPort int) {
h.sm.Add(label, addr, mcastGroup, dataPort)
}(cmd.label, cmd.addr, cmd.multicastGroup, cmd.dataPort)
}
case "wsRemoveSource":
@@ -718,9 +727,10 @@ func (h *Hub) buildDataMessageForSource(src *sourceHubState, batch []DataSample)
decimT, decimV := lttbDecimate(allT, allV, maxPushPoints)
out[pfx+sig.Name] = sigData{T: decimT, V: decimV}
case n > 1 && sig.TimeMode == TimeModeFullArray:
// The time signal has the same N elements as the data signal.
case sig.TimeMode == TimeModeFullArray:
// Each element pair (timeSig[k], dataSig[k]) is one (t, v) sample.
// This handles both standard N-element FullArray signals and
// Accumulate-mode scalars (n=1) auto-assigned FullArray time mode.
hasTimeSig := sig.TimeSignalIdx != NoTimeSignal && int(sig.TimeSignalIdx) < len(sigs)
var timeSigName string
timerToSec := 1e-6
@@ -918,7 +928,9 @@ func (h *Hub) buildBinaryDataMessageForSource(src *sourceHubState, batch []DataS
decimT, decimV := lttbDecimate(allT, allV, maxPushPoints)
pairs[sig.Name] = pairBuf{t: decimT, v: decimV}
case n > 1 && sig.TimeMode == TimeModeFullArray:
case sig.TimeMode == TimeModeFullArray:
// Handles both standard N-element FullArray signals and
// Accumulate-mode scalars (n=1) with auto-assigned FullArray time mode.
hasTimeSig := sig.TimeSignalIdx != NoTimeSignal && int(sig.TimeSignalIdx) < len(sigs)
var timeSigName string
timerToSec := 1e-6
@@ -957,9 +969,11 @@ func (h *Hub) buildBinaryDataMessageForSource(src *sourceHubState, batch []DataS
allV = append(allV, vals[k])
}
}
ringT, ringV := lttbDecimate(allT, allV, maxRingPoints)
if rb := h.getRing(pfx + sig.Name); rb != nil {
rb.write(ringT, ringV)
if writeRing {
ringT, ringV := lttbDecimate(allT, allV, maxRingPoints)
if rb := h.getRing(pfx + sig.Name); rb != nil {
rb.write(ringT, ringV)
}
}
decimT, decimV := lttbDecimate(allT, allV, maxPushPoints)
pairs[sig.Name] = pairBuf{t: decimT, v: decimV}