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
 | 
						|
}
 |