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)) }