From 18c2085fd93bc1cc8d07c9f55d71a881c2fe13f7 Mon Sep 17 00:00:00 2001 From: sqshq Date: Sun, 3 Feb 2019 23:03:59 -0500 Subject: [PATCH] added time axis autoscale --- config.yml | 2 +- config/config.go | 1 + config/default.go | 4 ++ main.go | 2 +- widgets/runchart.go | 94 ++++++++++++++++++++++++++------------------- 5 files changed, 61 insertions(+), 42 deletions(-) diff --git a/config.yml b/config.yml index 75c6653..a9aae98 100644 --- a/config.yml +++ b/config.yml @@ -9,7 +9,7 @@ runcharts: - label: BING script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/ refresh-rate-ms: 200 - decimal-places: 1 + decimal-places: 3 alert: value: more-than: 0.231 diff --git a/config/config.go b/config/config.go index 3df6aad..5840c93 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,7 @@ type RunChartConfig struct { Position Position `yaml:"position"` Size Size `yaml:"size"` RefreshRateMs int `yaml:"refresh-rate-ms"` + Precision int `yaml:"decimal-places"` } func Load(location string) *Config { diff --git a/config/default.go b/config/default.go index a212ad9..389cae6 100644 --- a/config/default.go +++ b/config/default.go @@ -6,6 +6,7 @@ import ( const ( defaultRefreshRateMs = 300 + defaultPrecision = 1 defaultTheme = console.ThemeDark ) @@ -19,6 +20,9 @@ func (self *Config) setDefaultValues() { if chart.RefreshRateMs == 0 { chart.RefreshRateMs = defaultRefreshRateMs } + if chart.Precision == 0 { + chart.Precision = defaultPrecision + } self.RunCharts[i] = chart } } diff --git a/main.go b/main.go index c5a90b1..1396c37 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,7 @@ func main() { for _, chartConfig := range cfg.RunCharts { - chart := widgets.NewRunChart(chartConfig.Title) + chart := widgets.NewRunChart(chartConfig.Title, chartConfig.Precision, chartConfig.RefreshRateMs) layout.AddComponent(chart, chartConfig.Position, chartConfig.Size, widgets.TypeRunChart) for _, item := range chartConfig.Items { diff --git a/widgets/runchart.go b/widgets/runchart.go index c182b7b..f2df6ba 100644 --- a/widgets/runchart.go +++ b/widgets/runchart.go @@ -15,13 +15,16 @@ import ( ) const ( - chartHistoryReserve = 10 - xAxisLabelsHeight = 1 - xAxisLabelsWidth = 8 - xAxisLabelsGap = 2 - yAxisLabelsHeight = 1 - yAxisLabelsGap = 1 - xAxisLegendWidth = 20 + xAxisLegendWidth = 20 + xAxisLabelsHeight = 1 + xAxisLabelsWidth = 8 + xAxisLabelsGap = 2 + xAxisGridWidth = xAxisLabelsGap + xAxisLabelsWidth + + yAxisLabelsHeight = 1 + yAxisLabelsGap = 1 + + chartHistoryReserve = 5 ) type RunChart struct { @@ -29,6 +32,7 @@ type RunChart struct { lines []TimeLine grid ChartGrid precision int + timescale time.Duration selection *time.Time mutex *sync.Mutex } @@ -46,13 +50,12 @@ type TimeLine struct { } type ChartGrid struct { - linesCount int - paddingDuration time.Duration - paddingWidth int - maxTimeWidth int - minTimeWidth int - valueExtremum ValueExtremum - timeExtremum TimeExtremum + linesCount int + paddingWidth int + maxTimeWidth int + minTimeWidth int + valueExtremum ValueExtremum + timeExtremum TimeExtremum } type TimeExtremum struct { @@ -65,30 +68,29 @@ type ValueExtremum struct { min float64 } -func NewRunChart(title string) *RunChart { +func NewRunChart(title string, precision int, refreshRateMs int) *RunChart { block := *NewBlock() block.Title = title return &RunChart{ Block: block, lines: []TimeLine{}, mutex: &sync.Mutex{}, - precision: 2, // TODO move to config + precision: precision, + timescale: calculateTimescale(refreshRateMs), } } func (self *RunChart) newChartGrid() ChartGrid { - linesCount := (self.Inner.Max.X - self.Inner.Min.X - self.grid.minTimeWidth) / (xAxisLabelsGap + xAxisLabelsWidth) - paddingDuration := time.Duration(time.Second) // TODO support others and/or adjust automatically depending on refresh rate + linesCount := (self.Inner.Max.X - self.Inner.Min.X - self.grid.minTimeWidth) / xAxisGridWidth return ChartGrid{ - linesCount: linesCount, - paddingDuration: paddingDuration, - paddingWidth: xAxisLabelsGap + xAxisLabelsWidth, - maxTimeWidth: self.Inner.Max.X, - minTimeWidth: self.getMaxValueLength(), - timeExtremum: GetTimeExtremum(linesCount, paddingDuration), - valueExtremum: GetChartValueExtremum(self.lines), + linesCount: linesCount, + paddingWidth: xAxisGridWidth, + maxTimeWidth: self.Inner.Max.X, + minTimeWidth: self.getMaxValueLength(), + timeExtremum: getTimeExtremum(linesCount, self.timescale), + valueExtremum: getChartValueExtremum(self.lines), } } @@ -159,8 +161,8 @@ func (self *RunChart) SelectPoint(x int, y int) { return } - timeDeltaToPaddingRelation := (self.grid.maxTimeWidth - x) / self.grid.paddingWidth - timeDeltaWithGridMaxTime := timeDeltaToPaddingRelation * int(self.grid.paddingDuration.Nanoseconds()) + timeDeltaToPaddingRelation := (self.grid.maxTimeWidth - x) / xAxisGridWidth + timeDeltaWithGridMaxTime := timeDeltaToPaddingRelation * int(self.timescale.Nanoseconds()) selection := self.grid.timeExtremum.max.Add(-time.Duration(timeDeltaWithGridMaxTime) * time.Nanosecond) self.selection = &selection @@ -200,7 +202,7 @@ func (self *RunChart) getSelectedTimePoints() []TimePoint { func (self *RunChart) trimOutOfRangeValues() { - historyReserve := self.grid.paddingDuration * time.Duration(self.grid.linesCount) * chartHistoryReserve + historyReserve := self.timescale * time.Duration(self.grid.linesCount) * chartHistoryReserve minRangeTime := self.grid.timeExtremum.min.Add(-historyReserve) for i, item := range self.lines { @@ -232,8 +234,8 @@ func (self *RunChart) renderItems(buffer *Buffer, drawArea image.Rectangle) { for _, timePoint := range line.points { timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.time).Nanoseconds() - timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.grid.paddingDuration.Nanoseconds()) - x := self.grid.maxTimeWidth - (int(float64(self.grid.paddingWidth) * timeDeltaToPaddingRelation)) + timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.timescale.Nanoseconds()) + x := self.grid.maxTimeWidth - (int(float64(xAxisGridWidth) * timeDeltaToPaddingRelation)) var y int if self.grid.valueExtremum.max-self.grid.valueExtremum.min == 0 { @@ -299,7 +301,7 @@ func (self *RunChart) renderAxes(buffer *Buffer) { 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), + image.Pt(self.grid.maxTimeWidth-x*xAxisGridWidth, y+self.Inner.Min.Y+1), ) } } @@ -314,11 +316,11 @@ func (self *RunChart) renderAxes(buffer *Buffer) { // draw x axis time labels for i := 1; i <= self.grid.linesCount; i++ { - labelTime := self.grid.timeExtremum.max.Add(time.Duration(-i) * self.grid.paddingDuration) + labelTime := self.grid.timeExtremum.max.Add(time.Duration(-i) * self.timescale) 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), + image.Pt(self.grid.maxTimeWidth-xAxisLabelsWidth/2-i*(xAxisGridWidth), self.Inner.Max.Y-1), ) } @@ -380,7 +382,7 @@ func (self *RunChart) renderLegend(buffer *Buffer, rectangle image.Rectangle, se ) } } else { - extremum := GetLineValueExtremum(line.points) + extremum := getLineValueExtremum(line.points) buffer.SetString( fmt.Sprintf("cur %s", formatValue(line.points[len(line.points)-1].value, self.precision)), @@ -406,8 +408,8 @@ func (self *RunChart) renderSelection(buffer *Buffer, drawArea image.Rectangle, for _, timePoint := range selectedPoints { timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.time).Nanoseconds() - timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.grid.paddingDuration.Nanoseconds()) - x := self.grid.maxTimeWidth - (int(float64(self.grid.paddingWidth) * timeDeltaToPaddingRelation)) + timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.timescale.Nanoseconds()) + x := self.grid.maxTimeWidth - (int(float64(xAxisGridWidth) * timeDeltaToPaddingRelation)) var y int if self.grid.valueExtremum.max-self.grid.valueExtremum.min == 0 { @@ -446,7 +448,7 @@ func formatValue(value float64, precision int) string { return fmt.Sprintf(format, value) } -func GetChartValueExtremum(items []TimeLine) ValueExtremum { +func getChartValueExtremum(items []TimeLine) ValueExtremum { if len(items) == 0 { return ValueExtremum{0, 0} @@ -468,7 +470,7 @@ func GetChartValueExtremum(items []TimeLine) ValueExtremum { return ValueExtremum{max: max, min: min} } -func GetLineValueExtremum(points []TimePoint) ValueExtremum { +func getLineValueExtremum(points []TimePoint) ValueExtremum { if len(points) == 0 { return ValueExtremum{0, 0} @@ -488,10 +490,22 @@ func GetLineValueExtremum(points []TimePoint) ValueExtremum { return ValueExtremum{max: max, min: min} } -func GetTimeExtremum(linesCount int, paddingDuration time.Duration) TimeExtremum { +func getTimeExtremum(linesCount int, scale time.Duration) TimeExtremum { maxTime := time.Now() return TimeExtremum{ max: maxTime, - min: maxTime.Add(-time.Duration(paddingDuration.Nanoseconds() * int64(linesCount))), + min: maxTime.Add(-time.Duration(scale.Nanoseconds() * int64(linesCount))), + } +} + +func calculateTimescale(refreshRateMs int) time.Duration { + + multiplier := refreshRateMs * xAxisGridWidth / 2 + timescale := time.Duration(time.Millisecond * time.Duration(multiplier)).Round(time.Second) + + if timescale.Seconds() == 0 { + return time.Second + } else { + return timescale } }