fix: proper LCA and bounding box for multi-root ELK layout
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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user