Finish Event
This commit is contained in:
137
_widget/barchart.go
Normal file
137
_widget/barchart.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// +build ignore
|
||||
|
||||
// Copyright 2015 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
|
||||
|
||||
import "fmt"
|
||||
|
||||
// BarChart creates multiple bars in a widget:
|
||||
/*
|
||||
bc := termui.NewBarChart()
|
||||
data := []int{3, 2, 5, 3, 9, 5}
|
||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
|
||||
bc.Border.Label = "Bar Chart"
|
||||
bc.Data = data
|
||||
bc.Width = 26
|
||||
bc.Height = 10
|
||||
bc.DataLabels = bclabels
|
||||
bc.TextColor = termui.ColorGreen
|
||||
bc.BarColor = termui.ColorRed
|
||||
bc.NumColor = termui.ColorYellow
|
||||
*/
|
||||
type BarChart struct {
|
||||
Block
|
||||
BarColor Attribute
|
||||
TextColor Attribute
|
||||
NumColor Attribute
|
||||
Data []int
|
||||
DataLabels []string
|
||||
BarWidth int
|
||||
BarGap int
|
||||
labels [][]rune
|
||||
dataNum [][]rune
|
||||
numBar int
|
||||
scale float64
|
||||
max int
|
||||
}
|
||||
|
||||
// NewBarChart returns a new *BarChart with current theme.
|
||||
func NewBarChart() *BarChart {
|
||||
bc := &BarChart{Block: *NewBlock()}
|
||||
bc.BarColor = theme.BarChartBar
|
||||
bc.NumColor = theme.BarChartNum
|
||||
bc.TextColor = theme.BarChartText
|
||||
bc.BarGap = 1
|
||||
bc.BarWidth = 3
|
||||
return bc
|
||||
}
|
||||
|
||||
func (bc *BarChart) layout() {
|
||||
bc.numBar = bc.innerWidth / (bc.BarGap + bc.BarWidth)
|
||||
bc.labels = make([][]rune, bc.numBar)
|
||||
bc.dataNum = make([][]rune, len(bc.Data))
|
||||
|
||||
for i := 0; i < bc.numBar && i < len(bc.DataLabels) && i < len(bc.Data); i++ {
|
||||
bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
|
||||
n := bc.Data[i]
|
||||
s := fmt.Sprint(n)
|
||||
bc.dataNum[i] = trimStr2Runes(s, bc.BarWidth)
|
||||
}
|
||||
|
||||
//bc.max = bc.Data[0] // what if Data is nil? Sometimes when bar graph is nill it produces panic with panic: runtime error: index out of range
|
||||
// Asign a negative value to get maxvalue auto-populates
|
||||
if bc.max == 0 {
|
||||
bc.max = -1
|
||||
}
|
||||
for i := 0; i < len(bc.Data); i++ {
|
||||
if bc.max < bc.Data[i] {
|
||||
bc.max = bc.Data[i]
|
||||
}
|
||||
}
|
||||
bc.scale = float64(bc.max) / float64(bc.innerHeight-1)
|
||||
}
|
||||
|
||||
func (bc *BarChart) SetMax(max int) {
|
||||
|
||||
if max > 0 {
|
||||
bc.max = max
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (bc *BarChart) Buffer() []Point {
|
||||
ps := bc.Block.Buffer()
|
||||
bc.layout()
|
||||
|
||||
for i := 0; i < bc.numBar && i < len(bc.Data) && i < len(bc.DataLabels); i++ {
|
||||
h := int(float64(bc.Data[i]) / bc.scale)
|
||||
oftX := i * (bc.BarWidth + bc.BarGap)
|
||||
// plot bar
|
||||
for j := 0; j < bc.BarWidth; j++ {
|
||||
for k := 0; k < h; k++ {
|
||||
p := Point{}
|
||||
p.Ch = ' '
|
||||
p.Bg = bc.BarColor
|
||||
if bc.BarColor == ColorDefault { // when color is default, space char treated as transparent!
|
||||
p.Bg |= AttrReverse
|
||||
}
|
||||
p.X = bc.innerX + i*(bc.BarWidth+bc.BarGap) + j
|
||||
p.Y = bc.innerY + bc.innerHeight - 2 - k
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
// plot text
|
||||
for j, k := 0, 0; j < len(bc.labels[i]); j++ {
|
||||
w := charWidth(bc.labels[i][j])
|
||||
p := Point{}
|
||||
p.Ch = bc.labels[i][j]
|
||||
p.Bg = bc.BgColor
|
||||
p.Fg = bc.TextColor
|
||||
p.Y = bc.innerY + bc.innerHeight - 1
|
||||
p.X = bc.innerX + oftX + k
|
||||
ps = append(ps, p)
|
||||
k += w
|
||||
}
|
||||
// plot num
|
||||
for j := 0; j < len(bc.dataNum[i]); j++ {
|
||||
p := Point{}
|
||||
p.Ch = bc.dataNum[i][j]
|
||||
p.Fg = bc.NumColor
|
||||
p.Bg = bc.BarColor
|
||||
if bc.BarColor == ColorDefault { // the same as above
|
||||
p.Bg |= AttrReverse
|
||||
}
|
||||
if h == 0 {
|
||||
p.Bg = bc.BgColor
|
||||
}
|
||||
p.X = bc.innerX + oftX + (bc.BarWidth-len(bc.dataNum[i]))/2 + j
|
||||
p.Y = bc.innerY + bc.innerHeight - 2
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
|
||||
return bc.Block.chopOverflow(ps)
|
||||
}
|
||||
76
_widget/canvas.go
Normal file
76
_widget/canvas.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// +build ignore
|
||||
|
||||
// Copyright 2015 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
|
||||
|
||||
/*
|
||||
dots:
|
||||
,___,
|
||||
|1 4|
|
||||
|2 5|
|
||||
|3 6|
|
||||
|7 8|
|
||||
`````
|
||||
*/
|
||||
|
||||
var brailleBase = '\u2800'
|
||||
|
||||
var brailleOftMap = [4][2]rune{
|
||||
{'\u0001', '\u0008'},
|
||||
{'\u0002', '\u0010'},
|
||||
{'\u0004', '\u0020'},
|
||||
{'\u0040', '\u0080'}}
|
||||
|
||||
// Canvas contains drawing map: i,j -> rune
|
||||
type Canvas map[[2]int]rune
|
||||
|
||||
// NewCanvas returns an empty Canvas
|
||||
func NewCanvas() Canvas {
|
||||
return make(map[[2]int]rune)
|
||||
}
|
||||
|
||||
func chOft(x, y int) rune {
|
||||
return brailleOftMap[y%4][x%2]
|
||||
}
|
||||
|
||||
func (c Canvas) rawCh(x, y int) rune {
|
||||
if ch, ok := c[[2]int{x, y}]; ok {
|
||||
return ch
|
||||
}
|
||||
return '\u0000' //brailleOffset
|
||||
}
|
||||
|
||||
// return coordinate in terminal
|
||||
func chPos(x, y int) (int, int) {
|
||||
return y / 4, x / 2
|
||||
}
|
||||
|
||||
// Set sets a point (x,y) in the virtual coordinate
|
||||
func (c Canvas) Set(x, y int) {
|
||||
i, j := chPos(x, y)
|
||||
ch := c.rawCh(i, j)
|
||||
ch |= chOft(x, y)
|
||||
c[[2]int{i, j}] = ch
|
||||
}
|
||||
|
||||
// Unset removes point (x,y)
|
||||
func (c Canvas) Unset(x, y int) {
|
||||
i, j := chPos(x, y)
|
||||
ch := c.rawCh(i, j)
|
||||
ch &= ^chOft(x, y)
|
||||
c[[2]int{i, j}] = ch
|
||||
}
|
||||
|
||||
// Buffer returns un-styled points
|
||||
func (c Canvas) Buffer() []Point {
|
||||
ps := make([]Point, len(c))
|
||||
i := 0
|
||||
for k, v := range c {
|
||||
ps[i] = newPoint(v+brailleBase, k[0], k[1])
|
||||
i++
|
||||
}
|
||||
return ps
|
||||
}
|
||||
57
_widget/canvas_test.go
Normal file
57
_widget/canvas_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
//+build ignore
|
||||
|
||||
package termui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func TestCanvasSet(t *testing.T) {
|
||||
c := NewCanvas()
|
||||
c.Set(0, 0)
|
||||
c.Set(0, 1)
|
||||
c.Set(0, 2)
|
||||
c.Set(0, 3)
|
||||
c.Set(1, 3)
|
||||
c.Set(2, 3)
|
||||
c.Set(3, 3)
|
||||
c.Set(4, 3)
|
||||
c.Set(5, 3)
|
||||
spew.Dump(c)
|
||||
}
|
||||
|
||||
func TestCanvasUnset(t *testing.T) {
|
||||
c := NewCanvas()
|
||||
c.Set(0, 0)
|
||||
c.Set(0, 1)
|
||||
c.Set(0, 2)
|
||||
c.Unset(0, 2)
|
||||
spew.Dump(c)
|
||||
c.Unset(0, 3)
|
||||
spew.Dump(c)
|
||||
}
|
||||
|
||||
func TestCanvasBuffer(t *testing.T) {
|
||||
c := NewCanvas()
|
||||
c.Set(0, 0)
|
||||
c.Set(0, 1)
|
||||
c.Set(0, 2)
|
||||
c.Set(0, 3)
|
||||
c.Set(1, 3)
|
||||
c.Set(2, 3)
|
||||
c.Set(3, 3)
|
||||
c.Set(4, 3)
|
||||
c.Set(5, 3)
|
||||
c.Set(6, 3)
|
||||
c.Set(7, 2)
|
||||
c.Set(8, 1)
|
||||
c.Set(9, 0)
|
||||
bufs := c.Buffer()
|
||||
rs := make([]rune, len(bufs))
|
||||
for i, v := range bufs {
|
||||
rs[i] = v.Ch
|
||||
}
|
||||
spew.Dump(string(rs))
|
||||
}
|
||||
113
_widget/gauge.go
Normal file
113
_widget/gauge.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2015 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
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Gauge is a progress bar like widget.
|
||||
// A simple example:
|
||||
/*
|
||||
g := termui.NewGauge()
|
||||
g.Percent = 40
|
||||
g.Width = 50
|
||||
g.Height = 3
|
||||
g.Border.Label = "Slim Gauge"
|
||||
g.BarColor = termui.ColorRed
|
||||
g.PercentColor = termui.ColorBlue
|
||||
*/
|
||||
|
||||
// Align is the position of the gauge's label.
|
||||
type Align int
|
||||
|
||||
// All supported positions.
|
||||
const (
|
||||
AlignLeft Align = iota
|
||||
AlignCenter
|
||||
AlignRight
|
||||
)
|
||||
|
||||
type Gauge struct {
|
||||
Block
|
||||
Percent int
|
||||
BarColor Attribute
|
||||
PercentColor Attribute
|
||||
Label string
|
||||
LabelAlign Align
|
||||
}
|
||||
|
||||
// NewGauge return a new gauge with current theme.
|
||||
func NewGauge() *Gauge {
|
||||
g := &Gauge{
|
||||
Block: *NewBlock(),
|
||||
PercentColor: theme.GaugePercent,
|
||||
BarColor: theme.GaugeBar,
|
||||
Label: "{{percent}}%",
|
||||
LabelAlign: AlignCenter,
|
||||
}
|
||||
|
||||
g.Width = 12
|
||||
g.Height = 5
|
||||
return g
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (g *Gauge) Buffer() []Point {
|
||||
ps := g.Block.Buffer()
|
||||
|
||||
// plot bar
|
||||
w := g.Percent * g.innerWidth / 100
|
||||
for i := 0; i < g.innerHeight; i++ {
|
||||
for j := 0; j < w; j++ {
|
||||
p := Point{}
|
||||
p.X = g.innerX + j
|
||||
p.Y = g.innerY + i
|
||||
p.Ch = ' '
|
||||
p.Bg = g.BarColor
|
||||
if p.Bg == ColorDefault {
|
||||
p.Bg |= AttrReverse
|
||||
}
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
|
||||
// plot percentage
|
||||
s := strings.Replace(g.Label, "{{percent}}", strconv.Itoa(g.Percent), -1)
|
||||
pry := g.innerY + g.innerHeight/2
|
||||
rs := str2runes(s)
|
||||
var pos int
|
||||
switch g.LabelAlign {
|
||||
case AlignLeft:
|
||||
pos = 0
|
||||
|
||||
case AlignCenter:
|
||||
pos = (g.innerWidth - strWidth(s)) / 2
|
||||
|
||||
case AlignRight:
|
||||
pos = g.innerWidth - strWidth(s)
|
||||
}
|
||||
|
||||
for i, v := range rs {
|
||||
p := Point{}
|
||||
p.X = 1 + pos + i
|
||||
p.Y = pry
|
||||
p.Ch = v
|
||||
p.Fg = g.PercentColor
|
||||
if w+g.innerX > pos+i {
|
||||
p.Bg = g.BarColor
|
||||
if p.Bg == ColorDefault {
|
||||
p.Bg |= AttrReverse
|
||||
}
|
||||
|
||||
} else {
|
||||
p.Bg = g.Block.BgColor
|
||||
}
|
||||
|
||||
ps = append(ps, p)
|
||||
}
|
||||
return g.Block.chopOverflow(ps)
|
||||
}
|
||||
338
_widget/linechart.go
Normal file
338
_widget/linechart.go
Normal file
@@ -0,0 +1,338 @@
|
||||
// +build ignore
|
||||
|
||||
// Copyright 2015 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// only 16 possible combinations, why bother
|
||||
var braillePatterns = map[[2]int]rune{
|
||||
[2]int{0, 0}: '⣀',
|
||||
[2]int{0, 1}: '⡠',
|
||||
[2]int{0, 2}: '⡐',
|
||||
[2]int{0, 3}: '⡈',
|
||||
|
||||
[2]int{1, 0}: '⢄',
|
||||
[2]int{1, 1}: '⠤',
|
||||
[2]int{1, 2}: '⠔',
|
||||
[2]int{1, 3}: '⠌',
|
||||
|
||||
[2]int{2, 0}: '⢂',
|
||||
[2]int{2, 1}: '⠢',
|
||||
[2]int{2, 2}: '⠒',
|
||||
[2]int{2, 3}: '⠊',
|
||||
|
||||
[2]int{3, 0}: '⢁',
|
||||
[2]int{3, 1}: '⠡',
|
||||
[2]int{3, 2}: '⠑',
|
||||
[2]int{3, 3}: '⠉',
|
||||
}
|
||||
|
||||
var lSingleBraille = [4]rune{'\u2840', '⠄', '⠂', '⠁'}
|
||||
var rSingleBraille = [4]rune{'\u2880', '⠠', '⠐', '⠈'}
|
||||
|
||||
// LineChart has two modes: braille(default) and dot. Using braille gives 2x capicity as dot mode,
|
||||
// because one braille char can represent two data points.
|
||||
/*
|
||||
lc := termui.NewLineChart()
|
||||
lc.Border.Label = "braille-mode Line Chart"
|
||||
lc.Data = [1.2, 1.3, 1.5, 1.7, 1.5, 1.6, 1.8, 2.0]
|
||||
lc.Width = 50
|
||||
lc.Height = 12
|
||||
lc.AxesColor = termui.ColorWhite
|
||||
lc.LineColor = termui.ColorGreen | termui.AttrBold
|
||||
// termui.Render(lc)...
|
||||
*/
|
||||
type LineChart struct {
|
||||
Block
|
||||
Data []float64
|
||||
DataLabels []string // if unset, the data indices will be used
|
||||
Mode string // braille | dot
|
||||
DotStyle rune
|
||||
LineColor Attribute
|
||||
scale float64 // data span per cell on y-axis
|
||||
AxesColor Attribute
|
||||
drawingX int
|
||||
drawingY int
|
||||
axisYHeight int
|
||||
axisXWidth int
|
||||
axisYLebelGap int
|
||||
axisXLebelGap int
|
||||
topValue float64
|
||||
bottomValue float64
|
||||
labelX [][]rune
|
||||
labelY [][]rune
|
||||
labelYSpace int
|
||||
maxY float64
|
||||
minY float64
|
||||
}
|
||||
|
||||
// NewLineChart returns a new LineChart with current theme.
|
||||
func NewLineChart() *LineChart {
|
||||
lc := &LineChart{Block: *NewBlock()}
|
||||
lc.AxesColor = theme.LineChartAxes
|
||||
lc.LineColor = theme.LineChartLine
|
||||
lc.Mode = "braille"
|
||||
lc.DotStyle = '•'
|
||||
lc.axisXLebelGap = 2
|
||||
lc.axisYLebelGap = 1
|
||||
lc.bottomValue = math.Inf(1)
|
||||
lc.topValue = math.Inf(-1)
|
||||
return lc
|
||||
}
|
||||
|
||||
// one cell contains two data points
|
||||
// so the capicity is 2x as dot-mode
|
||||
func (lc *LineChart) renderBraille() []Point {
|
||||
ps := []Point{}
|
||||
|
||||
// return: b -> which cell should the point be in
|
||||
// m -> in the cell, divided into 4 equal height levels, which subcell?
|
||||
getPos := func(d float64) (b, m int) {
|
||||
cnt4 := int((d-lc.bottomValue)/(lc.scale/4) + 0.5)
|
||||
b = cnt4 / 4
|
||||
m = cnt4 % 4
|
||||
return
|
||||
}
|
||||
// plot points
|
||||
for i := 0; 2*i+1 < len(lc.Data) && i < lc.axisXWidth; i++ {
|
||||
b0, m0 := getPos(lc.Data[2*i])
|
||||
b1, m1 := getPos(lc.Data[2*i+1])
|
||||
|
||||
if b0 == b1 {
|
||||
p := Point{}
|
||||
p.Ch = braillePatterns[[2]int{m0, m1}]
|
||||
p.Bg = lc.BgColor
|
||||
p.Fg = lc.LineColor
|
||||
p.Y = lc.innerY + lc.innerHeight - 3 - b0
|
||||
p.X = lc.innerX + lc.labelYSpace + 1 + i
|
||||
ps = append(ps, p)
|
||||
} else {
|
||||
p0 := newPointWithAttrs(lSingleBraille[m0],
|
||||
lc.innerX+lc.labelYSpace+1+i,
|
||||
lc.innerY+lc.innerHeight-3-b0,
|
||||
lc.LineColor,
|
||||
lc.BgColor)
|
||||
p1 := newPointWithAttrs(rSingleBraille[m1],
|
||||
lc.innerX+lc.labelYSpace+1+i,
|
||||
lc.innerY+lc.innerHeight-3-b1,
|
||||
lc.LineColor,
|
||||
lc.BgColor)
|
||||
ps = append(ps, p0, p1)
|
||||
}
|
||||
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
func (lc *LineChart) renderDot() []Point {
|
||||
ps := []Point{}
|
||||
for i := 0; i < len(lc.Data) && i < lc.axisXWidth; i++ {
|
||||
p := Point{}
|
||||
p.Ch = lc.DotStyle
|
||||
p.Fg = lc.LineColor
|
||||
p.Bg = lc.BgColor
|
||||
p.X = lc.innerX + lc.labelYSpace + 1 + i
|
||||
p.Y = lc.innerY + lc.innerHeight - 3 - int((lc.Data[i]-lc.bottomValue)/lc.scale+0.5)
|
||||
ps = append(ps, p)
|
||||
}
|
||||
|
||||
return ps
|
||||
}
|
||||
|
||||
func (lc *LineChart) calcLabelX() {
|
||||
lc.labelX = [][]rune{}
|
||||
|
||||
for i, l := 0, 0; i < len(lc.DataLabels) && l < lc.axisXWidth; i++ {
|
||||
if lc.Mode == "dot" {
|
||||
if l >= len(lc.DataLabels) {
|
||||
break
|
||||
}
|
||||
|
||||
s := str2runes(lc.DataLabels[l])
|
||||
w := strWidth(lc.DataLabels[l])
|
||||
if l+w <= lc.axisXWidth {
|
||||
lc.labelX = append(lc.labelX, s)
|
||||
}
|
||||
l += w + lc.axisXLebelGap
|
||||
} else { // braille
|
||||
if 2*l >= len(lc.DataLabels) {
|
||||
break
|
||||
}
|
||||
|
||||
s := str2runes(lc.DataLabels[2*l])
|
||||
w := strWidth(lc.DataLabels[2*l])
|
||||
if l+w <= lc.axisXWidth {
|
||||
lc.labelX = append(lc.labelX, s)
|
||||
}
|
||||
l += w + lc.axisXLebelGap
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shortenFloatVal(x float64) string {
|
||||
s := fmt.Sprintf("%.2f", x)
|
||||
if len(s)-3 > 3 {
|
||||
s = fmt.Sprintf("%.2e", x)
|
||||
}
|
||||
|
||||
if x < 0 {
|
||||
s = fmt.Sprintf("%.2f", x)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (lc *LineChart) calcLabelY() {
|
||||
span := lc.topValue - lc.bottomValue
|
||||
lc.scale = span / float64(lc.axisYHeight)
|
||||
|
||||
n := (1 + lc.axisYHeight) / (lc.axisYLebelGap + 1)
|
||||
lc.labelY = make([][]rune, n)
|
||||
maxLen := 0
|
||||
for i := 0; i < n; i++ {
|
||||
s := str2runes(shortenFloatVal(lc.bottomValue + float64(i)*span/float64(n)))
|
||||
if len(s) > maxLen {
|
||||
maxLen = len(s)
|
||||
}
|
||||
lc.labelY[i] = s
|
||||
}
|
||||
|
||||
lc.labelYSpace = maxLen
|
||||
}
|
||||
|
||||
func (lc *LineChart) calcLayout() {
|
||||
// set datalabels if it is not provided
|
||||
if lc.DataLabels == nil || len(lc.DataLabels) == 0 {
|
||||
lc.DataLabels = make([]string, len(lc.Data))
|
||||
for i := range lc.Data {
|
||||
lc.DataLabels[i] = fmt.Sprint(i)
|
||||
}
|
||||
}
|
||||
|
||||
// lazy increase, to avoid y shaking frequently
|
||||
// update bound Y when drawing is gonna overflow
|
||||
lc.minY = lc.Data[0]
|
||||
lc.maxY = lc.Data[0]
|
||||
|
||||
// valid visible range
|
||||
vrange := lc.innerWidth
|
||||
if lc.Mode == "braille" {
|
||||
vrange = 2 * lc.innerWidth
|
||||
}
|
||||
if vrange > len(lc.Data) {
|
||||
vrange = len(lc.Data)
|
||||
}
|
||||
|
||||
for _, v := range lc.Data[:vrange] {
|
||||
if v > lc.maxY {
|
||||
lc.maxY = v
|
||||
}
|
||||
if v < lc.minY {
|
||||
lc.minY = v
|
||||
}
|
||||
}
|
||||
|
||||
span := lc.maxY - lc.minY
|
||||
|
||||
if lc.minY < lc.bottomValue {
|
||||
lc.bottomValue = lc.minY - 0.2*span
|
||||
}
|
||||
|
||||
if lc.maxY > lc.topValue {
|
||||
lc.topValue = lc.maxY + 0.2*span
|
||||
}
|
||||
|
||||
lc.axisYHeight = lc.innerHeight - 2
|
||||
lc.calcLabelY()
|
||||
|
||||
lc.axisXWidth = lc.innerWidth - 1 - lc.labelYSpace
|
||||
lc.calcLabelX()
|
||||
|
||||
lc.drawingX = lc.innerX + 1 + lc.labelYSpace
|
||||
lc.drawingY = lc.innerY
|
||||
}
|
||||
|
||||
func (lc *LineChart) plotAxes() []Point {
|
||||
origY := lc.innerY + lc.innerHeight - 2
|
||||
origX := lc.innerX + lc.labelYSpace
|
||||
|
||||
ps := []Point{newPointWithAttrs(ORIGIN, origX, origY, lc.AxesColor, lc.BgColor)}
|
||||
|
||||
for x := origX + 1; x < origX+lc.axisXWidth; x++ {
|
||||
p := Point{}
|
||||
p.X = x
|
||||
p.Y = origY
|
||||
p.Bg = lc.BgColor
|
||||
p.Fg = lc.AxesColor
|
||||
p.Ch = HDASH
|
||||
ps = append(ps, p)
|
||||
}
|
||||
|
||||
for dy := 1; dy <= lc.axisYHeight; dy++ {
|
||||
p := Point{}
|
||||
p.X = origX
|
||||
p.Y = origY - dy
|
||||
p.Bg = lc.BgColor
|
||||
p.Fg = lc.AxesColor
|
||||
p.Ch = VDASH
|
||||
ps = append(ps, p)
|
||||
}
|
||||
|
||||
// x label
|
||||
oft := 0
|
||||
for _, rs := range lc.labelX {
|
||||
if oft+len(rs) > lc.axisXWidth {
|
||||
break
|
||||
}
|
||||
for j, r := range rs {
|
||||
p := Point{}
|
||||
p.Ch = r
|
||||
p.Fg = lc.AxesColor
|
||||
p.Bg = lc.BgColor
|
||||
p.X = origX + oft + j
|
||||
p.Y = lc.innerY + lc.innerHeight - 1
|
||||
ps = append(ps, p)
|
||||
}
|
||||
oft += len(rs) + lc.axisXLebelGap
|
||||
}
|
||||
|
||||
// y labels
|
||||
for i, rs := range lc.labelY {
|
||||
for j, r := range rs {
|
||||
p := Point{}
|
||||
p.Ch = r
|
||||
p.Fg = lc.AxesColor
|
||||
p.Bg = lc.BgColor
|
||||
p.X = lc.innerX + j
|
||||
p.Y = origY - i*(lc.axisYLebelGap+1)
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
|
||||
return ps
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (lc *LineChart) Buffer() []Point {
|
||||
ps := lc.Block.Buffer()
|
||||
if lc.Data == nil || len(lc.Data) == 0 {
|
||||
return ps
|
||||
}
|
||||
lc.calcLayout()
|
||||
ps = append(ps, lc.plotAxes()...)
|
||||
|
||||
if lc.Mode == "dot" {
|
||||
ps = append(ps, lc.renderDot()...)
|
||||
} else {
|
||||
ps = append(ps, lc.renderBraille()...)
|
||||
}
|
||||
|
||||
return lc.Block.chopOverflow(ps)
|
||||
}
|
||||
13
_widget/linechart_others.go
Normal file
13
_widget/linechart_others.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build ignore
|
||||
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package termui
|
||||
|
||||
const VDASH = '┊'
|
||||
const HDASH = '┈'
|
||||
const ORIGIN = '└'
|
||||
13
_widget/linechart_windows.go
Normal file
13
_widget/linechart_windows.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build ignore
|
||||
|
||||
// Copyright 2015 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package termui
|
||||
|
||||
const VDASH = '|'
|
||||
const HDASH = '-'
|
||||
const ORIGIN = '+'
|
||||
97
_widget/list.go
Normal file
97
_widget/list.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// +build ignore
|
||||
|
||||
// Copyright 2015 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
|
||||
|
||||
// List displays []string as its items,
|
||||
// it has a Overflow option (default is "hidden"), when set to "hidden",
|
||||
// the item exceeding List's width is truncated, but when set to "wrap",
|
||||
// the overflowed text breaks into next line.
|
||||
/*
|
||||
strs := []string{
|
||||
"[0] github.com/gizak/termui",
|
||||
"[1] editbox.go",
|
||||
"[2] iterrupt.go",
|
||||
"[3] keyboard.go",
|
||||
"[4] output.go",
|
||||
"[5] random_out.go",
|
||||
"[6] dashboard.go",
|
||||
"[7] nsf/termbox-go"}
|
||||
|
||||
ls := termui.NewList()
|
||||
ls.Items = strs
|
||||
ls.ItemFgColor = termui.ColorYellow
|
||||
ls.Border.Label = "List"
|
||||
ls.Height = 7
|
||||
ls.Width = 25
|
||||
ls.Y = 0
|
||||
*/
|
||||
type List struct {
|
||||
Block
|
||||
Items []string
|
||||
Overflow string
|
||||
ItemFgColor Attribute
|
||||
ItemBgColor Attribute
|
||||
RendererFactory TextRendererFactory
|
||||
}
|
||||
|
||||
// NewList returns a new *List with current theme.
|
||||
func NewList() *List {
|
||||
l := &List{Block: *NewBlock()}
|
||||
l.Overflow = "hidden"
|
||||
l.ItemFgColor = theme.ListItemFg
|
||||
l.ItemBgColor = theme.ListItemBg
|
||||
l.RendererFactory = PlainRendererFactory{}
|
||||
return l
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (l *List) Buffer() []Point {
|
||||
buffer := l.Block.Buffer()
|
||||
|
||||
breakLoop := func(y int) bool {
|
||||
return y+1 > l.innerHeight
|
||||
}
|
||||
y := 0
|
||||
|
||||
MainLoop:
|
||||
for _, item := range l.Items {
|
||||
x := 0
|
||||
bg, fg := l.ItemFgColor, l.ItemBgColor
|
||||
renderer := l.RendererFactory.TextRenderer(item)
|
||||
sequence := renderer.Render(bg, fg)
|
||||
|
||||
for n := range []rune(sequence.NormalizedText) {
|
||||
point, width := sequence.PointAt(n, x+l.innerX, y+l.innerY)
|
||||
|
||||
if width+x <= l.innerWidth {
|
||||
buffer = append(buffer, point)
|
||||
x += width
|
||||
} else {
|
||||
if l.Overflow == "wrap" {
|
||||
y++
|
||||
if breakLoop(y) {
|
||||
break MainLoop
|
||||
}
|
||||
x = 0
|
||||
} else {
|
||||
dotR := []rune(dot)[0]
|
||||
dotX := l.innerWidth + l.innerX - charWidth(dotR)
|
||||
p := newPointWithAttrs(dotR, dotX, y+l.innerY, bg, fg)
|
||||
buffer = append(buffer, p)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
y++
|
||||
if breakLoop(y) {
|
||||
break MainLoop
|
||||
}
|
||||
}
|
||||
|
||||
return l.Block.chopOverflow(buffer)
|
||||
}
|
||||
233
_widget/mbar.go
Normal file
233
_widget/mbar.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright 2015 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// This is the implemetation of multi-colored or stacked bar graph. This is different from default barGraph which is implemented in bar.go
|
||||
// Multi-Colored-BarChart creates multiple bars in a widget:
|
||||
/*
|
||||
bc := termui.NewMBarChart()
|
||||
data := make([][]int, 2)
|
||||
data[0] := []int{3, 2, 5, 7, 9, 4}
|
||||
data[1] := []int{7, 8, 5, 3, 1, 6}
|
||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
|
||||
bc.Border.Label = "Bar Chart"
|
||||
bc.Data = data
|
||||
bc.Width = 26
|
||||
bc.Height = 10
|
||||
bc.DataLabels = bclabels
|
||||
bc.TextColor = termui.ColorGreen
|
||||
bc.BarColor = termui.ColorRed
|
||||
bc.NumColor = termui.ColorYellow
|
||||
*/
|
||||
type MBarChart struct {
|
||||
Block
|
||||
BarColor [NumberofColors]Attribute
|
||||
TextColor Attribute
|
||||
NumColor [NumberofColors]Attribute
|
||||
Data [NumberofColors][]int
|
||||
DataLabels []string
|
||||
BarWidth int
|
||||
BarGap int
|
||||
labels [][]rune
|
||||
dataNum [NumberofColors][][]rune
|
||||
numBar int
|
||||
scale float64
|
||||
max int
|
||||
minDataLen int
|
||||
numStack int
|
||||
ShowScale bool
|
||||
maxScale []rune
|
||||
}
|
||||
|
||||
// NewBarChart returns a new *BarChart with current theme.
|
||||
func NewMBarChart() *MBarChart {
|
||||
bc := &MBarChart{Block: *NewBlock()}
|
||||
bc.BarColor[0] = theme.MBarChartBar
|
||||
bc.NumColor[0] = theme.MBarChartNum
|
||||
bc.TextColor = theme.MBarChartText
|
||||
bc.BarGap = 1
|
||||
bc.BarWidth = 3
|
||||
return bc
|
||||
}
|
||||
|
||||
func (bc *MBarChart) layout() {
|
||||
bc.numBar = bc.innerWidth / (bc.BarGap + bc.BarWidth)
|
||||
bc.labels = make([][]rune, bc.numBar)
|
||||
DataLen := 0
|
||||
LabelLen := len(bc.DataLabels)
|
||||
bc.minDataLen = 9999 //Set this to some very hight value so that we find the minimum one We want to know which array among data[][] has got the least length
|
||||
|
||||
// We need to know how many stack/data array data[0] , data[1] are there
|
||||
for i := 0; i < len(bc.Data); i++ {
|
||||
if bc.Data[i] == nil {
|
||||
break
|
||||
}
|
||||
DataLen++
|
||||
}
|
||||
bc.numStack = DataLen
|
||||
|
||||
//We need to know what is the mimimum size of data array data[0] could have 10 elements data[1] could have only 5, so we plot only 5 bar graphs
|
||||
|
||||
for i := 0; i < DataLen; i++ {
|
||||
if bc.minDataLen > len(bc.Data[i]) {
|
||||
bc.minDataLen = len(bc.Data[i])
|
||||
}
|
||||
}
|
||||
|
||||
if LabelLen > bc.minDataLen {
|
||||
LabelLen = bc.minDataLen
|
||||
}
|
||||
|
||||
for i := 0; i < LabelLen && i < bc.numBar; i++ {
|
||||
bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
|
||||
}
|
||||
|
||||
for i := 0; i < bc.numStack; i++ {
|
||||
bc.dataNum[i] = make([][]rune, len(bc.Data[i]))
|
||||
//For each stack of bar calcualte the rune
|
||||
for j := 0; j < LabelLen && i < bc.numBar; j++ {
|
||||
n := bc.Data[i][j]
|
||||
s := fmt.Sprint(n)
|
||||
bc.dataNum[i][j] = trimStr2Runes(s, bc.BarWidth)
|
||||
}
|
||||
//If color is not defined by default then populate a color that is different from the prevous bar
|
||||
if bc.BarColor[i] == ColorDefault && bc.NumColor[i] == ColorDefault {
|
||||
if i == 0 {
|
||||
bc.BarColor[i] = ColorBlack
|
||||
} else {
|
||||
bc.BarColor[i] = bc.BarColor[i-1] + 1
|
||||
if bc.BarColor[i] > NumberofColors {
|
||||
bc.BarColor[i] = ColorBlack
|
||||
}
|
||||
}
|
||||
bc.NumColor[i] = (NumberofColors + 1) - bc.BarColor[i] //Make NumColor opposite of barColor for visibility
|
||||
}
|
||||
}
|
||||
|
||||
//If Max value is not set then we have to populate, this time the max value will be max(sum(d1[0],d2[0],d3[0]) .... sum(d1[n], d2[n], d3[n]))
|
||||
|
||||
if bc.max == 0 {
|
||||
bc.max = -1
|
||||
}
|
||||
for i := 0; i < bc.minDataLen && i < LabelLen; i++ {
|
||||
var dsum int
|
||||
for j := 0; j < bc.numStack; j++ {
|
||||
dsum += bc.Data[j][i]
|
||||
}
|
||||
if dsum > bc.max {
|
||||
bc.max = dsum
|
||||
}
|
||||
}
|
||||
|
||||
//Finally Calculate max sale
|
||||
if bc.ShowScale {
|
||||
s := fmt.Sprintf("%d", bc.max)
|
||||
bc.maxScale = trimStr2Runes(s, len(s))
|
||||
bc.scale = float64(bc.max) / float64(bc.innerHeight-2)
|
||||
} else {
|
||||
bc.scale = float64(bc.max) / float64(bc.innerHeight-1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (bc *MBarChart) SetMax(max int) {
|
||||
|
||||
if max > 0 {
|
||||
bc.max = max
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (bc *MBarChart) Buffer() []Point {
|
||||
ps := bc.Block.Buffer()
|
||||
bc.layout()
|
||||
var oftX int
|
||||
|
||||
for i := 0; i < bc.numBar && i < bc.minDataLen && i < len(bc.DataLabels); i++ {
|
||||
ph := 0 //Previous Height to stack up
|
||||
oftX = i * (bc.BarWidth + bc.BarGap)
|
||||
for i1 := 0; i1 < bc.numStack; i1++ {
|
||||
h := int(float64(bc.Data[i1][i]) / bc.scale)
|
||||
// plot bars
|
||||
for j := 0; j < bc.BarWidth; j++ {
|
||||
for k := 0; k < h; k++ {
|
||||
p := Point{}
|
||||
p.Ch = ' '
|
||||
p.Bg = bc.BarColor[i1]
|
||||
if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
|
||||
p.Bg |= AttrReverse
|
||||
}
|
||||
p.X = bc.innerX + i*(bc.BarWidth+bc.BarGap) + j
|
||||
p.Y = bc.innerY + bc.innerHeight - 2 - k - ph
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
ph += h
|
||||
}
|
||||
// plot text
|
||||
for j, k := 0, 0; j < len(bc.labels[i]); j++ {
|
||||
w := charWidth(bc.labels[i][j])
|
||||
p := Point{}
|
||||
p.Ch = bc.labels[i][j]
|
||||
p.Bg = bc.BgColor
|
||||
p.Fg = bc.TextColor
|
||||
p.Y = bc.innerY + bc.innerHeight - 1
|
||||
p.X = bc.innerX + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
|
||||
ps = append(ps, p)
|
||||
k += w
|
||||
}
|
||||
// plot num
|
||||
ph = 0 //re-initialize previous height
|
||||
for i1 := 0; i1 < bc.numStack; i1++ {
|
||||
h := int(float64(bc.Data[i1][i]) / bc.scale)
|
||||
for j := 0; j < len(bc.dataNum[i1][i]) && h > 0; j++ {
|
||||
p := Point{}
|
||||
p.Ch = bc.dataNum[i1][i][j]
|
||||
p.Fg = bc.NumColor[i1]
|
||||
p.Bg = bc.BarColor[i1]
|
||||
if bc.BarColor[i1] == ColorDefault { // the same as above
|
||||
p.Bg |= AttrReverse
|
||||
}
|
||||
if h == 0 {
|
||||
p.Bg = bc.BgColor
|
||||
}
|
||||
p.X = bc.innerX + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
|
||||
p.Y = bc.innerY + bc.innerHeight - 2 - ph
|
||||
ps = append(ps, p)
|
||||
}
|
||||
ph += h
|
||||
}
|
||||
}
|
||||
|
||||
if bc.ShowScale {
|
||||
//Currently bar graph only supprts data range from 0 to MAX
|
||||
//Plot 0
|
||||
p := Point{}
|
||||
p.Ch = '0'
|
||||
p.Bg = bc.BgColor
|
||||
p.Fg = bc.TextColor
|
||||
p.Y = bc.innerY + bc.innerHeight - 2
|
||||
p.X = bc.X
|
||||
ps = append(ps, p)
|
||||
|
||||
//Plot the maximum sacle value
|
||||
for i := 0; i < len(bc.maxScale); i++ {
|
||||
p := Point{}
|
||||
p.Ch = bc.maxScale[i]
|
||||
p.Bg = bc.BgColor
|
||||
p.Fg = bc.TextColor
|
||||
p.Y = bc.innerY
|
||||
p.X = bc.X + i
|
||||
ps = append(ps, p)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bc.Block.chopOverflow(ps)
|
||||
}
|
||||
71
_widget/par.go
Normal file
71
_widget/par.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// +build ignore
|
||||
|
||||
// Copyright 2015 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
|
||||
|
||||
// Par displays a paragraph.
|
||||
/*
|
||||
par := termui.NewPar("Simple Text")
|
||||
par.Height = 3
|
||||
par.Width = 17
|
||||
par.Border.Label = "Label"
|
||||
*/
|
||||
type Par struct {
|
||||
Block
|
||||
Text string
|
||||
TextFgColor Attribute
|
||||
TextBgColor Attribute
|
||||
RendererFactory TextRendererFactory
|
||||
}
|
||||
|
||||
// NewPar returns a new *Par with given text as its content.
|
||||
func NewPar(s string) *Par {
|
||||
return &Par{
|
||||
Block: *NewBlock(),
|
||||
Text: s,
|
||||
TextFgColor: theme.ParTextFg,
|
||||
TextBgColor: theme.ParTextBg,
|
||||
RendererFactory: PlainRendererFactory{},
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (p *Par) Buffer() []Point {
|
||||
ps := p.Block.Buffer()
|
||||
|
||||
fg, bg := p.TextFgColor, p.TextBgColor
|
||||
sequence := p.RendererFactory.TextRenderer(p.Text).Render(fg, bg)
|
||||
runes := []rune(sequence.NormalizedText)
|
||||
|
||||
y, x, n := 0, 0, 0
|
||||
for y < p.innerHeight && n < len(runes) {
|
||||
point, width := sequence.PointAt(n, x+p.innerX, y+p.innerY)
|
||||
|
||||
if runes[n] == '\n' || x+width > p.innerWidth {
|
||||
y++
|
||||
x = 0 // set x = 0
|
||||
if runes[n] == '\n' {
|
||||
n++
|
||||
}
|
||||
|
||||
if y >= p.innerHeight {
|
||||
ps = append(ps, newPointWithAttrs('…',
|
||||
p.innerX+p.innerWidth-1,
|
||||
p.innerY+p.innerHeight-1,
|
||||
p.TextFgColor, p.TextBgColor))
|
||||
break
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ps = append(ps, point)
|
||||
n++
|
||||
x += width
|
||||
}
|
||||
|
||||
return p.Block.chopOverflow(ps)
|
||||
}
|
||||
158
_widget/sparkline.go
Normal file
158
_widget/sparkline.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// +build ignore
|
||||
|
||||
// Copyright 2015 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
|
||||
|
||||
import "math"
|
||||
|
||||
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃
|
||||
/*
|
||||
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: theme.SparklineTitle,
|
||||
LineColor: theme.SparklineLine}
|
||||
}
|
||||
|
||||
// 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.innerWidth
|
||||
|
||||
// get how many lines gotta display
|
||||
h := 0
|
||||
sl.displayLines = 0
|
||||
for _, v := range sl.Lines {
|
||||
if h+v.displayHeight <= sl.innerHeight {
|
||||
sl.displayLines++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
h += v.displayHeight
|
||||
}
|
||||
|
||||
for i := 0; i < sl.displayLines; i++ {
|
||||
data := sl.Lines[i].Data
|
||||
|
||||
max := math.MinInt32
|
||||
for _, v := range data {
|
||||
if max < v {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
sl.Lines[i].max = max
|
||||
sl.Lines[i].scale = float32(8*sl.Lines[i].Height) / float32(max)
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (sl *Sparklines) Buffer() []Point {
|
||||
ps := 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.innerWidth {
|
||||
data = data[len(data)-sl.innerWidth:]
|
||||
}
|
||||
|
||||
if l.Title != "" {
|
||||
rs := trimStr2Runes(l.Title, sl.innerWidth)
|
||||
oftX := 0
|
||||
for _, v := range rs {
|
||||
w := charWidth(v)
|
||||
p := Point{}
|
||||
p.Ch = v
|
||||
p.Fg = l.TitleColor
|
||||
p.Bg = sl.BgColor
|
||||
p.X = sl.innerX + oftX
|
||||
p.Y = sl.innerY + oftY
|
||||
ps = append(ps, p)
|
||||
oftX += w
|
||||
}
|
||||
}
|
||||
|
||||
for j, v := range data {
|
||||
h := int(float32(v)*l.scale + 0.5)
|
||||
barCnt := h / 8
|
||||
barMod := h % 8
|
||||
for jj := 0; jj < barCnt; jj++ {
|
||||
p := Point{}
|
||||
p.X = sl.innerX + j
|
||||
p.Y = sl.innerY + oftY + l.Height - jj
|
||||
p.Ch = ' ' // => sparks[7]
|
||||
p.Bg = l.LineColor
|
||||
//p.Bg = sl.BgColor
|
||||
ps = append(ps, p)
|
||||
}
|
||||
if barMod != 0 {
|
||||
p := Point{}
|
||||
p.X = sl.innerX + j
|
||||
p.Y = sl.innerY + oftY + l.Height - barCnt
|
||||
p.Ch = sparks[barMod-1]
|
||||
p.Fg = l.LineColor
|
||||
p.Bg = sl.BgColor
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
|
||||
oftY += l.displayHeight
|
||||
}
|
||||
|
||||
return sl.Block.chopOverflow(ps)
|
||||
}
|
||||
Reference in New Issue
Block a user