added support for multiple items within one chart

This commit is contained in:
sqshq 2019-01-30 23:15:15 -05:00
parent b71d5556c2
commit e977d045f6
2 changed files with 109 additions and 86 deletions

View File

@ -41,7 +41,7 @@ func main() {
} }
} }
ticker := time.NewTicker(50 * time.Millisecond) ticker := time.NewTicker(30 * time.Millisecond)
for { for {
select { select {

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"image" "image"
"log" "log"
"math"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@ -21,17 +22,9 @@ const (
type RunChart struct { type RunChart struct {
Block Block
DataLabels []string items []ChartItem
LineColors []Color grid ChartGrid
timePoints []TimePoint mutex *sync.Mutex
dataMutex *sync.Mutex
grid ChartGrid
}
type Item struct {
timePoints []TimePoint
color Color
label string
} }
type TimePoint struct { type TimePoint struct {
@ -39,16 +32,10 @@ type TimePoint struct {
Time time.Time Time time.Time
} }
func NewRunChart(title string) *RunChart { type ChartItem struct {
block := *NewBlock() timePoints []TimePoint
block.Title = title label string
//self.LineColors[0] = ui.ColorYellow color Color
return &RunChart{
Block: block,
LineColors: Theme.Plot.Lines,
timePoints: make([]TimePoint, 0),
dataMutex: &sync.Mutex{},
}
} }
type ChartGrid struct { type ChartGrid struct {
@ -70,6 +57,16 @@ type ValueExtremum struct {
min float64 min float64
} }
func NewRunChart(title string) *RunChart {
block := *NewBlock()
block.Title = title
return &RunChart{
Block: block,
items: []ChartItem{},
mutex: &sync.Mutex{},
}
}
func (self *RunChart) newChartGrid() ChartGrid { func (self *RunChart) newChartGrid() ChartGrid {
linesCount := (self.Inner.Max.X - self.Inner.Min.X) / (xAxisLabelsGap + xAxisLabelsWidth) linesCount := (self.Inner.Max.X - self.Inner.Min.X) / (xAxisLabelsGap + xAxisLabelsWidth)
@ -81,13 +78,13 @@ func (self *RunChart) newChartGrid() ChartGrid {
paddingWidth: xAxisLabelsGap + xAxisLabelsWidth, paddingWidth: xAxisLabelsGap + xAxisLabelsWidth,
maxTimeWidth: self.Inner.Max.X - xAxisLabelsWidth/2 - xAxisLabelsGap, maxTimeWidth: self.Inner.Max.X - xAxisLabelsWidth/2 - xAxisLabelsGap,
timeExtremum: GetTimeExtremum(linesCount, paddingDuration), timeExtremum: GetTimeExtremum(linesCount, paddingDuration),
valueExtremum: GetValueExtremum(self.timePoints), valueExtremum: GetValueExtremum(self.items),
} }
} }
func (self *RunChart) Draw(buf *Buffer) { func (self *RunChart) Draw(buf *Buffer) {
self.dataMutex.Lock() self.mutex.Lock()
self.Block.Draw(buf) self.Block.Draw(buf)
self.grid = self.newChartGrid() self.grid = self.newChartGrid()
self.plotAxes(buf) self.plotAxes(buf)
@ -97,8 +94,8 @@ func (self *RunChart) Draw(buf *Buffer) {
self.Inner.Max.X, self.Inner.Max.Y-xAxisLabelsHeight-1, self.Inner.Max.X, self.Inner.Max.Y-xAxisLabelsHeight-1,
) )
self.renderBraille(buf, drawArea) self.renderItems(buf, drawArea)
self.dataMutex.Unlock() self.mutex.Unlock()
} }
func (self *RunChart) ConsumeValue(value string, label string) { func (self *RunChart) ConsumeValue(value string, label string) {
@ -108,10 +105,29 @@ func (self *RunChart) ConsumeValue(value string, label string) {
log.Fatalf("Expected float number, but got %v", value) // TODO visual notification log.Fatalf("Expected float number, but got %v", value) // TODO visual notification
} }
self.dataMutex.Lock() timePoint := TimePoint{Value: float, Time: time.Now()}
self.timePoints = append(self.timePoints, TimePoint{Value: float, Time: time.Now()}) 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
itemExists = true
}
}
if !itemExists {
item := &ChartItem{
timePoints: []TimePoint{timePoint},
label: label,
color: ColorYellow,
}
self.items = append(self.items, *item)
}
self.trimOutOfRangeValues() self.trimOutOfRangeValues()
self.dataMutex.Unlock() self.mutex.Unlock()
} }
func (self *RunChart) ConsumeError(err error) { func (self *RunChart) ConsumeError(err error) {
@ -119,70 +135,75 @@ func (self *RunChart) ConsumeError(err error) {
} }
func (self *RunChart) trimOutOfRangeValues() { func (self *RunChart) trimOutOfRangeValues() {
for i, item := range self.items {
lastOutOfRangeValueIndex := -1
lastOutOfRangeValueIndex := -1 for j, timePoint := range item.timePoints {
if !self.isTimePointInRange(timePoint) {
for i, timePoint := range self.timePoints { lastOutOfRangeValueIndex = j
if !self.isTimePointInRange(timePoint) { }
lastOutOfRangeValueIndex = i
} }
}
if lastOutOfRangeValueIndex > 0 { if lastOutOfRangeValueIndex > 0 {
self.timePoints = append(self.timePoints[:0], self.timePoints[lastOutOfRangeValueIndex+1:]...) item.timePoints = append(item.timePoints[:0], item.timePoints[lastOutOfRangeValueIndex+1:]...)
self.items[i] = item
}
} }
} }
func (self *RunChart) renderBraille(buf *Buffer, drawArea image.Rectangle) { func (self *RunChart) renderItems(buf *Buffer, drawArea image.Rectangle) {
canvas := NewCanvas() canvas := NewCanvas()
canvas.Rectangle = drawArea canvas.Rectangle = drawArea
pointPerX := make(map[int]image.Point) for _, item := range self.items {
pointsOrder := make([]int, 0)
for _, timePoint := range self.timePoints { xToPoint := make(map[int]image.Point)
pointsOrder := make([]int, 0)
if !self.isTimePointInRange(timePoint) { for _, timePoint := range item.timePoints {
continue
if !self.isTimePointInRange(timePoint) {
continue
}
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.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)
if _, exists := xToPoint[x]; exists {
continue
}
xToPoint[x] = image.Pt(x, drawArea.Max.Y-y-1)
pointsOrder = append(pointsOrder, x)
} }
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.Time) for i, x := range pointsOrder {
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) currentPoint := xToPoint[x]
y := int(float64(timePoint.Value-self.grid.valueExtremum.min) / valuePerYDot) var previousPoint image.Point
if _, exists := pointPerX[x]; exists { if i == 0 {
continue previousPoint = currentPoint
} else {
previousPoint = xToPoint[pointsOrder[i-1]]
}
//buf.SetCell(
// NewCell(self.DotRune, NewStyle(SelectColor(self.LineColors, 0))),
// currentPoint,
//)
canvas.Line(
braillePoint(previousPoint),
braillePoint(currentPoint),
item.color,
)
} }
pointPerX[x] = image.Pt(x, drawArea.Max.Y-y-1)
pointsOrder = append(pointsOrder, x)
}
for i, x := range pointsOrder {
currentPoint := pointPerX[x]
var previousPoint image.Point
if i == 0 {
previousPoint = currentPoint
} else {
previousPoint = pointPerX[pointsOrder[i-1]]
}
//buf.SetCell(
// NewCell(self.DotRune, NewStyle(SelectColor(self.LineColors, 0))),
// currentPoint,
//)
canvas.Line(
braillePoint(previousPoint),
braillePoint(currentPoint),
SelectColor(self.LineColors, 0), //i
)
} }
canvas.Draw(buf) canvas.Draw(buf)
@ -246,24 +267,26 @@ func (self *RunChart) plotAxes(buf *Buffer) {
} }
} }
func GetValueExtremum(points []TimePoint) ValueExtremum { func GetValueExtremum(items []ChartItem) ValueExtremum {
if len(points) == 0 { if len(items) == 0 {
return ValueExtremum{0, 0} return ValueExtremum{0, 0}
} }
var max, min = points[0], points[0] var max, min = -math.MaxFloat64, math.MaxFloat64
for _, point := range points { for _, item := range items {
if point.Value > max.Value { for _, point := range item.timePoints {
max = point if point.Value > max {
} max = point.Value
if point.Value < min.Value { }
min = point if point.Value < min {
min = point.Value
}
} }
} }
return ValueExtremum{max: max.Value, min: min.Value} return ValueExtremum{max: max, min: min}
} }
func GetTimeExtremum(linesCount int, paddingDuration time.Duration) TimeExtremum { func GetTimeExtremum(linesCount int, paddingDuration time.Duration) TimeExtremum {