Skip to content

elk: expose edgeEdgeBetweenLayers spacing - #2887

Open
jonapoul wants to merge 3 commits into
d2lang:masterfrom
jonapoul:elk-edge-edge-spacing
Open

jonapoul wants to merge 3 commits into
d2lang:masterfrom
jonapoul:elk-edge-edge-spacing

Conversation

@jonapoul

@jonapoul jonapoul commented Sep 8, 2026 •

Copy link
Copy Markdown

ELK's edgeEdgeBetweenLayers spacing is hardcoded to 50, so there's no way to change it.

Every edge between two layers gets its own routing slot, and each slot adds this much space. So the more links you have between two layers, the further apart ELK pushes them. Lowering --elk-nodeNodeBetweenLayers doesn't help, because it only sets a minimum. Especially noticeable in tall charts like #1221 (comment).

This adds a --elk-edgeEdgeBetweenLayers flag, next to the other four spacing flags. The default stays 50, so nothing changes unless you pass it. ELK's own default is 10, for reference.

Related to #1221.

edgeEdgeBetweenLayers=10 edgeEdgeBetweenLayers=50 (default) edgeEdgeBetweenLayers=100
spacing-10 spacing-50 spacing-100

@jonapoul
jonapoul force-pushed the elk-edge-edge-spacing branch from 06c4493 to 55977f5 Compare September 15, 2026 13:27
@jonapoul jonapoul changed the title Expose ELK edgeEdgeBetweenLayers spacing elk: expose edgeEdgeBetweenLayers spacing Sep 15, 2026

@alixander alixander left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found one compatibility regression for existing Go API callers: omitting the newly added option changes edge spacing from 50 to 0 and causes unrelated edge segments to overlap. Please preserve the previous default for callers that do not explicitly configure the new option.

Validation at 55977f5: native ELK and CLI test suites passed, selected rendering tests passed (sanity, stable, root, unicode, and themes), and targeted WASM option/profile tests passed. The regression was confirmed by running the same Go program against the PR head and the merge-base layout implementation.

  • sent from alixander's Codex

Comment thread d2layouts/d2elklayout/layout.go Outdated
Algorithm: opts.Algorithm,
NodeSpacing: opts.NodeSpacing,
EdgeNodeSpacing: opts.EdgeNodeSpacing,
EdgeEdgeSpacing: opts.EdgeEdgeSpacing,

@alixander alixander Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve the default for existing options literals

Existing Go callers that construct a ConfigurableOpts literal with the five previously available fields now implicitly pass EdgeEdgeSpacing == 0. Copying that value here (and in newContainerLayoutOptions) replaces the former hardcoded spacing of 50, even though the caller has not opted into changing it.

I verified this with all five previous fields set to their defaults and the graph a -> x; a -> y; b -> x; b -> y; c -> x; c -> y: the base uses horizontal routing lanes at y=92,142,192,242, while the PR head places every horizontal segment at y=92, causing unrelated links to overlap.

Please distinguish an unset option from an explicitly requested zero and resolve unset values to 50 in both root and container options. A regression test using an options literal that omits the new field would cover this compatibility case.

  • sent from alixander's Codex

@jonapoul jonapoul Sep 20, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

745a239

Made the new value an int pointer to handle fallback behaviour while still allowing explicit zero-values. nil means unset and gets 50.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this resolves the original compatibility issue: omitted values retain 50, explicit zero is preserved, and the original example now matches the pre-PR output.

There is one small follow-up before clearing the review: [P2] Copy the spacing value before layout. In edgeEdgeSpacingOrDefault, returning opts.EdgeEdgeSpacing directly shares the caller's storage (including the global DefaultOpts pointer). Layout later unmarshals ELK output into that same pointer, so concurrent DefaultLayout calls race even when their graphs are independent.

Please change the non-nil branch to:

return go2.Pointer(*opts.EdgeEdgeSpacing)

The nil branch already creates independent storage. I verified this on 745a239 with the existing parallel sanity tests:

go test -race ./e2etests -run '^TestE2E$/^sanity$' -count=1

They fail with a data race on the current revision and pass with just the pointer-copy change applied through a local Go overlay. The same tests also pass against the pre-PR layout implementation. The native ELK, CLI, and targeted WASM tests otherwise pass.

  • sent from alixander's Codex

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 6f19fd1

EdgeEdgeSpacing is a *int, so nil means unset and resolves to the 50 D2
sent before the option existed, while an explicit 0 still reaches ELK.
@jonapoul
jonapoul requested a review from alixander September 20, 2026 15:09
Returning the caller's pointer shared storage with DefaultOpts, and Layout
unmarshals ELK output back through it, racing across concurrent layouts.

This branch has not been deployed

No deployments
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.

2 participants