2019-01-28 23:09:52 +00:00
|
|
|
package widgets
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2019-02-02 14:45:53 +00:00
|
|
|
"github.com/sqshq/sampler/console"
|
|
|
|
"github.com/sqshq/sampler/data"
|
2019-01-28 23:09:52 +00:00
|
|
|
"image"
|
2019-01-31 00:02:38 +00:00
|
|
|
"log"
|
2019-01-31 04:15:15 +00:00
|
|
|
"math"
|
2019-01-31 00:02:38 +00:00
|
|
|
"strconv"
|
2019-01-28 23:09:52 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
. "github.com/sqshq/termui"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
xAxisLabelsHeight = 1
|
|
|
|
xAxisLabelsWidth = 8
|
|
|
|
xAxisLabelsGap = 2
|
2019-02-02 04:39:34 +00:00
|
|
|
yAxisLabelsHeight = 1
|
2019-01-28 23:09:52 +00:00
|
|
|
yAxisLabelsGap = 1
|
2019-02-01 05:07:25 +00:00
|
|
|
xAxisLegendWidth = 15
|
2019-01-28 23:09:52 +00:00
|
|
|
)
|
|
|
|
|
2019-01-31 01:41:51 +00:00
|
|
|
type RunChart struct {
|
|
|
|
Block
|
2019-02-02 04:39:34 +00:00
|
|
|
lines []TimeLine
|
|
|
|
grid ChartGrid
|
|
|
|
precision int
|
|
|
|
selection time.Time
|
|
|
|
mutex *sync.Mutex
|
2019-01-31 01:41:51 +00:00
|
|
|
}
|
|
|
|
|
2019-01-28 23:09:52 +00:00
|
|
|
type TimePoint struct {
|
|
|
|
Value float64
|
|
|
|
Time time.Time
|
|
|
|
}
|
|
|
|
|
2019-01-31 23:40:05 +00:00
|
|
|
type TimeLine struct {
|
|
|
|
points []TimePoint
|
|
|
|
item data.Item
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-31 01:41:51 +00:00
|
|
|
type ChartGrid struct {
|
|
|
|
linesCount int
|
|
|
|
paddingDuration time.Duration
|
|
|
|
paddingWidth int
|
|
|
|
maxTimeWidth int
|
2019-02-02 04:39:34 +00:00
|
|
|
minTimeWidth int
|
2019-01-31 01:41:51 +00:00
|
|
|
valueExtremum ValueExtremum
|
|
|
|
timeExtremum TimeExtremum
|
|
|
|
}
|
|
|
|
|
|
|
|
type TimeExtremum struct {
|
|
|
|
max time.Time
|
|
|
|
min time.Time
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-31 01:41:51 +00:00
|
|
|
type ValueExtremum struct {
|
|
|
|
max float64
|
|
|
|
min float64
|
|
|
|
}
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
func NewRunChart(title string) *RunChart {
|
|
|
|
block := *NewBlock()
|
|
|
|
block.Title = title
|
|
|
|
return &RunChart{
|
2019-02-02 04:39:34 +00:00
|
|
|
Block: block,
|
|
|
|
lines: []TimeLine{},
|
|
|
|
mutex: &sync.Mutex{},
|
|
|
|
precision: 2, // TODO config
|
2019-01-31 04:15:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-31 01:41:51 +00:00
|
|
|
func (self *RunChart) newChartGrid() ChartGrid {
|
|
|
|
|
2019-02-02 04:39:34 +00:00
|
|
|
linesCount := (self.Inner.Max.X - self.Inner.Min.X - self.grid.minTimeWidth) / (xAxisLabelsGap + xAxisLabelsWidth)
|
2019-01-31 01:41:51 +00:00
|
|
|
paddingDuration := time.Duration(time.Second) // TODO support others and/or adjust automatically depending on refresh rate
|
|
|
|
|
|
|
|
return ChartGrid{
|
|
|
|
linesCount: linesCount,
|
|
|
|
paddingDuration: paddingDuration,
|
|
|
|
paddingWidth: xAxisLabelsGap + xAxisLabelsWidth,
|
2019-02-02 04:39:34 +00:00
|
|
|
maxTimeWidth: self.Inner.Max.X,
|
|
|
|
minTimeWidth: self.getMaxValueLength(),
|
2019-01-31 01:41:51 +00:00
|
|
|
timeExtremum: GetTimeExtremum(linesCount, paddingDuration),
|
2019-02-01 05:07:25 +00:00
|
|
|
valueExtremum: GetChartValueExtremum(self.lines),
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-31 01:41:51 +00:00
|
|
|
func (self *RunChart) Draw(buf *Buffer) {
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
self.mutex.Lock()
|
2019-01-28 23:09:52 +00:00
|
|
|
self.Block.Draw(buf)
|
2019-01-31 01:41:51 +00:00
|
|
|
self.grid = self.newChartGrid()
|
2019-02-01 05:07:25 +00:00
|
|
|
self.renderAxes(buf)
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-29 14:34:15 +00:00
|
|
|
drawArea := image.Rect(
|
2019-02-02 04:39:34 +00:00
|
|
|
self.Inner.Min.X+self.grid.minTimeWidth+1, self.Inner.Min.Y,
|
2019-01-29 14:34:15 +00:00
|
|
|
self.Inner.Max.X, self.Inner.Max.Y-xAxisLabelsHeight-1,
|
|
|
|
)
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
self.renderItems(buf, drawArea)
|
2019-02-01 05:07:25 +00:00
|
|
|
self.renderLegend(buf, drawArea)
|
2019-01-31 04:15:15 +00:00
|
|
|
self.mutex.Unlock()
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-31 23:40:05 +00:00
|
|
|
func (self *RunChart) ConsumeValue(item data.Item, value string) {
|
2019-01-31 00:02:38 +00:00
|
|
|
|
|
|
|
float, err := strconv.ParseFloat(value, 64)
|
|
|
|
if err != nil {
|
2019-02-02 04:39:34 +00:00
|
|
|
log.Printf("Expected float number, but got %v", value) // TODO visual notification
|
2019-01-31 00:02:38 +00:00
|
|
|
}
|
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
timePoint := TimePoint{Value: float, Time: time.Now()}
|
|
|
|
self.mutex.Lock()
|
|
|
|
itemExists := false
|
|
|
|
|
2019-01-31 23:40:05 +00:00
|
|
|
for i, line := range self.lines {
|
|
|
|
if line.item.Label == item.Label {
|
|
|
|
line.points = append(line.points, timePoint)
|
|
|
|
self.lines[i] = line
|
2019-01-31 04:15:15 +00:00
|
|
|
itemExists = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !itemExists {
|
2019-01-31 23:40:05 +00:00
|
|
|
item := &TimeLine{
|
|
|
|
points: []TimePoint{timePoint},
|
|
|
|
item: item,
|
2019-01-31 04:15:15 +00:00
|
|
|
}
|
2019-01-31 23:40:05 +00:00
|
|
|
self.lines = append(self.lines, *item)
|
2019-01-31 04:15:15 +00:00
|
|
|
}
|
|
|
|
|
2019-01-28 23:09:52 +00:00
|
|
|
self.trimOutOfRangeValues()
|
2019-01-31 04:15:15 +00:00
|
|
|
self.mutex.Unlock()
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-31 23:40:05 +00:00
|
|
|
func (self *RunChart) ConsumeError(item data.Item, err error) {
|
2019-01-31 00:02:38 +00:00
|
|
|
// TODO visual notification
|
|
|
|
}
|
|
|
|
|
2019-02-02 04:39:34 +00:00
|
|
|
func (self *RunChart) SelectValue(x int, y int) {
|
|
|
|
// TODO instead of that, find actual time for the given X
|
|
|
|
// + make sure that Y is within the given chart
|
|
|
|
// once ensured, set "selected time" into the chart structure
|
|
|
|
// self.selection = image.Point{X: x, Y: y}
|
|
|
|
}
|
|
|
|
|
2019-01-31 01:41:51 +00:00
|
|
|
func (self *RunChart) trimOutOfRangeValues() {
|
2019-02-02 04:39:34 +00:00
|
|
|
|
|
|
|
minRangeTime := self.grid.timeExtremum.min.Add(-self.grid.paddingDuration * 10)
|
|
|
|
|
2019-01-31 23:40:05 +00:00
|
|
|
for i, item := range self.lines {
|
2019-01-31 04:15:15 +00:00
|
|
|
lastOutOfRangeValueIndex := -1
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-02-02 04:39:34 +00:00
|
|
|
for j, point := range item.points {
|
|
|
|
if point.Time.Before(minRangeTime) {
|
2019-01-31 04:15:15 +00:00
|
|
|
lastOutOfRangeValueIndex = j
|
|
|
|
}
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
if lastOutOfRangeValueIndex > 0 {
|
2019-01-31 23:40:05 +00:00
|
|
|
item.points = append(item.points[:0], item.points[lastOutOfRangeValueIndex+1:]...)
|
|
|
|
self.lines[i] = item
|
2019-01-31 04:15:15 +00:00
|
|
|
}
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-01 05:07:25 +00:00
|
|
|
func (self *RunChart) renderItems(buffer *Buffer, drawArea image.Rectangle) {
|
2019-01-28 23:09:52 +00:00
|
|
|
|
|
|
|
canvas := NewCanvas()
|
|
|
|
canvas.Rectangle = drawArea
|
|
|
|
|
2019-01-31 23:40:05 +00:00
|
|
|
for _, line := range self.lines {
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
xToPoint := make(map[int]image.Point)
|
|
|
|
pointsOrder := make([]int, 0)
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 23:40:05 +00:00
|
|
|
for _, point := range line.points {
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-02-02 04:39:34 +00:00
|
|
|
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(point.Time).Nanoseconds()
|
|
|
|
timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.grid.paddingDuration.Nanoseconds())
|
|
|
|
x := self.grid.maxTimeWidth - (int(float64(self.grid.paddingWidth) * timeDeltaToPaddingRelation))
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-02-02 04:39:34 +00:00
|
|
|
var y int
|
|
|
|
if self.grid.valueExtremum.max-self.grid.valueExtremum.min == 0 {
|
|
|
|
y = (drawArea.Dy() - 2) / 2
|
|
|
|
} else {
|
|
|
|
valuePerY := (self.grid.valueExtremum.max - self.grid.valueExtremum.min) / float64(drawArea.Dy()-2)
|
|
|
|
y = int(float64(point.Value-self.grid.valueExtremum.min) / valuePerY)
|
|
|
|
}
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-02-02 04:39:34 +00:00
|
|
|
point := image.Pt(x, drawArea.Max.Y-y-1)
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
if _, exists := xToPoint[x]; exists {
|
|
|
|
continue
|
|
|
|
}
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-02-02 04:39:34 +00:00
|
|
|
if !point.In(drawArea) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
xToPoint[x] = point
|
2019-01-31 04:15:15 +00:00
|
|
|
pointsOrder = append(pointsOrder, x)
|
|
|
|
}
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
for i, x := range pointsOrder {
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
currentPoint := xToPoint[x]
|
|
|
|
var previousPoint image.Point
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
if i == 0 {
|
|
|
|
previousPoint = currentPoint
|
|
|
|
} else {
|
|
|
|
previousPoint = xToPoint[pointsOrder[i-1]]
|
|
|
|
}
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
canvas.Line(
|
|
|
|
braillePoint(previousPoint),
|
|
|
|
braillePoint(currentPoint),
|
2019-01-31 23:40:05 +00:00
|
|
|
line.item.Color,
|
2019-01-31 04:15:15 +00:00
|
|
|
)
|
|
|
|
}
|
2019-02-02 04:39:34 +00:00
|
|
|
|
|
|
|
//if point, exists := xToPoint[self.selection.X]; exists {
|
|
|
|
// buffer.SetCell(
|
|
|
|
// NewCell(DOT, NewStyle(line.item.Color)),
|
|
|
|
// point,
|
|
|
|
// )
|
|
|
|
// log.Printf("EXIST!")
|
|
|
|
//} else {
|
|
|
|
// //log.Printf("DOES NOT EXIST")
|
|
|
|
//}
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
|
2019-02-01 05:07:25 +00:00
|
|
|
canvas.Draw(buffer)
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
|
2019-02-02 04:39:34 +00:00
|
|
|
func (self *RunChart) renderAxes(buffer *Buffer) {
|
|
|
|
// draw origin cell
|
|
|
|
buffer.SetCell(
|
|
|
|
NewCell(BOTTOM_LEFT, NewStyle(ColorWhite)),
|
|
|
|
image.Pt(self.Inner.Min.X+self.grid.minTimeWidth, self.Inner.Max.Y-xAxisLabelsHeight-1),
|
|
|
|
)
|
|
|
|
|
|
|
|
// draw x axis line
|
|
|
|
for i := self.grid.minTimeWidth + 1; i < self.Inner.Dx(); i++ {
|
|
|
|
buffer.SetCell(
|
|
|
|
NewCell(HORIZONTAL_DASH, NewStyle(ColorWhite)),
|
|
|
|
image.Pt(i+self.Inner.Min.X, self.Inner.Max.Y-xAxisLabelsHeight-1),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw grid lines
|
|
|
|
for y := 0; y < self.Inner.Dy()-xAxisLabelsHeight-2; y = y + 2 {
|
|
|
|
for x := 1; x <= self.grid.linesCount; x++ {
|
|
|
|
buffer.SetCell(
|
|
|
|
NewCell(VERTICAL_DASH, NewStyle(console.ColorDarkGrey)),
|
|
|
|
image.Pt(self.grid.maxTimeWidth-x*self.grid.paddingWidth, y+self.Inner.Min.Y+1),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw y axis line
|
|
|
|
for i := 0; i < self.Inner.Dy()-xAxisLabelsHeight-1; i++ {
|
|
|
|
buffer.SetCell(
|
|
|
|
NewCell(VERTICAL_DASH, NewStyle(ColorWhite)),
|
|
|
|
image.Pt(self.Inner.Min.X+self.grid.minTimeWidth, i+self.Inner.Min.Y),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw x axis time labels
|
|
|
|
for i := 1; i <= self.grid.linesCount; i++ {
|
2019-02-02 14:23:42 +00:00
|
|
|
labelTime := self.grid.timeExtremum.max.Add(time.Duration(-i) * self.grid.paddingDuration)
|
2019-02-02 04:39:34 +00:00
|
|
|
buffer.SetString(
|
|
|
|
labelTime.Format("15:04:05"),
|
|
|
|
NewStyle(ColorWhite),
|
|
|
|
image.Pt(self.grid.maxTimeWidth-xAxisLabelsWidth/2-i*(self.grid.paddingWidth), self.Inner.Max.Y-1),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw y axis labels
|
|
|
|
if self.grid.valueExtremum.max != self.grid.valueExtremum.min {
|
|
|
|
labelsCount := (self.Inner.Dy() - xAxisLabelsHeight - 1) / (yAxisLabelsGap + yAxisLabelsHeight)
|
|
|
|
valuePerY := (self.grid.valueExtremum.max - self.grid.valueExtremum.min) / float64(self.Inner.Dy()-xAxisLabelsHeight-3)
|
|
|
|
for i := 0; i < int(labelsCount); i++ {
|
|
|
|
value := self.grid.valueExtremum.max - (valuePerY * float64(i) * (yAxisLabelsGap + yAxisLabelsHeight))
|
|
|
|
buffer.SetString(
|
|
|
|
formatValue(value, self.precision),
|
|
|
|
NewStyle(ColorWhite),
|
|
|
|
image.Pt(self.Inner.Min.X, 1+self.Inner.Min.Y+i*(yAxisLabelsGap+yAxisLabelsHeight)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
buffer.SetString(
|
|
|
|
formatValue(self.grid.valueExtremum.max, self.precision),
|
|
|
|
NewStyle(ColorWhite),
|
|
|
|
image.Pt(self.Inner.Min.X, self.Inner.Dy()/2))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-01 05:07:25 +00:00
|
|
|
func (self *RunChart) renderLegend(buffer *Buffer, rectangle image.Rectangle) {
|
|
|
|
for i, line := range self.lines {
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-02-01 05:07:25 +00:00
|
|
|
extremum := GetLineValueExtremum(line.points)
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-02-01 05:07:25 +00:00
|
|
|
buffer.SetString(
|
2019-02-02 04:39:34 +00:00
|
|
|
string(DOT),
|
2019-02-01 05:07:25 +00:00
|
|
|
NewStyle(line.item.Color),
|
|
|
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth-2, self.Inner.Min.Y+1+i*5),
|
2019-01-28 23:09:52 +00:00
|
|
|
)
|
2019-02-01 05:07:25 +00:00
|
|
|
buffer.SetString(
|
|
|
|
fmt.Sprintf("%s", line.item.Label),
|
|
|
|
NewStyle(line.item.Color),
|
|
|
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+1+i*5),
|
2019-01-28 23:09:52 +00:00
|
|
|
)
|
2019-02-01 05:07:25 +00:00
|
|
|
buffer.SetString(
|
2019-02-02 04:39:34 +00:00
|
|
|
fmt.Sprintf("cur %s", formatValue(line.points[len(line.points)-1].Value, self.precision)),
|
2019-01-28 23:09:52 +00:00
|
|
|
NewStyle(ColorWhite),
|
2019-02-01 05:07:25 +00:00
|
|
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+2+i*5),
|
2019-01-28 23:09:52 +00:00
|
|
|
)
|
2019-02-01 05:07:25 +00:00
|
|
|
buffer.SetString(
|
2019-02-02 04:39:34 +00:00
|
|
|
fmt.Sprintf("max %s", formatValue(extremum.max, self.precision)),
|
2019-01-28 23:09:52 +00:00
|
|
|
NewStyle(ColorWhite),
|
2019-02-01 05:07:25 +00:00
|
|
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+3+i*5),
|
|
|
|
)
|
|
|
|
buffer.SetString(
|
2019-02-02 04:39:34 +00:00
|
|
|
fmt.Sprintf("min %s", formatValue(extremum.min, self.precision)),
|
2019-02-01 05:07:25 +00:00
|
|
|
NewStyle(ColorWhite),
|
|
|
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+4+i*5),
|
2019-01-28 23:09:52 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-02 04:39:34 +00:00
|
|
|
func (self *RunChart) getMaxValueLength() int {
|
|
|
|
|
|
|
|
maxValueLength := 0
|
|
|
|
|
|
|
|
for _, line := range self.lines {
|
|
|
|
for _, point := range line.points {
|
|
|
|
l := len(formatValue(point.Value, self.precision))
|
|
|
|
if l > maxValueLength {
|
|
|
|
maxValueLength = l
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return maxValueLength
|
|
|
|
}
|
|
|
|
|
|
|
|
func formatValue(value float64, precision int) string {
|
|
|
|
format := " %." + strconv.Itoa(precision) + "f"
|
|
|
|
return fmt.Sprintf(format, value)
|
2019-02-01 05:07:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetChartValueExtremum(items []TimeLine) ValueExtremum {
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
if len(items) == 0 {
|
2019-01-31 01:41:51 +00:00
|
|
|
return ValueExtremum{0, 0}
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
var max, min = -math.MaxFloat64, math.MaxFloat64
|
2019-01-28 23:09:52 +00:00
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
for _, item := range items {
|
2019-01-31 23:40:05 +00:00
|
|
|
for _, point := range item.points {
|
2019-01-31 04:15:15 +00:00
|
|
|
if point.Value > max {
|
|
|
|
max = point.Value
|
|
|
|
}
|
|
|
|
if point.Value < min {
|
|
|
|
min = point.Value
|
|
|
|
}
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-31 04:15:15 +00:00
|
|
|
return ValueExtremum{max: max, min: min}
|
2019-01-31 01:41:51 +00:00
|
|
|
}
|
|
|
|
|
2019-02-01 05:07:25 +00:00
|
|
|
func GetLineValueExtremum(points []TimePoint) ValueExtremum {
|
|
|
|
|
|
|
|
if len(points) == 0 {
|
|
|
|
return ValueExtremum{0, 0}
|
|
|
|
}
|
|
|
|
|
|
|
|
var max, min = -math.MaxFloat64, math.MaxFloat64
|
|
|
|
|
|
|
|
for _, point := range points {
|
|
|
|
if point.Value > max {
|
|
|
|
max = point.Value
|
|
|
|
}
|
|
|
|
if point.Value < min {
|
|
|
|
min = point.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ValueExtremum{max: max, min: min}
|
|
|
|
}
|
|
|
|
|
2019-01-31 01:41:51 +00:00
|
|
|
func GetTimeExtremum(linesCount int, paddingDuration time.Duration) TimeExtremum {
|
|
|
|
maxTime := time.Now()
|
|
|
|
return TimeExtremum{
|
|
|
|
max: maxTime,
|
|
|
|
min: maxTime.Add(-time.Duration(paddingDuration.Nanoseconds() * int64(linesCount))),
|
|
|
|
}
|
2019-01-28 23:09:52 +00:00
|
|
|
}
|