github mermaid-js/mermaid @mermaid-js/layout-elk@1.0.0

Major Changes

  • #8213 33442fd Thanks @aloisklink! - chore!: require ES2024, Safari 17.4+, Node.JS v22.12+

    Mermaid is now built to target Safari 17.4+ and ES2024. If you need to support
    older browsers, you may need to polyfill or transpile mermaid.

    Safari 17.4+ has been chosen as the floor, as unlike Firefox/Chrome,
    older iOS devices don't get major Safari updates.

    Node.JS v22.12+ is also declared as requirement in our package.json files,
    but as mermaid requires a browser, this is mainly so we can use dependencies that
    also declare a Node.JS v22.12+ requirement, without causing issues for users when
    running npm install.

Minor Changes

  • #8152 d4bea0d Thanks @knsv-bot! - feat: elk.preset picks a named combination of the options that decide where nodes end up.

    Three options settle node positions, and they sit in different phases of the layout: which layer a node lands in, where it goes within that layer, and which edges get reversed to make the graph acyclic. Choosing them well means knowing all three interact; preset names the combinations worth using.

    • default — network simplex layering, balanced Brandes-Koepf placement at the top level and inside subgraphs, and depth-first cycle breaking. Balanced placement centres branches and composite-state entries; depth-first breaking gives shorter back edges on graphs that loop.
    • legacy — reproduces what earlier versions actually rendered: Brandes-Koepf placement with ELK's own greedy cycle breaking.
    • modelOrder — as depthFirst, but breaks cycles by greedy model order, which disturbs declaration order least at the cost of longer back edges.
    • depthFirst — the previous default: network simplex layering and top-level placement, Brandes-Koepf placement inside subgraphs, NONE alignment, and depth-first cycle breaking.
    ---
    config:
      layout: elk
      elk:
        preset: legacy
    ---

    Setting layeringStrategy, nodePlacementStrategy, nodePlacementAlignment or cycleBreakingStrategy explicitly overrides the preset for that one option and leaves the rest in place, so a preset is a starting point rather than a lock.

    Node placement keeps BRANDES_KOEPF, but its alignment changes from NONE to BALANCED and cycle breaking from GREEDY to DEPTH_FIRST, so existing ELK diagrams will lay out differently. preset: legacy restores the previous behaviour, and is the single switch for it — this is the net change against the last release, measured from what shipped rather than from any intermediate state.

    Subgraph contents use BRANDES_KOEPF under every preset. For modelOrder and depthFirst that is deliberately not the root's strategy: network simplex inside a frame produced routes that left the subgraph on its bounding-box corner. The two sides are tuned independently, so nodePlacementStrategy set explicitly still applies to both.

    Note that legacy uses GREEDY cycle breaking rather than the GREEDY_MODEL_ORDER the schema previously advertised. That default was declared in the schema but never listed in the shipped defaults, so it reached ELK as undefined and ELK's own default applied — legacy reproduces what was rendered, not what was documented.

  • #8152 1246a55 Thanks @knsv-bot! - feat: draw line hops where ELK edges cross, controlled by elk.lineHops.

    Where two edges cross, the one that gives way is drawn with a small arc (or a visible gap) so it is clear which line passes over which. On by default; set elk.lineHops: false to draw plain crossings, or 'gap' to use gaps instead of arcs.

    ---
    config:
      layout: elk
      elk:
        lineHops: gap
    ---

    The crossing detection and both styles already existed and were used by swimlanes — this registers the afterPaint hook that lets ELK use them. An edge that takes a hop loses its corner rounding on that segment, which is the trade for a readable crossing; curved edges are skipped rather than rewritten, to avoid corrupting their geometry.

    Existing ELK diagrams with crossing edges will render differently.

  • #8155 810893c Thanks @ashishjain0512! - feat: this package is now built from mermaid's own ELK implementation instead of carrying its own copy, and is only needed for Mermaid builds that ship without ELK.

    mermaid bundles ELK and registers it automatically, so most projects can drop the dependency and the registerLayoutLoaders call. The package remains published and fully functional for the tiny build (mermaid.tiny.js), which omits ELK to stay small and where registering this is the only way to get an ELK layout.

    Because it is compiled from mermaid's ELK source rather than importing the whole mermaid entry point, the published bundle no longer drags in every diagram type, parser and KaTeX: the minified ESM payload drops from roughly 1.58 MB to 728 kB gzipped. It stays self-contained, so it still loads from a CDN next to any Mermaid build with no import map.

    The rendering utilities the ELK source reaches (dompurify, katex, dayjs, dagre-d3-es) are now declared in the package's dependencies, so the npm build (.core.mjs) resolves them through your package manager — they dedupe against the host's copies and show up in audits — instead of carrying invisible inlined copies.

    Maintainers: peerDependencies.mermaid still reads ^11.0.2. It should be raised to the major that bundles ELK as part of the release.

