xmtop/termui/sparkline.go
Sean E. Russell 44b5d2d6ec Fixes #128 for LineGraph; there's probably another leak in Sparkline
Fixes #128 for Sparkline. Also, the amount of data held is tiny so bumped the buffer to 4x to reduce collections.

Minor comment change
2020-05-31 16:09:51 -05:00

107 lines
2.7 KiB
Go

package termui
import (
"image"
"log"
. "github.com/gizak/termui/v3"
)
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
type Sparkline struct {
Data []int
Title1 string
Title2 string
TitleColor Color
LineColor Color
}
// SparklineGroup is a renderable widget which groups together the given sparklines.
type SparklineGroup struct {
*Block
Lines []*Sparkline
}
// Add appends a given Sparkline to the *SparklineGroup.
func (self *SparklineGroup) Add(sl Sparkline) {
self.Lines = append(self.Lines, &sl)
}
// NewSparkline returns an unrenderable single sparkline that intended to be added into a SparklineGroup.
func NewSparkline() *Sparkline {
return &Sparkline{}
}
// NewSparklineGroup return a new *SparklineGroup with given Sparklines, you can always add a new Sparkline later.
func NewSparklineGroup(ss ...*Sparkline) *SparklineGroup {
return &SparklineGroup{
Block: NewBlock(),
Lines: ss,
}
}
func (self *SparklineGroup) Draw(buf *Buffer) {
self.Block.Draw(buf)
lc := len(self.Lines) // lineCount
// renders each sparkline and its titles
for i, line := range self.Lines {
// prints titles
title1Y := self.Inner.Min.Y + 1 + (self.Inner.Dy()/lc)*i
title2Y := self.Inner.Min.Y + 2 + (self.Inner.Dy()/lc)*i
title1 := TrimString(line.Title1, self.Inner.Dx())
title2 := TrimString(line.Title2, self.Inner.Dx())
if self.Inner.Dy() > 5 {
buf.SetString(
title1,
NewStyle(line.TitleColor, ColorClear, ModifierBold),
image.Pt(self.Inner.Min.X, title1Y),
)
}
if self.Inner.Dy() > 6 {
buf.SetString(
title2,
NewStyle(line.TitleColor, ColorClear, ModifierBold),
image.Pt(self.Inner.Min.X, title2Y),
)
}
sparkY := (self.Inner.Dy() / lc) * (i + 1)
// finds max data in current view used for relative heights
max := 1
for i := len(line.Data) - 1; i >= 0 && self.Inner.Dx()-((len(line.Data)-1)-i) >= 1; i-- {
if line.Data[i] > max {
max = line.Data[i]
}
}
// prints sparkline
for x := self.Inner.Dx(); x >= 1; x-- {
char := BARS[1]
if (self.Inner.Dx() - x) < len(line.Data) {
offset := self.Inner.Dx() - x
curItem := line.Data[(len(line.Data)-1)-offset]
percent := float64(curItem) / float64(max)
index := int(percent*float64(len(BARS)-2)) + 1
if index < 1 || index >= len(BARS) {
log.Printf(
"invalid sparkline data value. index: %v, percent: %v, curItem: %v, offset: %v",
index, percent, curItem, offset,
)
} else {
char = BARS[index]
}
}
buf.SetCell(
NewCell(char, NewStyle(line.LineColor)),
image.Pt(self.Inner.Min.X+x-1, self.Inner.Min.Y+sparkY-1),
)
}
dx := self.Inner.Dx()
if len(line.Data) > 4*dx {
line.Data = line.Data[dx-1:]
}
}
}