Skip to content

Layout presets by graph size: large graphs in seconds, not minutes - #81

Merged
endrix merged 1 commit into
mainfrom
feat/size-tiered-layout
Oct 17, 2026
Merged

endrix merged 1 commit into
mainfrom
feat/size-tiered-layout

Conversation

@endrix

@endrix endrix commented Oct 16, 2026

Copy link
Copy Markdown
Owner

The server's ELK layout now picks its option preset by the graph's node and edge counts, so large graphs lay out in seconds instead of minutes (or a stack overflow). Graphs up to the first threshold get exactly the options they did before, byte for byte.

What was measured

ELK standalone on the exact graphs the engine hands to elk.layout, four real netlist modules of a hardware product:

module nodes edges default middle fast (POLYLINE) fast (ORTHOGONAL)
storeack 212 445 5.8 s ~1 s 0.9 s
loadreq 344 936 41 s 1.7 s (2.6 s re-run) 1.3 s (0.9 s re-run) 2.7 s
priv 1528 3617 334 s 9.3 s (10.6 s re-run) 3.8 s 30.8 s
conv 3293 8112 RangeError: Maximum call stack size exceeded inside ELK not run 11.5 s 2289 s

Two options carry the default preset's cost: elk.layered.nodePlacement.strategy: NETWORK_SIMPLEX (dominant: switching it alone takes 344 nodes from 41 s to 3.8 s) and elk.layered.considerModelOrder.strategy: NODES_AND_EDGES (ten-fold on the cheap presets). Crossing minimisation is not the cost. Orthogonal routing of a simply placed graph is: the fast preset was measured with both routings on this branch, and ORTHOGONAL is 38 minutes on conv, so the fast preset routes polylines. The middle preset stays orthogonal, where POLYLINE was the slower of the two.

Tiers

Constants in packages/diagram-server/src/server/layout-configurator.ts, with the measurements in their doc comment:

  • default: up to 150 nodes and 400 edges. The options are unchanged.
  • middle (MIDDLE_LAYOUT_OPTIONS): above 150 nodes or 400 edges. Default plus considerModelOrder.strategy: NONE, nodePlacement.strategy: BRANDES_KOEPF, thoroughness: 3.
  • fast (FAST_LAYOUT_OPTIONS): above 1000 nodes or 3000 edges. Middle plus crossingMinimization.strategy: NONE, nodePlacement.strategy: SIMPLE, thoroughness: 1, edgeRouting: POLYLINE.

The count is recursive over the graph's children, GNodes that are not compartments and GEdges. The rule reads nothing else, so the core learns nothing product-specific.

The tier applies wherever the configurator does: the initial layout on open, Layout Diagram, boundary flow, Untangled, Free Ports and the Free Ports probe. The engine still merges a layout's own graph options (UNTANGLED_ELK_OPTIONS) over the preset, except in the fast tier, where it re-asserts the fast preset: Brandes-Koepf placement is among the options that take minutes there. In the middle tier Untangled's placement is the middle preset's already. Documented in both files.

Each layout logs [dialogram perf] layout <network>: tier=<default|middle|fast> nodes=<n> edges=<m> beside the existing elk= timing, and a fast-tier layout sends a StatusAction ("N nodes: laid out roughly. Run Layout for the full layout.", 10 s) through the ActionDispatcher the engine factory now passes in; the stock GLSP status module shows it, so no client code changed.

Tests

packages/diagram-server/test/layout-tier.test.ts, 13 tests on synthetic GGraph trees: the counting (depth, compartments skipped), the thresholds (at and above), the default preset pinned for a small graph and equal to an empty graph's, the middle and fast presets by node and by edge count, the fixed post-drag configuration untouched, Untangled overriding in the middle tier and overridden in the fast tier, the log line and the status action. The existing elk-layout-port-order.test.ts suite runs the real ELK on the 40-node speculation flow and reports tier=default, every crossing count unchanged. No netlist dump runs in the suite.

Gates

  • npm run typecheck: PASSED (5/5 packages)
  • npm run check:neutrality: PASSED (5/5 gates)
  • npm run build: succeeded
  • npm test --workspace packages/diagram-server: 68 files, 339 tests passed
  • npm test --workspace packages/sidecar-toolkit: 42 files, 264 tests passed

https://claude.ai/code/session_01UtmwstZRcWJeg3C69s1TnS

The ELK options in WorkflowLayoutConfigurator were tuned on diagrams of a
few dozen nodes and applied to every graph alike. Measured standalone on
the exact graphs the engine hands to elk.layout, four real netlist modules
of a hardware product, the default preset took 5.8 s on 212 nodes, 41 s on
344, 334 s on 1528 and overflowed ELK's call stack on 3293. Two options
carry the cost: NETWORK_SIMPLEX node placement (344 nodes go from 41 s to
3.8 s by that switch alone) and NODES_AND_EDGES model order (ten-fold on
the cheap presets); crossing minimisation is not it. The configurator now
counts the graph's nodes and edges, recursively and without compartments,
and lays a preset over the default by size: above 150 nodes or 400 edges
the middle preset (model order off, Brandes-Koepf placement, thoroughness
3: 1.7 s on 344 nodes, 9.3 s on 1528), above 1000 nodes or 3000 edges the
fast preset (crossing minimisation off, simple placement, thoroughness 1,
polyline routing: 3.8 s on 1528 nodes, 11.5 s on 3293). The fast preset
routes polylines because orthogonal routing of a simply placed graph took
2289 s on 3293 nodes and 31 s on 1528. A graph under the first threshold
gets exactly the options it did, byte for byte, so every existing layout
test holds. The thresholds and presets are exported constants with the
measurements in their doc comment. The engine merges a layout's own graph
options over the preset as before, except in the fast tier, where it
re-asserts the fast preset: Untangled's Brandes-Koepf placement is among
the options that take minutes there. Each layout logs
`[dialogram perf] layout <network>: tier=... nodes=... edges=...`, and a
fast-tier layout tells the user in the diagram's status line that it was
laid out roughly, through the ActionDispatcher the engine factory now
passes in. Thirteen tests hold the counting, the thresholds, the presets,
the fast-tier override and the log and status lines on synthetic graphs.

Claude-Session: https://claude.ai/code/session_01UtmwstZRcWJeg3C69s1TnS
@endrix
endrix force-pushed the feat/size-tiered-layout branch from bae9594 to c7874ac Compare October 17, 2026 07:00
@endrix
endrix merged commit b8d425a into main Oct 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant