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/
- label: BING
script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/
refresh-rate-ms: 200
refresh-rate-ms: 500
decimal-places: 3
alert:
value:
@ -25,7 +25,7 @@ runcharts:
x: 0
y: 0
size:
x: 15
x: 30
y: 15
- title: SEARCH ENGINE RESPONSE TIME 2 (sec)
items:
@ -60,7 +60,7 @@ runcharts:
script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()"
position:
x: 15
y: 0
y: 15
size:
x: 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:
payload := e.Payload.(ui.Resize)
self.Layout.ChangeDimensions(payload.Width, payload.Height)
case EventMouseClick:
//payload := e.Payload.(ui.Mouse)
case "a":
self.Layout.GetComponent(0).DisableSelection()
case EventKeyboardLeft:
// here we are going to move selection (special type of layout item)
//layout.GetItem("").Move(-1, 0)
self.Layout.GetComponent(0).MoveSelection(-1)
case EventKeyboardRight:
//layout.GetItem(0).Move(1, 0)
self.Layout.GetComponent(0).MoveSelection(+1)
case EventKeyboardDown:
//layout.GetItem(0).Move(0, 1)
case EventKeyboardUp:

View File

@ -42,6 +42,11 @@ func (self *Layout) GetComponents(Type ComponentType) []Drawable {
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) {
self.SetRect(0, 0, width, height)
}

View File

@ -20,49 +20,56 @@ const (
xAxisLabelsWidth = 8
xAxisLabelsGap = 2
xAxisGridWidth = xAxisLabelsGap + xAxisLabelsWidth
yAxisLabelsHeight = 1
yAxisLabelsGap = 1
)
chartHistoryReserve = 5
type ScrollMode int
const (
Auto ScrollMode = 0
Manual ScrollMode = 1
)
type RunChart struct {
ui.Block
lines []timeLine
grid chartGrid
precision int
timescale time.Duration
mutex *sync.Mutex
lines []TimeLine
grid ChartGrid
timescale time.Duration
mutex *sync.Mutex
scrollMode ScrollMode
selection time.Time
precision int
}
type chartGrid struct {
valueExtrema valueExtrema
timeRange timeRange
type ChartGrid struct {
timeRange TimeRange
timePerPoint time.Duration
valueExtrema ValueExtrema
linesCount int
paddingWidth int
maxTimeWidth int
minTimeWidth int
}
type timePoint struct {
value float64
time time.Time
timeCoordinate int
type TimePoint struct {
value float64
time time.Time
coordinate int
}
type timeLine struct {
points []timePoint
color ui.Color
label string
type TimeLine struct {
points []TimePoint
color ui.Color
label string
selection int
}
type timeRange struct {
type TimeRange struct {
max time.Time
min time.Time
}
type valueExtrema struct {
type ValueExtrema struct {
max float64
min float64
}
@ -71,35 +78,36 @@ func NewRunChart(title string, precision int, refreshRateMs int) *RunChart {
block := *ui.NewBlock()
block.Title = title
return &RunChart{
Block: block,
lines: []timeLine{},
mutex: &sync.Mutex{},
precision: precision,
timescale: calculateTimescale(refreshRateMs),
Block: block,
lines: []TimeLine{},
timescale: calculateTimescale(refreshRateMs),
mutex: &sync.Mutex{},
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
timeRange := getTimeRange(linesCount, self.timescale)
timeRange := self.getTimeRange(linesCount)
return chartGrid{
return ChartGrid{
timeRange: timeRange,
timePerPoint: self.timescale / time.Duration(xAxisGridWidth),
valueExtrema: getValueExtrema(self.lines, timeRange),
linesCount: linesCount,
paddingWidth: xAxisGridWidth,
maxTimeWidth: self.Inner.Max.X,
minTimeWidth: self.getMaxValueLength(),
}
}
func (self *RunChart) newTimePoint(value float64) timePoint {
func (self *RunChart) newTimePoint(value float64) TimePoint {
now := time.Now()
return timePoint{
value: value,
time: now,
timeCoordinate: self.calculateTimeCoordinate(now),
return TimePoint{
value: value,
time: now,
coordinate: self.calculateTimeCoordinate(now),
}
}
@ -139,8 +147,8 @@ func (self *RunChart) ConsumeSample(sample data.Sample) {
}
if lineIndex == -1 {
line := &timeLine{
points: []timePoint{},
line := &TimeLine{
points: []TimePoint{},
color: sample.Color,
label: sample.Label,
}
@ -166,18 +174,26 @@ func (self *RunChart) renderLines(buffer *ui.Buffer, drawArea image.Rectangle) {
return
}
selectionCoordinate := self.calculateTimeCoordinate(self.selection)
selectionPoints := make(map[int]image.Point)
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)
pointsOrder := make([]int, 0)
xPoint := make(map[int]image.Point)
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
line.points[i] = timePoint
for j, timePoint := range line.points {
timePoint.coordinate -= delta
line.points[j] = timePoint
var y int
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)
}
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
}
@ -197,19 +213,29 @@ func (self *RunChart) renderLines(buffer *ui.Buffer, drawArea image.Rectangle) {
continue
}
xToPoint[point.X] = point
pointsOrder = append(pointsOrder, point.X)
if line.selection == 0 {
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
if i == 0 {
previousPoint = currentPoint
} else {
previousPoint = xToPoint[pointsOrder[i-1]]
previousPoint = xPoint[xOrder[i-1]]
}
canvas.Line(
@ -221,6 +247,15 @@ func (self *RunChart) renderLines(buffer *ui.Buffer, drawArea image.Rectangle) {
}
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) {
@ -282,7 +317,7 @@ func (self *RunChart) renderAxes(buffer *ui.Buffer) {
buffer.SetString(
formatValue(self.grid.valueExtrema.max, self.precision),
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 {
timeDeltaWithGridMaxTime := self.grid.timeRange.max.Sub(t).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 {
@ -346,15 +381,47 @@ func (self *RunChart) getMaxValueLength() int {
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 {
format := "%." + strconv.Itoa(precision) + "f"
return fmt.Sprintf(format, value)
}
func getValueExtrema(items []timeLine, timeRange timeRange) valueExtrema {
func getValueExtrema(items []TimeLine, timeRange TimeRange) ValueExtrema {
if len(items) == 0 {
return valueExtrema{0, 0}
return ValueExtrema{0, 0}
}
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)
}
func getLineValueExtremum(points []timePoint) valueExtrema {
func getLineValueExtremum(points []TimePoint) ValueExtrema {
if len(points) == 0 {
return valueExtrema{0, 0}
return ValueExtrema{0, 0}
}
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 {
maxTime := time.Now()
return timeRange{
max: maxTime,
min: maxTime.Add(-time.Duration(scale.Nanoseconds() * int64(linesCount))),
func (self *RunChart) getTimeRange(linesCount int) TimeRange {
if self.scrollMode == Manual {
return self.grid.timeRange
}
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 {
multiplier := refreshRateMs * xAxisGridWidth / 2