Negative numbers are suppressed as zeros. The height of each data point is based off zero, so it will be confusing when it comes to negative numbers. Simply treat negative values data points as zeroes.
168 lines
3.7 KiB
Go
168 lines
3.7 KiB
Go
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
|
// Use of this source code is governed by a MIT license that can
|
|
// be found in the LICENSE file.
|
|
|
|
package termui
|
|
|
|
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
|
|
/*
|
|
data := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1}
|
|
spl := termui.NewSparkline()
|
|
spl.Data = data
|
|
spl.Title = "Sparkline 0"
|
|
spl.LineColor = termui.ColorGreen
|
|
*/
|
|
type Sparkline struct {
|
|
Data []int
|
|
Height int
|
|
Title string
|
|
TitleColor Attribute
|
|
LineColor Attribute
|
|
displayHeight int
|
|
scale float32
|
|
max int
|
|
}
|
|
|
|
// Sparklines is a renderable widget which groups together the given sparklines.
|
|
/*
|
|
spls := termui.NewSparklines(spl0,spl1,spl2) //...
|
|
spls.Height = 2
|
|
spls.Width = 20
|
|
*/
|
|
type Sparklines struct {
|
|
Block
|
|
Lines []Sparkline
|
|
displayLines int
|
|
displayWidth int
|
|
}
|
|
|
|
var sparks = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
|
|
|
|
// Add appends a given Sparkline to s *Sparklines.
|
|
func (s *Sparklines) Add(sl Sparkline) {
|
|
s.Lines = append(s.Lines, sl)
|
|
}
|
|
|
|
// NewSparkline returns a unrenderable single sparkline that intended to be added into Sparklines.
|
|
func NewSparkline() Sparkline {
|
|
return Sparkline{
|
|
Height: 1,
|
|
TitleColor: ThemeAttr("sparkline.title.fg"),
|
|
LineColor: ThemeAttr("sparkline.line.fg")}
|
|
}
|
|
|
|
// NewSparklines return a new *Spaklines with given Sparkline(s), you can always add a new Sparkline later.
|
|
func NewSparklines(ss ...Sparkline) *Sparklines {
|
|
s := &Sparklines{Block: *NewBlock(), Lines: ss}
|
|
return s
|
|
}
|
|
|
|
func (sl *Sparklines) update() {
|
|
for i, v := range sl.Lines {
|
|
if v.Title == "" {
|
|
sl.Lines[i].displayHeight = v.Height
|
|
} else {
|
|
sl.Lines[i].displayHeight = v.Height + 1
|
|
}
|
|
}
|
|
sl.displayWidth = sl.innerArea.Dx()
|
|
|
|
// get how many lines gotta display
|
|
h := 0
|
|
sl.displayLines = 0
|
|
for _, v := range sl.Lines {
|
|
if h+v.displayHeight <= sl.innerArea.Dy() {
|
|
sl.displayLines++
|
|
} else {
|
|
break
|
|
}
|
|
h += v.displayHeight
|
|
}
|
|
|
|
for i := 0; i < sl.displayLines; i++ {
|
|
data := sl.Lines[i].Data
|
|
|
|
max := 0
|
|
for _, v := range data {
|
|
if max < v {
|
|
max = v
|
|
}
|
|
}
|
|
sl.Lines[i].max = max
|
|
if max != 0 {
|
|
sl.Lines[i].scale = float32(8*sl.Lines[i].Height) / float32(max)
|
|
} else { // when all negative
|
|
sl.Lines[i].scale = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
// Buffer implements Bufferer interface.
|
|
func (sl *Sparklines) Buffer() Buffer {
|
|
buf := sl.Block.Buffer()
|
|
sl.update()
|
|
|
|
oftY := 0
|
|
for i := 0; i < sl.displayLines; i++ {
|
|
l := sl.Lines[i]
|
|
data := l.Data
|
|
|
|
if len(data) > sl.innerArea.Dx() {
|
|
data = data[len(data)-sl.innerArea.Dx():]
|
|
}
|
|
|
|
if l.Title != "" {
|
|
rs := trimStr2Runes(l.Title, sl.innerArea.Dx())
|
|
oftX := 0
|
|
for _, v := range rs {
|
|
w := charWidth(v)
|
|
c := Cell{
|
|
Ch: v,
|
|
Fg: l.TitleColor,
|
|
Bg: sl.Bg,
|
|
}
|
|
x := sl.innerArea.Min.X + oftX
|
|
y := sl.innerArea.Min.Y + oftY
|
|
buf.Set(x, y, c)
|
|
oftX += w
|
|
}
|
|
}
|
|
|
|
for j, v := range data {
|
|
// display height of the data point, zero when data is negative
|
|
h := int(float32(v)*l.scale + 0.5)
|
|
if v < 0 {
|
|
h = 0
|
|
}
|
|
|
|
barCnt := h / 8
|
|
barMod := h % 8
|
|
for jj := 0; jj < barCnt; jj++ {
|
|
c := Cell{
|
|
Ch: ' ', // => sparks[7]
|
|
Bg: l.LineColor,
|
|
}
|
|
x := sl.innerArea.Min.X + j
|
|
y := sl.innerArea.Min.Y + oftY + l.Height - jj
|
|
|
|
//p.Bg = sl.BgColor
|
|
buf.Set(x, y, c)
|
|
}
|
|
if barMod != 0 {
|
|
c := Cell{
|
|
Ch: sparks[barMod-1],
|
|
Fg: l.LineColor,
|
|
Bg: sl.Bg,
|
|
}
|
|
x := sl.innerArea.Min.X + j
|
|
y := sl.innerArea.Min.Y + oftY + l.Height - barCnt
|
|
buf.Set(x, y, c)
|
|
}
|
|
}
|
|
|
|
oftY += l.displayHeight
|
|
}
|
|
|
|
return buf
|
|
}
|