This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch fix/camel-tui-circuit-breaker
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/fix/camel-tui-circuit-breaker
by this push:
new 3170c1cf2755 TUI overview: add split green/red throughput chart with
Y/X axes and legend
3170c1cf2755 is described below
commit 3170c1cf27554354c63af314a89a70e334c2d0a0
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat May 16 16:10:48 2026 +0200
TUI overview: add split green/red throughput chart with Y/X axes and legend
- Replace single-color sparkline with BarGroup (green=ok, red=failed) bars
- Dynamic tick count: fills available terminal width (up to 60s of history)
- Y-axis scale labels (max, max/2, 0) aligned with bar chart rows
- X-axis time markers (-Ns labels and "now") drawn below the chart
- Styled title legend showing current msg/s, ok count and fail count
- barWidth=1, textValue("") suppresses per-bar numeric labels
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 90 +++++++++++++++++++---
1 file changed, 78 insertions(+), 12 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 213c4bc6071d..c8a3d27cbda2 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -875,12 +875,12 @@ public class CamelMonitor extends CamelCommand {
List<IntegrationInfo> infos = new ArrayList<>(data.get());
infos.sort(this::sortOverview);
- // Split: table (fill) + sparkline (height 8) if we have data
+ // Split: table (fill) + chart (14 rows: 13 chart + 1 x-axis) if we
have data
boolean hasSparkline = !throughputHistory.isEmpty();
List<Rect> chunks;
if (hasSparkline) {
chunks = Layout.vertical()
- .constraints(Constraint.fill(), Constraint.length(13))
+ .constraints(Constraint.fill(), Constraint.length(14))
.split(area);
} else {
chunks = List.of(area);
@@ -976,11 +976,27 @@ public class CamelMonitor extends CamelCommand {
frame.renderStatefulWidget(table, chunks.get(0), overviewTableState);
- // Split green/red throughput bar chart
+ // Split green/red throughput bar chart with Y and X axes
if (hasSparkline && chunks.size() > 1) {
- // Render last 20 ticks as pairs of bars (ok=green, failed=red);
barWidth=2 so
- // double-digit values fit: 20 ticks × 2 bars × width 2 = ~80
columns
- int renderPoints = Math.min(20, MAX_SPARKLINE_POINTS);
+ Rect chartTotalArea = chunks.get(1);
+
+ // Split chart area: chart rows (13) + x-axis label row (1)
+ List<Rect> vChunks = Layout.vertical()
+ .constraints(Constraint.fill(), Constraint.length(1))
+ .split(chartTotalArea);
+
+ // Split chart rows: y-axis labels (4 cols) + bar chart (fill)
+ List<Rect> hChunks = Layout.horizontal()
+ .constraints(Constraint.length(4), Constraint.fill())
+ .split(vChunks.get(0));
+
+ Rect barChartArea = hChunks.get(1);
+
+ // Compute how many ticks fit: each tick = 2 bars × barWidth=1 = 2
cols
+ int innerBarCols = Math.max(2, barChartArea.width() - 2); // minus
block borders
+ int renderPoints = Math.min(MAX_SPARKLINE_POINTS, innerBarCols /
2);
+
+ // Merge throughput histories across all PIDs
long[] mergedTotal = new long[renderPoints];
long[] mergedFailed = new long[renderPoints];
for (int i = 0; i < renderPoints; i++) {
@@ -1005,27 +1021,77 @@ public class CamelMonitor extends CamelCommand {
long curTp = mergedTotal[renderPoints - 1];
long curFailed = mergedFailed[renderPoints - 1];
long curOk = Math.max(0, curTp - curFailed);
- String chartTitle = String.format(" Throughput: %d msg/s (%d ok /
%d failed) ", curTp, curOk, curFailed);
+ // Styled legend in chart title
+ Line titleLine = Line.from(
+ Span.raw(String.format(" Throughput: %d msg/s ", curTp)),
+ Span.styled("■", Style.EMPTY.fg(Color.GREEN)),
+ Span.raw(String.format(" ok:%d ", curOk)),
+ Span.styled("■", Style.EMPTY.fg(Color.RED)),
+ Span.raw(String.format(" fail:%d ", curFailed)));
+
+ // Build bar groups (ok=green, failed=red), no bar value labels
List<BarGroup> groups = new ArrayList<>();
for (int i = 0; i < renderPoints; i++) {
long failed = Math.min(mergedFailed[i], mergedTotal[i]);
long ok = Math.max(0, mergedTotal[i] - failed);
groups.add(BarGroup.of(
-
Bar.builder().value(ok).style(Style.EMPTY.fg(Color.GREEN)).build(),
-
Bar.builder().value(failed).style(Style.EMPTY.fg(Color.RED)).build()));
+
Bar.builder().value(ok).textValue("").style(Style.EMPTY.fg(Color.GREEN)).build(),
+
Bar.builder().value(failed).textValue("").style(Style.EMPTY.fg(Color.RED)).build()));
}
BarChart barChart = BarChart.builder()
.data(groups)
.max(maxTp > 0 ? maxTp + 2 : 2)
- .barWidth(2)
+ .barWidth(1)
.barGap(0)
.groupGap(0)
-
.block(Block.builder().borderType(BorderType.ROUNDED).title(chartTitle).build())
+ .block(Block.builder().borderType(BorderType.ROUNDED)
+ .title(Title.from(titleLine)).build())
.build();
- frame.renderWidget(barChart, chunks.get(1));
+ frame.renderWidget(barChart, barChartArea);
+
+ // Y-axis: scale labels aligned with bar chart inner rows
+ int barRows = vChunks.get(0).height() - 2; // minus top + bottom
border
+ List<Line> yLines = new ArrayList<>();
+ Style dimStyle = Style.EMPTY.dim();
+ for (int row = 0; row < vChunks.get(0).height(); row++) {
+ int barRow = row - 1; // bar area starts after top border
+ if (barRow == 0) {
+ yLines.add(Line.from(Span.styled(String.format("%3d",
maxTp), dimStyle)));
+ } else if (barRows > 4 && barRow == barRows / 2) {
+ yLines.add(Line.from(Span.styled(String.format("%3d",
maxTp / 2), dimStyle)));
+ } else if (barRow == barRows - 1) {
+ yLines.add(Line.from(Span.styled(" 0", dimStyle)));
+ } else {
+ yLines.add(Line.from(""));
+ }
+ }
+
frame.renderWidget(Paragraph.builder().text(Text.from(yLines)).build(),
hChunks.get(0));
+
+ // X-axis: time labels drawn into the bottom row
+ if (!vChunks.get(1).isEmpty()) {
+ int barInnerStartX = barChartArea.x() + 1; // inside left
border
+ int xAxisY = vChunks.get(1).y();
+ // Markers at: oldest, 1/4, 1/2, 3/4, newest
+ int[][] markerIndices = {
+ { 0, renderPoints },
+ { renderPoints / 4, renderPoints - renderPoints / 4 },
+ { renderPoints / 2, renderPoints / 2 },
+ { 3 * renderPoints / 4, renderPoints / 4 },
+ { renderPoints - 1, 0 }
+ };
+ for (int[] m : markerIndices) {
+ int groupIdx = m[0];
+ int secsAgo = m[1];
+ String label = secsAgo == 0 ? "now" : "-" + secsAgo + "s";
+ int markerX = barInnerStartX + groupIdx * 2;
+ if (markerX + label.length() <= barChartArea.right()) {
+ frame.buffer().setString(markerX, xAxisY, label,
dimStyle);
+ }
+ }
+ }
}
}