added palette

This commit is contained in:
sqshq 2019-01-31 18:40:05 -05:00
parent e977d045f6
commit 843375bc14
9 changed files with 149 additions and 75 deletions

View File

@ -1,12 +1,14 @@
theme: dark / bright
theme: dark
run-charts:
- title: curl-latency
data:
- label: example.com
script: curl -o /dev/null -s -w '%{time_total}' http://example.com
- label: google.com
script: curl -o /dev/null -s -w '%{time_total}' http://google.com
refresh-rate-ms: 200
- label: yahoo.com
script: curl -o /dev/null -s -w '%{time_total}' http://yahoo.com
- label: example.com
script: curl -o /dev/null -s -w '%{time_total}' http://example.com
refresh-rate-ms: 300
time-scale-sec: 1
style: dots/lines
position:
@ -19,7 +21,7 @@ run-charts:
data:
- label: posts
script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()" | grep 2
color: red
color: 3
refresh-rate-ms: 200
time-scale-sec: 1
position:

View File

@ -1,24 +1,22 @@
package config
import (
"github.com/sqshq/vcmd/data"
. "github.com/sqshq/vcmd/layout"
"github.com/sqshq/vcmd/settings"
"gopkg.in/yaml.v2"
"io/ioutil"
"log"
)
type Config struct {
Theme settings.Theme `yaml:"theme"`
RunCharts []RunChart `yaml:"run-charts"`
}
type DataConfig struct {
Script string `yaml:"script"`
Label string `yaml:"label"`
}
type RunChart struct {
Title string `yaml:"title"`
DataConfig []DataConfig `yaml:"data"`
Items []data.Item `yaml:"data"`
Position Position `yaml:"position"`
Size Size `yaml:"size"`
RefreshRateMs int `yaml:"refresh-rate-ms"`
@ -27,6 +25,15 @@ type RunChart struct {
func Load(location string) *Config {
cfg := readFile(location)
validate(cfg)
setColors(cfg)
return cfg
}
func readFile(location string) *Config {
yamlFile, err := ioutil.ReadFile(location)
if err != nil {
log.Fatalf("Can't read config file: %s", location)
@ -41,3 +48,26 @@ func Load(location string) *Config {
return cfg
}
/*
TODO validation
- title uniquness and mandatory within a single type of widget
- label uniqueness and mandatory (if > 1 data bullets)
*/
func validate(config *Config) {
}
func setColors(config *Config) {
palette := settings.GetPalette(config.Theme)
for i, chart := range config.RunCharts {
for j, item := range chart.Items {
if item.Color == 0 {
item.Color = palette.Colors[i+j]
chart.Items[j] = item
}
}
}
}

View File

@ -1,6 +1,6 @@
package data
type Consumer interface {
ConsumeValue(value string, label string)
ConsumeError(err error)
ConsumeValue(item Item, value string)
ConsumeError(item Item, err error)
}

24
data/item.go Normal file
View File

@ -0,0 +1,24 @@
package data
import (
ui "github.com/sqshq/termui"
"os/exec"
"strings"
)
type Item struct {
Script string `yaml:"script"`
Label string `yaml:"label"`
Color ui.Color `yaml:"color"`
}
func (self *Item) nextValue() (value string, err error) {
output, err := exec.Command("sh", "-c", self.Script).Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(output)), nil
}

View File

@ -1,22 +1,19 @@
package data
import (
"os/exec"
"strings"
"time"
)
type Poller struct {
consumer Consumer
script string
label string
item Item
pause bool
}
func NewPoller(consumer Consumer, script string, label string, rateMs int) Poller {
func NewPoller(consumer Consumer, item Item, rateMs int) Poller {
ticker := time.NewTicker(time.Duration(rateMs * int(time.Millisecond)))
poller := Poller{consumer, script, label, false}
poller := Poller{consumer, item, false}
go func() {
for {
@ -40,12 +37,11 @@ func (self *Poller) poll() {
return
}
output, err := exec.Command("sh", "-c", self.script).Output()
value, err := self.item.nextValue()
if err != nil {
self.consumer.ConsumeError(err)
self.consumer.ConsumeError(self.item, err)
}
value := strings.TrimSpace(string(output))
self.consumer.ConsumeValue(value, self.label)
self.consumer.ConsumeValue(self.item, value)
}

10
main.go
View File

@ -10,14 +10,8 @@ import (
"time"
)
/*
TODO validation
- title uniquness and mandatory within a single type of widget
- label uniqueness and mandatory (if > 1 data bullets)
*/
func main() {
// todo error handling + validation
cfg := config.Load("/Users/sqshq/Go/src/github.com/sqshq/vcmd/config.yml")
if err := ui.Init(); err != nil {
@ -35,9 +29,9 @@ func main() {
chart := widgets.NewRunChart(chartConfig.Title)
lout.AddItem(chart, chartConfig.Position, chartConfig.Size)
for _, chartData := range chartConfig.DataConfig {
for _, item := range chartConfig.Items {
pollers = append(pollers,
data.NewPoller(chart, chartData.Script, chartData.Label, chartConfig.RefreshRateMs))
data.NewPoller(chart, item, chartConfig.RefreshRateMs))
}
}

35
settings/color.go Normal file
View File

@ -0,0 +1,35 @@
package settings
import (
"fmt"
ui "github.com/sqshq/termui"
)
type Theme string
const (
ThemeDark Theme = "dark"
ThemeLight Theme = "light"
)
const (
ColorOlive ui.Color = 178
ColorDeepSkyBlue ui.Color = 39
ColorDeepPink ui.Color = 162
ColorDarkGrey ui.Color = 240
)
type Palette struct {
Colors []ui.Color
}
func GetPalette(theme Theme) Palette {
switch theme {
case ThemeDark:
return Palette{Colors: []ui.Color{ColorOlive, ColorDeepSkyBlue, ColorDeepPink}}
case ThemeLight:
return Palette{Colors: []ui.Color{ColorOlive, ColorDeepSkyBlue, ColorDeepPink}}
default:
panic(fmt.Sprintf("Following theme is not supported: %v", theme))
}
}

View File

@ -2,6 +2,8 @@ package widgets
import (
"fmt"
"github.com/sqshq/vcmd/data"
"github.com/sqshq/vcmd/settings"
"image"
"log"
"math"
@ -22,7 +24,7 @@ const (
type RunChart struct {
Block
items []ChartItem
lines []TimeLine
grid ChartGrid
mutex *sync.Mutex
}
@ -32,10 +34,9 @@ type TimePoint struct {
Time time.Time
}
type ChartItem struct {
timePoints []TimePoint
label string
color Color
type TimeLine struct {
points []TimePoint
item data.Item
}
type ChartGrid struct {
@ -62,7 +63,7 @@ func NewRunChart(title string) *RunChart {
block.Title = title
return &RunChart{
Block: block,
items: []ChartItem{},
lines: []TimeLine{},
mutex: &sync.Mutex{},
}
}
@ -78,7 +79,7 @@ func (self *RunChart) newChartGrid() ChartGrid {
paddingWidth: xAxisLabelsGap + xAxisLabelsWidth,
maxTimeWidth: self.Inner.Max.X - xAxisLabelsWidth/2 - xAxisLabelsGap,
timeExtremum: GetTimeExtremum(linesCount, paddingDuration),
valueExtremum: GetValueExtremum(self.items),
valueExtremum: GetValueExtremum(self.lines),
}
}
@ -98,7 +99,7 @@ func (self *RunChart) Draw(buf *Buffer) {
self.mutex.Unlock()
}
func (self *RunChart) ConsumeValue(value string, label string) {
func (self *RunChart) ConsumeValue(item data.Item, value string) {
float, err := strconv.ParseFloat(value, 64)
if err != nil {
@ -109,44 +110,43 @@ func (self *RunChart) ConsumeValue(value string, label string) {
self.mutex.Lock()
itemExists := false
for i, item := range self.items {
if item.label == label {
item.timePoints = append(item.timePoints, timePoint)
self.items[i] = item
for i, line := range self.lines {
if line.item.Label == item.Label {
line.points = append(line.points, timePoint)
self.lines[i] = line
itemExists = true
}
}
if !itemExists {
item := &ChartItem{
timePoints: []TimePoint{timePoint},
label: label,
color: ColorYellow,
item := &TimeLine{
points: []TimePoint{timePoint},
item: item,
}
self.items = append(self.items, *item)
self.lines = append(self.lines, *item)
}
self.trimOutOfRangeValues()
self.mutex.Unlock()
}
func (self *RunChart) ConsumeError(err error) {
func (self *RunChart) ConsumeError(item data.Item, err error) {
// TODO visual notification
}
func (self *RunChart) trimOutOfRangeValues() {
for i, item := range self.items {
for i, item := range self.lines {
lastOutOfRangeValueIndex := -1
for j, timePoint := range item.timePoints {
for j, timePoint := range item.points {
if !self.isTimePointInRange(timePoint) {
lastOutOfRangeValueIndex = j
}
}
if lastOutOfRangeValueIndex > 0 {
item.timePoints = append(item.timePoints[:0], item.timePoints[lastOutOfRangeValueIndex+1:]...)
self.items[i] = item
item.points = append(item.points[:0], item.points[lastOutOfRangeValueIndex+1:]...)
self.lines[i] = item
}
}
}
@ -156,23 +156,23 @@ func (self *RunChart) renderItems(buf *Buffer, drawArea image.Rectangle) {
canvas := NewCanvas()
canvas.Rectangle = drawArea
for _, item := range self.items {
for _, line := range self.lines {
xToPoint := make(map[int]image.Point)
pointsOrder := make([]int, 0)
for _, timePoint := range item.timePoints {
for _, point := range line.points {
if !self.isTimePointInRange(timePoint) {
if !self.isTimePointInRange(point) {
continue
}
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.Time)
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(point.Time)
deltaToPaddingRelation := float64(timeDeltaWithGridMaxTime.Nanoseconds()) / float64(self.grid.paddingDuration.Nanoseconds())
x := self.grid.maxTimeWidth - (int(float64(self.grid.paddingWidth) * deltaToPaddingRelation))
valuePerYDot := (self.grid.valueExtremum.max - self.grid.valueExtremum.min) / float64(drawArea.Dy()-1)
y := int(float64(timePoint.Value-self.grid.valueExtremum.min) / valuePerYDot)
y := int(float64(point.Value-self.grid.valueExtremum.min) / valuePerYDot)
if _, exists := xToPoint[x]; exists {
continue
@ -201,7 +201,7 @@ func (self *RunChart) renderItems(buf *Buffer, drawArea image.Rectangle) {
canvas.Line(
braillePoint(previousPoint),
braillePoint(currentPoint),
item.color,
line.item.Color,
)
}
}
@ -232,7 +232,7 @@ func (self *RunChart) plotAxes(buf *Buffer) {
for y := 0; y < self.Inner.Dy()-xAxisLabelsHeight-1; y = y + 2 {
for x := 0; x < self.grid.linesCount; x++ {
buf.SetCell(
NewCell(VERTICAL_DASH, NewStyle(ColorDarkGrey)),
NewCell(VERTICAL_DASH, NewStyle(settings.ColorDarkGrey)),
image.Pt(self.grid.maxTimeWidth-x*self.grid.paddingWidth, y+self.Inner.Min.Y+1),
)
}
@ -267,7 +267,7 @@ func (self *RunChart) plotAxes(buf *Buffer) {
}
}
func GetValueExtremum(items []ChartItem) ValueExtremum {
func GetValueExtremum(items []TimeLine) ValueExtremum {
if len(items) == 0 {
return ValueExtremum{0, 0}
@ -276,7 +276,7 @@ func GetValueExtremum(items []ChartItem) ValueExtremum {
var max, min = -math.MaxFloat64, math.MaxFloat64
for _, item := range items {
for _, point := range item.timePoints {
for _, point := range item.points {
if point.Value > max {
max = point.Value
}

View File

@ -1,7 +0,0 @@
package widgets
import "github.com/sqshq/termui"
const (
ColorDarkGrey termui.Color = 240
)