diff options
Diffstat (limited to 'internal/tui/dashboard')
| -rw-r--r-- | internal/tui/dashboard/layout.go | 3 | ||||
| -rw-r--r-- | internal/tui/dashboard/overview.go | 2 | ||||
| -rw-r--r-- | internal/tui/dashboard/overview_test.go | 12 | ||||
| -rw-r--r-- | internal/tui/dashboard/sparkline.go | 33 | ||||
| -rw-r--r-- | internal/tui/dashboard/sparkline_test.go | 21 |
5 files changed, 47 insertions, 24 deletions
diff --git a/internal/tui/dashboard/layout.go b/internal/tui/dashboard/layout.go index 38cce18..75cbafb 100644 --- a/internal/tui/dashboard/layout.go +++ b/internal/tui/dashboard/layout.go @@ -1,3 +1,6 @@ package dashboard const panelHorizontalChrome = 4 + +// Keep a small guard so sparkline rows never soft-wrap in panel cells. +const sparklineSafetyMargin = 3 diff --git a/internal/tui/dashboard/overview.go b/internal/tui/dashboard/overview.go index 9a77da0..990e36d 100644 --- a/internal/tui/dashboard/overview.go +++ b/internal/tui/dashboard/overview.go @@ -220,7 +220,7 @@ func summaryBoxInnerWidth(width int) int { } func renderOverviewSparkline(label string, data []float64, panelInner int) string { - w := panelInner - utf8.RuneCountInString(label) - 1 + w := panelInner - utf8.RuneCountInString(label) - 1 - sparklineSafetyMargin if w < 8 { w = 8 } diff --git a/internal/tui/dashboard/overview_test.go b/internal/tui/dashboard/overview_test.go index 89f00fb..706661e 100644 --- a/internal/tui/dashboard/overview_test.go +++ b/internal/tui/dashboard/overview_test.go @@ -117,3 +117,15 @@ func TestRenderOverviewDoesNotOverflowWidth(t *testing.T) { } } } + +func TestRenderOverviewSparklineHasSafetyMargin(t *testing.T) { + const panelInner = 80 + out := renderOverviewSparkline("Latency:", []float64{1, 2, 3, 4, 5}, panelInner) + lines := strings.Split(out, "\n") + if len(lines) != 2 { + t.Fatalf("expected 2-line sparkline, got %q", out) + } + if got, max := lipgloss.Width(lines[0]), panelInner-sparklineSafetyMargin; got > max { + t.Fatalf("expected sparkline width <= %d with safety margin, got %d", max, got) + } +} diff --git a/internal/tui/dashboard/sparkline.go b/internal/tui/dashboard/sparkline.go index 93a6789..2ce8c90 100644 --- a/internal/tui/dashboard/sparkline.go +++ b/internal/tui/dashboard/sparkline.go @@ -11,15 +11,23 @@ func renderSparkline(data []float64, width int) string { } samples := sampleForWidth(data, width) + leftPad := 0 + if len(samples) < width { + leftPad = width - len(samples) + } min, max := minMax(samples) if min == max { top := repeatRune(' ', width) - bottom := repeatRune('█', width) + bottom := repeatRune(' ', leftPad) + repeatRune('█', len(samples)) return top + "\n" + bottom } top := make([]rune, width) bottom := make([]rune, width) + for i := 0; i < leftPad; i++ { + top[i] = ' ' + bottom[i] = ' ' + } scale := 16.0 denom := max - min for i, value := range samples { @@ -40,7 +48,7 @@ func renderSparkline(data []float64, width int) string { bottomLevel = 8 } - col := i + col := leftPad + i top[col] = sparkRowChars[topLevel] bottom[col] = sparkRowChars[bottomLevel] } @@ -61,24 +69,11 @@ func renderLabeledSparkline(label string, data []float64, width int) string { } func sampleForWidth(data []float64, width int) []float64 { - if width == 1 { - return []float64{data[len(data)-1]} - } - if len(data) == 1 { - out := make([]float64, width) - for i := range out { - out[i] = data[0] - } - return out - } - - last := len(data) - 1 - samples := make([]float64, width) - for i := 0; i < width; i++ { - idx := int(math.Round(float64(i) * float64(last) / float64(width-1))) - samples[i] = data[idx] + if width >= len(data) { + return append([]float64(nil), data...) } - return samples + start := len(data) - width + return append([]float64(nil), data[start:]...) } func minMax(values []float64) (float64, float64) { diff --git a/internal/tui/dashboard/sparkline_test.go b/internal/tui/dashboard/sparkline_test.go index 66d1673..d7acd33 100644 --- a/internal/tui/dashboard/sparkline_test.go +++ b/internal/tui/dashboard/sparkline_test.go @@ -16,7 +16,7 @@ func TestRenderSparklineEmptyOrInvalidWidth(t *testing.T) { func TestRenderSparklineSingleValue(t *testing.T) { got := renderSparkline([]float64{10}, 8) - if got != " \n████████" { + if got != " \n █" { t.Fatalf("expected two-line constant sparkline, got %q", got) } } @@ -28,14 +28,14 @@ func TestRenderSparklineAllEqualValues(t *testing.T) { } } -func TestRenderSparklineStretchesShortHistoryToWidth(t *testing.T) { +func TestRenderSparklineRightAlignsShortHistory(t *testing.T) { got := renderSparkline([]float64{1, 2, 3}, 6) lines := strings.Split(got, "\n") if len(lines) != 2 { t.Fatalf("expected 2 lines, got %q", got) } - if strings.HasPrefix(lines[1], " ") { - t.Fatalf("expected short history to fill width, got %q", lines[1]) + if !strings.HasPrefix(lines[1], " ") { + t.Fatalf("expected left padding for short history, got %q", lines[1]) } } @@ -50,6 +50,19 @@ func TestRenderSparklineRespectsWidthTruncation(t *testing.T) { } } +func TestSampleForWidthUsesRecentTail(t *testing.T) { + got := sampleForWidth([]float64{1, 2, 3, 4, 5, 6}, 3) + want := []float64{4, 5, 6} + if len(got) != len(want) { + t.Fatalf("expected tail length %d, got %d", len(want), len(got)) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("expected tail %v, got %v", want, got) + } + } +} + func TestRenderSparklineSpansLowToHigh(t *testing.T) { got := renderSparkline([]float64{0, 10}, 2) lines := strings.Split(got, "\n") |
