From 79e5caaf7af44ad029ce181740cf6db5c52e6dee Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 28 Mar 2026 10:03:50 +0100 Subject: [PATCH] fix: post-process ELK graph to enforce DO_TRY section order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../app/diagram/ElkDiagramRenderer.java | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java index 361a02c4..8dc12604 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java @@ -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 doTryNodeIds = new HashSet<>(); + Map> 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> entry : doTrySectionOrder.entrySet()) { + List orderedIds = entry.getValue(); + List 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 positionedNodes = new ArrayList<>(); Map compoundInfos = new HashMap<>(); @@ -532,7 +554,7 @@ public class ElkDiagramRenderer implements DiagramRenderer { RouteNode rn, ElkNode parentElk, ElkGraphFactory factory, Map elkNodeMap, Map nodeColors, Set compoundNodeIds, Set childNodeIds, - Set doTryNodeIds) { + Set doTryNodeIds, Map> 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 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);