fix: straight edge routing and handler section edge extraction
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 58s
CI / docker (push) Successful in 53s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s

Backend:
- Set POLYLINE edge routing on ELK root — eliminates curved/bent edges
  between horizontally aligned nodes
- Collect edges from handler section roots (not just main root) so
  internal handler edges are included in the layout output
- Use correct root reference for coordinate calculation per edge

Frontend:
- Render ALL edge points as line segments (polylines), not cubic bezier.
  ELK bend points are waypoints, not bezier control points — the cubic
  bezier interpretation caused false curves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-27 20:59:38 +01:00
parent cbe41d7ac7
commit 56f98671ca
2 changed files with 21 additions and 20 deletions

View File

@@ -12,6 +12,7 @@ import org.eclipse.elk.alg.layered.options.LayeredMetaDataProvider;
import org.eclipse.elk.core.RecursiveGraphLayoutEngine;
import org.eclipse.elk.core.options.CoreOptions;
import org.eclipse.elk.core.options.Direction;
import org.eclipse.elk.core.options.EdgeRouting;
import org.eclipse.elk.core.options.HierarchyHandling;
import org.eclipse.elk.alg.layered.options.NodePlacementStrategy;
import org.eclipse.elk.core.util.BasicProgressMonitor;
@@ -188,6 +189,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
rootNode.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING);
rootNode.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING);
rootNode.setProperty(CoreOptions.HIERARCHY_HANDLING, HierarchyHandling.INCLUDE_CHILDREN);
rootNode.setProperty(CoreOptions.EDGE_ROUTING, EdgeRouting.POLYLINE);
rootNode.setProperty(org.eclipse.elk.alg.layered.options.LayeredOptions.NODE_PLACEMENT_STRATEGY,
NodePlacementStrategy.LINEAR_SEGMENTS);
@@ -305,27 +307,34 @@ public class ElkDiagramRenderer implements DiagramRenderer {
}
}
// Extract edges
// Extract edges from main root + all handler roots
List<PositionedEdge> positionedEdges = new ArrayList<>();
for (ElkEdge elkEdge : collectAllEdges(rootNode)) {
List<ElkEdge> allEdges = collectAllEdges(rootNode);
for (ElkNode hr : handlerRoots) {
allEdges.addAll(collectAllEdges(hr));
}
for (ElkEdge elkEdge : allEdges) {
String sourceId = elkEdge.getSources().isEmpty() ? "" : elkEdge.getSources().get(0).getIdentifier();
String targetId = elkEdge.getTargets().isEmpty() ? "" : elkEdge.getTargets().get(0).getIdentifier();
// Determine which root this edge belongs to for coordinate calculation
ElkNode edgeRoot = getElkRoot(elkEdge.getContainingNode());
List<double[]> points = new ArrayList<>();
for (ElkEdgeSection section : elkEdge.getSections()) {
points.add(new double[]{
section.getStartX() + getAbsoluteX(elkEdge.getContainingNode(), rootNode),
section.getStartY() + getAbsoluteY(elkEdge.getContainingNode(), rootNode)
section.getStartX() + getAbsoluteX(elkEdge.getContainingNode(), edgeRoot),
section.getStartY() + getAbsoluteY(elkEdge.getContainingNode(), edgeRoot)
});
for (ElkBendPoint bp : section.getBendPoints()) {
points.add(new double[]{
bp.getX() + getAbsoluteX(elkEdge.getContainingNode(), rootNode),
bp.getY() + getAbsoluteY(elkEdge.getContainingNode(), rootNode)
bp.getX() + getAbsoluteX(elkEdge.getContainingNode(), edgeRoot),
bp.getY() + getAbsoluteY(elkEdge.getContainingNode(), edgeRoot)
});
}
points.add(new double[]{
section.getEndX() + getAbsoluteX(elkEdge.getContainingNode(), rootNode),
section.getEndY() + getAbsoluteY(elkEdge.getContainingNode(), rootNode)
section.getEndX() + getAbsoluteX(elkEdge.getContainingNode(), edgeRoot),
section.getEndY() + getAbsoluteY(elkEdge.getContainingNode(), edgeRoot)
});
}

View File

@@ -11,19 +11,11 @@ export function DiagramEdge({ edge, offsetY = 0, traversed }: DiagramEdgeProps)
const pts = edge.points;
if (!pts || pts.length < 2) return null;
// Build SVG path: move to first point, then cubic bezier or line to rest
// Build SVG path: move to first point, then line segments through all waypoints.
// ELK bend points are waypoints, not bezier control points.
let d = `M ${pts[0][0]} ${pts[0][1] + offsetY}`;
if (pts.length === 2) {
d += ` L ${pts[1][0]} ${pts[1][1] + offsetY}`;
} else if (pts.length === 4) {
// 4 points: start, control1, control2, end → cubic bezier
d += ` C ${pts[1][0]} ${pts[1][1] + offsetY}, ${pts[2][0]} ${pts[2][1] + offsetY}, ${pts[3][0]} ${pts[3][1] + offsetY}`;
} else {
// Multiple points: connect with line segments through intermediate points
for (let i = 1; i < pts.length; i++) {
d += ` L ${pts[i][0]} ${pts[i][1] + offsetY}`;
}
for (let i = 1; i < pts.length; i++) {
d += ` L ${pts[i][0]} ${pts[i][1] + offsetY}`;
}
return (