aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/dashboard.md13
-rw-r--r--src/api/dashboard/dashboard.html53
2 files changed, 45 insertions, 21 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md
index e6e3c0c..a877945 100644
--- a/docs/dashboard.md
+++ b/docs/dashboard.md
@@ -100,16 +100,19 @@ else the fresher. Then:
* **Narrow, over a red band**: either end holds the other dormant, or the path in use
is down (no path to the peer answers; `/peers` shows its connection as `null`).
-Messages this node exchanges run along its links as dots, from sender to receiver.
+Messages this node exchanges run along its links as dots, from sender to receiver, all
+at one speed: over a longer link they arrive later.
They come from the per-peer message counts, which arrive every 2 s: what was sent and
received since is spread over the next 2 s, at most 6 dots per link and direction,
shared out by message kind, each kind in its colour (named in the legend; the console
colours them alike). A dot is as large as its kind's messages were on average
-(by the size's logarithm: a ping small, a large sync message large); the slider right
-after their legend makes all dots larger or smaller, kept in the browser. A picture
+(by the size's logarithm: a ping small, a large sync message large); the `size` slider
+right after their legend makes all dots larger or smaller, and the `speed` slider after
+it makes them run faster or slower (0.1 to 0.9 map units per millisecond, 0.3 at
+first: a link of 200 in about 0.7 s); both are kept in the browser. A picture
of the traffic, not a record of it; links between other members show none.
-`messages: on` after the slider hides them (`messages: off` shows them again; the
-legend and the slider greyed meanwhile); the choice is kept in the browser.
+`messages: on` after the sliders hides them (`messages: off` shows them again; the
+legend and the sliders greyed meanwhile); the choice is kept in the browser.
A label on the map that changes, a round trip or a node's name, breathes once: it
glows around its new text and fades (not where the system asks for reduced motion).
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) });
}