sampler-fork/component/sparkline/sparkline.go

145 lines
3.1 KiB
Go
Raw Normal View History

package sparkline
import (
ui "github.com/gizak/termui/v3"
"github.com/sqshq/sampler/component"
2019-03-26 03:29:23 +00:00
"github.com/sqshq/sampler/component/util"
"github.com/sqshq/sampler/config"
"github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/data"
2019-03-26 03:29:23 +00:00
"image"
"sync"
)
2019-07-27 04:15:35 +00:00
// SparkLine displays general shape of a measurement variation over time
type SparkLine struct {
*ui.Block
*data.Consumer
2019-03-26 03:29:23 +00:00
values []float64
maxValue float64
minValue float64
scale int
2019-04-01 04:35:55 +00:00
gradient []ui.Color
2019-03-26 03:29:23 +00:00
palette console.Palette
mutex *sync.Mutex
}
func NewSparkLine(c config.SparkLineConfig, palette console.Palette) *SparkLine {
line := &SparkLine{
Block: component.NewBlock(c.Title, true, palette),
Consumer: data.NewConsumer(),
values: []float64{},
2019-03-26 03:29:23 +00:00
scale: *c.Scale,
2019-04-01 04:35:55 +00:00
gradient: *c.Gradient,
palette: palette,
mutex: &sync.Mutex{},
}
go func() {
for {
select {
case sample := <-line.SampleChannel:
line.consumeSample(sample)
case alert := <-line.AlertChannel:
line.Alert = alert
}
}
}()
return line
}
func (s *SparkLine) consumeSample(sample *data.Sample) {
2019-04-07 18:26:20 +00:00
float, err := util.ParseFloat(sample.Value)
if err != nil {
s.HandleConsumeFailure("Failed to parse a number", err, sample)
return
}
2019-07-29 00:27:14 +00:00
s.HandleConsumeSuccess()
s.values = append(s.values, float)
2019-03-27 03:52:41 +00:00
max, min := s.values[0], s.values[0]
2019-03-26 03:29:23 +00:00
for i := len(s.values) - 1; i >= 0; i-- {
if len(s.values)-i > s.Dx() {
break
}
2019-03-27 03:52:41 +00:00
if s.values[i] > max {
max = s.values[i]
2019-03-26 03:29:23 +00:00
}
2019-03-27 03:52:41 +00:00
if s.values[i] < min {
min = s.values[i]
2019-03-26 03:29:23 +00:00
}
}
2019-03-27 03:52:41 +00:00
s.maxValue = max
s.minValue = min
if len(s.values)%100 == 0 {
s.mutex.Lock()
s.trimOutOfRangeValues(s.Dx())
s.mutex.Unlock()
}
}
func (s *SparkLine) trimOutOfRangeValues(maxSize int) {
if maxSize < len(s.values) {
s.values = append(s.values[:0], s.values[len(s.values)-maxSize:]...)
2019-03-27 03:52:41 +00:00
}
}
func (s *SparkLine) Draw(buffer *ui.Buffer) {
2019-03-26 03:29:23 +00:00
s.mutex.Lock()
2019-03-26 03:29:23 +00:00
textStyle := ui.NewStyle(s.palette.BaseColor)
2019-04-01 03:02:58 +00:00
height := s.Dy() - 2
2019-03-26 03:29:23 +00:00
minValue := util.FormatValue(s.minValue, s.scale)
maxValue := util.FormatValue(s.maxValue, s.scale)
curValue := util.FormatValue(0, s.scale)
if len(s.values) > 0 {
curValue = util.FormatValue(s.values[len(s.values)-1], s.scale)
}
2019-03-26 03:29:23 +00:00
indent := 2 + util.Max([]int{
len(minValue), len(maxValue), len(curValue),
})
for i := len(s.values) - 1; i >= 0; i-- {
n := len(s.values) - i
2019-03-27 03:52:41 +00:00
if n > s.Dx()-indent-3 {
2019-03-26 03:29:23 +00:00
break
}
2019-07-01 06:07:39 +00:00
top := 0
2019-03-27 03:52:41 +00:00
2019-07-01 06:07:39 +00:00
if s.maxValue != s.minValue {
top = int((s.values[i] - s.minValue) * float64(height) / (s.maxValue - s.minValue))
2019-03-27 03:52:41 +00:00
}
2019-07-01 06:07:39 +00:00
for j := 0; j <= top; j++ {
buffer.SetCell(ui.NewCell(console.SymbolVerticalBar, ui.NewStyle(console.GetGradientColor(s.gradient, j, height))), image.Pt(s.Inner.Max.X-n-indent, s.Inner.Max.Y-j-1))
}
if i == len(s.values)-1 {
buffer.SetString(curValue, textStyle, image.Pt(s.Inner.Max.X-n-indent+2, s.Inner.Max.Y-top-1))
if s.maxValue != s.minValue {
buffer.SetString(minValue, textStyle, image.Pt(s.Inner.Max.X-n-indent+2, s.Max.Y-2))
buffer.SetString(maxValue, textStyle, image.Pt(s.Inner.Max.X-n-indent+2, s.Min.Y+1))
2019-03-27 03:52:41 +00:00
}
2019-03-26 03:29:23 +00:00
}
}
s.mutex.Unlock()
s.Block.Draw(buffer)
component.RenderAlert(s.Alert, s.Rectangle, buffer)
}