sampler-fork/component/runchart/grid.go

148 lines
4.0 KiB
Go
Raw Normal View History

2019-02-15 04:34:45 +00:00
package runchart
import (
2019-03-14 03:01:44 +00:00
ui "github.com/gizak/termui/v3"
2019-03-26 03:29:23 +00:00
"github.com/sqshq/sampler/component/util"
2019-02-15 04:34:45 +00:00
"image"
"math"
"time"
)
2019-06-03 00:53:15 +00:00
const defaultValueLength = 4
2019-04-09 02:04:08 +00:00
2019-07-27 04:15:35 +00:00
type chartGrid struct {
2019-02-15 04:34:45 +00:00
timeRange TimeRange
timePerPoint time.Duration
valueExtrema ValueExtrema
linesCount int
maxTimeWidth int
minTimeWidth int
}
2019-07-27 04:15:35 +00:00
func (c *RunChart) newChartGrid() chartGrid {
2019-02-15 04:34:45 +00:00
linesCount := (c.Inner.Max.X - c.Inner.Min.X - c.grid.minTimeWidth) / xAxisGridWidth
timeRange := c.getTimeRange(linesCount)
2019-07-27 04:15:35 +00:00
return chartGrid{
2019-02-15 04:34:45 +00:00
timeRange: timeRange,
timePerPoint: c.timescale / time.Duration(xAxisGridWidth),
valueExtrema: getLocalExtrema(c.lines, timeRange),
linesCount: linesCount,
maxTimeWidth: c.Inner.Max.X,
2019-06-03 00:53:15 +00:00
minTimeWidth: defaultValueLength,
2019-02-15 04:34:45 +00:00
}
}
func (c *RunChart) renderAxes(buffer *ui.Buffer) {
2019-06-03 00:14:32 +00:00
// draw y axis labels
if c.grid.valueExtrema.max != c.grid.valueExtrema.min {
labelsCount := (c.Inner.Dy() - xAxisLabelsHeight - 1) / (yAxisLabelsIndent + yAxisLabelsHeight)
valuePerY := (c.grid.valueExtrema.max - c.grid.valueExtrema.min) / float64(c.Inner.Dy()-xAxisLabelsHeight-3)
for i := 0; i < int(labelsCount); i++ {
val := c.grid.valueExtrema.max - (valuePerY * float64(i) * (yAxisLabelsIndent + yAxisLabelsHeight))
fmt := util.FormatValue(val, c.scale)
if len(fmt) > c.grid.minTimeWidth {
c.grid.minTimeWidth = len(fmt)
}
buffer.SetString(
fmt,
ui.NewStyle(c.palette.BaseColor),
image.Pt(c.Inner.Min.X, 1+c.Inner.Min.Y+i*(yAxisLabelsIndent+yAxisLabelsHeight)))
}
} else {
fmt := util.FormatValue(c.grid.valueExtrema.max, c.scale)
c.grid.minTimeWidth = len(fmt)
buffer.SetString(
fmt,
ui.NewStyle(c.palette.BaseColor),
image.Pt(c.Inner.Min.X, c.Inner.Min.Y+c.Inner.Dy()/2))
}
2019-02-15 04:34:45 +00:00
// draw origin cell
buffer.SetCell(
ui.NewCell(ui.BOTTOM_LEFT, ui.NewStyle(c.palette.BaseColor)),
image.Pt(c.Inner.Min.X+c.grid.minTimeWidth, c.Inner.Max.Y-xAxisLabelsHeight-1))
2019-02-15 04:34:45 +00:00
// draw x axis line
for i := c.grid.minTimeWidth + 1; i < c.Inner.Dx(); i++ {
buffer.SetCell(
ui.NewCell(ui.HORIZONTAL_DASH, ui.NewStyle(c.palette.BaseColor)),
image.Pt(i+c.Inner.Min.X, c.Inner.Max.Y-xAxisLabelsHeight-1))
2019-02-15 04:34:45 +00:00
}
// draw grid lines
for y := 0; y < c.Inner.Dy()-xAxisLabelsHeight-2; y = y + 2 {
for x := 1; x <= c.grid.linesCount; x++ {
buffer.SetCell(
ui.NewCell(ui.VERTICAL_DASH, ui.NewStyle(c.palette.MediumColor)),
image.Pt(c.grid.maxTimeWidth-x*xAxisGridWidth, y+c.Inner.Min.Y+1))
2019-02-15 04:34:45 +00:00
}
}
// draw y axis line
for i := 0; i < c.Inner.Dy()-xAxisLabelsHeight-1; i++ {
buffer.SetCell(
ui.NewCell(ui.VERTICAL_DASH, ui.NewStyle(c.palette.BaseColor)),
image.Pt(c.Inner.Min.X+c.grid.minTimeWidth, i+c.Inner.Min.Y))
2019-02-15 04:34:45 +00:00
}
// draw x axis time labels
for i := 1; i <= c.grid.linesCount; i++ {
labelTime := c.grid.timeRange.max.Add(time.Duration(-i) * c.timescale)
buffer.SetString(
labelTime.Format("15:04:05"),
ui.NewStyle(c.palette.BaseColor),
image.Pt(c.grid.maxTimeWidth-xAxisLabelsWidth/2-i*(xAxisGridWidth), c.Inner.Max.Y-1))
2019-02-15 04:34:45 +00:00
}
}
func (c *RunChart) getTimeRange(linesCount int) TimeRange {
if c.mode == ModePinpoint {
2019-02-15 04:34:45 +00:00
return c.grid.timeRange
}
width := time.Duration(c.timescale.Nanoseconds() * int64(linesCount))
max := time.Now()
return TimeRange{
max: max,
min: max.Add(-width),
}
}
func getLocalExtrema(items []TimeLine, timeRange TimeRange) ValueExtrema {
if len(items) == 0 {
return ValueExtrema{0, 0}
}
var max, min = -math.MaxFloat64, math.MaxFloat64
for _, item := range items {
started := false
for i := len(item.points) - 1; i > 0; i-- {
point := item.points[i]
if timeRange.isInRange(point.time) {
started = true
} else if started == true && !timeRange.isInRange(point.time) {
break
}
if point.value > max && timeRange.isInRange(point.time) {
max = point.value
}
if point.value < min && timeRange.isInRange(point.time) {
min = point.value
}
}
}
return ValueExtrema{max: max, min: min}
}
func (r *TimeRange) isInRange(time time.Time) bool {
return time.After(r.min) && time.Before(r.max)
}