implemented pinpoint mode (based on this, scroll feature may be considered later)
This commit is contained in:
parent
ddec6de927
commit
f5ccf6c464
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package console
|
||||||
|
|
||||||
|
const (
|
||||||
|
SymbolSelection rune = '▲'
|
||||||
|
)
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue