sparkline main functionality

This commit is contained in:
sqshq 2019-03-25 23:29:23 -04:00
parent db3888e540
commit e67ac7c19a
12 changed files with 128 additions and 51 deletions

View File

@ -3,6 +3,7 @@ package component
import (
"fmt"
ui "github.com/gizak/termui/v3"
"github.com/sqshq/sampler/component/util"
"github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/data"
"image"
@ -21,7 +22,7 @@ func RenderAlert(alert *data.Alert, area image.Rectangle, buffer *ui.Buffer) {
color = *alert.Color
}
width := max(len(alert.Title), len(alert.Text)) + 10
width := util.Max([]int{len(alert.Title), len(alert.Text)}) + 10
if width > area.Dx() {
width = area.Dx()
@ -55,15 +56,7 @@ func RenderAlert(alert *data.Alert, area image.Rectangle, buffer *ui.Buffer) {
}
}
//TODO move to utils
func max(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
// TODO move to utils
func getRectCoordinates(area image.Rectangle, width int, height int) (int, int, int, int) {
x1 := area.Min.X + area.Dx()/2 - width/2
y1 := area.Min.Y + area.Dy()/2 - height

View File

@ -134,7 +134,7 @@ func (b *BarChart) Draw(buffer *ui.Buffer) {
maxYCoordinate := b.Inner.Max.Y - height
for x := barXCoordinate; x < ui.MinInt(barXCoordinate+barWidth, b.Inner.Max.X-barIndent); x++ {
for y := b.Inner.Max.Y - 2; y >= maxYCoordinate; y-- {
c := ui.NewCell(console.SymbolShade, ui.NewStyle(bar.color))
c := ui.NewCell(console.SymbolHorizontalBar, ui.NewStyle(bar.color))
buffer.SetCell(c, image.Pt(x, y))
}
}

View File

@ -198,7 +198,7 @@ func (m *Menu) printAllDirectionsArrowSign(buffer *ui.Buffer, y int) {
func (m *Menu) renderOptions(buffer *ui.Buffer) {
highlightedStyle := ui.NewStyle(m.palette.BaseColor, console.ColorOlive)
highlightedStyle := ui.NewStyle(m.palette.ReverseColor, console.ColorOlive)
regularStyle := ui.NewStyle(m.palette.BaseColor, m.palette.ReverseColor)
offset := 1

View File

@ -2,6 +2,7 @@ package runchart
import (
ui "github.com/gizak/termui/v3"
"github.com/sqshq/sampler/component/util"
"image"
"math"
"time"
@ -76,13 +77,13 @@ func (c *RunChart) renderAxes(buffer *ui.Buffer) {
for i := 0; i < int(labelsCount); i++ {
value := c.grid.valueExtrema.max - (valuePerY * float64(i) * (yAxisLabelsIndent + yAxisLabelsHeight))
buffer.SetString(
formatValue(value, c.scale),
util.FormatValue(value, c.scale),
ui.NewStyle(c.palette.BaseColor),
image.Pt(c.Inner.Min.X, 1+c.Inner.Min.Y+i*(yAxisLabelsIndent+yAxisLabelsHeight)))
}
} else {
buffer.SetString(
formatValue(c.grid.valueExtrema.max, c.scale),
util.FormatValue(c.grid.valueExtrema.max, c.scale),
ui.NewStyle(c.palette.BaseColor),
image.Pt(c.Inner.Min.X, c.Inner.Min.Y+c.Inner.Dy()/2))
}

View File

@ -3,6 +3,7 @@ package runchart
import (
"fmt"
ui "github.com/gizak/termui/v3"
"github.com/sqshq/sampler/component/util"
"image"
"math"
)
@ -61,7 +62,7 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) {
if c.mode == ModePinpoint {
buffer.SetString(fmt.Sprintf("time %s", line.selectionPoint.time.Format("15:04:05.000")), detailsStyle, image.Pt(x, y+1))
buffer.SetString(fmt.Sprintf("value %s", formatValue(line.selectionPoint.value, c.scale)), detailsStyle, image.Pt(x, y+2))
buffer.SetString(fmt.Sprintf("value %s", util.FormatValue(line.selectionPoint.value, c.scale)), detailsStyle, image.Pt(x, y+2))
continue
}
@ -70,10 +71,10 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) {
}
details := [4]string{
fmt.Sprintf("cur %s", formatValue(getCurrentValue(line), c.scale)),
fmt.Sprintf("dlt %s", formatValueWithSign(getDiffWithPreviousValue(line), c.scale)),
fmt.Sprintf("max %s", formatValue(line.extrema.max, c.scale)),
fmt.Sprintf("min %s", formatValue(line.extrema.min, c.scale)),
fmt.Sprintf("cur %s", util.FormatValue(getCurrentValue(line), c.scale)),
fmt.Sprintf("dlt %s", util.FormatValueWithSign(getDiffWithPreviousValue(line), c.scale)),
fmt.Sprintf("max %s", util.FormatValue(line.extrema.max, c.scale)),
fmt.Sprintf("min %s", util.FormatValue(line.extrema.min, c.scale)),
}
for i, detail := range details {
@ -89,7 +90,7 @@ func getColumnWidth(mode Mode, lines []TimeLine, scale int) int {
return len(timeFormat)
}
width := len(formatValue(0, scale))
width := len(util.FormatValue(0, scale))
for _, line := range lines {
if len(line.label) > width {
width = len(line.label)

View File

@ -1,8 +1,8 @@
package runchart
import (
"fmt"
"github.com/sqshq/sampler/component"
"github.com/sqshq/sampler/component/util"
"github.com/sqshq/sampler/config"
"github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/data"
@ -324,7 +324,7 @@ func (c *RunChart) getMaxValueLength() int {
for _, line := range c.lines {
for _, point := range line.points {
l := len(formatValue(point.value, c.scale))
l := len(util.FormatValue(point.value, c.scale))
if l > maxValueLength {
maxValueLength = l
}
@ -366,25 +366,6 @@ func getMidRangeTime(r TimeRange) time.Time {
return r.max.Add(-delta / 2)
}
func formatValue(value float64, scale int) string {
if math.Abs(value) == math.MaxFloat64 {
return "Inf"
} else {
format := "%." + strconv.Itoa(scale) + "f"
return fmt.Sprintf(format, value)
}
}
func formatValueWithSign(value float64, scale int) string {
if value == 0 {
return " 0"
} else if value > 0 {
return "+" + formatValue(value, scale)
} else {
return formatValue(value, scale)
}
}
// time duration between grid lines
func calculateTimescale(refreshRateMs int) time.Duration {

View File

@ -3,18 +3,24 @@ package sparkline
import (
ui "github.com/gizak/termui/v3"
"github.com/sqshq/sampler/component"
"github.com/sqshq/sampler/component/util"
"github.com/sqshq/sampler/config"
"github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/data"
"image"
"strconv"
)
type SparkLine struct {
*ui.Block
*data.Consumer
alert *data.Alert
values []float64
palette console.Palette
alert *data.Alert
values []float64
maxValue float64
minValue float64
scale int
color ui.Color
palette console.Palette
}
func NewSparkLine(c config.SparkLineConfig, palette console.Palette) *SparkLine {
@ -23,6 +29,8 @@ func NewSparkLine(c config.SparkLineConfig, palette console.Palette) *SparkLine
Block: component.NewBlock(c.Title, true, palette),
Consumer: data.NewConsumer(),
values: []float64{},
scale: *c.Scale,
color: *c.Item.Color,
palette: palette,
}
@ -54,8 +62,54 @@ func (s *SparkLine) consumeSample(sample *data.Sample) {
s.values = append(s.values, float)
// TODO cleanup old ones
for i := len(s.values) - 1; i >= 0; i-- {
if len(s.values)-i > s.Dx() {
break
}
if s.values[i] > s.maxValue {
s.maxValue = s.values[i]
}
if s.values[i] < s.minValue {
s.minValue = s.values[i]
}
}
}
// TODO make sure that 0 value is still printed
// TODO make sure that cur value is printed on the same Y as sparkline (include in for loop for last iteratiton)
// TODO gradient color
func (s *SparkLine) Draw(buffer *ui.Buffer) {
textStyle := ui.NewStyle(s.palette.BaseColor)
lineStyle := ui.NewStyle(s.color)
minValue := util.FormatValue(s.minValue, s.scale)
maxValue := util.FormatValue(s.maxValue, s.scale)
curValue := util.FormatValue(s.values[len(s.values)-1], s.scale)
buffer.SetString(minValue, textStyle, image.Pt(s.Min.X+2, s.Max.Y-2))
buffer.SetString(maxValue, textStyle, image.Pt(s.Min.X+2, s.Min.Y+2))
curY := int((s.values[len(s.values)-1]/s.maxValue)*float64(s.Dy())) - 1
buffer.SetString(curValue, textStyle, image.Pt(s.Max.X-len(curValue)-2, s.Max.Y-util.Max([]int{curY, 2})))
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
if n > s.Dx()-indent*2-2 {
break
}
for j := 1; j < int((s.values[i]/s.maxValue)*float64(s.Dy()-2))+2; j++ {
buffer.SetString("▪", lineStyle, image.Pt(s.Inner.Max.X-n-indent, s.Inner.Max.Y-j))
}
}
s.Block.Draw(buffer)
}

26
component/util/format.go Normal file
View File

@ -0,0 +1,26 @@
package util
import (
"fmt"
"math"
"strconv"
)
func FormatValue(value float64, scale int) string {
if math.Abs(value) == math.MaxFloat64 {
return "Inf"
} else {
format := "%." + strconv.Itoa(scale) + "f"
return fmt.Sprintf(format, value)
}
}
func FormatValueWithSign(value float64, scale int) string {
if value == 0 {
return " 0"
} else if value > 0 {
return "+" + FormatValue(value, scale)
} else {
return FormatValue(value, scale)
}
}

14
component/util/math.go Normal file
View File

@ -0,0 +1,14 @@
package util
func Max(numbers []int) int {
max := numbers[0]
for _, n := range numbers {
if n > max {
max = n
}
}
return max
}

View File

@ -1,9 +1,9 @@
runcharts:
- title: SEARCH ENGINE RESPONSE TIME (sec)
position: [[0, 0], [53, 16]]
position: [[0, 0], [52, 16]]
triggers:
- title: Latency threshold exceeded
condition: echo "$prev < 0.35 && $cur > 0.35" |bc -l
condition: echo "$prev < 0.55 && $cur > 0.55" |bc -l
actions:
terminal-bell: true
sound: true
@ -31,7 +31,7 @@ runcharts:
barcharts:
- title: EVENTS BY STATUS
refresh-rate-ms: 1000
position: [[0, 17], [28, 12]]
position: [[0, 17], [27, 12]]
scale: 0
items:
- label: NEW
@ -85,6 +85,6 @@ asciiboxes:
font: 3d
sparklines:
- title: CPU usage
position: [[28, 17], [25, 12]]
position: [[27, 17], [25, 5]]
scale: 0
value: ps -A -o %cpu | awk '{s+=$1} END {print s}'

View File

@ -172,6 +172,13 @@ func (c *Config) setDefaultColors() {
}
}
for i, s := range c.SparkLines {
if s.Item.Color == nil {
s.Item.Color = &palette.ContentColors[i%colorsCount]
c.SparkLines[i] = s
}
}
for i, g := range c.Gauges {
if g.Color == nil {
g.Color = &palette.ContentColors[i%colorsCount]

View File

@ -1,7 +1,7 @@
package console
const (
SymbolSelection rune = '▲'
SymbolVerticalBar rune = '▎'
SymbolShade rune = '═'
SymbolSelection rune = '▲'
SymbolVerticalBar rune = '▎'
SymbolHorizontalBar rune = '═'
)