diff --git a/README.md b/README.md
index 55c12f1..bcf57ef 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,9 @@
-# termui [![Build Status](https://travis-ci.org/gizak/termui.svg)](https://travis-ci.org/gizak/termui) [![Doc Status](https://godoc.org/github.com/gizak/termui?status.png)](https://godoc.org/github.com/gizak/termui)
+# termui [![Build Status](https://travis-ci.org/gizak/termui.svg?branch=master)](https://travis-ci.org/gizak/termui) [![Doc Status](https://godoc.org/github.com/gizak/termui?status.png)](https://godoc.org/github.com/gizak/termui)
----
+## Notice
+termui comes with ABSOLUTELY NO WARRANTY, and there is a breaking change coming up (see refactoring branch) which will change the `Bufferer` interface and many others. These changes reduce calculation overhead and introduce a new drawing buffer with better capacibilities. We will step into the next stage (call it beta) after merging these changes.
+## Introduction
Go terminal dashboard. Inspired by [blessed-contrib](https://github.com/yaronn/blessed-contrib), but purely in Go.
Cross-platform, easy to compile, and fully-customizable.
@@ -132,6 +134,11 @@ TODO: Image (let's wait until the implementation is finished).
+#### Mult-Bar / Stacked-Bar Chart
+[demo code](https://github.com/gizak/termui/blob/master/example/mbarchart.go)
+
+
+
#### Sparklines
[demo code](https://github.com/gizak/termui/blob/master/example/sparklines.go)
diff --git a/_grid_test.go b/_grid_test.go
index 3230033..cdafb20 100644
--- a/_grid_test.go
+++ b/_grid_test.go
@@ -10,7 +10,7 @@ import (
"github.com/davecgh/go-spew/spew"
)
-var r *row
+var r *Row
func TestRowWidth(t *testing.T) {
p0 := NewPar("p0")
diff --git a/example/dashboard.go b/example/dashboard.go
index d855ef2..c14bb44 100644
--- a/example/dashboard.go
+++ b/example/dashboard.go
@@ -47,7 +47,7 @@ func main() {
spark := ui.Sparkline{}
spark.Height = 1
spark.Title = "srv 0:"
- spdata := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6}
+ spdata := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6}
spark.Data = spdata
spark.LineColor = ui.ColorCyan
spark.TitleColor = ui.ColorWhite
@@ -119,8 +119,8 @@ func main() {
draw := func(t int) {
g.Percent = t % 101
list.Items = strs[t%9:]
- sp.Lines[0].Data = spdata[t%10:]
- sp.Lines[1].Data = spdata[t/2%10:]
+ sp.Lines[0].Data = spdata[:30+t%50]
+ sp.Lines[1].Data = spdata[:35+t%50]
lc.Data = sinps[t/2:]
lc1.Data = sinps[2*t:]
bc.Data = bcdata[t/2%10:]
diff --git a/example/gauge.go b/example/gauge.go
index f598d0e..26a2f12 100644
--- a/example/gauge.go
+++ b/example/gauge.go
@@ -55,7 +55,15 @@ func main() {
g1.Border.Fg = termui.ColorWhite
g1.Border.LabelFgClr = termui.ColorMagenta
- termui.Render(g0, g1, g2, gg)
+ g3 := termui.NewGauge()
+ g3.Percent = 50
+ g3.Width = 50
+ g3.Height = 3
+ g3.Y = 11
+ g3.Border.Label = "Gauge with custom label"
+ g3.Label = "{{percent}}% (100MBs free)"
+ g3.LabelAlign = termui.AlignRight
+ termui.Render(g0, g1, g2, g3)
<-termui.EventCh()
}
diff --git a/example/gauge.png b/example/gauge.png
index 17eb3ea..5c20e6e 100644
Binary files a/example/gauge.png and b/example/gauge.png differ
diff --git a/example/grid.go b/example/grid.go
index c010b4b..4912141 100644
--- a/example/grid.go
+++ b/example/grid.go
@@ -8,7 +8,6 @@ package main
import ui "github.com/gizak/termui"
import "math"
-
import "time"
func main() {
@@ -39,7 +38,7 @@ func main() {
spark := ui.Sparkline{}
spark.Height = 8
spdata := sinpsint
- spark.Data = spdata
+ spark.Data = spdata[:100]
spark.LineColor = ui.ColorCyan
spark.TitleColor = ui.ColorWhite
@@ -92,34 +91,44 @@ func main() {
// calculate layout
ui.Body.Align()
- draw := func(t int) {
- sp.Lines[0].Data = spdata[t:]
- lc.Data = sinps[2*t:]
- ui.Render(ui.Body)
+ done := make(chan bool)
+ redraw := make(chan bool)
+
+ update := func() {
+ for i := 0; i < 103; i++ {
+ for _, g := range gs {
+ g.Percent = (g.Percent + 3) % 100
+ }
+
+ sp.Lines[0].Data = spdata[:100+i]
+ lc.Data = sinps[2*i:]
+
+ time.Sleep(time.Second / 2)
+ redraw <- true
+ }
+ done <- true
}
evt := ui.EventCh()
- i := 0
+ ui.Render(ui.Body)
+ go update()
+
for {
select {
case e := <-evt:
if e.Type == ui.EventKey && e.Ch == 'q' {
return
}
- default:
- for _, g := range gs {
- g.Percent = (g.Percent + 3) % 100
+ if e.Type == ui.EventResize {
+ ui.Body.Width = ui.TermWidth()
+ ui.Body.Align()
+ go func() { redraw <- true }()
}
- ui.Body.Width = ui.TermWidth()
- ui.Body.Align()
-
- draw(i)
- i++
- if i == 102 {
- return
- }
- time.Sleep(time.Second / 2)
+ case <-done:
+ return
+ case <-redraw:
+ ui.Render(ui.Body)
}
}
}
diff --git a/example/mbarchart.go b/example/mbarchart.go
new file mode 100644
index 0000000..a32a28e
--- /dev/null
+++ b/example/mbarchart.go
@@ -0,0 +1,50 @@
+// Copyright 2015 Zack Guo . All rights reserved.
+// Use of this source code is governed by a MIT license that can
+// be found in the LICENSE file.
+
+// +build ignore
+
+package main
+
+import "github.com/gizak/termui"
+
+func main() {
+ err := termui.Init()
+ if err != nil {
+ panic(err)
+ }
+ defer termui.Close()
+
+ termui.UseTheme("helloworld")
+
+ bc := termui.NewMBarChart()
+ math := []int{90, 85, 90, 80}
+ english := []int{70, 85, 75, 60}
+ science := []int{75, 60, 80, 85}
+ compsci := []int{100, 100, 100, 100}
+ bc.Data[0] = math
+ bc.Data[1] = english
+ bc.Data[2] = science
+ bc.Data[3] = compsci
+ studentsName := []string{"Ken", "Rob", "Dennis", "Linus"}
+ bc.Border.Label = "Student's Marks X-Axis=Name Y-Axis=Marks[Math,English,Science,ComputerScience] in %"
+ bc.Width = 100
+ bc.Height = 50
+ bc.Y = 10
+ bc.BarWidth = 10
+ bc.DataLabels = studentsName
+ bc.ShowScale = true //Show y_axis scale value (min and max)
+ bc.SetMax(400)
+
+ bc.TextColor = termui.ColorGreen //this is color for label (x-axis)
+ bc.BarColor[3] = termui.ColorGreen //BarColor for computerscience
+ bc.BarColor[1] = termui.ColorYellow //Bar Color for english
+ bc.NumColor[3] = termui.ColorRed // Num color for computerscience
+ bc.NumColor[1] = termui.ColorRed // num color for english
+
+ //Other colors are automatically populated, btw All the students seems do well in computerscience. :p
+
+ termui.Render(bc)
+
+ <-termui.EventCh()
+}
diff --git a/example/mbarchart.png b/example/mbarchart.png
new file mode 100644
index 0000000..9a42526
Binary files /dev/null and b/example/mbarchart.png differ
diff --git a/gauge.go b/gauge.go
new file mode 100644
index 0000000..986f4f3
--- /dev/null
+++ b/gauge.go
@@ -0,0 +1,113 @@
+// Copyright 2015 Zack Guo . 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)
+}
diff --git a/helper.go b/helper.go
index b73010c..1c8f5ef 100644
--- a/helper.go
+++ b/helper.go
@@ -29,6 +29,7 @@ const (
ColorWhite
)
+const NumberofColors = 8 //Have a constant that defines number of colors
const (
AttrBold Attribute = 1 << (iota + 9)
AttrUnderline
diff --git a/mbar.go b/mbar.go
new file mode 100644
index 0000000..9d18c2c
--- /dev/null
+++ b/mbar.go
@@ -0,0 +1,233 @@
+// Copyright 2015 Zack Guo . 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)
+}
diff --git a/render.go b/render.go
index c471238..ce3bdb3 100644
--- a/render.go
+++ b/render.go
@@ -35,12 +35,14 @@ func Close() {
// TermWidth returns the current terminal's width.
func TermWidth() int {
+ tm.Sync()
w, _ := tm.Size()
return w
}
// TermHeight returns the current terminal's height.
func TermHeight() int {
+ tm.Sync()
_, h := tm.Size()
return h
}
diff --git a/theme.go b/theme.go
index 092d06b..c8ad947 100644
--- a/theme.go
+++ b/theme.go
@@ -26,6 +26,9 @@ type ColorScheme struct {
BarChartBar Attribute
BarChartText Attribute
BarChartNum Attribute
+ MBarChartBar Attribute
+ MBarChartText Attribute
+ MBarChartNum Attribute
}
// default color scheme depends on the user's terminal setting.
@@ -52,6 +55,9 @@ var themeHelloWorld = ColorScheme{
BarChartBar: ColorRed,
BarChartNum: ColorWhite,
BarChartText: ColorCyan,
+ MBarChartBar: ColorRed,
+ MBarChartNum: ColorWhite,
+ MBarChartText: ColorCyan,
}
var theme = themeDefault // global dep
diff --git a/widget/sparkline.go b/widget/sparkline.go
index 809b9e6..cabfd32 100644
--- a/widget/sparkline.go
+++ b/widget/sparkline.go
@@ -108,7 +108,7 @@ func (sl *Sparklines) Buffer() []Point {
data := l.Data
if len(data) > sl.innerWidth {
- data = data[:sl.innerWidth]
+ data = data[len(data)-sl.innerWidth:]
}
if l.Title != "" {