Files
statuspanel/internal/ui/graph.go
2026-07-05 14:26:03 -03:00

114 lines
2.6 KiB
Go

package ui
import (
"math"
"sync"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
type rateSample struct {
inBitsPerSecond float64
outBitsPerSecond float64
}
// RateGraph draws inbound and outbound bit-rate history.
type RateGraph struct {
*tview.Box
mu sync.Mutex
samples []rateSample
maxSamples int
}
// NewRateGraph creates a graph primitive with bounded history.
func NewRateGraph(maxSamples int) *RateGraph {
graph := &RateGraph{
Box: tview.NewBox().SetBorder(true).SetTitle(" Interface traffic "),
maxSamples: maxSamples,
}
return graph
}
// AddSample appends one inbound/outbound sample to the graph.
func (g *RateGraph) AddSample(inBitsPerSecond float64, outBitsPerSecond float64) {
g.mu.Lock()
defer g.mu.Unlock()
g.samples = append(g.samples, rateSample{
inBitsPerSecond: inBitsPerSecond,
outBitsPerSecond: outBitsPerSecond,
})
if len(g.samples) > g.maxSamples {
g.samples = g.samples[len(g.samples)-g.maxSamples:]
}
}
// Draw renders the graph primitive.
func (g *RateGraph) Draw(screen tcell.Screen) {
g.Box.DrawForSubclass(screen, g)
x, y, width, height := g.GetInnerRect()
if width <= 0 || height <= 0 {
return
}
samples := g.copySamples()
if len(samples) == 0 {
return
}
maxRate := maxSampleRate(samples)
if maxRate <= 0 {
maxRate = 1
}
label := "in: green out: blue"
for i, r := range label {
if i >= width {
break
}
screen.SetContent(x+i, y, r, nil, tcell.StyleDefault.Foreground(tcell.ColorGray))
}
start := 0
if len(samples) > width {
start = len(samples) - width
}
plotHeight := height - 1
for col, sample := range samples[start:] {
drawPoint(screen, x+col, y+1, plotHeight, sample.inBitsPerSecond, maxRate, tcell.ColorGreen)
drawPoint(screen, x+col, y+1, plotHeight, sample.outBitsPerSecond, maxRate, tcell.ColorDodgerBlue)
}
}
func (g *RateGraph) copySamples() []rateSample {
g.mu.Lock()
defer g.mu.Unlock()
samples := make([]rateSample, len(g.samples))
copy(samples, g.samples)
return samples
}
func maxSampleRate(samples []rateSample) float64 {
var maxRate float64
for _, sample := range samples {
maxRate = math.Max(maxRate, sample.inBitsPerSecond)
maxRate = math.Max(maxRate, sample.outBitsPerSecond)
}
return maxRate
}
func drawPoint(screen tcell.Screen, x int, y int, height int, value float64, maxRate float64, color tcell.Color) {
if height <= 0 {
return
}
ratio := value / maxRate
if ratio < 0 {
ratio = 0
}
if ratio > 1 {
ratio = 1
}
row := height - 1 - int(math.Round(ratio*float64(height-1)))
screen.SetContent(x, y+row, '●', nil, tcell.StyleDefault.Foreground(color))
}