This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch 
CAMEL-23631-route-diagram-highlight-error-path
in repository https://gitbox.apache.org/repos/asf/camel.git

commit d9cc166ec214db590febcc54250773c2b3156c79
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed May 27 21:55:46 2026 +0200

    CAMEL-23631: Highlight arrows/lines instead of boxes in route diagram
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../camel/diagram/RouteDiagramAsciiRenderer.java   | 72 +++++++++++++++-------
 .../apache/camel/diagram/RouteDiagramRenderer.java | 20 ++----
 .../org/apache/camel/diagram/RouteDiagramTest.java | 34 ++++++++--
 3 files changed, 84 insertions(+), 42 deletions(-)

diff --git 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java
 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java
index 150fb77a736a..a09e23bb7e4c 100644
--- 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java
+++ 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java
@@ -188,22 +188,26 @@ public class RouteDiagramAsciiRenderer {
 
         for (LayoutNode ln : lr.nodes) {
             if (ln.parentNode != null) {
+                boolean highlightArrow = highlightedNodeIds != null
+                        && isHighlighted(ln, highlightedNodeIds)
+                        && isHighlighted(ln.parentNode, highlightedNodeIds);
                 if (ln.connectFromMerge) {
-                    drawMergeArrow(grid, ln);
+                    drawMergeArrow(grid, ln, highlightArrow, highlightStyle);
                 } else {
-                    drawArrow(grid, ln.parentNode, ln);
+                    drawArrow(grid, ln.parentNode, ln, highlightArrow, 
highlightStyle);
                 }
             }
         }
 
         for (LayoutNode ln : lr.nodes) {
             drawNode(grid, ln);
-            if (highlightedNodeIds != null && ln.id != null && 
highlightedNodeIds.contains(ln.id)) {
-                recordHighlightPositions(grid, ln, highlightStyle);
-            }
         }
     }
 
+    private static boolean isHighlighted(LayoutNode node, Set<String> 
highlightedNodeIds) {
+        return node.id != null && highlightedNodeIds.contains(node.id);
+    }
+
     private void drawRoute(char[][] grid, LayoutRoute lr) {
         drawRoute(grid, lr, null, null);
     }
@@ -251,24 +255,13 @@ public class RouteDiagramAsciiRenderer {
         }
     }
 
