• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mlange-42 / ark-pixel / 13725782068

07 Mar 2025 05:16PM CUT coverage: 28.089% (-19.3%) from 47.374%
13725782068

Pull #8

github

web-flow
Merge c22a6e346 into 2253cb584
Pull Request #8: Add first plot drawers

7 of 677 new or added lines in 11 files covered. (1.03%)

457 of 1627 relevant lines covered (28.09%)

55.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/plot/time_series.go
1
package plot
2

3
import (
4
        "fmt"
5
        "image/color"
6

7
        pixel "github.com/gopxl/pixel/v2"
8
        "github.com/gopxl/pixel/v2/backends/opengl"
9
        "github.com/mlange-42/ark-tools/observer"
10
        "github.com/mlange-42/ark/ecs"
11
        "gonum.org/v1/plot"
12
        "gonum.org/v1/plot/plotter"
13
        "gonum.org/v1/plot/vg"
14
        "gonum.org/v1/plot/vg/draw"
15
        "gonum.org/v1/plot/vg/vgimg"
16
)
17

18
// TimeSeries plot drawer.
19
//
20
// Creates a line series per column of the observer.
21
// Adds one row to the data per update.
22
type TimeSeries struct {
23
        Observer       observer.Row // Observer providing a data row per update.
24
        Columns        []string     // Columns to show, by name. Optional, default all.
25
        UpdateInterval int          // Interval for getting data from the the observer, in model ticks. Optional.
26
        Labels         Labels       // Labels for plot and axes. Optional.
27
        MaxRows        int          // Maximum number of rows to keep. Zero means unlimited. Optional.
28

29
        indices []int
30
        headers []string
31
        series  []plotter.XYs
32
        scale   float64
33
        step    int64
34
}
35

36
// append a y value to each series, with a common x value.
NEW
37
func (t *TimeSeries) append(x float64, values []float64) {
×
NEW
38
        for i := 0; i < len(t.series); i++ {
×
NEW
39
                t.series[i] = append(t.series[i], plotter.XY{X: x, Y: values[i]})
×
NEW
40
                if t.MaxRows > 0 && len(t.series[i]) > t.MaxRows {
×
NEW
41
                        t.series[i] = t.series[i][len(t.series[i])-t.MaxRows:]
×
NEW
42
                }
×
43
        }
44
}
45

46
// Initialize the drawer.
NEW
47
func (t *TimeSeries) Initialize(w *ecs.World, win *opengl.Window) {
×
NEW
48
        t.Observer.Initialize(w)
×
NEW
49

×
NEW
50
        t.headers = t.Observer.Header()
×
NEW
51

×
NEW
52
        if len(t.Columns) == 0 {
×
NEW
53
                t.indices = make([]int, len(t.headers))
×
NEW
54
                for i := 0; i < len(t.indices); i++ {
×
NEW
55
                        t.indices[i] = i
×
NEW
56
                }
×
NEW
57
        } else {
×
NEW
58
                t.indices = make([]int, len(t.Columns))
×
NEW
59
                var ok bool
×
NEW
60
                for i := 0; i < len(t.indices); i++ {
×
NEW
61
                        t.indices[i], ok = find(t.headers, t.Columns[i])
×
NEW
62
                        if !ok {
×
NEW
63
                                panic(fmt.Sprintf("column '%s' not found", t.Columns[i]))
×
64
                        }
65
                }
66
        }
67

NEW
68
        t.series = make([]plotter.XYs, len(t.headers))
×
NEW
69

×
NEW
70
        t.scale = calcScaleCorrection()
×
NEW
71
        t.step = 0
×
72
}
73

74
// Update the drawer.
NEW
75
func (t *TimeSeries) Update(w *ecs.World) {
×
NEW
76
        t.Observer.Update(w)
×
NEW
77
        if t.UpdateInterval <= 1 || t.step%int64(t.UpdateInterval) == 0 {
×
NEW
78
                t.append(float64(t.step), t.Observer.Values(w))
×
NEW
79
        }
×
NEW
80
        t.step++
×
81
}
82

83
// UpdateInputs handles input events of the previous frame update.
NEW
84
func (t *TimeSeries) UpdateInputs(w *ecs.World, win *opengl.Window) {}
×
85

86
// Draw the drawer.
NEW
87
func (t *TimeSeries) Draw(w *ecs.World, win *opengl.Window) {
×
NEW
88
        width := win.Canvas().Bounds().W()
×
NEW
89
        height := win.Canvas().Bounds().H()
×
NEW
90

×
NEW
91
        c := vgimg.New(vg.Points(width*t.scale)-10, vg.Points(height*t.scale)-10)
×
NEW
92

×
NEW
93
        p := plot.New()
×
NEW
94
        setLabels(p, t.Labels)
×
NEW
95

×
NEW
96
        p.X.Tick.Marker = removeLastTicks{}
×
NEW
97

×
NEW
98
        p.Legend = plot.NewLegend()
×
NEW
99
        p.Legend.TextStyle.Font.Variant = "Mono"
×
NEW
100

×
NEW
101
        for i, idx := range t.indices {
×
NEW
102
                lines, err := plotter.NewLine(t.series[idx])
×
NEW
103
                if err != nil {
×
NEW
104
                        panic(err)
×
105
                }
NEW
106
                lines.Color = defaultColors[i%len(defaultColors)]
×
NEW
107
                p.Add(lines)
×
NEW
108
                p.Legend.Add(t.headers[idx], lines)
×
109
        }
110

NEW
111
        win.Clear(color.White)
×
NEW
112
        p.Draw(draw.New(c))
×
NEW
113

×
NEW
114
        img := c.Image()
×
NEW
115
        picture := pixel.PictureDataFromImage(img)
×
NEW
116

×
NEW
117
        sprite := pixel.NewSprite(picture, picture.Bounds())
×
NEW
118
        sprite.Draw(win, pixel.IM.Moved(pixel.V(picture.Rect.W()/2.0+5, picture.Rect.H()/2.0+5)))
×
119
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc