123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- // 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
- // Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
- /*
- 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}
- spl := termui.NewSparkline()
- spl.Data = data
- spl.Title = "Sparkline 0"
- spl.LineColor = termui.ColorGreen
- */
- type Sparkline struct {
- Data []int
- Height int
- Title string
- TitleColor Attribute
- LineColor Attribute
- displayHeight int
- scale float32
- max int
- }
- // Sparklines is a renderable widget which groups together the given sparklines.
- /*
- spls := termui.NewSparklines(spl0,spl1,spl2) //...
- spls.Height = 2
- spls.Width = 20
- */
- type Sparklines struct {
- Block
- Lines []Sparkline
- displayLines int
- displayWidth int
- }
- var sparks = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
- // Add appends a given Sparkline to s *Sparklines.
- func (s *Sparklines) Add(sl Sparkline) {
- s.Lines = append(s.Lines, sl)
- }
- // NewSparkline returns a unrenderable single sparkline that intended to be added into Sparklines.
- func NewSparkline() Sparkline {
- return Sparkline{
- Height: 1,
- TitleColor: ThemeAttr("sparkline.title.fg"),
- LineColor: ThemeAttr("sparkline.line.fg")}
- }
- // NewSparklines return a new *Spaklines with given Sparkline(s), you can always add a new Sparkline later.
- func NewSparklines(ss ...Sparkline) *Sparklines {
- s := &Sparklines{Block: *NewBlock(), Lines: ss}
- return s
- }
- func (sl *Sparklines) update() {
- for i, v := range sl.Lines {
- if v.Title == "" {
- sl.Lines[i].displayHeight = v.Height
- } else {
- sl.Lines[i].displayHeight = v.Height + 1
- }
- }
- sl.displayWidth = sl.innerArea.Dx()
- // get how many lines gotta display
- h := 0
- sl.displayLines = 0
- for _, v := range sl.Lines {
- if h+v.displayHeight <= sl.innerArea.Dy() {
- sl.displayLines++
- } else {
- break
- }
- h += v.displayHeight
- }
- for i := 0; i < sl.displayLines; i++ {
- data := sl.Lines[i].Data
- max := 0
- for _, v := range data {
- if max < v {
- max = v
- }
- }
- sl.Lines[i].max = max
- if max != 0 {
- sl.Lines[i].scale = float32(8*sl.Lines[i].Height) / float32(max)
- } else { // when all negative
- sl.Lines[i].scale = 0
- }
- }
- }
- // Buffer implements Bufferer interface.
- func (sl *Sparklines) Buffer() Buffer {
- buf := sl.Block.Buffer()
- sl.update()
- oftY := 0
- for i := 0; i < sl.displayLines; i++ {
- l := sl.Lines[i]
- data := l.Data
- if len(data) > sl.innerArea.Dx() {
- data = data[len(data)-sl.innerArea.Dx():]
- }
- if l.Title != "" {
- rs := trimStr2Runes(l.Title, sl.innerArea.Dx())
- oftX := 0
- for _, v := range rs {
- w := charWidth(v)
- c := Cell{
- Ch: v,
- Fg: l.TitleColor,
- Bg: sl.Bg,
- }
- x := sl.innerArea.Min.X + oftX
- y := sl.innerArea.Min.Y + oftY
- buf.Set(x, y, c)
- oftX += w
- }
- }
- for j, v := range data {
- // display height of the data point, zero when data is negative
- h := int(float32(v)*l.scale + 0.5)
- if v < 0 {
- h = 0
- }
- barCnt := h / 8
- barMod := h % 8
- for jj := 0; jj < barCnt; jj++ {
- c := Cell{
- Ch: ' ', // => sparks[7]
- Bg: l.LineColor,
- }
- x := sl.innerArea.Min.X + j
- y := sl.innerArea.Min.Y + oftY + l.Height - jj
- //p.Bg = sl.BgColor
- buf.Set(x, y, c)
- }
- if barMod != 0 {
- c := Cell{
- Ch: sparks[barMod-1],
- Fg: l.LineColor,
- Bg: sl.Bg,
- }
- x := sl.innerArea.Min.X + j
- y := sl.innerArea.Min.Y + oftY + l.Height - barCnt
- buf.Set(x, y, c)
- }
- }
- oftY += l.displayHeight
- }
- return buf
- }
|