fix: proper LCA and bounding box for multi-root ELK layout
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s

1. findCommonParent: replaced with correct lowest common ancestor
   algorithm using ancestor set intersection (previous version only
   walked from node 'a', not a true LCA)

2. Bounding box: compute totalWidth/totalHeight from actual positioned
   node coordinates instead of rootNode.getWidth/Height. The rootNode
   dimensions don't account for handler sections in separate ELK roots.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-27 22:09:16 +01:00
parent 3751762c69
commit 1855153dbe

View File

@@ -355,8 +355,25 @@ public class ElkDiagramRenderer implements DiagramRenderer {
positionedEdges.add(new PositionedEdge(sourceId, targetId, label, points));
}
double totalWidth = rootNode.getWidth();
double totalHeight = rootNode.getHeight();
// Compute bounding box from actual positioned node coordinates
// (not rootNode dimensions, which ignore handler roots)
double totalWidth = 0;
double totalHeight = 0;
for (PositionedNode pn : positionedNodes) {
double right = pn.x() + pn.width();
double bottom = pn.y() + pn.height();
if (right > totalWidth) totalWidth = right;
if (bottom > totalHeight) totalHeight = bottom;
// Also check children bounds for compounds
if (pn.children() != null) {
for (PositionedNode child : pn.children()) {
double cr = pn.x() + child.x() + child.width();
double cb = pn.y() + child.y() + child.height();
if (cr > totalWidth) totalWidth = cr;
if (cb > totalHeight) totalHeight = cb;
}
}
}
DiagramLayout layout = new DiagramLayout(totalWidth, totalHeight, positionedNodes, positionedEdges);
return new LayoutResult(layout, nodeColors, compoundInfos);
@@ -572,19 +589,25 @@ public class ElkDiagramRenderer implements DiagramRenderer {
return current;
}
/** Proper lowest common ancestor of two ELK nodes. */
private ElkNode findCommonParent(ElkNode a, ElkNode b) {
if (a.getParent() == b.getParent()) {
return a.getParent();
// Collect all ancestors of 'a' (including a itself)
Set<ElkNode> ancestorsOfA = new HashSet<>();
ElkNode current = a;
while (current != null) {
ancestorsOfA.add(current);
current = current.getParent();
}
// If one is the parent of the other
if (a.getParent() != null && a.getParent() == b) return b;
if (b.getParent() != null && b.getParent() == a) return a;
// Default: root (grandparent)
ElkNode parent = a.getParent();
while (parent != null && parent.getParent() != null) {
parent = parent.getParent();
// Walk up from 'b' until we find a common ancestor
current = b;
while (current != null) {
if (ancestorsOfA.contains(current)) {
return current;
}
current = current.getParent();
}
return parent != null ? parent : a.getParent();
// Fallback: root of 'a'
return getElkRoot(a);
}
private double getAbsoluteX(ElkNode node, ElkNode root) {