Skip to content
This repository has been archived by the owner on Aug 29, 2020. It is now read-only.

Commit

Permalink
Add widget files from cjbassi/termui
Browse files Browse the repository at this point in the history
  • Loading branch information
cjbassi committed Dec 31, 2018
1 parent 7b77956 commit b73fe56
Show file tree
Hide file tree
Showing 3 changed files with 422 additions and 0 deletions.
128 changes: 128 additions & 0 deletions src/termui/linegraph.go
@@ -0,0 +1,128 @@
package termui

import (
"sort"

drawille "github.com/cjbassi/drawille-go"
)

// LineGraph implements a line graph of data points.
type LineGraph struct {
*Block
Data map[string][]float64
LineColor map[string]Color
Zoom int
Labels map[string]string

DefaultLineColor Color
}

// NewLineGraph returns a new LineGraph with current theme.
func NewLineGraph() *LineGraph {
return &LineGraph{
Block: NewBlock(),
Data: make(map[string][]float64),
LineColor: make(map[string]Color),
Labels: make(map[string]string),
Zoom: 5,

DefaultLineColor: Theme.LineGraph,
}
}

// Buffer implements Bufferer interface.
func (self *LineGraph) Buffer() *Buffer {
buf := self.Block.Buffer()
// 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.X+2)
for i := range colors {
colors[i] = make([]Color, self.Y+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.LineColor[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.X + 1) * 2) - 1 - (((len(seriesData) - 1) - i) * self.Zoom)
y := ((self.Y + 1) * 4) - 1 - int((float64((self.Y)*4)-1)*(seriesData[i]/100))
if x < 0 {
// render the line to the last point up to the wall
if x > 0-self.Zoom {
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(x, y, Cell{char, colors[x][y], self.Bg})
}
}
}
}

// renders key/label ontop
for i, seriesName := range seriesList {
if i+2 > self.Y {
continue
}
seriesLineColor, ok := self.LineColor[seriesName]
if !ok {
seriesLineColor = self.DefaultLineColor
}

// render key ontop, but let braille be drawn over space characters
str := seriesName + " " + self.Labels[seriesName]
for k, char := range str {
if char != ' ' {
buf.SetCell(3+k, i+2, Cell{char, seriesLineColor, self.Bg})
}
}

}

return buf
}
99 changes: 99 additions & 0 deletions src/termui/sparkline.go
@@ -0,0 +1,99 @@
package termui

import (
"fmt"
)

var SPARKS = [8]rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}

// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
type Sparkline struct {
Data []int
Title1 string
Title2 string
TitleColor Color
LineColor Color
}

// Sparklines is a renderable widget which groups together the given sparklines.
type Sparklines struct {
*Block
Lines []*Sparkline
}

// Add appends a given Sparkline to the *Sparklines.
func (self *Sparklines) Add(sl Sparkline) {
self.Lines = append(self.Lines, &sl)
}

// NewSparkline returns an unrenderable single sparkline that intended to be added into a Sparklines.
func NewSparkline() *Sparkline {
return &Sparkline{
TitleColor: Theme.Fg,
LineColor: Theme.Sparkline,
}
}

// NewSparklines return a new *Sparklines with given Sparklines, you can always add a new Sparkline later.
func NewSparklines(ss ...*Sparkline) *Sparklines {
return &Sparklines{
Block: NewBlock(),
Lines: ss,
}
}

// Buffer implements Bufferer interface.
func (self *Sparklines) Buffer() *Buffer {
buf := self.Block.Buffer()

lc := len(self.Lines) // lineCount

// renders each sparkline and its titles
for i, line := range self.Lines {

// prints titles
title1Y := 2 + (self.Y/lc)*i
title2Y := (2 + (self.Y/lc)*i) + 1
title1 := MaxString(line.Title1, self.X)
title2 := MaxString(line.Title2, self.X)
buf.SetString(1, title1Y, title1, line.TitleColor|AttrBold, self.Bg)
buf.SetString(1, title2Y, title2, line.TitleColor|AttrBold, self.Bg)

sparkY := (self.Y / lc) * (i + 1)
// finds max data in current view used for relative heights
max := 1
for i := len(line.Data) - 1; i >= 0 && self.X-((len(line.Data)-1)-i) >= 1; i-- {
if line.Data[i] > max {
max = line.Data[i]
}
}
// prints sparkline
for x := self.X; x >= 1; x-- {
char := SPARKS[0]
if (self.X - x) < len(line.Data) {
offset := self.X - x
cur_item := line.Data[(len(line.Data)-1)-offset]
percent := float64(cur_item) / float64(max)
index := int(percent * 7)
if index < 0 || index >= len(SPARKS) {
Error("sparkline",
fmt.Sprint(
"len(line.Data): ", len(line.Data), "\n",
"max: ", max, "\n",
"x: ", x, "\n",
"self.X: ", self.X, "\n",
"offset: ", offset, "\n",
"cur_item: ", cur_item, "\n",
"percent: ", percent, "\n",
"index: ", index, "\n",
"len(SPARKS): ", len(SPARKS),
))
}
char = SPARKS[index]
}
buf.SetCell(x, sparkY, Cell{char, line.LineColor, self.Bg})
}
}

return buf
}

0 comments on commit b73fe56

Please sign in to comment.