243 lines
6.1 KiB
Go
243 lines
6.1 KiB
Go
|
// Copyright 2017 Zack Guo <zack.y.guo@gmail.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.BorderLabel = "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] = ThemeAttr("mbarchart.bar.bg")
|
||
|
bc.NumColor[0] = ThemeAttr("mbarchart.num.fg")
|
||
|
bc.TextColor = ThemeAttr("mbarchart.text.fg")
|
||
|
bc.BarGap = 1
|
||
|
bc.BarWidth = 3
|
||
|
return bc
|
||
|
}
|
||
|
|
||
|
func (bc *MBarChart) layout() {
|
||
|
bc.numBar = bc.innerArea.Dx() / (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.innerArea.Dy()-2)
|
||
|
} else {
|
||
|
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func (bc *MBarChart) SetMax(max int) {
|
||
|
|
||
|
if max > 0 {
|
||
|
bc.max = max
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Buffer implements Bufferer interface.
|
||
|
func (bc *MBarChart) Buffer() Buffer {
|
||
|
buf := 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++ {
|
||
|
c := Cell{
|
||
|
Ch: ' ',
|
||
|
Bg: bc.BarColor[i1],
|
||
|
}
|
||
|
if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
|
||
|
c.Bg |= AttrReverse
|
||
|
}
|
||
|
x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
|
||
|
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k - ph
|
||
|
buf.Set(x, y, c)
|
||
|
|
||
|
}
|
||
|
}
|
||
|
ph += h
|
||
|
}
|
||
|
// plot text
|
||
|
for j, k := 0, 0; j < len(bc.labels[i]); j++ {
|
||
|
w := charWidth(bc.labels[i][j])
|
||
|
c := Cell{
|
||
|
Ch: bc.labels[i][j],
|
||
|
Bg: bc.Bg,
|
||
|
Fg: bc.TextColor,
|
||
|
}
|
||
|
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
|
||
|
x := bc.innerArea.Max.X + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
|
||
|
buf.Set(x, y, c)
|
||
|
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++ {
|
||
|
c := Cell{
|
||
|
Ch: bc.dataNum[i1][i][j],
|
||
|
Fg: bc.NumColor[i1],
|
||
|
Bg: bc.BarColor[i1],
|
||
|
}
|
||
|
if bc.BarColor[i1] == ColorDefault { // the same as above
|
||
|
c.Bg |= AttrReverse
|
||
|
}
|
||
|
if h == 0 {
|
||
|
c.Bg = bc.Bg
|
||
|
}
|
||
|
x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
|
||
|
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - ph
|
||
|
buf.Set(x, y, c)
|
||
|
}
|
||
|
ph += h
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if bc.ShowScale {
|
||
|
//Currently bar graph only supprts data range from 0 to MAX
|
||
|
//Plot 0
|
||
|
c := Cell{
|
||
|
Ch: '0',
|
||
|
Bg: bc.Bg,
|
||
|
Fg: bc.TextColor,
|
||
|
}
|
||
|
|
||
|
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
|
||
|
x := bc.X
|
||
|
buf.Set(x, y, c)
|
||
|
|
||
|
//Plot the maximum sacle value
|
||
|
for i := 0; i < len(bc.maxScale); i++ {
|
||
|
c := Cell{
|
||
|
Ch: bc.maxScale[i],
|
||
|
Bg: bc.Bg,
|
||
|
Fg: bc.TextColor,
|
||
|
}
|
||
|
|
||
|
y := bc.innerArea.Min.Y
|
||
|
x := bc.X + i
|
||
|
|
||
|
buf.Set(x, y, c)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return buf
|
||
|
}
|