Patch Changes

  • #8152 785ca77 Thanks @knsv-bot! - fix: draw ELK subgraph frames an even distance from their contents.

    A subgraph could sit 76px from its nodes on one side and 24px on the other, with nothing visible in the gap. ELK sizes a container around everything it put inside, edges included, and an edge that runs against the flow of the layout is routed back around the outside — so a group holding one grew on whichever side that edge left by, and a group without one did not.

    The lane is real and the edge still needs it, so the space is not reclaimed. What changes is that the frame is no longer drawn around it: the frame is pulled in to an even distance from the group's own children, and the edge keeps its lane just outside, which is what an edge routed around a group should look like anyway.

    The top is left as ELK set it, since it carries the subgraph's title strip and there is no way to tell how much of that padding is the title and how much is spare.

    A frame still holds the lanes that genuinely belong to it. An edge with both endpoints inside a group never leaves it, so its lane is part of that group's interior and the frame stays drawn around it — which matters for nested groups, where a lane routed around an inner group sits inside the outer one.

    Subgraphs render tighter, and groups that used to be visibly lopsided are now even.

  • #8228 e36b883 Thanks @knsv-bot! - Reserve subgraph title padding before ELK routes edges so attachments stay on the painted frame. Clip stale interior endpoints along the incoming segment, avoiding edges that run along the subgraph border.

  • #8152 1befa91 Thanks @knsv-bot! - fix: edges leave diamonds, stadiums and other non-rectangular shapes without kinking.

    ELK routes to ports on a node's bounding box and always leaves one perpendicular to the side it sits on. For a rectangle that port is the attachment point; for anything else the outline is inside the box, so the attachment has to move inwards — and the direction it moves in decides whether the edge stays orthogonal.

    It used to move along the ray from the node's centre, which lands on the outline at a different offset along the side than the port ELK chose, so the opening segment came out diagonal. The attachment now walks the outline along the edge's own departure axis, staying collinear with ELK's stub: the edge leaves the outline, crosses the box, and carries on in one straight line. Rectangular nodes are unaffected.

  • #8152 1348b4f Thanks @knsv-bot! - fix: an edge no longer leaves a subgraph from the frame's corner.

    elk.spacing.portsSurrounding was left at ELK's default of 0, which permits a
    port to sit exactly on a node's corner. A corner is the one boundary point with
    no side to leave from, so the edge came out of the vertex and then ran ALONG the
    frame's own edge before turning away from it. Subgraphs showed it first, because
    an edge that crosses a subgraph boundary attaches to the frame rather than to a
    node inside it, and a frame is large enough for the result to be obvious.

    A margin is now reserved at the ends of every side, so ELK keeps ports off the
    corners itself rather than the renderer correcting them afterwards. Over the
    elk-edge-cases corpus this takes the fixtures with a corner endpoint from 8 of
    30 down to 3.

    The value is 12, chosen by measurement: it is the smallest that clears the
    corner on that corpus. It is not a free parameter — 30 was tried and reorders
    layers.

  • #8194 012e1f7 Thanks @knsv-bot! - fix: center edges attached to small nodes such as start/end state circles. ELK's ports-surrounding margin exceeded the side length of nodes narrower than 24px, parking the edge anchor off-center; such anchors are now discarded so the edge aims at the node center.

  • #8152 1befa91 Thanks @knsv-bot! - fix: an edge no longer leaves a node with a tiny kink.

    ELK spreads an edge's port evenly along a node's side, then routes the edge down a channel whose row rarely lines up with that port exactly. The leftover is a staircase of a few pixels right at the border: leave the port, run a short distance, step perpendicular onto the channel, carry on. With rounded corners the two micro-bends sit on top of each other and read as a glitch — one edge in the sample corpus stepped 3.25px and rendered as two quadratic curves with a zero-length segment between them.

    The step is now removed by moving the channel onto the port's row, so the edge draws as one straight line and both ports stay exactly where the layout put them — sliding an attachment along a node's border leaves a node whose other edges are still evenly spread looking lopsided. Only a step next to a node is touched, and only when it is small and the edge continues the same way afterwards, so a real turn is never collapsed. An edge is left alone entirely when moving its run would drag the far port, or would buy a crossing.

    Set elk.straightenEdges: false to keep the previous behaviour.

  • #8152 3b09225 Thanks @knsv-bot! - fix: stop ELK subgraphs padding one side more than the other.

    A subgraph could end up with far more space on one side than the other for no reason a reader could see — 74px on the right of one group against 24px everywhere else. The extra space was a routing lane held open for an edge that runs against the flow of the layout and has to be routed back around, and its width came from spacing.baseValue.

    That base value was doing two jobs at once. Every unset ELK spacing derives from it, so it had to stay large enough that an edge got a straight run before the node it enters — below about 40 the approach came out shorter than the arrowhead and the turn read as happening underneath it. But an edge routed down the inside of a frame claims a lane the same width, so paying for the approach out of the base value also pushed groups clear of their own borders.

    The two are now bought separately. The base value drops to 24, and the approach run, node separation and edge separation are set explicitly, so edges keep the run they had without the frame paying for it.

    elk.layered.spacing.edgeNodeBetweenLayers is the key that buys the approach. An earlier attempt used elk.layered.spacing.edgeEdgeBetweenLayers, which is edge-to-edge and a different quantity, and a note in the source concluded from it that ELK ignored edge-node spacing "in every key form". It does not; that note was wrong and is corrected.

    Subgraph contents also gain PORT_POSITION node flexibility, which lets a node shift so an edge can leave straight rather than bending off the port. (Their placement strategy is covered in the elk.preset note.)

    Existing diagrams with subgraphs will render differently — groups get tighter and more even.

  • #8129 83f5c47 Thanks @ashishjain0512! - fix(build): externalize peerDependencies in core builds so the layout plugins no longer inline a second copy of mermaid

    getBuildConfig only externalized dependencies, so a runtime (non-type) import of the peer-depended mermaid resolved through exports to dist/mermaid.core.mjs and esbuild inlined the whole bundle. @mermaid-js/layout-elk's core entry had grown to 106 files / 6.6 MB, carrying its own mermaid with separate module-level singletons — so mermaid rendering fixes did not reach the ELK layout path until the plugin itself was republished. The core entry is back to 3 files / ~41 KB and now defers to the host's mermaid. The self-contained esm entry is unchanged.

  • Updated dependencies [7a3c1a8, 0cf3797, 846fd65, 33442fd, dc2e453, ce0302d, ad98070, e2aab3f, 6b79518, 30325d4, cd48a64, c3ee3c7, 1ee934d, a7831c5, 0cf3797, 0320406, 810893c, e36b883, b993915, 4e00c5c, 3f05015, d57ed55, 1fc5bb3, b979eb4, 7ee4c3f, a6ec7ff, 1246a55, 813c766, 8603bdd, 43d9fbc, 2878cf3, 16b9a7d, def4c81, 3802472, a19bd08, f5af413, a3a92ba, e691042, ba0deed, b05824a, 0ec29e9, 0d77926, 93edd72, 75e6c30, 6df1149, a31ecb7]:

    • mermaid@12.0.0

Don't miss a new mermaid release

NewReleases is sending notifications on new releases.