aboutsummaryrefslogtreecommitdiffstats
path: root/src/api
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-01 20:18:17 +0100
committertv <tv@krebsco.de>2026-11-01 20:18:17 +0100
commitbb0716c8f199f7efb0f89a711a649f89cb09931f (patch)
treebc87ad56220dd626b1c40ebe6e6b8b9a99444305 /src/api
parent40d362b3714ed78a7c1afa329888e6daf215a9ba (diff)
dashboard: intended edges are loose
Diffstat (limited to 'src/api')
-rw-r--r--src/api/dashboard/dashboard.html21
1 files changed, 14 insertions, 7 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index 4acc507..ba2b893 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -552,7 +552,10 @@ function edges() {
function idealLength(count, width, height) {
return Math.min(width, height) / Math.max(2, Math.sqrt(count) * 1.5);
}
-// `pairs`: [a, b, length], the length the link pulls towards (else `idealLength`).
+// `pairs`: [a, b, length, loose], the length the link pulls towards (else
+// `idealLength`), and whether it is loose (`lengths`).
+// How hard a loose link pulls, against a measured one.
+const LOOSE = .1;
// `only`, if given: the nodes laid out; the others stay where they are.
function layout(nodes, pairs, width, height, only) {
for (const id of nodes) {
@@ -580,11 +583,13 @@ function relax(nodes, pairs, width, height, gain, most, only) {
force.get(nodes[j]).x -= dx * push / 10; force.get(nodes[j]).y -= dy * push / 10;
}
}
- for (const [a, b, length] of pairs) {
+ for (const [a, b, length, loose] of pairs) {
const p = positions.get(a), q = positions.get(b);
if (!p || !q) continue;
const dx = q.x - p.x, dy = q.y - p.y, d = Math.max(Math.hypot(dx, dy), 1);
- const pull = (d - (length || ideal)) / d / 4;
+ const stretch = d - (length || ideal);
+ // A loose link pulls a little, and never pushes.
+ const pull = loose ? Math.max(0, stretch) / d / 4 * LOOSE : stretch / d / 4;
force.get(a).x += dx * pull; force.get(a).y += dy * pull;
force.get(b).x -= dx * pull; force.get(b).y -= dy * pull;
}
@@ -609,19 +614,21 @@ function relax(nodes, pairs, width, height, gain, most, only) {
// them a little every frame (`settle`), not at once.
let byRtt = false;
try { byRtt = localStorage.getItem("continuum-by-rtt") === "1"; } catch (_) {}
-// One spring per pair of nodes, intended or measured or both, all pulling alike: a pair
-// both intended and measured doesn't pull twice as hard as one only intended.
+// One spring per pair of nodes, intended or measured or both: [a, b, length, loose].
+// Measured links shape the map; a link only intended (nothing measured backs it) is
+// `loose`: it gives way to them, pulling only a little where stretched and never pushing
+// where they hold its ends closer (`LOOSE`).
function lengths(intended, measured, count, width, height) {
const ideal = idealLength(count, width, height);
const rtts = [...measured.values()].map(edge => edge.link.rttMs)
.filter(rtt => typeof rtt === "number" && rtt > 0).sort((x, y) => x - y);
const median = rtts[Math.floor(rtts.length / 2)];
- const pairs = new Map([...intended].map(([key, [a, b]]) => [key, [a, b, ideal]]));
+ const pairs = new Map([...intended].map(([key, [a, b]]) => [key, [a, b, ideal, true]]));
for (const [key, { a, b, link }] of measured) {
const rtt = link.rttMs;
const length = byRtt && median && typeof rtt === "number" && rtt > 0
? ideal * Math.min(2.5, Math.max(.4, Math.sqrt(rtt / median))) : ideal;
- pairs.set(key, [a, b, Math.round(length)]);
+ pairs.set(key, [a, b, Math.round(length), false]);
}
return [...pairs.values()];
}