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

mlange-42 / ark-pixel / 13728789674

07 Mar 2025 08:31PM CUT coverage: 89.367% (+61.3%) from 28.089%
13728789674

push

github

web-flow
Re-activate unit tests (#9)

1454 of 1627 relevant lines covered (89.37%)

222405.48 hits per line

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

92.86
/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.
39
func (s *Scatter) Initialize(w *ecs.World, win *opengl.Window) {
6✔
40
        numObs := len(s.Observers)
6✔
41
        if len(s.X) != 0 && len(s.X) != numObs {
7✔
42
                panic("length of X not equal to length of Observers")
1✔
43
        }
44
        if len(s.Y) != 0 && len(s.Y) != numObs {
6✔
45
                panic("length of Y not equal to length of Observers")
1✔
46
        }
47

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

83
                }
84
        }
85

86
        s.scale = calcScaleCorrection()
2✔
87
}
88

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

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

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

111✔
104
        s.updateData(w)
111✔
105

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

111✔
108
        p := plot.New()
111✔
109
        setLabels(p, s.Labels)
111✔
110

111✔
111
        p.X.Tick.Marker = removeLastTicks{}
111✔
112

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

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

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

141
        win.Clear(color.White)
111✔
142
        p.Draw(draw.New(c))
111✔
143

111✔
144
        img := c.Image()
111✔
145
        picture := pixel.PictureDataFromImage(img)
111✔
146

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

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

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