• 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/scatter.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
// Scatter plot drawer.
19
//
20
// Creates a scatter plot from multiple observers.
21
// Supports multiple series per observer. The series in a particular observer must share a common X column.
22
type Scatter struct {
23
        Observers []observer.Table // Observers providing XY data series.
24
        X         []string         // X column name per observer. Optional. Defaults to first column. Empty strings also falls back to the default.
25
        Y         [][]string       // Y column names per observer. Optional. Defaults to second column. Empty strings also falls back to the default.
26
        XLim      [2]float64       // X axis limits. Optional, default auto.
27
        YLim      [2]float64       // Y axis limits. Optional, default auto.
28
        Labels    Labels           // Labels for plot and axes. Optional.
29

30
        xIndices []int
31
        yIndices [][]int
32
        labels   [][]string
33

34
        series [][]plotter.XYs
35
        scale  float64
36
}
37

38
// Initialize the drawer.
NEW
39
func (s *Scatter) Initialize(w *ecs.World, win *opengl.Window) {
×
NEW
40
        numObs := len(s.Observers)
×
NEW
41
        if len(s.X) != 0 && len(s.X) != numObs {
×
NEW
42
                panic("length of X not equal to length of Observers")
×
43
        }
NEW
44
        if len(s.Y) != 0 && len(s.Y) != numObs {
×
NEW
45
                panic("length of Y not equal to length of Observers")
×
46
        }
47

NEW
48
        s.xIndices = make([]int, numObs)
×
NEW
49
        s.yIndices = make([][]int, numObs)
×
NEW
50
        s.labels = make([][]string, numObs)
×
NEW
51
        s.series = make([][]plotter.XYs, numObs)
×
NEW
52
        var ok bool
×
NEW
53
        for i := 0; i < numObs; i++ {
×
NEW
54
                obs := s.Observers[i]
×
NEW
55
                obs.Initialize(w)
×
NEW
56
                header := obs.Header()
×
NEW
57
                if len(s.X) == 0 || s.X[i] == "" {
×
NEW
58
                        s.xIndices[i] = 0
×
NEW
59
                } else {
×
NEW
60
                        s.xIndices[i], ok = find(header, s.X[i])
×
NEW
61
                        if !ok {
×
NEW
62
                                panic(fmt.Sprintf("x column '%s' not found", s.X[i]))
×
63
                        }
64
                }
NEW
65
                if len(s.Y) == 0 || len(s.Y[i]) == 0 {
×
NEW
66
                        s.yIndices[i] = []int{1}
×
NEW
67
                        s.labels[i] = []string{header[1]}
×
NEW
68
                        s.series[i] = make([]plotter.XYs, 1)
×
NEW
69
                } else {
×
NEW
70
                        numY := len(s.Y[i])
×
NEW
71
                        s.yIndices[i] = make([]int, numY)
×
NEW
72
                        s.labels[i] = make([]string, numY)
×
NEW
73
                        s.series[i] = make([]plotter.XYs, numY)
×
NEW
74
                        for j, y := range s.Y[i] {
×
NEW
75
                                idx, ok := find(header, y)
×
NEW
76
                                if !ok {
×
NEW
77
                                        panic(fmt.Sprintf("y column '%s' not found", y))
×
78
                                }
NEW
79
                                s.yIndices[i][j] = idx
×
NEW
80
                                s.labels[i][j] = header[idx]
×
81
                        }
82

83
                }
84
        }
85

NEW
86
        s.scale = calcScaleCorrection()
×
87
}
88

89
// Update the drawer.
NEW
90
func (s *Scatter) Update(w *ecs.World) {
×
NEW
91
        for _, obs := range s.Observers {
×
NEW
92
                obs.Update(w)
×
NEW
93
        }
×
94
}
95

96
// UpdateInputs handles input events of the previous frame update.
NEW
97
func (s *Scatter) UpdateInputs(w *ecs.World, win *opengl.Window) {}
×
98

99
// Draw the drawer.
NEW
100
func (s *Scatter) Draw(w *ecs.World, win *opengl.Window) {
×
NEW
101
        width := win.Canvas().Bounds().W()
×
NEW
102
        height := win.Canvas().Bounds().H()
×
NEW
103

×
NEW
104
        s.updateData(w)
×
NEW
105

×
NEW
106
        c := vgimg.New(vg.Points(width*s.scale)-10, vg.Points(height*s.scale)-10)
×
NEW
107

×
NEW
108
        p := plot.New()
×
NEW
109
        setLabels(p, s.Labels)
×
NEW
110

×
NEW
111
        p.X.Tick.Marker = removeLastTicks{}
×
NEW
112

×
NEW
113
        if s.XLim[0] != 0 || s.XLim[1] != 0 {
×
NEW
114
                p.X.Min = s.XLim[0]
×
NEW
115
                p.X.Max = s.XLim[1]
×
NEW
116
        }
×
NEW
117
        if s.YLim[0] != 0 || s.YLim[1] != 0 {
×
NEW
118
                p.Y.Min = s.YLim[0]
×
NEW
119
                p.Y.Max = s.YLim[1]
×
NEW
120
        }
×
121

NEW
122
        p.Legend = plot.NewLegend()
×
NEW
123
        p.Legend.TextStyle.Font.Variant = "Mono"
×
NEW
124

×
NEW
125
        cnt := 0
×
NEW
126
        for i := 0; i < len(s.xIndices); i++ {
×
NEW
127
                ys := s.yIndices[i]
×
NEW
128
                for j := 0; j < len(ys); j++ {
×
NEW
129
                        points, err := plotter.NewScatter(s.series[i][j])
×
NEW
130
                        if err != nil {
×
NEW
131
                                panic(err)
×
132
                        }
NEW
133
                        points.Shape = draw.CircleGlyph{}
×
NEW
134
                        points.Color = defaultColors[cnt%len(defaultColors)]
×
NEW
135
                        p.Add(points)
×
NEW
136
                        p.Legend.Add(s.labels[i][j], points)
×
NEW
137
                        cnt++
×
138
                }
139
        }
140

NEW
141
        win.Clear(color.White)
×
NEW
142
        p.Draw(draw.New(c))
×
NEW
143

×
NEW
144
        img := c.Image()
×
NEW
145
        picture := pixel.PictureDataFromImage(img)
×
NEW
146

×
NEW
147
        sprite := pixel.NewSprite(picture, picture.Bounds())
×
NEW
148
        sprite.Draw(win, pixel.IM.Moved(pixel.V(picture.Rect.W()/2.0+5, picture.Rect.H()/2.0+5)))
×
149
}
150

NEW
151
func (s *Scatter) updateData(w *ecs.World) {
×
NEW
152
        xis := s.xIndices
×
NEW
153

×
NEW
154
        for i := 0; i < len(xis); i++ {
×
NEW
155
                data := s.Observers[i].Values(w)
×
NEW
156
                xi := xis[i]
×
NEW
157
                ys := s.yIndices[i]
×
NEW
158
                for j := 0; j < len(ys); j++ {
×
NEW
159
                        s.series[i][j] = s.series[i][j][:0]
×
NEW
160
                        for _, row := range data {
×
NEW
161
                                s.series[i][j] = append(s.series[i][j], plotter.XY{X: row[xi], Y: row[ys[j]]})
×
NEW
162
                        }
×
163
                }
164
        }
165
}
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