fix: post-process ELK graph to enforce DO_TRY section order
ELK's partitioning doesn't reliably order disconnected children within a compound node. Instead, let ELK lay out freely then re-stack sections in correct order (try_body → doFinally → doCatch) by adjusting Y positions in the ELK graph before extraction. This propagates correctly to both node and edge coordinates via getAbsoluteY(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@ import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -266,10 +267,11 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
|
||||
// Process main flow nodes into the root ELK graph
|
||||
Set<String> doTryNodeIds = new HashSet<>();
|
||||
Map<String, List<String>> doTrySectionOrder = new LinkedHashMap<>();
|
||||
for (RouteNode rn : mainNodes) {
|
||||
if (!elkNodeMap.containsKey(rn.getId())) {
|
||||
createElkNodeRecursive(rn, rootNode, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds);
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds, doTrySectionOrder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +290,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
NodePlacementStrategy.LINEAR_SEGMENTS);
|
||||
|
||||
createElkNodeRecursive(rn, handlerRoot, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds);
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds, doTrySectionOrder);
|
||||
handlerRoots.add(handlerRoot);
|
||||
}
|
||||
|
||||
@@ -331,6 +333,26 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
engine.layout(handlerRoot, new BasicProgressMonitor());
|
||||
}
|
||||
|
||||
// Post-process DO_TRY compounds: re-stack sections in correct order
|
||||
// (ELK doesn't reliably order disconnected children within a compound)
|
||||
for (Map.Entry<String, List<String>> entry : doTrySectionOrder.entrySet()) {
|
||||
List<String> orderedIds = entry.getValue();
|
||||
List<ElkNode> sections = new ArrayList<>();
|
||||
for (String id : orderedIds) {
|
||||
ElkNode section = elkNodeMap.get(id);
|
||||
if (section != null) sections.add(section);
|
||||
}
|
||||
if (sections.size() < 2) continue;
|
||||
|
||||
double startY = sections.stream().mapToDouble(ElkNode::getY).min().orElse(0);
|
||||
double spacing = NODE_SPACING * 0.4; // matches DO_TRY spacing
|
||||
double currentY = startY;
|
||||
for (ElkNode section : sections) {
|
||||
section.setY(currentY);
|
||||
currentY += section.getHeight() + spacing;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract positioned nodes
|
||||
List<PositionedNode> positionedNodes = new ArrayList<>();
|
||||
Map<String, CompoundInfo> compoundInfos = new HashMap<>();
|
||||
@@ -532,7 +554,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
RouteNode rn, ElkNode parentElk, ElkGraphFactory factory,
|
||||
Map<String, ElkNode> elkNodeMap, Map<String, Color> nodeColors,
|
||||
Set<String> compoundNodeIds, Set<String> childNodeIds,
|
||||
Set<String> doTryNodeIds) {
|
||||
Set<String> doTryNodeIds, Map<String, List<String>> doTrySectionOrder) {
|
||||
|
||||
boolean isCompound = rn.getType() != null && COMPOUND_TYPES.contains(rn.getType())
|
||||
&& rn.getChildren() != null && !rn.getChildren().isEmpty();
|
||||
@@ -550,7 +572,6 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
elkNode.setHeight(100);
|
||||
elkNode.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
elkNode.setProperty(CoreOptions.DIRECTION, Direction.DOWN);
|
||||
elkNode.setProperty(CoreOptions.PARTITIONING_ACTIVATE, true);
|
||||
elkNode.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.4);
|
||||
elkNode.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.3);
|
||||
elkNode.setProperty(CoreOptions.PADDING,
|
||||
@@ -568,7 +589,10 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Virtual _TRY_BODY wrapper (partition 0 = top)
|
||||
// Track desired section order: try_body → doFinally → doCatch
|
||||
List<String> sectionOrder = new ArrayList<>();
|
||||
|
||||
// Virtual _TRY_BODY wrapper
|
||||
if (!tryBodyChildren.isEmpty()) {
|
||||
String wrapperId = rn.getId() + "._try_body";
|
||||
ElkNode wrapper = factory.createElkNode();
|
||||
@@ -582,40 +606,37 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
wrapper.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.5);
|
||||
wrapper.setProperty(CoreOptions.PADDING,
|
||||
new org.eclipse.elk.core.math.ElkPadding(8, 8, 8, 8));
|
||||
wrapper.setProperty(CoreOptions.PARTITIONING_PARTITION, 0);
|
||||
compoundNodeIds.add(wrapperId);
|
||||
elkNodeMap.put(wrapperId, wrapper);
|
||||
sectionOrder.add(wrapperId);
|
||||
|
||||
for (RouteNode child : tryBodyChildren) {
|
||||
childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, wrapper, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds);
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds, doTrySectionOrder);
|
||||
}
|
||||
}
|
||||
|
||||
// DO_FINALLY in the middle (partition 1), DO_CATCH at the bottom (partition 2)
|
||||
// DO_FINALLY sections (middle)
|
||||
for (RouteNode child : handlerChildren) {
|
||||
if (child.getType() == NodeType.DO_FINALLY) {
|
||||
childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, elkNode, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds);
|
||||
ElkNode finallyElk = elkNodeMap.get(child.getId());
|
||||
if (finallyElk != null) {
|
||||
finallyElk.setProperty(CoreOptions.PARTITIONING_PARTITION, 1);
|
||||
}
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds, doTrySectionOrder);
|
||||
sectionOrder.add(child.getId());
|
||||
}
|
||||
}
|
||||
// DO_CATCH sections (bottom)
|
||||
for (RouteNode child : handlerChildren) {
|
||||
if (child.getType() == NodeType.DO_CATCH) {
|
||||
childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, elkNode, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds);
|
||||
ElkNode catchElk = elkNodeMap.get(child.getId());
|
||||
if (catchElk != null) {
|
||||
catchElk.setProperty(CoreOptions.PARTITIONING_PARTITION, 2);
|
||||
}
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds, doTrySectionOrder);
|
||||
sectionOrder.add(child.getId());
|
||||
}
|
||||
}
|
||||
|
||||
doTrySectionOrder.put(rn.getId(), sectionOrder);
|
||||
} else if (isCompound) {
|
||||
compoundNodeIds.add(rn.getId());
|
||||
elkNode.setWidth(200);
|
||||
@@ -631,7 +652,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, elkNode, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds);
|
||||
compoundNodeIds, childNodeIds, doTryNodeIds, doTrySectionOrder);
|
||||
}
|
||||
} else {
|
||||
elkNode.setWidth(NODE_WIDTH);
|
||||
|
||||
Reference in New Issue
Block a user