From 3a81dcf69b603f7c92707cd6f6d92a9de05b2327 Mon Sep 17 00:00:00 2001 From: "Valentin V. Bartenev" Date: Mon, 11 May 2026 07:16:01 +0300 Subject: [PATCH] Web: synchronize sparkline hover highlight across all trend charts When hovering over a sparkline chart, the hover point is now drawn simultaneously on all other trend charts at the same relative time position. The tooltip, however, is shown only for the chart under the cursor to avoid visual noise. Implementation: store _updateHover and _points directly on each canvas element after binding. On mousemove/touchstart, compute a normalized position fraction (0..1) and broadcast it to all canvases inside #statsTrends via querySelectorAll, translating the fraction to each chart's own point index. On mouseleave/touchend, reset the hover state on all canvases. The showTooltip parameter added to updateHover controls whether the tooltip is updated, defaulting to true to preserve existing behavior. --- src/helpers/web/WebPanelServer.cpp | 33 +++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index f37e75e5..2647c3af 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -1965,8 +1965,9 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( function bindSparkHover(canvas, points, key) { const tooltip = document.getElementById("tooltip-" + key); if (!canvas || !tooltip || !Array.isArray(points) || points.length < 1) return; - const updateHover = (index) => { + const updateHover = (index, showTooltip = true) => { drawSparkline(canvas, points, key, index); + if (!showTooltip) return; if (index == null || index < 0 || index >= points.length) { tooltip.textContent = ""; tooltip.classList.remove("visible"); @@ -1979,24 +1980,42 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( } tooltip.classList.add("visible"); }; + canvas._updateHover = updateHover; + canvas._points = points; canvas.onmousemove = (event) => { const rect = canvas.getBoundingClientRect(); const width = rect.width || 1; const x = Math.max(0, Math.min(width, event.clientX - rect.left)); - const index = Math.max(0, Math.min(points.length - 1, Math.round((x / width) * (points.length - 1)))); - updateHover(index); + const fraction = x / width; + document.querySelectorAll('#statsTrends canvas').forEach(c => { + if (!c._updateHover || !c._points) return; + const idx = Math.max(0, Math.min(c._points.length - 1, Math.round(fraction * (c._points.length - 1)))); + c._updateHover(idx, c === canvas); + }); + }; + canvas.onmouseleave = () => { + document.querySelectorAll('#statsTrends canvas').forEach(c => { + if (c._updateHover) c._updateHover(null, c === canvas); + }); }; - canvas.onmouseleave = () => updateHover(null); canvas.ontouchstart = (event) => { const touch = event.touches && event.touches[0]; if (!touch) return; const rect = canvas.getBoundingClientRect(); const width = rect.width || 1; const x = Math.max(0, Math.min(width, touch.clientX - rect.left)); - const index = Math.max(0, Math.min(points.length - 1, Math.round((x / width) * (points.length - 1)))); - updateHover(index); + const fraction = x / width; + document.querySelectorAll('#statsTrends canvas').forEach(c => { + if (!c._updateHover || !c._points) return; + const idx = Math.max(0, Math.min(c._points.length - 1, Math.round(fraction * (c._points.length - 1)))); + c._updateHover(idx, c === canvas); + }); + }; + canvas.ontouchend = () => { + document.querySelectorAll('#statsTrends canvas').forEach(c => { + if (c._updateHover) c._updateHover(null, c === canvas); + }); }; - canvas.ontouchend = () => updateHover(null); } function setTrendCardState(key, title, value) { const card = document.getElementById("trend-" + key);