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

mlange-42 / arche-pixel / 6789378614

07 Nov 2023 07:28PM CUT coverage: 86.552% (+3.0%) from 83.59%
6789378614

Pull #46

github

web-flow
Merge f22905710 into 318673dd0
Pull Request #46: Add more tests for plots and window

1403 of 1621 relevant lines covered (86.55%)

212408.88 hits per line

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

84.69
/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/arche-model/observer"
10
        "github.com/mlange-42/arche/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) {
1✔
40
        numObs := len(s.Observers)
1✔
41
        if len(s.X) != 0 && len(s.X) != numObs {
1✔
42
                panic("length of X not equal to length of Observers")
×
43
        }
44
        if len(s.Y) != 0 && len(s.Y) != numObs {
1✔
45
                panic("length of Y not equal to length of Observers")
×
46
        }
47

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

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

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

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

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

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

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

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

100✔
113
        if s.XLim[0] != 0 || s.XLim[1] != 0 {
100✔
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 {
100✔
118
                p.Y.Min = s.YLim[0]
×
119
                p.Y.Max = s.YLim[1]
×
120
        }
×
121

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

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

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

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

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

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

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