diff options
Diffstat (limited to 'termui/sparkline.go')
-rw-r--r-- | termui/sparkline.go | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/termui/sparkline.go b/termui/sparkline.go new file mode 100644 index 0000000..2f27d9e --- /dev/null +++ b/termui/sparkline.go @@ -0,0 +1,102 @@ +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), + ) + } + } +} |