mbarchart.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
  2. // Use of this source code is governed by a MIT license that can
  3. // be found in the LICENSE file.
  4. package termui
  5. import (
  6. "fmt"
  7. )
  8. // This is the implementation of multi-colored or stacked bar graph. This is different from default barGraph which is implemented in bar.go
  9. // Multi-Colored-BarChart creates multiple bars in a widget:
  10. /*
  11. bc := termui.NewMBarChart()
  12. data := make([][]int, 2)
  13. data[0] := []int{3, 2, 5, 7, 9, 4}
  14. data[1] := []int{7, 8, 5, 3, 1, 6}
  15. bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
  16. bc.BorderLabel = "Bar Chart"
  17. bc.Data = data
  18. bc.Width = 26
  19. bc.Height = 10
  20. bc.DataLabels = bclabels
  21. bc.TextColor = termui.ColorGreen
  22. bc.BarColor = termui.ColorRed
  23. bc.NumColor = termui.ColorYellow
  24. */
  25. type MBarChart struct {
  26. Block
  27. BarColor [NumberofColors]Attribute
  28. TextColor Attribute
  29. NumColor [NumberofColors]Attribute
  30. Data [NumberofColors][]int
  31. DataLabels []string
  32. BarWidth int
  33. BarGap int
  34. labels [][]rune
  35. dataNum [NumberofColors][][]rune
  36. numBar int
  37. scale float64
  38. max int
  39. minDataLen int
  40. numStack int
  41. ShowScale bool
  42. maxScale []rune
  43. }
  44. // NewBarChart returns a new *BarChart with current theme.
  45. func NewMBarChart() *MBarChart {
  46. bc := &MBarChart{Block: *NewBlock()}
  47. bc.BarColor[0] = ThemeAttr("mbarchart.bar.bg")
  48. bc.NumColor[0] = ThemeAttr("mbarchart.num.fg")
  49. bc.TextColor = ThemeAttr("mbarchart.text.fg")
  50. bc.BarGap = 1
  51. bc.BarWidth = 3
  52. return bc
  53. }
  54. func (bc *MBarChart) layout() {
  55. bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
  56. bc.labels = make([][]rune, bc.numBar)
  57. DataLen := 0
  58. LabelLen := len(bc.DataLabels)
  59. 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
  60. // We need to know how many stack/data array data[0] , data[1] are there
  61. for i := 0; i < len(bc.Data); i++ {
  62. if bc.Data[i] == nil {
  63. break
  64. }
  65. DataLen++
  66. }
  67. bc.numStack = DataLen
  68. //We need to know what is the minimum size of data array data[0] could have 10 elements data[1] could have only 5, so we plot only 5 bar graphs
  69. for i := 0; i < DataLen; i++ {
  70. if bc.minDataLen > len(bc.Data[i]) {
  71. bc.minDataLen = len(bc.Data[i])
  72. }
  73. }
  74. if LabelLen > bc.minDataLen {
  75. LabelLen = bc.minDataLen
  76. }
  77. for i := 0; i < LabelLen && i < bc.numBar; i++ {
  78. bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
  79. }
  80. for i := 0; i < bc.numStack; i++ {
  81. bc.dataNum[i] = make([][]rune, len(bc.Data[i]))
  82. //For each stack of bar calculate the rune
  83. for j := 0; j < LabelLen && i < bc.numBar; j++ {
  84. n := bc.Data[i][j]
  85. s := fmt.Sprint(n)
  86. bc.dataNum[i][j] = trimStr2Runes(s, bc.BarWidth)
  87. }
  88. //If color is not defined by default then populate a color that is different from the previous bar
  89. if bc.BarColor[i] == ColorDefault && bc.NumColor[i] == ColorDefault {
  90. if i == 0 {
  91. bc.BarColor[i] = ColorBlack
  92. } else {
  93. bc.BarColor[i] = bc.BarColor[i-1] + 1
  94. if bc.BarColor[i] > NumberofColors {
  95. bc.BarColor[i] = ColorBlack
  96. }
  97. }
  98. bc.NumColor[i] = (NumberofColors + 1) - bc.BarColor[i] //Make NumColor opposite of barColor for visibility
  99. }
  100. }
  101. //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]))
  102. if bc.max == 0 {
  103. bc.max = -1
  104. }
  105. for i := 0; i < bc.minDataLen && i < LabelLen; i++ {
  106. var dsum int
  107. for j := 0; j < bc.numStack; j++ {
  108. dsum += bc.Data[j][i]
  109. }
  110. if dsum > bc.max {
  111. bc.max = dsum
  112. }
  113. }
  114. //Finally Calculate max sale
  115. if bc.ShowScale {
  116. s := fmt.Sprintf("%d", bc.max)
  117. bc.maxScale = trimStr2Runes(s, len(s))
  118. bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-2)
  119. } else {
  120. bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
  121. }
  122. }
  123. func (bc *MBarChart) SetMax(max int) {
  124. if max > 0 {
  125. bc.max = max
  126. }
  127. }
  128. // Buffer implements Bufferer interface.
  129. func (bc *MBarChart) Buffer() Buffer {
  130. buf := bc.Block.Buffer()
  131. bc.layout()
  132. var oftX int
  133. for i := 0; i < bc.numBar && i < bc.minDataLen && i < len(bc.DataLabels); i++ {
  134. ph := 0 //Previous Height to stack up
  135. oftX = i * (bc.BarWidth + bc.BarGap)
  136. for i1 := 0; i1 < bc.numStack; i1++ {
  137. h := int(float64(bc.Data[i1][i]) / bc.scale)
  138. // plot bars
  139. for j := 0; j < bc.BarWidth; j++ {
  140. for k := 0; k < h; k++ {
  141. c := Cell{
  142. Ch: ' ',
  143. Bg: bc.BarColor[i1],
  144. }
  145. if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
  146. c.Bg |= AttrReverse
  147. }
  148. x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
  149. y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k - ph
  150. buf.Set(x, y, c)
  151. }
  152. }
  153. ph += h
  154. }
  155. // plot text
  156. for j, k := 0, 0; j < len(bc.labels[i]); j++ {
  157. w := charWidth(bc.labels[i][j])
  158. c := Cell{
  159. Ch: bc.labels[i][j],
  160. Bg: bc.Bg,
  161. Fg: bc.TextColor,
  162. }
  163. y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
  164. x := bc.innerArea.Max.X + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
  165. buf.Set(x, y, c)
  166. k += w
  167. }
  168. // plot num
  169. ph = 0 //re-initialize previous height
  170. for i1 := 0; i1 < bc.numStack; i1++ {
  171. h := int(float64(bc.Data[i1][i]) / bc.scale)
  172. for j := 0; j < len(bc.dataNum[i1][i]) && h > 0; j++ {
  173. c := Cell{
  174. Ch: bc.dataNum[i1][i][j],
  175. Fg: bc.NumColor[i1],
  176. Bg: bc.BarColor[i1],
  177. }
  178. if bc.BarColor[i1] == ColorDefault { // the same as above
  179. c.Bg |= AttrReverse
  180. }
  181. if h == 0 {
  182. c.Bg = bc.Bg
  183. }
  184. x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
  185. y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - ph
  186. buf.Set(x, y, c)
  187. }
  188. ph += h
  189. }
  190. }
  191. if bc.ShowScale {
  192. //Currently bar graph only supprts data range from 0 to MAX
  193. //Plot 0
  194. c := Cell{
  195. Ch: '0',
  196. Bg: bc.Bg,
  197. Fg: bc.TextColor,
  198. }
  199. y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
  200. x := bc.X
  201. buf.Set(x, y, c)
  202. //Plot the maximum sacle value
  203. for i := 0; i < len(bc.maxScale); i++ {
  204. c := Cell{
  205. Ch: bc.maxScale[i],
  206. Bg: bc.Bg,
  207. Fg: bc.TextColor,
  208. }
  209. y := bc.innerArea.Min.Y
  210. x := bc.X + i
  211. buf.Set(x, y, c)
  212. }
  213. }
  214. return buf
  215. }