implemented pinpoint mode (based on this, scroll feature may be considered later)

This commit is contained in:
sqshq 2019-02-07 22:47:43 -05:00
parent ddec6de927
commit f5ccf6c464
5 changed files with 157 additions and 73 deletions

View File

@ -8,7 +8,7 @@ runcharts:
script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/ script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/
- label: BING - label: BING
script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/ script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/
refresh-rate-ms: 200 refresh-rate-ms: 500
decimal-places: 3 decimal-places: 3
alert: alert:
value: value:
@ -25,7 +25,7 @@ runcharts:
x: 0 x: 0
y: 0 y: 0
size: size:
x: 15 x: 30
y: 15 y: 15
- title: SEARCH ENGINE RESPONSE TIME 2 (sec) - title: SEARCH ENGINE RESPONSE TIME 2 (sec)
items: items:
@ -60,7 +60,7 @@ runcharts:
script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()" script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()"
position: position:
x: 15 x: 15
y: 0 y: 15
size: size:
x: 15 x: 15
y: 15 y: 15

5
console/symbol.go Normal file
View File

@ -0,0 +1,5 @@
package console
const (
SymbolSelection rune = '▲'
)

View File

@ -31,13 +31,12 @@ func (self *Handler) HandleEvents() {
case EventResize: case EventResize:
payload := e.Payload.(ui.Resize) payload := e.Payload.(ui.Resize)
self.Layout.ChangeDimensions(payload.Width, payload.Height) self.Layout.ChangeDimensions(payload.Width, payload.Height)
case EventMouseClick: case "a":
//payload := e.Payload.(ui.Mouse) self.Layout.GetComponent(0).DisableSelection()
case EventKeyboardLeft: case EventKeyboardLeft:
// here we are going to move selection (special type of layout item) self.Layout.GetComponent(0).MoveSelection(-1)
//layout.GetItem("").Move(-1, 0)
case EventKeyboardRight: case EventKeyboardRight:
//layout.GetItem(0).Move(1, 0) self.Layout.GetComponent(0).MoveSelection(+1)
case EventKeyboardDown: case EventKeyboardDown:
//layout.GetItem(0).Move(0, 1) //layout.GetItem(0).Move(0, 1)
case EventKeyboardUp: case EventKeyboardUp:

View File

@ -42,6 +42,11 @@ func (self *Layout) GetComponents(Type ComponentType) []Drawable {
return components return components
} }
// temp function until ui item selection is implemented
func (self *Layout) GetComponent(i int) *RunChart {
return self.components[i].Drawable.(*RunChart)
}
func (self *Layout) ChangeDimensions(width, height int) { func (self *Layout) ChangeDimensions(width, height int) {
self.SetRect(0, 0, width, height) self.SetRect(0, 0, width, height)
} }

View File

