aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 23:35:18 +0000
committertv <tv@krebsco.de>2026-11-09 00:42:27 +0100
commit4a34a9185be56464de95bacdc02f168058adcc0f (patch)
tree942367655d5a10212c873d96b3e13fc6535b3ff6
parente1afd4d69b8e134674ad9c45fb3723612381dafb (diff)
dashboard: how hard links pull, on a dynamic map
On a dynamic map, links pull toward their lengths against the nodes pushing each other apart, at one strength. A `pull` slider before the layout's toggle, shown while dynamic, sets it from a quarter to four times as hard (2 to its value, once at first), kept in the browser; the map moves to the new balance smoothly.
-rw-r--r--docs/dashboard.md8
-rw-r--r--src/api/dashboard/dashboard.html28
2 files changed, 32 insertions, 4 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md
index 5c7ee13..de5fe3e 100644
--- a/docs/dashboard.md
+++ b/docs/dashboard.md
@@ -175,8 +175,12 @@ long as its round trip is slow, against the median link's, by the square root, f
0.4 to 2.5 times the usual length; links without a round trip keep the usual length.
In either layout, a link only intended (dashed grey) gives way to the measured ones: it
pulls its ends together only a little when stretched, and doesn't push them apart. As
-round trips change, the map moves to the new lengths smoothly. Clicked again, it's
-static, the nodes staying where they are. The choice is kept in the browser.
+round trips change, the map moves to the new lengths smoothly. While dynamic, the
+`pull` slider before it sets how hard the links pull their ends together against the
+nodes pushing each other apart, from a quarter to four times as hard (once at first):
+harder, links keep their lengths more closely; softer, the nodes spread out more. The
+map moves to the new balance smoothly. Clicked again, it's static, the nodes staying
+where they are. The choice and the slider are kept in the browser.
`tree: off`, next, switched to `tree: on`, shows a writer's tree ([`peers.md`](peers.md)): of the node in
focus, else of this node. A chevron on the line from each member's parent to it, at its
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index 086c03f..b586834 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -214,6 +214,11 @@
drop-shadow(-2px 0 0 var(--panel)) drop-shadow(0 2px 0 var(--panel))
drop-shadow(0 -2px 0 var(--panel)); }
.map-overlay button, .map-overlay input, .map-overlay label { pointer-events: auto; }
+ /* How hard links pull, beside the layout's toggle while dynamic, as tall. */
+ .map-controls .slider { display: inline-flex; align-items: center; gap: 4px;
+ font-size: 12px; color: var(--muted); height: 1.4em; }
+ .map-controls .slider[hidden] { display: none; }
+ .map-controls .slider input { width: 6em; height: 1em; margin: 0; }
/* Hides the legend, and shows it again. */
button.toggle { font: inherit; font-size: 12px; text-transform: none; letter-spacing: 0;
float: right; margin-left: 6px; padding: 0 6px; color: var(--muted); background: none;
@@ -330,6 +335,9 @@
<div class="map-area">
<div class="map-overlay">
<div class="map-controls">
+ <label id="pull" class="slider" hidden
+ title="How hard the links pull their ends together, against the nodes pushing each other apart">pull
+ <input id="pull-slider" type="range" min="-2" max="2" step=".25"></label>
<button id="rtt-toggle" class="toggle">layout: static</button>
<button id="tree-toggle" class="toggle">tree: off</button>
<button id="legend-toggle" class="toggle">hide legend</button>
@@ -1011,6 +1019,11 @@ function idealLength(count, width, height) {
// `idealLength`), and whether it is loose (`lengths`).
// How hard a loose link pulls, against a measured one.
const LOOSE = .1;
+// How hard links pull on a dynamic map, times what they do on a static one: 2 to the
+// `pull` slider's value (ΒΌ to 4; 1 at first), kept in this browser.
+let pullStrength = 1;
+try { pullStrength = 2 ** (Number(localStorage.getItem("continuum-pull")) || 0); } catch (_) {}
+pullStrength = Math.min(4, Math.max(.25, pullStrength));
// `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) {
@@ -1053,8 +1066,10 @@ function relax(nodes, pairs, width, height, gain, most, only, placing) {
if (!p || !q) continue;
const dx = q.x - p.x, dy = q.y - p.y, d = Math.max(Math.hypot(dx, dy), 1);
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;
+ // A loose link pulls a little, and never pushes; on a dynamic map, as hard as the
+ // slider says.
+ const strength = byRtt ? pullStrength : 1;
+ const pull = (loose ? Math.max(0, stretch) / d / 4 * LOOSE : stretch / d / 4) * strength;
force.get(a).x += dx * pull; force.get(a).y += dy * pull;
force.get(b).x -= dx * pull; force.get(b).y -= dy * pull;
}
@@ -2030,10 +2045,19 @@ treeToggle.addEventListener("click", () => {
});
// Links as long as their round trips, and back.
const rttToggle = document.getElementById("rtt-toggle");
+const pullSlider = document.getElementById("pull-slider");
+pullSlider.value = String(Math.log2(pullStrength));
+pullSlider.addEventListener("input", () => {
+ pullStrength = 2 ** Number(pullSlider.value);
+ try { localStorage.setItem("continuum-pull", pullSlider.value); } catch (_) {}
+ // Moved to the new balance, smoothly (`settle`).
+ if (state && byRtt && settleTo) settle();
+});
function showRttToggle() {
rttToggle.textContent = byRtt ? "layout: dynamic" : "layout: static";
rttToggle.title = "Click for " + (byRtt ? "a static layout"
: "a dynamic one: links as long as their round trips");
+ document.getElementById("pull").hidden = !byRtt;
}
showRttToggle();
rttToggle.addEventListener("click", () => {