-    private void recordHighlightPositions(
-            char[][] grid, LayoutNode node, RouteDiagramHelper.HighlightStyle 
style) {
-        int col = toCol(node.x);
-        int row = toRow(node.y);
-        int innerWidth = boxWidth - 4;
-        List<String> lines = rewrapText(node, innerWidth);
-        int height = 2 + lines.size();
-
-        CounterType ct = style == RouteDiagramHelper.HighlightStyle.FAIL
-                ? CounterType.HIGHLIGHT_FAIL
-                : CounterType.HIGHLIGHT_SUCCESS;
-
-        for (int r = row; r < row + height && r < grid.length; r++) {
-            counterPositions.add(new CounterPos(r, col, boxWidth, ct));
-        }
+    private void drawArrow(char[][] grid, LayoutNode from, LayoutNode to) {
+        drawArrow(grid, from, to, false, null);
     }
 
-    private void drawArrow(char[][] grid, LayoutNode from, LayoutNode to) {
+    private void drawArrow(
+            char[][] grid, LayoutNode from, LayoutNode to,
+            boolean highlighted, RouteDiagramHelper.HighlightStyle 
highlightStyle) {
         int fromCx = centerCol(from);
         int fromBottom = toRow(from.y) + boxHeight(from);
         int toCx = centerCol(to);
@@ -279,10 +272,19 @@ public class RouteDiagramAsciiRenderer {
         boolean dashed = metrics && total == 0;
 
         drawArrowPath(grid, fromCx, fromBottom, toCx, toTop, dashed);
+        if (highlighted) {
+            recordArrowHighlight(fromCx, fromBottom, toCx, toTop, 
highlightStyle);
+        }
         drawCounters(grid, toCx, toTop, stat);
     }
 
     private void drawMergeArrow(char[][] grid, LayoutNode to) {
+        drawMergeArrow(grid, to, false, null);
+    }
+
+    private void drawMergeArrow(
+            char[][] grid, LayoutNode to,
+            boolean highlighted, RouteDiagramHelper.HighlightStyle 
highlightStyle) {
         int fromCx = toCol(to.mergeCx);
         int fromRow = toRow(to.mergeY);
         int toCx = centerCol(to);
@@ -293,9 +295,37 @@ public class RouteDiagramAsciiRenderer {
         boolean dashed = metrics && total == 0;
 
         drawArrowPath(grid, fromCx, fromRow, toCx, toTop, dashed);
+        if (highlighted) {
+            recordArrowHighlight(fromCx, fromRow, toCx, toTop, highlightStyle);
+        }
         drawCounters(grid, toCx, toTop, stat);
     }
 
+    private void recordArrowHighlight(
+            int fromCx, int fromRow, int toCx, int toRow,
+            RouteDiagramHelper.HighlightStyle style) {
+        CounterType ct = style == RouteDiagramHelper.HighlightStyle.FAIL
+                ? CounterType.HIGHLIGHT_FAIL
+                : CounterType.HIGHLIGHT_SUCCESS;
+
+        if (fromCx == toCx) {
+            for (int r = fromRow; r < toRow; r++) {
+                counterPositions.add(new CounterPos(r, fromCx, 1, ct));
+            }
+        } else {
+            int midRow = fromRow + (toRow - fromRow) / 2;
+            for (int r = fromRow; r <= midRow; r++) {
+                counterPositions.add(new CounterPos(r, fromCx, 1, ct));
+            }
+            int minC = Math.min(fromCx, toCx);
+            int maxC = Math.max(fromCx, toCx);
+            counterPositions.add(new CounterPos(midRow, minC, maxC - minC + 1, 
ct));
+            for (int r = midRow; r < toRow; r++) {
+                counterPositions.add(new CounterPos(r, toCx, 1, ct));
+            }
+        }
+    }
+
     private StatInfo resolveStatInfo(LayoutNode to) {
         if (!metrics) {
             return null;
diff --git 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java
 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java
index a87b85001636..789d04a5b314 100644
--- 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java
+++ 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java
@@ -324,7 +324,7 @@ public class RouteDiagramRenderer {
         }
 
         for (LayoutNode ln : lr.nodes) {
-            drawNode(g, ln, colors, highlightedNodeIds, highlightStyle);
+            drawNode(g, ln, colors);
         }
     }
 
@@ -417,22 +417,14 @@ public class RouteDiagramRenderer {
         drawArrowFromMerge(g, to, colors, false, null);
     }
 
-    private void drawNode(
-            Graphics2D g, LayoutNode node, DiagramColors colors,
-            Set<String> highlightedNodeIds, RouteDiagramHelper.HighlightStyle 
highlightStyle) {
+    private void drawNode(Graphics2D g, LayoutNode node, DiagramColors colors) 
{
         Color color = getNodeColor(node.type, colors);
 
         g.setColor(color);
         g.fillRoundRect(node.x, node.y, nodeWidth, node.height, ARC, ARC);
 
-        boolean highlighted = highlightedNodeIds != null && 
isHighlighted(node, highlightedNodeIds);
-        if (highlighted) {
-            g.setColor(highlightColor(highlightStyle));
-            g.setStroke(new BasicStroke(HIGHLIGHT_STROKE_WIDTH));
-        } else {
-            g.setColor(color.brighter());
-            g.setStroke(new BasicStroke(BORDER_STROKE_WIDTH));
-        }
+        g.setColor(color.brighter());
+        g.setStroke(new BasicStroke(BORDER_STROKE_WIDTH));
         g.drawRoundRect(node.x, node.y, nodeWidth, node.height, ARC, ARC);
 
         g.setColor(colors.getText());
@@ -458,10 +450,6 @@ public class RouteDiagramRenderer {
         }
     }
 
-    private void drawNode(Graphics2D g, LayoutNode node, DiagramColors colors) 
{
-        drawNode(g, node, colors, null, null);
-    }
-
     private void drawArrow(
             Graphics2D g, LayoutNode from, LayoutNode to, DiagramColors colors,
             boolean highlighted, RouteDiagramHelper.HighlightStyle 
highlightStyle) {
diff --git 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
index 829d711b4cf4..4024de903f33 100644
--- 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
+++ 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
@@ -1308,7 +1308,7 @@ class RouteDiagramTest {
     }
 
     @Test
-    void testAsciiDiagramHighlightOnlyTargetedNodes() {
+    void testAsciiDiagramHighlightArrowBetweenNodes() {
         RouteInfo route = new RouteInfo();
         route.routeId = "route1";
         route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
@@ -1318,7 +1318,31 @@ class RouteDiagramTest {
         RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
         LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
 
-        // only highlight from1, not to1 or to2
+        // highlight from1 and to1 — the arrow between them should be 
highlighted
+        Set<String> highlighted = Set.of("from1", "to1");
+        RouteDiagramAsciiRenderer renderer = new 
RouteDiagramAsciiRenderer(engine.getNodeWidth());
+        renderer.renderDiagram(
+                List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
+                highlighted, HighlightStyle.SUCCESS);
+
+        long highlightCount = renderer.getCounterPositions().stream()
+                .filter(cp -> cp.type() == 
RouteDiagramAsciiRenderer.CounterType.HIGHLIGHT_SUCCESS)
+                .count();
+        assertTrue(highlightCount > 0, "Should have highlight positions for 
arrow between from1 and to1");
+    }
+
+    @Test
+    void testAsciiDiagramNoHighlightForSingleNode() {
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+        route.nodes.add(nodeWithId("to", "log:a", 1, "to1"));
+        route.nodes.add(nodeWithId("to", "log:b", 1, "to2"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        // only from1 highlighted — no arrow between two highlighted nodes
         Set<String> highlighted = Set.of("from1");
         RouteDiagramAsciiRenderer renderer = new 
RouteDiagramAsciiRenderer(engine.getNodeWidth());
         renderer.renderDiagram(
@@ -1328,7 +1352,7 @@ class RouteDiagramTest {
         long highlightCount = renderer.getCounterPositions().stream()
                 .filter(cp -> cp.type() == 
RouteDiagramAsciiRenderer.CounterType.HIGHLIGHT_SUCCESS)
                 .count();
-        assertTrue(highlightCount > 0, "Should have highlight positions for 
from1");
+        assertEquals(0, highlightCount, "No arrow highlight when only one 
endpoint is highlighted");
     }
 
     @Test
@@ -1431,13 +1455,13 @@ class RouteDiagramTest {
         RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
         LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
 
-        Set<String> highlighted = Set.of("from1");
+        Set<String> highlighted = Set.of("from1", "to1");
         RouteDiagramAsciiRenderer renderer = new 
RouteDiagramAsciiRenderer(engine.getNodeWidth(), true);
         String ansi = renderer.renderDiagramAnsi(
                 List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
                 highlighted, HighlightStyle.SUCCESS);
 
-        assertTrue(ansi.contains("\033[32m"), "Unicode mode should also apply 
ANSI highlight colors");
+        assertTrue(ansi.contains("\033[32m"), "Unicode mode should also apply 
ANSI highlight colors on arrows");
         assertTrue(ansi.contains("┌"), "Should still use Unicode box-drawing 
characters");
     }
 

Reply via email to