WIP use Buffer instead of []Point in Bufferer
Merge https://github.com/Matt3o12/termui.git colored-list Merge https://github.com/funkygao/termui.git master Add subdir widget Use image Rectangle represent buffer area
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))
|
||||
}
|
||||
67
widget/gauge.go
Normal file
67
widget/gauge.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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 widget
|
||||
|
||||
import "github.com/gizak/termui"
|
||||
import "strconv"
|
||||
|
||||
// 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
|
||||
*/
|
||||
type Gauge struct {
|
||||
termui.Block
|
||||
Percent int
|
||||
BarColor termui.Attribute
|
||||
PercentColor termui.Attribute
|
||||
}
|
||||
|
||||
// NewGauge return a new gauge with current theme.
|
||||
func NewGauge() *Gauge {
|
||||
g := &Gauge{
|
||||
Block: *termui.NewBlock(),
|
||||
PercentColor: termui.Theme().GaugePercent,
|
||||
BarColor: termui.Theme().GaugeBar}
|
||||
g.Width = 12
|
||||
g.Height = 3
|
||||
return g
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (g *Gauge) Buffer() termui.Buffer {
|
||||
buf := g.Block.Buffer()
|
||||
|
||||
inner := g.InnerBounds()
|
||||
w := g.Percent * (inner.Dx() + 1) / 100
|
||||
s := strconv.Itoa(g.Percent) + "%"
|
||||
tx := termui.TextCells(s, g.PercentColor, g.Bg)
|
||||
|
||||
prx := inner.Min.X + (inner.Dx()+1)/2 - 1
|
||||
pry := inner.Min.Y + (inner.Dy()+1)/2
|
||||
|
||||
// plot bar
|
||||
for i := 0; i <= inner.Dy(); i++ {
|
||||
for j := 0; j < w; j++ {
|
||||
c := termui.Cell{' ', g.BarColor, g.BarColor}
|
||||
buf.Set(inner.Min.X+j, inner.Min.Y+i, c)
|
||||
}
|
||||
}
|
||||
|
||||
// plot percentage
|
||||
for i, v := range tx {
|
||||
if w > (inner.Dx()+1)/2-1+i {
|
||||
v.Bg = g.BarColor
|
||||
}
|
||||
buf.Set(prx+i, pry, v)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
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)
|
||||
}
|
||||
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[: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