@ -20,49 +20,56 @@ const (
xAxisLabelsWidth = 8 xAxisLabelsWidth = 8
xAxisLabelsGap = 2 xAxisLabelsGap = 2
xAxisGridWidth = xAxisLabelsGap + xAxisLabelsWidth xAxisGridWidth = xAxisLabelsGap + xAxisLabelsWidth
yAxisLabelsHeight = 1 yAxisLabelsHeight = 1
yAxisLabelsGap = 1 yAxisLabelsGap = 1
)
chartHistoryReserve = 5 type ScrollMode int
const (
Auto ScrollMode = 0
Manual ScrollMode = 1
) )
type RunChart struct { type RunChart struct {
ui.Block ui.Block
lines []timeLine lines []TimeLine
grid chartGrid grid ChartGrid
precision int timescale time.Duration
timescale time.Duration mutex *sync.Mutex
mutex *sync.Mutex scrollMode ScrollMode
selection time.Time
precision int
} }
type chartGrid struct { type ChartGrid struct {
valueExtrema valueExtrema timeRange TimeRange
timeRange timeRange timePerPoint time.Duration
valueExtrema ValueExtrema
linesCount int linesCount int
paddingWidth int
maxTimeWidth int maxTimeWidth int
minTimeWidth int minTimeWidth int
} }
type timePoint struct { type TimePoint struct {
value float64 value float64
time time.Time time time.Time
timeCoordinate int coordinate int
} }
type timeLine struct { type TimeLine struct {
points []timePoint points []TimePoint
color ui.Color color ui.Color
label string label string
selection int
} }
type timeRange struct { type TimeRange struct {
max time.Time max time.Time
min time.Time min time.Time
} }
type valueExtrema struct { type ValueExtrema struct {
max float64 max float64
min float64 min float64
} }
@ -71,35 +78,36 @@ func NewRunChart(title string, precision int, refreshRateMs int) *RunChart {
block := *ui.NewBlock() block := *ui.NewBlock()
block.Title = title block.Title = title
return &RunChart{ return &RunChart{
Block: block, Block: block,
lines: []timeLine{}, lines: []TimeLine{},
mutex: &sync.Mutex{}, timescale: calculateTimescale(refreshRateMs),
precision: precision, mutex: &sync.Mutex{},
timescale: calculateTimescale(refreshRateMs), precision: precision,
scrollMode: Auto,
} }
} }
func (self *RunChart) newChartGrid() chartGrid { func (self *RunChart) newChartGrid() ChartGrid {
linesCount := (self.Inner.Max.X - self.Inner.Min.X - self.grid.minTimeWidth) / xAxisGridWidth linesCount := (self.Inner.Max.X - self.Inner.Min.X - self.grid.minTimeWidth) / xAxisGridWidth
timeRange := getTimeRange(linesCount, self.timescale) timeRange := self.getTimeRange(linesCount)
return chartGrid{ return ChartGrid{
timeRange: timeRange, timeRange: timeRange,
timePerPoint: self.timescale / time.Duration(xAxisGridWidth),
valueExtrema: getValueExtrema(self.lines, timeRange), valueExtrema: getValueExtrema(self.lines, timeRange),
linesCount: linesCount, linesCount: linesCount,
paddingWidth: xAxisGridWidth,
maxTimeWidth: self.Inner.Max.X, maxTimeWidth: self.Inner.Max.X,
minTimeWidth: self.getMaxValueLength(), minTimeWidth: self.getMaxValueLength(),
} }
} }
func (self *RunChart) newTimePoint(value float64) timePoint { func (self *RunChart) newTimePoint(value float64) TimePoint {
now := time.Now() now := time.Now()
return timePoint{ return TimePoint{
value: value, value: value,
time: now, time: now,
timeCoordinate: self.calculateTimeCoordinate(now), coordinate: self.calculateTimeCoordinate(now),
} }
} }
@ -139,8 +147,8 @@ func (self *RunChart) ConsumeSample(sample data.Sample) {
} }
if lineIndex == -1 { if lineIndex == -1 {
line := &timeLine{ line := &TimeLine{
points: []timePoint{}, points: []TimePoint{},
color: sample.Color, color: sample.Color,
label: sample.Label, label: sample.Label,
} }
@ -166,18 +174,26 @@ func (self *RunChart) renderLines(buffer *ui.Buffer, drawArea image.Rectangle) {
return return
} }
selectionCoordinate := self.calculateTimeCoordinate(self.selection)
selectionPoints := make(map[int]image.Point)
probe := self.lines[0].points[0] probe := self.lines[0].points[0]
delta := ui.AbsInt(self.calculateTimeCoordinate(probe.time) - probe.timeCoordinate) delta := ui.AbsInt(self.calculateTimeCoordinate(probe.time) - probe.coordinate)
for _, line := range self.lines { for i, line := range self.lines { // TODO start from right side, break on out of range
xToPoint := make(map[int]image.Point) xPoint := make(map[int]image.Point)
pointsOrder := make([]int, 0) xOrder := make([]int, 0)
for i, timePoint := range line.points { if line.selection != 0 {
line.selection -= delta
self.lines[i].selection = line.selection
}
timePoint.timeCoordinate = timePoint.timeCoordinate - delta for j, timePoint := range line.points {
line.points[i] = timePoint
timePoint.coordinate -= delta
line.points[j] = timePoint
var y int var y int
if self.grid.valueExtrema.max == self.grid.valueExtrema.min { if self.grid.valueExtrema.max == self.grid.valueExtrema.min {
@ -187,9 +203,9 @@ func (self *RunChart) renderLines(buffer *ui.Buffer, drawArea image.Rectangle) {
y = int(float64(timePoint.value-self.grid.valueExtrema.min) / valuePerY) y = int(float64(timePoint.value-self.grid.valueExtrema.min) / valuePerY)
} }
point := image.Pt(timePoint.timeCoordinate, drawArea.Max.Y-y-1) point := image.Pt(timePoint.coordinate, drawArea.Max.Y-y-1)
if _, exists := xToPoint[point.X]; exists { if _, exists := xPoint[point.X]; exists {
continue continue
} }
@ -197,19 +213,29 @@ func (self *RunChart) renderLines(buffer *ui.Buffer, drawArea image.Rectangle) {
continue continue
} }
xToPoint[point.X] = point if line.selection == 0 {
pointsOrder = append(pointsOrder, point.X) if len(line.points) > j+1 && ui.AbsInt(timePoint.coordinate-selectionCoordinate) > ui.AbsInt(line.points[j+1].coordinate-selectionCoordinate) {
selectionPoints[i] = point
}
} else {
if timePoint.coordinate == line.selection {
selectionPoints[i] = point
}
}
xPoint[point.X] = point
xOrder = append(xOrder, point.X)
} }
for i, x := range pointsOrder { for i, x := range xOrder {
currentPoint := xToPoint[x] currentPoint := xPoint[x]
var previousPoint image.Point var previousPoint image.Point
if i == 0 { if i == 0 {
previousPoint = currentPoint previousPoint = currentPoint
} else { } else {
previousPoint = xToPoint[pointsOrder[i-1]] previousPoint = xPoint[xOrder[i-1]]
} }
canvas.Line( canvas.Line(
@ -221,6 +247,15 @@ func (self *RunChart) renderLines(buffer *ui.Buffer, drawArea image.Rectangle) {
} }
canvas.Draw(buffer) canvas.Draw(buffer)
if self.scrollMode == Manual {
for lineIndex, point := range selectionPoints {
buffer.SetCell(ui.NewCell(console.SymbolSelection, ui.NewStyle(self.lines[lineIndex].color)), point)
if self.lines[lineIndex].selection == 0 {
self.lines[lineIndex].selection = point.X
}
}
}
} }
func (self *RunChart) renderAxes(buffer *ui.Buffer) { func (self *RunChart) renderAxes(buffer *ui.Buffer) {
@ -282,7 +317,7 @@ func (self *RunChart) renderAxes(buffer *ui.Buffer) {
buffer.SetString( buffer.SetString(
formatValue(self.grid.valueExtrema.max, self.precision), formatValue(self.grid.valueExtrema.max, self.precision),
ui.NewStyle(ui.ColorWhite), ui.NewStyle(ui.ColorWhite),
image.Pt(self.Inner.Min.X, self.Inner.Dy()/2)) image.Pt(self.Inner.Min.X, self.Inner.Min.Y+self.Inner.Dy()/2))
} }
} }
@ -327,7 +362,7 @@ func (self *RunChart) trimOutOfRangeValues() {
func (self *RunChart) calculateTimeCoordinate(t time.Time) int { func (self *RunChart) calculateTimeCoordinate(t time.Time) int {
timeDeltaWithGridMaxTime := self.grid.timeRange.max.Sub(t).Nanoseconds() timeDeltaWithGridMaxTime := self.grid.timeRange.max.Sub(t).Nanoseconds()
timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.timescale.Nanoseconds()) timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.timescale.Nanoseconds())
return self.grid.maxTimeWidth - (int(float64(xAxisGridWidth) * timeDeltaToPaddingRelation)) return self.grid.maxTimeWidth - int(math.Ceil(float64(xAxisGridWidth)*timeDeltaToPaddingRelation))
} }
func (self *RunChart) getMaxValueLength() int { func (self *RunChart) getMaxValueLength() int {
@ -346,15 +381,47 @@ func (self *RunChart) getMaxValueLength() int {
return maxValueLength return maxValueLength
} }
func (self *RunChart) MoveSelection(shift int) {
if self.scrollMode == Auto {
self.scrollMode = Manual
self.selection = getMidRangeTime(self.grid.timeRange)
return
} else {
self.selection = self.selection.Add(self.grid.timePerPoint * time.Duration(shift))
if self.selection.After(self.grid.timeRange.max) {
self.selection = self.grid.timeRange.max
} else if self.selection.Before(self.grid.timeRange.min) {
self.selection = self.grid.timeRange.min
}
}
for i := range self.lines {
self.lines[i].selection = 0
}
}
func (self *RunChart) DisableSelection() {
if self.scrollMode == Manual {
self.scrollMode = Auto
return
}
}
func getMidRangeTime(r TimeRange) time.Time {
delta := r.max.Sub(r.min)
return r.max.Add(-delta / 2)
}
func formatValue(value float64, precision int) string { func formatValue(value float64, precision int) string {
format := "%." + strconv.Itoa(precision) + "f" format := "%." + strconv.Itoa(precision) + "f"
return fmt.Sprintf(format, value) return fmt.Sprintf(format, value)
} }
func getValueExtrema(items []timeLine, timeRange timeRange) valueExtrema { func getValueExtrema(items []TimeLine, timeRange TimeRange) ValueExtrema {
if len(items) == 0 { if len(items) == 0 {
return valueExtrema{0, 0} return ValueExtrema{0, 0}
} }
var max, min = -math.MaxFloat64, math.MaxFloat64 var max, min = -math.MaxFloat64, math.MaxFloat64
@ -370,17 +437,17 @@ func getValueExtrema(items []timeLine, timeRange timeRange) valueExtrema {
} }
} }
return valueExtrema{max: max, min: min} return ValueExtrema{max: max, min: min}
} }
func (r *timeRange) isInRange(time time.Time) bool { func (r *TimeRange) isInRange(time time.Time) bool {
return time.After(r.min) && time.Before(r.max) return time.After(r.min) && time.Before(r.max)
} }
func getLineValueExtremum(points []timePoint) valueExtrema { func getLineValueExtremum(points []TimePoint) ValueExtrema {
if len(points) == 0 { if len(points) == 0 {
return valueExtrema{0, 0} return ValueExtrema{0, 0}
} }
var max, min = -math.MaxFloat64, math.MaxFloat64 var max, min = -math.MaxFloat64, math.MaxFloat64
@ -394,17 +461,25 @@ func getLineValueExtremum(points []timePoint) valueExtrema {
} }
} }
return valueExtrema{max: max, min: min} return ValueExtrema{max: max, min: min}
} }
func getTimeRange(linesCount int, scale time.Duration) timeRange { func (self *RunChart) getTimeRange(linesCount int) TimeRange {
maxTime := time.Now()
return timeRange{ if self.scrollMode == Manual {
max: maxTime, return self.grid.timeRange
min: maxTime.Add(-time.Duration(scale.Nanoseconds() * int64(linesCount))), }
width := time.Duration(self.timescale.Nanoseconds() * int64(linesCount))
max := time.Now()
return TimeRange{
max: max,
min: max.Add(-width),
} }
} }
// time duration between grid lines
func calculateTimescale(refreshRateMs int) time.Duration { func calculateTimescale(refreshRateMs int) time.Duration {
multiplier := refreshRateMs * xAxisGridWidth / 2 multiplier := refreshRateMs * xAxisGridWidth / 2