sampler-fork/widgets/runchart/grid.go

145 lines
3.8 KiB
Go

package runchart
import (
"github.com/sqshq/sampler/console"
ui "github.com/sqshq/termui"
"image"
"math"
"time"
)
type ChartGrid struct {
timeRange TimeRange
timePerPoint time.Duration
valueExtrema ValueExtrema
linesCount int
maxTimeWidth int
minTimeWidth int
}
func (c *RunChart) newChartGrid() ChartGrid {
linesCount := (c.Inner.Max.X - c.Inner.Min.X - c.grid.minTimeWidth) / xAxisGridWidth
timeRange := c.getTimeRange(linesCount)
return ChartGrid{
timeRange: timeRange,
timePerPoint: c.timescale / time.Duration(xAxisGridWidth),
valueExtrema: getLocalExtrema(c.lines, timeRange),
linesCount: linesCount,
maxTimeWidth: c.Inner.Max.X,
minTimeWidth: c.getMaxValueLength(),
}
}
func (c *RunChart) renderAxes(buffer *ui.Buffer) {
// draw origin cell
buffer.SetCell(
ui.NewCell(ui.BOTTOM_LEFT, ui.NewStyle(ui.ColorWhite)),
image.Pt(c.Inner.Min.X+c.grid.minTimeWidth, c.Inner.Max.Y-xAxisLabelsHeight-1),
)
// draw x axis line
for i := c.grid.minTimeWidth + 1; i < c.Inner.Dx(); i++ {
buffer.SetCell(
ui.NewCell(ui.HORIZONTAL_DASH, ui.NewStyle(ui.ColorWhite)),
image.Pt(i+c.Inner.Min.X, c.Inner.Max.Y-xAxisLabelsHeight-1),
)
}
// 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(console.ColorDarkGrey)),
image.Pt(c.grid.maxTimeWidth-x*xAxisGridWidth, y+c.Inner.Min.Y+1),
)
}
}
// draw y axis line
for i := 0; i < c.Inner.Dy()-xAxisLabelsHeight-1; i++ {
buffer.SetCell(
ui.NewCell(ui.VERTICAL_DASH, ui.NewStyle(ui.ColorWhite)),
image.Pt(c.Inner.Min.X+c.grid.minTimeWidth, i+c.Inner.Min.Y),
)
}
// 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(ui.ColorWhite),
image.Pt(c.grid.maxTimeWidth-xAxisLabelsWidth/2-i*(xAxisGridWidth), c.Inner.Max.Y-1),
)
}
// 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++ {
value := c.grid.valueExtrema.max - (valuePerY * float64(i) * (yAxisLabelsIndent + yAxisLabelsHeight))
buffer.SetString(
formatValue(value, c.precision),
ui.NewStyle(ui.ColorWhite),
image.Pt(c.Inner.Min.X, 1+c.Inner.Min.Y+i*(yAxisLabelsIndent+yAxisLabelsHeight)),
)
}
} else {
buffer.SetString(
formatValue(c.grid.valueExtrema.max, c.precision),
ui.NewStyle(ui.ColorWhite),
image.Pt(c.Inner.Min.X, c.Inner.Min.Y+c.Inner.Dy()/2))
}
}
func (c *RunChart) getTimeRange(linesCount int) TimeRange {
if c.mode == Pinpoint {
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)
}