From 1a9442fae286a2a886d478e4fa368f7692806137 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Fri, 14 Aug 2026 13:36:15 +0200 Subject: [PATCH] network graph: relayout when the slider is released, not on every pixel layoutGraph() runs 140 iterations of an O(n^2) force loop. Measured by calling it standalone with the same constants: 20 nodes 24 ms 50 nodes 57 ms 100 nodes 199 ms 200 nodes 729 ms (NODE_LIMIT) The edge length slider called it from oninput, which a range input fires dozens of times per drag, each call blocking the main thread. Dragging it on a level 2 graph froze the tab for several seconds. oninput now only moves the label, which is what has to stay live, and onchange does the layout once, on release. --- webui-src/app/network/network_graph.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/webui-src/app/network/network_graph.js b/webui-src/app/network/network_graph.js index 6732541..53730ee 100644 --- a/webui-src/app/network/network_graph.js +++ b/webui-src/app/network/network_graph.js @@ -209,10 +209,15 @@ const NetworkGraph = () => { `Edge length ${edgeLength}`, m('input[type=range][min=60][max=180][step=5]', { value: edgeLength, + // The label follows the slider, the layout waits for the release: + // layoutGraph() is 140 iterations of an O(n^2) force loop, which + // measures 24 ms at 20 nodes, 199 ms at 100 and 729 ms at the 200 + // node cap. A range input fires oninput dozens of times per drag, + // each one blocking the main thread for that long. oninput: (event) => { edgeLength = Number(event.target.value); - redrawLayout(); }, + onchange: redrawLayout, }), ]), m('.network-graph__zoom-control', [