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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
package termui
import (
"image"
"sort"
. "github.com/gizak/termui/v3"
drawille "github.com/xxxserxxx/gotop/v3/termui/drawille-go"
)
// LineGraph implements a line graph of data points.
type LineGraph struct {
*Block
Data map[string][]float64
Labels map[string]string
HorizontalScale int
LineColors map[string]Color
DefaultLineColor Color
}
func NewLineGraph() *LineGraph {
return &LineGraph{
Block: NewBlock(),
Data: make(map[string][]float64),
Labels: make(map[string]string),
HorizontalScale: 5,
LineColors: make(map[string]Color),
}
}
func (self *LineGraph) Draw(buf *Buffer) {
self.Block.Draw(buf)
// we render each data point on to the canvas then copy over the braille to the buffer at the end
// fyi braille characters have 2x4 dots for each character
c := drawille.NewCanvas()
// used to keep track of the braille colors until the end when we render the braille to the buffer
colors := make([][]Color, self.Inner.Dx()+2)
for i := range colors {
colors[i] = make([]Color, self.Inner.Dy()+2)
}
// sort the series so that overlapping data will overlap the same way each time
seriesList := make([]string, len(self.Data))
i := 0
for seriesName := range self.Data {
seriesList[i] = seriesName
i++
}
sort.Strings(seriesList)
// draw lines in reverse order so that the first color defined in the colorscheme is on top
for i := len(seriesList) - 1; i >= 0; i-- {
seriesName := seriesList[i]
seriesData := self.Data[seriesName]
seriesLineColor, ok := self.LineColors[seriesName]
if !ok {
seriesLineColor = self.DefaultLineColor
}
// coordinates of last point
lastY, lastX := -1, -1
// assign colors to `colors` and lines/points to the canvas
for i := len(seriesData) - 1; i >= 0; i-- {
x := ((self.Inner.Dx() + 1) * 2) - 1 - (((len(seriesData) - 1) - i) * self.HorizontalScale)
y := ((self.Inner.Dy() + 1) * 4) - 1 - int((float64((self.Inner.Dy())*4)-1)*(seriesData[i]/100))
if x < 0 {
// render the line to the last point up to the wall
if x > -self.HorizontalScale {
for _, p := range drawille.Line(lastX, lastY, x, y) {
if p.X > 0 {
c.Set(p.X, p.Y)
colors[p.X/2][p.Y/4] = seriesLineColor
}
}
}
break
}
if lastY == -1 { // if this is the first point
c.Set(x, y)
colors[x/2][y/4] = seriesLineColor
} else {
c.DrawLine(lastX, lastY, x, y)
for _, p := range drawille.Line(lastX, lastY, x, y) {
colors[p.X/2][p.Y/4] = seriesLineColor
}
}
lastX, lastY = x, y
}
// copy braille and colors to buffer
for y, line := range c.Rows(c.MinX(), c.MinY(), c.MaxX(), c.MaxY()) {
for x, char := range line {
x /= 3 // idk why but it works
if x == 0 {
continue
}
if char != 10240 { // empty braille character
buf.SetCell(
NewCell(char, NewStyle(colors[x][y])),
image.Pt(self.Inner.Min.X+x-1, self.Inner.Min.Y+y-1),
)
}
}
}
}
// renders key/label ontop
maxWid := 0
xoff := 0 // X offset for additional columns of text
yoff := 0 // Y offset for resetting column to top of widget
for i, seriesName := range seriesList {
if yoff+i+2 > self.Inner.Dy() {
xoff += maxWid + 2
yoff = -i
maxWid = 0
}
seriesLineColor, ok := self.LineColors[seriesName]
if !ok {
seriesLineColor = self.DefaultLineColor
}
// render key ontop, but let braille be drawn over space characters
str := seriesName + " " + self.Labels[seriesName]
if len(str) > maxWid {
maxWid = len(str)
}
for k, char := range str {
if char != ' ' {
buf.SetCell(
NewCell(char, NewStyle(seriesLineColor)),
image.Pt(xoff+self.Inner.Min.X+2+k, yoff+self.Inner.Min.Y+i+1),
)
}
}
}
}
|