fix: lay out handler sections in separate ELK graphs
ON_EXCEPTION, ON_COMPLETION, and ERROR_HANDLER compounds were included in the same root ELK graph as the main flow. ELK's layered algorithm offset the main flow nodes vertically to accommodate the handler compounds, causing bent arrows between the ENDPOINT and first processor. Now handler sections get their own independent ELK root graphs. The frontend already separates and repositions them, so they just need correct internal layout — not positioning relative to the main flow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -107,6 +107,12 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
NodeType.ON_COMPLETION
|
||||
);
|
||||
|
||||
/** Top-level handler types that are laid out in their own separate ELK graph
|
||||
* to prevent them from affecting the main flow's node positioning. */
|
||||
private static final Set<NodeType> HANDLER_SECTION_TYPES = EnumSet.of(
|
||||
NodeType.ON_EXCEPTION, NodeType.ERROR_HANDLER, NodeType.ON_COMPLETION
|
||||
);
|
||||
|
||||
public ElkDiagramRenderer() {
|
||||
// Ensure the layered algorithm meta data provider is registered.
|
||||
// LayoutMetaDataService uses ServiceLoader, but explicit registration
|
||||
@@ -201,16 +207,49 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
Map<String, Color> nodeColors = new HashMap<>();
|
||||
Set<String> compoundNodeIds = new HashSet<>();
|
||||
|
||||
// Process top-level nodes from the graph
|
||||
// Separate handler sections (ON_EXCEPTION, ON_COMPLETION, ERROR_HANDLER)
|
||||
// from main flow nodes. Handler sections are laid out in their own ELK
|
||||
// graphs to prevent them from affecting the main flow's Y positioning.
|
||||
List<RouteNode> mainNodes = new ArrayList<>();
|
||||
List<RouteNode> handlerNodes = new ArrayList<>();
|
||||
if (graph.getNodes() != null) {
|
||||
for (RouteNode rn : graph.getNodes()) {
|
||||
if (!elkNodeMap.containsKey(rn.getId())) {
|
||||
createElkNodeRecursive(rn, rootNode, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds);
|
||||
if (rn.getType() != null && HANDLER_SECTION_TYPES.contains(rn.getType())
|
||||
&& rn.getChildren() != null && !rn.getChildren().isEmpty()) {
|
||||
handlerNodes.add(rn);
|
||||
} else {
|
||||
mainNodes.add(rn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process main flow nodes into the root ELK graph
|
||||
for (RouteNode rn : mainNodes) {
|
||||
if (!elkNodeMap.containsKey(rn.getId())) {
|
||||
createElkNodeRecursive(rn, rootNode, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds);
|
||||
}
|
||||
}
|
||||
|
||||
// Process handler sections into their OWN separate ELK root graphs.
|
||||
// This prevents them from affecting the main flow's Y positioning.
|
||||
// Each handler gets its own independent layout.
|
||||
List<ElkNode> handlerRoots = new ArrayList<>();
|
||||
for (RouteNode rn : handlerNodes) {
|
||||
ElkNode handlerRoot = factory.createElkNode();
|
||||
handlerRoot.setIdentifier("handler-root-" + rn.getId());
|
||||
handlerRoot.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
handlerRoot.setProperty(CoreOptions.DIRECTION, rootDirection);
|
||||
handlerRoot.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.5);
|
||||
handlerRoot.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.5);
|
||||
handlerRoot.setProperty(org.eclipse.elk.alg.layered.options.LayeredOptions.NODE_PLACEMENT_STRATEGY,
|
||||
NodePlacementStrategy.LINEAR_SEGMENTS);
|
||||
|
||||
createElkNodeRecursive(rn, handlerRoot, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds);
|
||||
handlerRoots.add(handlerRoot);
|
||||
}
|
||||
|
||||
// Create ELK edges
|
||||
if (graph.getEdges() != null) {
|
||||
for (RouteEdge re : graph.getEdges()) {
|
||||
@@ -230,10 +269,15 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Run layout
|
||||
// Run layout — main flow
|
||||
RecursiveGraphLayoutEngine engine = new RecursiveGraphLayoutEngine();
|
||||
engine.layout(rootNode, new BasicProgressMonitor());
|
||||
|
||||
// Run layout — each handler section independently
|
||||
for (ElkNode handlerRoot : handlerRoots) {
|
||||
engine.layout(handlerRoot, new BasicProgressMonitor());
|
||||
}
|
||||
|
||||
// Extract results — only top-level nodes (children collected recursively)
|
||||
List<PositionedNode> positionedNodes = new ArrayList<>();
|
||||
Map<String, CompoundInfo> compoundInfos = new HashMap<>();
|
||||
|
||||
Reference in New Issue
Block a user