sparkline.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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. // Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
  6. /*
  7. data := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1}
  8. spl := termui.NewSparkline()
  9. spl.Data = data
  10. spl.Title = "Sparkline 0"
  11. spl.LineColor = termui.ColorGreen
  12. */
  13. type Sparkline struct {
  14. Data []int
  15. Height int
  16. Title string
  17. TitleColor Attribute
  18. LineColor Attribute
  19. displayHeight int
  20. scale float32
  21. max int
  22. }
  23. // Sparklines is a renderable widget which groups together the given sparklines.
  24. /*
  25. spls := termui.NewSparklines(spl0,spl1,spl2) //...
  26. spls.Height = 2
  27. spls.Width = 20
  28. */
  29. type Sparklines struct {
  30. Block
  31. Lines []Sparkline
  32. displayLines int
  33. displayWidth int
  34. }
  35. var sparks = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
  36. // Add appends a given Sparkline to s *Sparklines.
  37. func (s *Sparklines) Add(sl Sparkline) {
  38. s.Lines = append(s.Lines, sl)
  39. }
  40. // NewSparkline returns a unrenderable single sparkline that intended to be added into Sparklines.
  41. func NewSparkline() Sparkline {
  42. return Sparkline{
  43. Height: 1,
  44. TitleColor: ThemeAttr("sparkline.title.fg"),
  45. LineColor: ThemeAttr("sparkline.line.fg")}
  46. }
  47. // NewSparklines return a new *Sparklines with given Sparkline(s), you can always add a new Sparkline later.
  48. func NewSparklines(ss ...Sparkline) *Sparklines {
  49. s := &Sparklines{Block: *NewBlock(), Lines: ss}
  50. return s
  51. }
  52. func (sl *Sparklines) update() {
  53. for i, v := range sl.Lines {
  54. if v.Title == "" {
  55. sl.Lines[i].displayHeight = v.Height
  56. } else {
  57. sl.Lines[i].displayHeight = v.Height + 1
  58. }
  59. }
  60. sl.displayWidth = sl.innerArea.Dx()
  61. // get how many lines gotta display
  62. h := 0
  63. sl.displayLines = 0
  64. for _, v := range sl.Lines {
  65. if h+v.displayHeight <= sl.innerArea.Dy() {
  66. sl.displayLines++
  67. } else {
  68. break
  69. }
  70. h += v.displayHeight
  71. }
  72. for i := 0; i < sl.displayLines; i++ {
  73. data := sl.Lines[i].Data
  74. max := 0
  75. for _, v := range data {
  76. if max < v {
  77. max = v
  78. }
  79. }
  80. sl.Lines[i].max = max
  81. if max != 0 {
  82. sl.Lines[i].scale = float32(8*sl.Lines[i].Height) / float32(max)
  83. } else { // when all negative
  84. sl.Lines[i].scale = 0
  85. }
  86. }
  87. }
  88. // Buffer implements Bufferer interface.
  89. func (sl *Sparklines) Buffer() Buffer {
  90. buf := sl.Block.Buffer()
  91. sl.update()
  92. oftY := 0
  93. for i := 0; i < sl.displayLines; i++ {
  94. l := sl.Lines[i]
  95. data := l.Data
  96. if len(data) > sl.innerArea.Dx() {
  97. data = data[len(data)-sl.innerArea.Dx():]
  98. }
  99. if l.Title != "" {
  100. rs := trimStr2Runes(l.Title, sl.innerArea.Dx())
  101. oftX := 0
  102. for _, v := range rs {
  103. w := charWidth(v)
  104. c := Cell{
  105. Ch: v,
  106. Fg: l.TitleColor,
  107. Bg: sl.Bg,
  108. }
  109. x := sl.innerArea.Min.X + oftX
  110. y := sl.innerArea.Min.Y + oftY
  111. buf.Set(x, y, c)
  112. oftX += w
  113. }
  114. }
  115. for j, v := range data {
  116. // display height of the data point, zero when data is negative
  117. h := int(float32(v)*l.scale + 0.5)
  118. if v < 0 {
  119. h = 0
  120. }
  121. barCnt := h / 8
  122. barMod := h % 8
  123. for jj := 0; jj < barCnt; jj++ {
  124. c := Cell{
  125. Ch: ' ', // => sparks[7]
  126. Bg: l.LineColor,
  127. }
  128. x := sl.innerArea.Min.X + j
  129. y := sl.innerArea.Min.Y + oftY + l.Height - jj
  130. //p.Bg = sl.BgColor
  131. buf.Set(x, y, c)
  132. }
  133. if barMod != 0 {
  134. c := Cell{
  135. Ch: sparks[barMod-1],
  136. Fg: l.LineColor,
  137. Bg: sl.Bg,
  138. }
  139. x := sl.innerArea.Min.X + j
  140. y := sl.innerArea.Min.Y + oftY + l.Height - barCnt
  141. buf.Set(x, y, c)
  142. }
  143. }
  144. oftY += l.displayHeight
  145. }
  146. return buf
  147. }