added mouse selection handling
This commit is contained in:
parent
41abf40647
commit
d713d5be0a
|
@ -8,7 +8,7 @@ run-charts:
|
||||||
script: curl -o /dev/null -s -w '%{time_total}' http://yahoo.com
|
script: curl -o /dev/null -s -w '%{time_total}' http://yahoo.com
|
||||||
- label: YANDEX
|
- label: YANDEX
|
||||||
script: curl -o /dev/null -s -w '%{time_total}' http://yandex.com
|
script: curl -o /dev/null -s -w '%{time_total}' http://yandex.com
|
||||||
refresh-rate-ms: 300
|
refresh-rate-ms: 200 # TODO consider remove time-scale-sec property, and adjust it automatically based on refresh-rate-ms
|
||||||
time-scale-sec: 1
|
time-scale-sec: 1
|
||||||
decimal-places: 1
|
decimal-places: 1
|
||||||
legend:
|
legend:
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RenderRate = 30 * time.Millisecond
|
RenderRate = 50 * time.Millisecond // TODO not a constant, should be dynamically chosen based on min X scale (per each chart? should be tested). if it is 1 sec, it should be 100 ms, if 2 - 200 ms, if 3 - 300, 4 - 400, 5 - 500 and 500 is max. smth like that.
|
||||||
Title = "sampler"
|
Title = "sampler"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
|
import . "github.com/sqshq/termui"
|
||||||
|
|
||||||
type Consumer interface {
|
type Consumer interface {
|
||||||
ConsumeValue(item Item, value string)
|
ConsumeSample(sample Sample)
|
||||||
ConsumeError(item Item, err error)
|
}
|
||||||
|
|
||||||
|
type Sample struct {
|
||||||
|
Label string
|
||||||
|
Color Color
|
||||||
|
Value string
|
||||||
|
Error error
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
ui "github.com/sqshq/termui"
|
. "github.com/sqshq/termui"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Item struct {
|
type Item struct {
|
||||||
Script string `yaml:"script"`
|
Script string `yaml:"script"`
|
||||||
Label string `yaml:"label"`
|
Label string `yaml:"label"`
|
||||||
Color ui.Color `yaml:"color"`
|
Color Color `yaml:"color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Item) nextValue() (value string, err error) {
|
func (self *Item) nextValue() (value string, err error) {
|
||||||
|
|
|
@ -30,9 +30,12 @@ func (self *Poller) poll() {
|
||||||
|
|
||||||
value, err := self.item.nextValue()
|
value, err := self.item.nextValue()
|
||||||
|
|
||||||
if err != nil {
|
sample := Sample{
|
||||||
self.consumer.ConsumeError(self.item, err)
|
Value: value,
|
||||||
|
Error: err,
|
||||||
|
Color: self.item.Color,
|
||||||
|
Label: self.item.Label,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.consumer.ConsumeValue(self.item, value)
|
self.consumer.ConsumeSample(sample)
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,6 @@ func (self *Handler) HandleEvents() {
|
||||||
func (self *Handler) handleMouseClick(x, y int) {
|
func (self *Handler) handleMouseClick(x, y int) {
|
||||||
for _, chart := range self.Layout.GetComponents(widgets.TypeRunChart) {
|
for _, chart := range self.Layout.GetComponents(widgets.TypeRunChart) {
|
||||||
runChart := chart.(*widgets.RunChart)
|
runChart := chart.(*widgets.RunChart)
|
||||||
runChart.SelectValue(x, y)
|
runChart.SelectPoint(x, y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ func (self *Layout) ChangeDimensions(width, height int) {
|
||||||
self.SetRect(0, 0, width, height)
|
self.SetRect(0, 0, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Layout) Draw(buf *Buffer) {
|
func (self *Layout) Draw(buffer *Buffer) {
|
||||||
|
|
||||||
columnWidth := float64(self.GetRect().Dx()) / columnsCount
|
columnWidth := float64(self.GetRect().Dx()) / columnsCount
|
||||||
rowHeight := float64(self.GetRect().Dy()) / rowsCount
|
rowHeight := float64(self.GetRect().Dy()) / rowsCount
|
||||||
|
@ -59,6 +59,6 @@ func (self *Layout) Draw(buf *Buffer) {
|
||||||
y2 := y1 + float64(component.Size.Y)*rowHeight
|
y2 := y1 + float64(component.Size.Y)*rowHeight
|
||||||
|
|
||||||
component.Drawable.SetRect(int(x1), int(y1), int(x2), int(y2))
|
component.Drawable.SetRect(int(x1), int(y1), int(x2), int(y2))
|
||||||
component.Drawable.Draw(buf)
|
component.Drawable.Draw(buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
xAxisLabelsHeight = 1
|
chartHistoryReserve = 10
|
||||||
xAxisLabelsWidth = 8
|
xAxisLabelsHeight = 1
|
||||||
xAxisLabelsGap = 2
|
xAxisLabelsWidth = 8
|
||||||
yAxisLabelsHeight = 1
|
xAxisLabelsGap = 2
|
||||||
yAxisLabelsGap = 1
|
yAxisLabelsHeight = 1
|
||||||
xAxisLegendWidth = 15
|
yAxisLabelsGap = 1
|
||||||
|
xAxisLegendWidth = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
type RunChart struct {
|
type RunChart struct {
|
||||||
|
@ -28,18 +29,20 @@ type RunChart struct {
|
||||||
lines []TimeLine
|
lines []TimeLine
|
||||||
grid ChartGrid
|
grid ChartGrid
|
||||||
precision int
|
precision int
|
||||||
selection time.Time
|
selection *time.Time
|
||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type TimePoint struct {
|
type TimePoint struct {
|
||||||
Value float64
|
time time.Time
|
||||||
Time time.Time
|
value float64
|
||||||
|
line *TimeLine
|
||||||
}
|
}
|
||||||
|
|
||||||
type TimeLine struct {
|
type TimeLine struct {
|
||||||
points []TimePoint
|
points []TimePoint
|
||||||
item data.Item
|
color Color
|
||||||
|
label string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChartGrid struct {
|
type ChartGrid struct {
|
||||||
|
@ -69,7 +72,7 @@ func NewRunChart(title string) *RunChart {
|
||||||
Block: block,
|
Block: block,
|
||||||
lines: []TimeLine{},
|
lines: []TimeLine{},
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
precision: 2, // TODO config
|
precision: 2, // TODO move to config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,74 +92,122 @@ func (self *RunChart) newChartGrid() ChartGrid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RunChart) Draw(buf *Buffer) {
|
func (self *RunChart) Draw(buffer *Buffer) {
|
||||||
|
|
||||||
self.mutex.Lock()
|
self.mutex.Lock()
|
||||||
self.Block.Draw(buf)
|
self.Block.Draw(buffer)
|
||||||
self.grid = self.newChartGrid()
|
self.grid = self.newChartGrid()
|
||||||
self.renderAxes(buf)
|
|
||||||
|
|
||||||
drawArea := image.Rect(
|
drawArea := image.Rect(
|
||||||
self.Inner.Min.X+self.grid.minTimeWidth+1, self.Inner.Min.Y,
|
self.Inner.Min.X+self.grid.minTimeWidth+1, self.Inner.Min.Y,
|
||||||
self.Inner.Max.X, self.Inner.Max.Y-xAxisLabelsHeight-1,
|
self.Inner.Max.X, self.Inner.Max.Y-xAxisLabelsHeight-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.renderItems(buf, drawArea)
|
selectedPoints := self.getSelectedTimePoints()
|
||||||
self.renderLegend(buf, drawArea)
|
|
||||||
|
self.renderAxes(buffer)
|
||||||
|
self.renderItems(buffer, drawArea)
|
||||||
|
self.renderSelection(buffer, drawArea, selectedPoints)
|
||||||
|
self.renderLegend(buffer, drawArea, selectedPoints)
|
||||||
self.mutex.Unlock()
|
self.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RunChart) ConsumeValue(item data.Item, value string) {
|
func (self *RunChart) ConsumeSample(sample data.Sample) {
|
||||||
|
|
||||||
|
float, err := strconv.ParseFloat(sample.Value, 64)
|
||||||
|
|
||||||
float, err := strconv.ParseFloat(value, 64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Expected float number, but got %v", value) // TODO visual notification
|
log.Printf("Expected float number, but got %v", sample.Value) // TODO visual notification + check sample.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
timePoint := TimePoint{Value: float, Time: time.Now()}
|
|
||||||
self.mutex.Lock()
|
self.mutex.Lock()
|
||||||
itemExists := false
|
|
||||||
|
lineIndex := -1
|
||||||
|
|
||||||
for i, line := range self.lines {
|
for i, line := range self.lines {
|
||||||
if line.item.Label == item.Label {
|
if line.label == sample.Label {
|
||||||
line.points = append(line.points, timePoint)
|
lineIndex = i
|
||||||
self.lines[i] = line
|
|
||||||
itemExists = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !itemExists {
|
if lineIndex == -1 {
|
||||||
item := &TimeLine{
|
line := &TimeLine{
|
||||||
points: []TimePoint{timePoint},
|
points: []TimePoint{},
|
||||||
item: item,
|
color: sample.Color,
|
||||||
|
label: sample.Label,
|
||||||
}
|
}
|
||||||
self.lines = append(self.lines, *item)
|
self.lines = append(self.lines, *line)
|
||||||
|
lineIndex = len(self.lines) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
line := self.lines[lineIndex]
|
||||||
|
|
||||||
|
timePoint := TimePoint{value: float, time: time.Now(), line: &line}
|
||||||
|
line.points = append(line.points, timePoint)
|
||||||
|
self.lines[lineIndex] = line
|
||||||
|
|
||||||
self.trimOutOfRangeValues()
|
self.trimOutOfRangeValues()
|
||||||
self.mutex.Unlock()
|
self.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RunChart) ConsumeError(item data.Item, err error) {
|
func (self *RunChart) SelectPoint(x int, y int) {
|
||||||
// TODO visual notification
|
|
||||||
|
point := image.Point{X: x, Y: y}
|
||||||
|
|
||||||
|
if !point.In(self.Rectangle) {
|
||||||
|
self.selection = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDeltaToPaddingRelation := (self.grid.maxTimeWidth - x) / self.grid.paddingWidth
|
||||||
|
timeDeltaWithGridMaxTime := timeDeltaToPaddingRelation * int(self.grid.paddingDuration.Nanoseconds())
|
||||||
|
selection := self.grid.timeExtremum.max.Add(-time.Duration(timeDeltaWithGridMaxTime) * time.Nanosecond)
|
||||||
|
|
||||||
|
self.selection = &selection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RunChart) SelectValue(x int, y int) {
|
func (self *RunChart) getSelectedTimePoints() []TimePoint {
|
||||||
// TODO instead of that, find actual time for the given X
|
|
||||||
// + make sure that Y is within the given chart
|
selected := []TimePoint{}
|
||||||
// once ensured, set "selected time" into the chart structure
|
|
||||||
// self.selection = image.Point{X: x, Y: y}
|
if self.selection == nil {
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range self.lines {
|
||||||
|
|
||||||
|
if len(line.points) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
closest := line.points[0]
|
||||||
|
|
||||||
|
for _, point := range line.points {
|
||||||
|
|
||||||
|
diffWithClosest := math.Abs(float64(self.selection.UnixNano() - closest.time.UnixNano()))
|
||||||
|
diffWithCurrent := math.Abs(float64(self.selection.UnixNano() - point.time.UnixNano()))
|
||||||
|
|
||||||
|
if diffWithClosest > diffWithCurrent {
|
||||||
|
closest = point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selected = append(selected, closest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RunChart) trimOutOfRangeValues() {
|
func (self *RunChart) trimOutOfRangeValues() {
|
||||||
|
|
||||||
minRangeTime := self.grid.timeExtremum.min.Add(-self.grid.paddingDuration * 10)
|
historyReserve := self.grid.paddingDuration * time.Duration(self.grid.linesCount) * chartHistoryReserve
|
||||||
|
minRangeTime := self.grid.timeExtremum.min.Add(-historyReserve)
|
||||||
|
|
||||||
for i, item := range self.lines {
|
for i, item := range self.lines {
|
||||||
lastOutOfRangeValueIndex := -1
|
lastOutOfRangeValueIndex := -1
|
||||||
|
|
||||||
for j, point := range item.points {
|
for j, point := range item.points {
|
||||||
if point.Time.Before(minRangeTime) {
|
if point.time.Before(minRangeTime) {
|
||||||
lastOutOfRangeValueIndex = j
|
lastOutOfRangeValueIndex = j
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,9 +229,9 @@ func (self *RunChart) renderItems(buffer *Buffer, drawArea image.Rectangle) {
|
||||||
xToPoint := make(map[int]image.Point)
|
xToPoint := make(map[int]image.Point)
|
||||||
pointsOrder := make([]int, 0)
|
pointsOrder := make([]int, 0)
|
||||||
|
|
||||||
for _, point := range line.points {
|
for _, timePoint := range line.points {
|
||||||
|
|
||||||
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(point.Time).Nanoseconds()
|
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.time).Nanoseconds()
|
||||||
timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.grid.paddingDuration.Nanoseconds())
|
timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.grid.paddingDuration.Nanoseconds())
|
||||||
x := self.grid.maxTimeWidth - (int(float64(self.grid.paddingWidth) * timeDeltaToPaddingRelation))
|
x := self.grid.maxTimeWidth - (int(float64(self.grid.paddingWidth) * timeDeltaToPaddingRelation))
|
||||||
|
|
||||||
|
@ -189,7 +240,7 @@ func (self *RunChart) renderItems(buffer *Buffer, drawArea image.Rectangle) {
|
||||||
y = (drawArea.Dy() - 2) / 2
|
y = (drawArea.Dy() - 2) / 2
|
||||||
} else {
|
} else {
|
||||||
valuePerY := (self.grid.valueExtremum.max - self.grid.valueExtremum.min) / float64(drawArea.Dy()-2)
|
valuePerY := (self.grid.valueExtremum.max - self.grid.valueExtremum.min) / float64(drawArea.Dy()-2)
|
||||||
y = int(float64(point.Value-self.grid.valueExtremum.min) / valuePerY)
|
y = int(float64(timePoint.value-self.grid.valueExtremum.min) / valuePerY)
|
||||||
}
|
}
|
||||||
|
|
||||||
point := image.Pt(x, drawArea.Max.Y-y-1)
|
point := image.Pt(x, drawArea.Max.Y-y-1)
|
||||||
|
@ -220,19 +271,9 @@ func (self *RunChart) renderItems(buffer *Buffer, drawArea image.Rectangle) {
|
||||||
canvas.Line(
|
canvas.Line(
|
||||||
braillePoint(previousPoint),
|
braillePoint(previousPoint),
|
||||||
braillePoint(currentPoint),
|
braillePoint(currentPoint),
|
||||||
line.item.Color,
|
line.color,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//if point, exists := xToPoint[self.selection.X]; exists {
|
|
||||||
// buffer.SetCell(
|
|
||||||
// NewCell(DOT, NewStyle(line.item.Color)),
|
|
||||||
// point,
|
|
||||||
// )
|
|
||||||
// log.Printf("EXIST!")
|
|
||||||
//} else {
|
|
||||||
// //log.Printf("DOES NOT EXIST")
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.Draw(buffer)
|
canvas.Draw(buffer)
|
||||||
|
@ -301,36 +342,86 @@ func (self *RunChart) renderAxes(buffer *Buffer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RunChart) renderLegend(buffer *Buffer, rectangle image.Rectangle) {
|
func (self *RunChart) renderLegend(buffer *Buffer, rectangle image.Rectangle, selectedPoints []TimePoint) {
|
||||||
for i, line := range self.lines {
|
|
||||||
|
|
||||||
extremum := GetLineValueExtremum(line.points)
|
for i, line := range self.lines {
|
||||||
|
|
||||||
buffer.SetString(
|
buffer.SetString(
|
||||||
string(DOT),
|
string(DOT),
|
||||||
NewStyle(line.item.Color),
|
NewStyle(line.color),
|
||||||
image.Pt(self.Inner.Max.X-xAxisLegendWidth-2, self.Inner.Min.Y+1+i*5),
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth-2, self.Inner.Min.Y+1+i*5),
|
||||||
)
|
)
|
||||||
buffer.SetString(
|
buffer.SetString(
|
||||||
fmt.Sprintf("%s", line.item.Label),
|
fmt.Sprintf("%s", line.label),
|
||||||
NewStyle(line.item.Color),
|
NewStyle(line.color),
|
||||||
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+1+i*5),
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+1+i*5),
|
||||||
)
|
)
|
||||||
buffer.SetString(
|
|
||||||
fmt.Sprintf("cur %s", formatValue(line.points[len(line.points)-1].Value, self.precision)),
|
if len(selectedPoints) > 0 {
|
||||||
NewStyle(ColorWhite),
|
|
||||||
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+2+i*5),
|
index := -1
|
||||||
)
|
|
||||||
buffer.SetString(
|
for i, p := range selectedPoints {
|
||||||
fmt.Sprintf("max %s", formatValue(extremum.max, self.precision)),
|
if p.line.label == line.label {
|
||||||
NewStyle(ColorWhite),
|
index = i
|
||||||
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+3+i*5),
|
}
|
||||||
)
|
}
|
||||||
buffer.SetString(
|
|
||||||
fmt.Sprintf("min %s", formatValue(extremum.min, self.precision)),
|
if index != -1 {
|
||||||
NewStyle(ColorWhite),
|
buffer.SetString(
|
||||||
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+4+i*5),
|
fmt.Sprintf("time: %v", selectedPoints[index].time.Format("15:04:05.000")),
|
||||||
)
|
NewStyle(ColorWhite),
|
||||||
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+2+i*5),
|
||||||
|
)
|
||||||
|
buffer.SetString(
|
||||||
|
fmt.Sprintf("value: %s", formatValue(selectedPoints[index].value, self.precision)),
|
||||||
|
NewStyle(ColorWhite),
|
||||||
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+3+i*5),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
extremum := GetLineValueExtremum(line.points)
|
||||||
|
|
||||||
|
buffer.SetString(
|
||||||
|
fmt.Sprintf("cur %s", formatValue(line.points[len(line.points)-1].value, self.precision)),
|
||||||
|
NewStyle(ColorWhite),
|
||||||
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+2+i*5),
|
||||||
|
)
|
||||||
|
buffer.SetString(
|
||||||
|
fmt.Sprintf("max %s", formatValue(extremum.max, self.precision)),
|
||||||
|
NewStyle(ColorWhite),
|
||||||
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+3+i*5),
|
||||||
|
)
|
||||||
|
buffer.SetString(
|
||||||
|
fmt.Sprintf("min %s", formatValue(extremum.min, self.precision)),
|
||||||
|
NewStyle(ColorWhite),
|
||||||
|
image.Pt(self.Inner.Max.X-xAxisLegendWidth, self.Inner.Min.Y+4+i*5),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RunChart) renderSelection(buffer *Buffer, drawArea image.Rectangle, selectedPoints []TimePoint) {
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
var y int
|
||||||
|
if self.grid.valueExtremum.max-self.grid.valueExtremum.min == 0 {
|
||||||
|
y = (drawArea.Dy() - 2) / 2
|
||||||
|
} else {
|
||||||
|
valuePerY := (self.grid.valueExtremum.max - self.grid.valueExtremum.min) / float64(drawArea.Dy()-2)
|
||||||
|
y = int(float64(timePoint.value-self.grid.valueExtremum.min) / valuePerY)
|
||||||
|
}
|
||||||
|
|
||||||
|
point := image.Pt(x, drawArea.Max.Y-y-1)
|
||||||
|
|
||||||
|
if point.In(drawArea) {
|
||||||
|
buffer.SetCell(NewCell('▲', NewStyle(timePoint.line.color)), point)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +431,7 @@ func (self *RunChart) getMaxValueLength() int {
|
||||||
|
|
||||||
for _, line := range self.lines {
|
for _, line := range self.lines {
|
||||||
for _, point := range line.points {
|
for _, point := range line.points {
|
||||||
l := len(formatValue(point.Value, self.precision))
|
l := len(formatValue(point.value, self.precision))
|
||||||
if l > maxValueLength {
|
if l > maxValueLength {
|
||||||
maxValueLength = l
|
maxValueLength = l
|
||||||
}
|
}
|
||||||
|
@ -351,7 +442,7 @@ func (self *RunChart) getMaxValueLength() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,11 +456,11 @@ func GetChartValueExtremum(items []TimeLine) ValueExtremum {
|
||||||
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
for _, point := range item.points {
|
for _, point := range item.points {
|
||||||
if point.Value > max {
|
if point.value > max {
|
||||||
max = point.Value
|
max = point.value
|
||||||
}
|
}
|
||||||
if point.Value < min {
|
if point.value < min {
|
||||||
min = point.Value
|
min = point.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,11 +477,11 @@ func GetLineValueExtremum(points []TimePoint) ValueExtremum {
|
||||||
var max, min = -math.MaxFloat64, math.MaxFloat64
|
var max, min = -math.MaxFloat64, math.MaxFloat64
|
||||||
|
|
||||||
for _, point := range points {
|
for _, point := range points {
|
||||||
if point.Value > max {
|
if point.value > max {
|
||||||
max = point.Value
|
max = point.value
|
||||||
}
|
}
|
||||||
if point.Value < min {
|
if point.value < min {
|
||||||
min = point.Value
|
min = point.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue