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
+30 -5
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
@@ -135,9 +136,33 @@ func (sm *SourceManager) Load(path string) error {
// ParseSourceArg parses "label@host:port" or "host:port".
func ParseSourceArg(s string) (label, addr string) {
s = strings.TrimSpace(s)
if idx := strings.Index(s, "@"); idx >= 0 {
return strings.TrimSpace(s[:idx]), strings.TrimSpace(s[idx+1:])
}
return "", s
label, addr, _, _ = ParseSourceArgFull(s)
return
}
// ParseSourceArgFull parses "[label@]host:port[/multicastGroup:dataPort]".
// Examples:
//
// "Streamer@127.0.0.1:44500/239.0.0.1:44503" → label="Streamer", addr="127.0.0.1:44500", group="239.0.0.1", port=44503
// "127.0.0.1:44501" → label="", addr="127.0.0.1:44501", group="", port=0
func ParseSourceArgFull(s string) (label, addr, multicastGroup string, dataPort int) {
s = strings.TrimSpace(s)
rest := s
if idx := strings.Index(s, "@"); idx >= 0 {
label = strings.TrimSpace(s[:idx])
rest = strings.TrimSpace(s[idx+1:])
}
if idx := strings.Index(rest, "/"); idx >= 0 {
addr = strings.TrimSpace(rest[:idx])
mcastPart := strings.TrimSpace(rest[idx+1:])
if lastColon := strings.LastIndex(mcastPart, ":"); lastColon >= 0 {
multicastGroup = strings.TrimSpace(mcastPart[:lastColon])
dataPort, _ = strconv.Atoi(strings.TrimSpace(mcastPart[lastColon+1:]))
} else {
multicastGroup = mcastPart
}
} else {
addr = rest
}
return
}