xmtop/termui/sparkline.go

107 lines
2.7 KiB
Go
Raw Normal View History

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