barchart.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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 "fmt"
  6. // BarChart creates multiple bars in a widget:
  7. /*
  8. bc := termui.NewBarChart()
  9. data := []int{3, 2, 5, 3, 9, 5}
  10. bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
  11. bc.BorderLabel = "Bar Chart"
  12. bc.Data = data
  13. bc.Width = 26
  14. bc.Height = 10
  15. bc.DataLabels = bclabels
  16. bc.TextColor = termui.ColorGreen
  17. bc.BarColor = termui.ColorRed
  18. bc.NumColor = termui.ColorYellow
  19. */
  20. type BarChart struct {
  21. Block
  22. BarColor Attribute
  23. TextColor Attribute
  24. NumColor Attribute
  25. Data []int
  26. DataLabels []string
  27. BarWidth int
  28. BarGap int
  29. CellChar rune
  30. labels [][]rune
  31. dataNum [][]rune
  32. numBar int
  33. scale float64
  34. max int
  35. }
  36. // NewBarChart returns a new *BarChart with current theme.
  37. func NewBarChart() *BarChart {
  38. bc := &BarChart{Block: *NewBlock()}
  39. bc.BarColor = ThemeAttr("barchart.bar.bg")
  40. bc.NumColor = ThemeAttr("barchart.num.fg")
  41. bc.TextColor = ThemeAttr("barchart.text.fg")
  42. bc.BarGap = 1
  43. bc.BarWidth = 3
  44. bc.CellChar = ' '
  45. return bc
  46. }
  47. func (bc *BarChart) layout() {
  48. bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
  49. bc.labels = make([][]rune, bc.numBar)
  50. bc.dataNum = make([][]rune, len(bc.Data))
  51. for i := 0; i < bc.numBar && i < len(bc.DataLabels) && i < len(bc.Data); i++ {
  52. bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
  53. n := bc.Data[i]
  54. s := fmt.Sprint(n)
  55. bc.dataNum[i] = trimStr2Runes(s, bc.BarWidth)
  56. }
  57. //bc.max = bc.Data[0] // what if Data is nil? Sometimes when bar graph is nill it produces panic with panic: runtime error: index out of range
  58. // Assign a negative value to get maxvalue auto-populates
  59. if bc.max == 0 {
  60. bc.max = -1
  61. }
  62. for i := 0; i < len(bc.Data); i++ {
  63. if bc.max < bc.Data[i] {
  64. bc.max = bc.Data[i]
  65. }
  66. }
  67. bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
  68. }
  69. func (bc *BarChart) SetMax(max int) {
  70. if max > 0 {
  71. bc.max = max
  72. }
  73. }
  74. // Buffer implements Bufferer interface.
  75. func (bc *BarChart) Buffer() Buffer {
  76. buf := bc.Block.Buffer()
  77. bc.layout()
  78. for i := 0; i < bc.numBar && i < len(bc.Data) && i < len(bc.DataLabels); i++ {
  79. h := int(float64(bc.Data[i]) / bc.scale)
  80. oftX := i * (bc.BarWidth + bc.BarGap)
  81. barBg := bc.Bg
  82. barFg := bc.BarColor
  83. if bc.CellChar == ' ' {
  84. barBg = bc.BarColor
  85. barFg = ColorDefault
  86. if bc.BarColor == ColorDefault { // the same as above
  87. barBg |= AttrReverse
  88. }
  89. }
  90. // plot bar
  91. for j := 0; j < bc.BarWidth; j++ {
  92. for k := 0; k < h; k++ {
  93. c := Cell{
  94. Ch: bc.CellChar,
  95. Bg: barBg,
  96. Fg: barFg,
  97. }
  98. x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
  99. y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k
  100. buf.Set(x, y, c)
  101. }
  102. }
  103. // plot text
  104. for j, k := 0, 0; j < len(bc.labels[i]); j++ {
  105. w := charWidth(bc.labels[i][j])
  106. c := Cell{
  107. Ch: bc.labels[i][j],
  108. Bg: bc.Bg,
  109. Fg: bc.TextColor,
  110. }
  111. y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
  112. x := bc.innerArea.Min.X + oftX + k
  113. buf.Set(x, y, c)
  114. k += w
  115. }
  116. // plot num
  117. for j := 0; j < len(bc.dataNum[i]); j++ {
  118. c := Cell{
  119. Ch: bc.dataNum[i][j],
  120. Fg: bc.NumColor,
  121. Bg: barBg,
  122. }
  123. if h == 0 {
  124. c.Bg = bc.Bg
  125. }
  126. x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i]))/2 + j
  127. y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
  128. buf.Set(x, y, c)
  129. }
  130. }
  131. return buf
  132. }