diff options
| author | tv <tv@krebsco.de> | 2026-11-02 19:45:57 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-02 19:45:57 +0100 |
| commit | fa065ad9e198d5012a53170267b2a52b34702e12 (patch) | |
| tree | a47c1cd2862df118f01ea86aa21dcab822e9c026 /src/api | |
| parent | 0dfcfa6a740bea1ca7af5099a9ff3f896a908d6a (diff) | |
dashboard: message dot speed is configurable
Diffstat (limited to 'src/api')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 53 |
1 files changed, 37 insertions, 16 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 2a841ae..4232cbd 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -145,13 +145,13 @@ /* A kind of message: a dot in its colour, as on the map. */ .legend .message::before { width: 8px; height: 8px; border: none; border-radius: 50%; background: var(--colour); } - /* The dots' size, right after their legend, no taller than its text. */ - .legend .dot-size { display: inline-flex; align-items: center; gap: 4px; } - .legend .dot-size::before { display: none; } - .legend .dot-size input { width: 6em; height: 1em; margin: 0; } - /* After the slider, as tall; the kinds and the slider greyed while the dots are off. */ + /* The dots' size and speed, right after their legend, no taller than its text. */ + .legend .slider { display: inline-flex; align-items: center; gap: 4px; } + .legend .slider::before { display: none; } + .legend .slider input { width: 6em; height: 1em; margin: 0; } + /* After the sliders, as tall; the kinds and the sliders greyed while the dots are off. */ .legend button.toggle { float: none; margin-left: 0; line-height: 1em; } - .legend.off .message, .legend.off .dot-size { opacity: .45; } + .legend.off .message, .legend.off .slider { opacity: .45; } #legends { flex: none; } /* Hides the legend, and shows it again. */ button.toggle { font: inherit; font-size: 12px; text-transform: none; letter-spacing: 0; @@ -1666,9 +1666,9 @@ graph.addEventListener("mouseleave", () => hover(null)); // as it grew since the last update, drawn as dots running along the link the way the // messages went. Counts come with each update (every 2 s), so the dots are spread // over the next 2 s, a few at most per link and way: a picture of the traffic, not a -// record of it. Kept above everything else on the map (`flows`, after what `show` -// places). --- -const FLOW_MS = 700, FLOW_SPREAD_MS = 2000, FLOWS_AT_MOST = 6; +// record of it. All at one speed (`flowSpeed`), so over a longer link they arrive +// later. Kept above everything else on the map (`flows`, after what `show` places). --- +const FLOW_SPREAD_MS = 2000, FLOWS_AT_MOST = 6; // Shown or not (`flows-toggle`, kept in this browser; shown at first). let flowsShown = true; try { flowsShown = localStorage.getItem("continuum-flows") !== "0"; } catch (_) {} @@ -1709,7 +1709,12 @@ const kindColour = kind => (KIND_COLOURS.find(([name]) => kind === name || let dotScale = 2; try { dotScale = Number(localStorage.getItem("continuum-dot-scale")) || 2; } catch (_) {} dotScale = Math.min(4, Math.max(1.25, dotScale)); -let dotSlider = null; +// How fast the dots run, in map units per ms (the slider after the size's, kept in this +// browser): .3 crosses a link of 200 in about 0.7 s. +let flowSpeed = .3; +try { flowSpeed = Number(localStorage.getItem("continuum-dot-speed")) || .3; } catch (_) {} +flowSpeed = Math.min(.9, Math.max(.1, flowSpeed)); +let dotSlider = null, speedSlider = null; function showMessagesLegend() { const legend = document.getElementById("messages-legend"); legend.classList.toggle("off", !flowsShown); @@ -1721,13 +1726,21 @@ function showMessagesLegend() { dotScale = Number(slider.value); try { localStorage.setItem("continuum-dot-scale", String(dotScale)); } catch (_) {} }); + const speed = speedSlider = el("input", { type: "range", min: ".1", max: ".9", step: ".1", + title: "How fast the dots run" }); + speed.value = String(flowSpeed); + speed.addEventListener("input", () => { + flowSpeed = Number(speed.value); + try { localStorage.setItem("continuum-dot-speed", String(flowSpeed)); } catch (_) {} + }); // Before the toggle, which ends the line. legend.prepend(...KIND_COLOURS.map(([kind, colour]) => el("span", { class: "message", style: "--colour: " + colour }, kind === "paths" ? "paths (pings)" : kind)), - el("label", { class: "dot-size" }, "size", slider)); + el("label", { class: "slider" }, "size", slider), + el("label", { class: "slider" }, "speed", speed)); } - dotSlider.disabled = !flowsShown; + dotSlider.disabled = speedSlider.disabled = !flowsShown; } showMessagesLegend(); // A dot's radius for a message of `bytes`: grows with the size's logarithm, from 1.5 @@ -1933,14 +1946,22 @@ function drawFlows() { flowFrame = null; const layer = document.getElementById("flows"); const now = performance.now(); + // How far along its link each is (0 to 1), as long as the link is now: arrived, or + // its link gone, it goes. + const along = flow => { + const p = positions.get(flow.from), q = positions.get(flow.to); + if (!p || !q) return null; + const length = Math.max(Math.hypot(q.x - p.x, q.y - p.y), 1); + return { p, q, t: (now - flow.start) * flowSpeed / length }; + }; for (let i = flows.length - 1; i >= 0; i--) { - if (now > flows[i].start + FLOW_MS) flows.splice(i, 1); + const at = along(flows[i]); + if (at === null || at.t >= 1) flows.splice(i, 1); } const dots = []; for (const flow of flows) { - const p = positions.get(flow.from), q = positions.get(flow.to); - if (now < flow.start || !p || !q) continue; - const t = (now - flow.start) / FLOW_MS; + if (now < flow.start) continue; + const { p, q, t } = along(flow); dots.push({ x: p.x + (q.x - p.x) * t, y: p.y + (q.y - p.y) * t, r: flow.r, kind: flow.kind, opacity: Math.sin(Math.PI * t) }); } |
