added support for multiple items within one chart
This commit is contained in:
parent
b71d5556c2
commit
e977d045f6
2
main.go
2
main.go
|
@ -41,7 +41,7 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ticker := time.NewTicker(50 * time.Millisecond)
|
ticker := time.NewTicker(30 * time.Millisecond)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue