summaryrefslogtreecommitdiffstats
path: root/termui/sparkline.go
blob: 3b3ad756fa89df5fd1c22db84f5277510cb7a89b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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:]
		}
	}
}