diff --git a/example/mbarchart.go b/example/mbarchart.go new file mode 100644 index 0000000..7729104 --- /dev/null +++ b/example/mbarchart.go @@ -0,0 +1,48 @@ +// 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{45, 75, 34, 62} + english := []int{50, 45, 25, 57} + science := []int{75, 60, 15, 50} + compsci := []int{90, 95, 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.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/helper.go b/helper.go index dec705e..80d8a02 100644 --- a/helper.go +++ b/helper.go @@ -24,6 +24,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..3b3c253 --- /dev/null +++ b/mbar.go @@ -0,0 +1,198 @@ +// 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 +} + +// 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 lenght + + // 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 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.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; 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 + } + } + 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 + for i1 := 0; i1 < bc.numStack; i1++ { + h := int(float64(bc.Data[i1][i]) / bc.scale) + oftX = i * (bc.BarWidth + bc.BarGap) + // 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 + 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]); 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[i]))/2 + j + p.Y = bc.innerY + bc.innerHeight - 2 - ph + ps = append(ps, p) + } + ph += h + } + } + + return bc.Block.chopOverflow(ps) +} 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