feat(tui): ntcharts sparkline with Y-axis labels and stats

Replaced streamline chart with ntcharts sparkline (block elements,
auto-scaling). Height=2, Y-axis labels (max/min ms) on the left,
Min/Avg/Max stats below. Denser and more readable than the line chart.
This commit is contained in:
2026-06-21 17:12:25 -04:00
parent d0805f61c6
commit a8c43bdb8e
3 changed files with 77 additions and 36 deletions
+75 -13
View File
@@ -1,36 +1,98 @@
package tui
import (
"fmt"
"strings"
"time"
"github.com/NimbleMarkets/ntcharts/canvas/runes"
"github.com/NimbleMarkets/ntcharts/linechart/streamlinechart"
"github.com/NimbleMarkets/ntcharts/sparkline"
"github.com/charmbracelet/lipgloss"
)
func (m Model) latencyChart(latencies []time.Duration, statuses []bool, width, height int) string {
if len(latencies) == 0 || width < 10 || height < 3 {
if len(latencies) == 0 || width < 20 || height < 2 {
return ""
}
chartW := len(latencies)
if chartW > width {
chartW = width
var minMs, maxMs, sumMs int64
minMs = latencies[0].Milliseconds()
maxMs = minMs
for i, l := range latencies {
ms := l.Milliseconds()
if i < len(statuses) && !statuses[i] {
continue
}
if ms < minMs {
minMs = ms
}
if ms > maxMs {
maxMs = ms
}
sumMs += ms
}
upCount := 0
for _, s := range statuses {
if s {
upCount++
}
}
var avgMs int64
if upCount > 0 {
avgMs = sumMs / int64(upCount)
}
lineStyle := lipgloss.NewStyle().Foreground(m.theme.Accent)
slc := streamlinechart.New(chartW, height,
streamlinechart.WithStyles(runes.ThinLineStyle, lineStyle),
)
maxLabel := fmt.Sprintf("%dms", maxMs)
minLabel := fmt.Sprintf("%dms", minMs)
labelW := len(maxLabel)
if len(minLabel) > labelW {
labelW = len(minLabel)
}
labelW += 1
chartW := width - labelW
if chartW > len(latencies) {
chartW = len(latencies)
}
if chartW < 10 {
chartW = 10
}
style := lipgloss.NewStyle().Foreground(m.theme.Accent)
sl := sparkline.New(chartW, height, sparkline.WithStyle(style))
vals := make([]float64, len(latencies))
for i, l := range latencies {
ms := float64(l.Milliseconds())
if i < len(statuses) && !statuses[i] {
ms = 0
}
slc.Push(ms)
vals[i] = ms
}
slc.Draw()
sl.PushAll(vals)
sl.Draw()
return slc.View()
chartLines := strings.Split(sl.View(), "\n")
var result []string
labelStyle := m.st.subtleStyle
for i, line := range chartLines {
var label string
if i == 0 {
label = fmt.Sprintf("%*s", labelW, maxLabel)
} else if i == len(chartLines)-1 {
label = fmt.Sprintf("%*s", labelW, minLabel)
} else {
label = strings.Repeat(" ", labelW)
}
result = append(result, labelStyle.Render(label)+line)
}
stats := fmt.Sprintf("%*s Min %s Avg %s Max %s",
labelW, "",
m.fmtLatency(time.Duration(minMs)*time.Millisecond),
m.fmtLatency(time.Duration(avgMs)*time.Millisecond),
m.fmtLatency(time.Duration(maxMs)*time.Millisecond))
result = append(result, stats)
return strings.Join(result, "\n")
}