-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathscatter_chart_test.go
476 lines (447 loc) · 47.7 KB
/
scatter_chart_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
package charts
import (
"math/rand"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func makeFullScatterChartOption() ScatterChartOption {
values := [][]float64{
{120, 132, 101, 134, 90, 230, 210},
{220, 182, 191, 234, 290, 330, 310},
{150, 232, 201, 154, 190, 330, 410},
{320, 332, 301, 334, 390, 330, 320},
{820, 932, 901, 934, 1290, 1330, 1320},
}
return ScatterChartOption{
Title: TitleOption{
Text: "Scatter",
},
Padding: NewBoxEqual(10),
XAxis: XAxisOption{
Labels: []string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"},
},
YAxis: make([]YAxisOption, 1),
Legend: LegendOption{
SeriesNames: []string{"Email", "Union Ads", "Video Ads", "Direct", "Search Engine"},
},
SeriesList: NewSeriesListScatter(values),
}
}
func makeBasicScatterChartOption() ScatterChartOption {
values := [][]float64{
{120, 132, 101, 134, 90, 230, 210},
{820, 932, 901, 934, 1290, 1330, 1320},
}
return ScatterChartOption{
Padding: NewBoxEqual(10),
XAxis: XAxisOption{
Labels: []string{"A", "B", "C", "D", "E", "F", "G"},
},
YAxis: make([]YAxisOption, 1),
Legend: LegendOption{
SeriesNames: []string{"1", "2"},
},
SeriesList: NewSeriesListScatter(values),
}
}
func makeMinimalScatterChartOption() ScatterChartOption {
values := [][]float64{
{120, 132, 101, 134, 90, 230, 210},
{820, 932, 901, 934, 1290, 1330, 1320},
}
return ScatterChartOption{
Padding: NewBoxEqual(10),
XAxis: XAxisOption{
Labels: []string{"1", "2", "3", "4", "5", "6", "7"},
Show: Ptr(false),
},
YAxis: []YAxisOption{
{
Show: Ptr(false),
},
},
SeriesList: NewSeriesListScatter(values),
}
}
func makeMinimalMultiValueScatterChartOption() ScatterChartOption {
values := [][][]float64{
{{120, GetNullValue()}, {132}, {101, 20}, {134}, {90, 28}, {230}, {210}},
{{820, GetNullValue()}, {932}, {901, 600}, {934}, {1290}, {1330}, {1320}},
}
return ScatterChartOption{
Padding: NewBoxEqual(10),
XAxis: XAxisOption{
Labels: []string{"1", "2", "3", "4", "5", "6", "7"},
Show: Ptr(false),
},
YAxis: []YAxisOption{
{
Show: Ptr(false),
},
},
SeriesList: NewSeriesListScatterMultiValue(values),
}
}
func generateRandomScatterData(seriesCount int, dataPointCount int, maxVariationPercentage float64) [][][]float64 {
data := make([][][]float64, seriesCount)
for i := 0; i < seriesCount; i++ {
data[i] = make([][]float64, dataPointCount)
}
r := rand.New(rand.NewSource(1))
for i := 0; i < seriesCount; i++ {
for j := 0; j < dataPointCount; j++ {
if j == 0 {
// Set the initial value for the line
data[i][j] = []float64{r.Float64() * 100}
} else {
// Calculate the allowed variation range
variationRange := data[i][j-1][0] * maxVariationPercentage / 100
min := data[i][j-1][0] - variationRange
max := data[i][j-1][0] + variationRange
// Generate a random value within the allowed range
values := []float64{min + r.Float64()*(max-min)}
if j%2 == 0 {
values = append(values, min+r.Float64()*(max-min))
}
if j%10 == 0 {
values = append(values, min+r.Float64()*(max-min))
}
data[i][j] = values
}
}
}
return data
}
func makeDenseScatterChartOption() ScatterChartOption {
const dataPointCount = 100
values := generateRandomScatterData(3, dataPointCount, 10)
xAxisLabels := make([]string, dataPointCount)
for i := 0; i < dataPointCount; i++ {
xAxisLabels[i] = strconv.Itoa(i)
}
return ScatterChartOption{
SeriesList: NewSeriesListScatterMultiValue(values, ScatterSeriesOption{
TrendLine: NewTrendLine(SeriesMarkTypeAverage),
Label: SeriesLabel{
ValueFormatter: func(f float64) string {
return FormatValueHumanizeShort(f, 0, false)
},
},
}),
Padding: NewBoxEqual(20),
Theme: GetTheme(ThemeLight),
YAxis: []YAxisOption{
{
Min: Ptr(0.0), // force min to be zero
Max: Ptr(200.0),
Unit: 10,
LabelSkipCount: 1,
},
},
XAxis: XAxisOption{
Labels: xAxisLabels,
BoundaryGap: Ptr(false),
LabelCount: 10,
LabelRotation: DegreesToRadians(45),
},
}
}
func TestNewScatterChartOptionWithData(t *testing.T) {
t.Parallel()
opt := NewScatterChartOptionWithData([][]float64{
{12, 24},
{24, 48},
})
assert.Len(t, opt.SeriesList, 2)
assert.Equal(t, ChartTypeScatter, opt.SeriesList[0].getType())
assert.Len(t, opt.YAxis, 1)
assert.Equal(t, defaultPadding, opt.Padding)
p := NewPainter(PainterOptions{})
assert.NoError(t, p.ScatterChart(opt))
}
func TestScatterChart(t *testing.T) {
t.Parallel()
tests := []struct {
name string
ignore string // specified if the test is ignored
themed bool
makeOptions func() ScatterChartOption
result string
}{
{
name: "basic_themed",
themed: true,
makeOptions: makeFullScatterChartOption,
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:rgb(40,40,40)\"/><path d=\"M 20 19\nL 50 19\" style=\"stroke-width:3;stroke:rgb(255,100,100);fill:none\"/><circle cx=\"35\" cy=\"19\" r=\"5\" style=\"stroke-width:3;stroke:rgb(255,100,100);fill:rgb(255,100,100)\"/><text x=\"52\" y=\"25\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Email</text><path d=\"M 111 19\nL 141 19\" style=\"stroke-width:3;stroke:rgb(255,210,100);fill:none\"/><circle cx=\"126\" cy=\"19\" r=\"5\" style=\"stroke-width:3;stroke:rgb(255,210,100);fill:rgb(255,210,100)\"/><text x=\"143\" y=\"25\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Union Ads</text><path d=\"M 234 19\nL 264 19\" style=\"stroke-width:3;stroke:rgb(100,180,210);fill:none\"/><circle cx=\"249\" cy=\"19\" r=\"5\" style=\"stroke-width:3;stroke:rgb(100,180,210);fill:rgb(100,180,210)\"/><text x=\"266\" y=\"25\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Video Ads</text><path d=\"M 357 19\nL 387 19\" style=\"stroke-width:3;stroke:rgb(64,160,110);fill:none\"/><circle cx=\"372\" cy=\"19\" r=\"5\" style=\"stroke-width:3;stroke:rgb(64,160,110);fill:rgb(64,160,110)\"/><text x=\"389\" y=\"25\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Direct</text><path d=\"M 450 19\nL 480 19\" style=\"stroke-width:3;stroke:rgb(154,100,180);fill:none\"/><circle cx=\"465\" cy=\"19\" r=\"5\" style=\"stroke-width:3;stroke:rgb(154,100,180);fill:rgb(154,100,180)\"/><text x=\"482\" y=\"25\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Search Engine</text><text x=\"10\" y=\"26\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Scatter</text><text x=\"9\" y=\"52\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.44k</text><text x=\"9\" y=\"87\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.28k</text><text x=\"9\" y=\"122\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.12k</text><text x=\"21\" y=\"157\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">960</text><text x=\"21\" y=\"192\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">800</text><text x=\"21\" y=\"227\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">640</text><text x=\"21\" y=\"262\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">480</text><text x=\"21\" y=\"297\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">320</text><text x=\"21\" y=\"332\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">160</text><text x=\"39\" y=\"368\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">0</text><path d=\"M 54 46\nL 590 46\" style=\"stroke-width:1;stroke:rgb(72,71,83);fill:none\"/><path d=\"M 54 81\nL 590 81\" style=\"stroke-width:1;stroke:rgb(72,71,83);fill:none\"/><path d=\"M 54 116\nL 590 116\" style=\"stroke-width:1;stroke:rgb(72,71,83);fill:none\"/><path d=\"M 54 152\nL 590 152\" style=\"stroke-width:1;stroke:rgb(72,71,83);fill:none\"/><path d=\"M 54 187\nL 590 187\" style=\"stroke-width:1;stroke:rgb(72,71,83);fill:none\"/><path d=\"M 54 222\nL 590 222\" style=\"stroke-width:1;stroke:rgb(72,71,83);fill:none\"/><path d=\"M 54 258\nL 590 258\" style=\"stroke-width:1;stroke:rgb(72,71,83);fill:none\"/><path d=\"M 54 293\nL 590 293\" style=\"stroke-width:1;stroke:rgb(72,71,83);fill:none\"/><path d=\"M 54 328\nL 590 328\" style=\"stroke-width:1;stroke:rgb(72,71,83);fill:none\"/><path d=\"M 58 364\nL 590 364\" style=\"stroke-width:1;stroke:rgb(185,184,206);fill:none\"/><path d=\"M 58 369\nL 58 364\" style=\"stroke-width:1;stroke:rgb(185,184,206);fill:none\"/><path d=\"M 146 369\nL 146 364\" style=\"stroke-width:1;stroke:rgb(185,184,206);fill:none\"/><path d=\"M 235 369\nL 235 364\" style=\"stroke-width:1;stroke:rgb(185,184,206);fill:none\"/><path d=\"M 324 369\nL 324 364\" style=\"stroke-width:1;stroke:rgb(185,184,206);fill:none\"/><path d=\"M 412 369\nL 412 364\" style=\"stroke-width:1;stroke:rgb(185,184,206);fill:none\"/><path d=\"M 501 369\nL 501 364\" style=\"stroke-width:1;stroke:rgb(185,184,206);fill:none\"/><path d=\"M 590 369\nL 590 364\" style=\"stroke-width:1;stroke:rgb(185,184,206);fill:none\"/><text x=\"57\" y=\"390\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Mon</text><text x=\"145\" y=\"390\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Tue</text><text x=\"234\" y=\"390\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Wed</text><text x=\"323\" y=\"390\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Thu</text><text x=\"411\" y=\"390\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Fri</text><text x=\"500\" y=\"390\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sat</text><text x=\"563\" y=\"390\" style=\"stroke:none;fill:rgb(238,238,238);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sun</text><circle cx=\"58\" cy=\"338\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,100,100);fill:rgb(255,100,100)\"/><circle cx=\"146\" cy=\"335\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,100,100);fill:rgb(255,100,100)\"/><circle cx=\"235\" cy=\"342\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,100,100);fill:rgb(255,100,100)\"/><circle cx=\"324\" cy=\"335\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,100,100);fill:rgb(255,100,100)\"/><circle cx=\"412\" cy=\"345\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,100,100);fill:rgb(255,100,100)\"/><circle cx=\"501\" cy=\"314\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,100,100);fill:rgb(255,100,100)\"/><circle cx=\"590\" cy=\"318\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,100,100);fill:rgb(255,100,100)\"/><circle cx=\"58\" cy=\"316\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,210,100);fill:rgb(255,210,100)\"/><circle cx=\"146\" cy=\"324\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,210,100);fill:rgb(255,210,100)\"/><circle cx=\"235\" cy=\"322\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,210,100);fill:rgb(255,210,100)\"/><circle cx=\"324\" cy=\"313\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,210,100);fill:rgb(255,210,100)\"/><circle cx=\"412\" cy=\"300\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,210,100);fill:rgb(255,210,100)\"/><circle cx=\"501\" cy=\"292\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,210,100);fill:rgb(255,210,100)\"/><circle cx=\"590\" cy=\"296\" r=\"2\" style=\"stroke-width:1;stroke:rgb(255,210,100);fill:rgb(255,210,100)\"/><circle cx=\"58\" cy=\"331\" r=\"2\" style=\"stroke-width:1;stroke:rgb(100,180,210);fill:rgb(100,180,210)\"/><circle cx=\"146\" cy=\"313\" r=\"2\" style=\"stroke-width:1;stroke:rgb(100,180,210);fill:rgb(100,180,210)\"/><circle cx=\"235\" cy=\"320\" r=\"2\" style=\"stroke-width:1;stroke:rgb(100,180,210);fill:rgb(100,180,210)\"/><circle cx=\"324\" cy=\"330\" r=\"2\" style=\"stroke-width:1;stroke:rgb(100,180,210);fill:rgb(100,180,210)\"/><circle cx=\"412\" cy=\"323\" r=\"2\" style=\"stroke-width:1;stroke:rgb(100,180,210);fill:rgb(100,180,210)\"/><circle cx=\"501\" cy=\"292\" r=\"2\" style=\"stroke-width:1;stroke:rgb(100,180,210);fill:rgb(100,180,210)\"/><circle cx=\"590\" cy=\"274\" r=\"2\" style=\"stroke-width:1;stroke:rgb(100,180,210);fill:rgb(100,180,210)\"/><circle cx=\"58\" cy=\"294\" r=\"2\" style=\"stroke-width:1;stroke:rgb(64,160,110);fill:rgb(64,160,110)\"/><circle cx=\"146\" cy=\"291\" r=\"2\" style=\"stroke-width:1;stroke:rgb(64,160,110);fill:rgb(64,160,110)\"/><circle cx=\"235\" cy=\"298\" r=\"2\" style=\"stroke-width:1;stroke:rgb(64,160,110);fill:rgb(64,160,110)\"/><circle cx=\"324\" cy=\"291\" r=\"2\" style=\"stroke-width:1;stroke:rgb(64,160,110);fill:rgb(64,160,110)\"/><circle cx=\"412\" cy=\"278\" r=\"2\" style=\"stroke-width:1;stroke:rgb(64,160,110);fill:rgb(64,160,110)\"/><circle cx=\"501\" cy=\"292\" r=\"2\" style=\"stroke-width:1;stroke:rgb(64,160,110);fill:rgb(64,160,110)\"/><circle cx=\"590\" cy=\"294\" r=\"2\" style=\"stroke-width:1;stroke:rgb(64,160,110);fill:rgb(64,160,110)\"/><circle cx=\"58\" cy=\"183\" r=\"2\" style=\"stroke-width:1;stroke:rgb(154,100,180);fill:rgb(154,100,180)\"/><circle cx=\"146\" cy=\"159\" r=\"2\" style=\"stroke-width:1;stroke:rgb(154,100,180);fill:rgb(154,100,180)\"/><circle cx=\"235\" cy=\"166\" r=\"2\" style=\"stroke-width:1;stroke:rgb(154,100,180);fill:rgb(154,100,180)\"/><circle cx=\"324\" cy=\"158\" r=\"2\" style=\"stroke-width:1;stroke:rgb(154,100,180);fill:rgb(154,100,180)\"/><circle cx=\"412\" cy=\"80\" r=\"2\" style=\"stroke-width:1;stroke:rgb(154,100,180);fill:rgb(154,100,180)\"/><circle cx=\"501\" cy=\"71\" r=\"2\" style=\"stroke-width:1;stroke:rgb(154,100,180);fill:rgb(154,100,180)\"/><circle cx=\"590\" cy=\"73\" r=\"2\" style=\"stroke-width:1;stroke:rgb(154,100,180);fill:rgb(154,100,180)\"/></svg>",
},
{
name: "boundary_gap_enable",
makeOptions: func() ScatterChartOption {
opt := makeMinimalScatterChartOption()
opt.XAxis.Show = Ptr(true)
opt.XAxis.BoundaryGap = Ptr(true)
return opt
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:white\"/><path d=\"M 10 364\nL 590 364\" style=\"stroke-width:1;stroke:rgb(110,112,121);fill:none\"/><path d=\"M 10 369\nL 10 364\" style=\"stroke-width:1;stroke:rgb(110,112,121);fill:none\"/><path d=\"M 92 369\nL 92 364\" style=\"stroke-width:1;stroke:rgb(110,112,121);fill:none\"/><path d=\"M 175 369\nL 175 364\" style=\"stroke-width:1;stroke:rgb(110,112,121);fill:none\"/><path d=\"M 258 369\nL 258 364\" style=\"stroke-width:1;stroke:rgb(110,112,121);fill:none\"/><path d=\"M 341 369\nL 341 364\" style=\"stroke-width:1;stroke:rgb(110,112,121);fill:none\"/><path d=\"M 424 369\nL 424 364\" style=\"stroke-width:1;stroke:rgb(110,112,121);fill:none\"/><path d=\"M 507 369\nL 507 364\" style=\"stroke-width:1;stroke:rgb(110,112,121);fill:none\"/><path d=\"M 590 369\nL 590 364\" style=\"stroke-width:1;stroke:rgb(110,112,121);fill:none\"/><text x=\"47\" y=\"390\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1</text><text x=\"129\" y=\"390\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">2</text><text x=\"212\" y=\"390\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">3</text><text x=\"295\" y=\"390\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">4</text><text x=\"378\" y=\"390\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">5</text><text x=\"461\" y=\"390\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">6</text><text x=\"544\" y=\"390\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">7</text><circle cx=\"51\" cy=\"335\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"133\" cy=\"332\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"216\" cy=\"340\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"299\" cy=\"332\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"382\" cy=\"342\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"465\" cy=\"308\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"548\" cy=\"313\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"51\" cy=\"163\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"133\" cy=\"135\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"216\" cy=\"143\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"299\" cy=\"135\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"382\" cy=\"47\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"465\" cy=\"38\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"548\" cy=\"40\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/></svg>",
},
{
name: "double_yaxis",
makeOptions: func() ScatterChartOption {
opt := makeBasicScatterChartOption()
opt.Theme = GetTheme(ThemeLight)
opt.SeriesList[1].YAxisIndex = 1
opt.YAxis = append(opt.YAxis, opt.YAxis[0])
opt.YAxis[0].Theme = opt.Theme.WithYAxisSeriesColor(0)
opt.YAxis[1].Theme = opt.Theme.WithYAxisSeriesColor(1)
opt.XAxis.Show = Ptr(false)
opt.Title.Show = Ptr(false)
opt.Legend.Show = Ptr(false)
return opt
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:white\"/><text x=\"552\" y=\"16\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.43k</text><text x=\"552\" y=\"58\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.36k</text><text x=\"552\" y=\"100\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.29k</text><text x=\"552\" y=\"142\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.22k</text><text x=\"552\" y=\"184\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.15k</text><text x=\"552\" y=\"226\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.08k</text><text x=\"552\" y=\"268\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.01k</text><text x=\"552\" y=\"310\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">940</text><text x=\"552\" y=\"352\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">870</text><text x=\"552\" y=\"394\" style=\"stroke:none;fill:rgb(145,204,117);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">800</text><text x=\"9\" y=\"16\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">234</text><text x=\"9\" y=\"58\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">218</text><text x=\"9\" y=\"100\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">202</text><text x=\"9\" y=\"142\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">186</text><text x=\"9\" y=\"184\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">170</text><text x=\"9\" y=\"226\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">154</text><text x=\"9\" y=\"268\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">138</text><text x=\"9\" y=\"310\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">122</text><text x=\"9\" y=\"352\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">106</text><text x=\"18\" y=\"394\" style=\"stroke:none;fill:rgb(84,112,198);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">90</text><path d=\"M 42 10\nL 542 10\" style=\"stroke-width:1;stroke:rgb(224,230,242);fill:none\"/><path d=\"M 42 52\nL 542 52\" style=\"stroke-width:1;stroke:rgb(224,230,242);fill:none\"/><path d=\"M 42 94\nL 542 94\" style=\"stroke-width:1;stroke:rgb(224,230,242);fill:none\"/><path d=\"M 42 136\nL 542 136\" style=\"stroke-width:1;stroke:rgb(224,230,242);fill:none\"/><path d=\"M 42 178\nL 542 178\" style=\"stroke-width:1;stroke:rgb(224,230,242);fill:none\"/><path d=\"M 42 221\nL 542 221\" style=\"stroke-width:1;stroke:rgb(224,230,242);fill:none\"/><path d=\"M 42 263\nL 542 263\" style=\"stroke-width:1;stroke:rgb(224,230,242);fill:none\"/><path d=\"M 42 305\nL 542 305\" style=\"stroke-width:1;stroke:rgb(224,230,242);fill:none\"/><path d=\"M 42 347\nL 542 347\" style=\"stroke-width:1;stroke:rgb(224,230,242);fill:none\"/><circle cx=\"46\" cy=\"311\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"128\" cy=\"280\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"211\" cy=\"361\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"294\" cy=\"274\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"376\" cy=\"390\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"459\" cy=\"21\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"542\" cy=\"74\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"46\" cy=\"378\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"128\" cy=\"311\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"211\" cy=\"330\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"294\" cy=\"310\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"376\" cy=\"95\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"459\" cy=\"71\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"542\" cy=\"77\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/></svg>",
},
{
name: "data_gap",
makeOptions: func() ScatterChartOption {
opt := makeMinimalScatterChartOption()
opt.SeriesList[0].Values[4] = []float64{GetNullValue()}
opt.SeriesList[1].Values[2] = []float64{GetNullValue()}
return opt
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:white\"/><circle cx=\"10\" cy=\"359\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"106\" cy=\"356\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"203\" cy=\"364\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"300\" cy=\"355\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"396\" cy=\"2147483657\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"493\" cy=\"330\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"590\" cy=\"335\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"10\" cy=\"174\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"106\" cy=\"145\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"203\" cy=\"2147483657\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"300\" cy=\"144\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"396\" cy=\"50\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"493\" cy=\"40\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"590\" cy=\"42\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/></svg>",
},
{
name: "mark_line",
makeOptions: func() ScatterChartOption {
opt := makeMinimalMultiValueScatterChartOption()
opt.Padding = NewBoxEqual(40)
opt.SymbolSize = 4.5
for i := range opt.SeriesList {
markLine := NewMarkLine("min", "max", "average")
markLine.ValueFormatter = func(f float64) string {
return FormatValueHumanizeShort(f, 0, false)
}
opt.SeriesList[i].MarkLine = markLine
}
return opt
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:white\"/><circle cx=\"40\" cy=\"334\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"126\" cy=\"331\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"213\" cy=\"338\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"213\" cy=\"356\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"300\" cy=\"331\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"386\" cy=\"340\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"386\" cy=\"354\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"473\" cy=\"309\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"560\" cy=\"314\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"40\" cy=\"178\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"126\" cy=\"153\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"213\" cy=\"160\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"213\" cy=\"227\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"300\" cy=\"153\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"386\" cy=\"74\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"473\" cy=\"65\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"560\" cy=\"67\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"43\" cy=\"356\" r=\"3\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 49 356\nL 542 356\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 542 351\nL 558 356\nL 542 361\nL 547 356\nL 542 351\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><text x=\"560\" y=\"360\" style=\"stroke:none;fill:rgb(70,70,70);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">20</text><circle cx=\"43\" cy=\"309\" r=\"3\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 49 309\nL 542 309\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 542 304\nL 558 309\nL 542 314\nL 547 309\nL 542 304\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><text x=\"560\" y=\"313\" style=\"stroke:none;fill:rgb(70,70,70);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">230</text><circle cx=\"43\" cy=\"334\" r=\"3\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 49 334\nL 542 334\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 542 329\nL 558 334\nL 542 339\nL 547 334\nL 542 329\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><text x=\"560\" y=\"338\" style=\"stroke:none;fill:rgb(70,70,70);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">118</text><circle cx=\"43\" cy=\"227\" r=\"3\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 49 227\nL 542 227\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 542 222\nL 558 227\nL 542 232\nL 547 227\nL 542 222\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><text x=\"560\" y=\"231\" style=\"stroke:none;fill:rgb(70,70,70);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">600</text><circle cx=\"43\" cy=\"65\" r=\"3\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 49 65\nL 542 65\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 542 60\nL 558 65\nL 542 70\nL 547 65\nL 542 60\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><text x=\"560\" y=\"69\" style=\"stroke:none;fill:rgb(70,70,70);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">1k</text><circle cx=\"43\" cy=\"135\" r=\"3\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 49 135\nL 542 135\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 542 130\nL 558 135\nL 542 140\nL 547 135\nL 542 130\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><text x=\"560\" y=\"139\" style=\"stroke:none;fill:rgb(70,70,70);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">1k</text></svg>",
},
{
name: "series_label",
makeOptions: func() ScatterChartOption {
opt := makeMinimalMultiValueScatterChartOption()
opt.YAxis[0].Show = Ptr(false)
for i := range opt.SeriesList {
opt.SeriesList[i].Label.Show = Ptr(true)
opt.SeriesList[i].Label.FontStyle = FontStyle{
FontSize: 12.0,
Font: GetDefaultFont(),
FontColor: ColorBlue,
}
opt.SeriesList[i].Label.ValueFormatter = func(f float64) string {
return FormatValueHumanizeShort(f, 2, false)
}
}
return opt
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:white\"/><circle cx=\"10\" cy=\"359\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"106\" cy=\"356\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"203\" cy=\"364\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"203\" cy=\"385\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"300\" cy=\"355\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"396\" cy=\"367\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"396\" cy=\"383\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"493\" cy=\"330\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"590\" cy=\"335\" r=\"2\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"10\" cy=\"174\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"106\" cy=\"145\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"203\" cy=\"153\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"203\" cy=\"232\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"300\" cy=\"144\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"396\" cy=\"50\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"493\" cy=\"40\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"590\" cy=\"42\" r=\"2\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><text x=\"15\" y=\"365\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">120</text><text x=\"111\" y=\"362\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">132</text><text x=\"208\" y=\"370\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">101</text><text x=\"208\" y=\"391\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">20</text><text x=\"305\" y=\"361\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">134</text><text x=\"401\" y=\"373\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">90</text><text x=\"401\" y=\"389\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">28</text><text x=\"498\" y=\"336\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">230</text><text x=\"595\" y=\"341\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">210</text><text x=\"15\" y=\"180\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">820</text><text x=\"111\" y=\"151\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">932</text><text x=\"208\" y=\"159\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">901</text><text x=\"208\" y=\"238\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">600</text><text x=\"305\" y=\"150\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">934</text><text x=\"401\" y=\"56\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.29k</text><text x=\"498\" y=\"46\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.33k</text><text x=\"595\" y=\"48\" style=\"stroke:none;fill:blue;font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1.32k</text></svg>",
},
{
name: "symbol_dot",
makeOptions: func() ScatterChartOption {
opt := makeBasicScatterChartOption()
opt.XAxis.Labels = opt.XAxis.Labels[:5]
for i := range opt.SeriesList {
opt.SeriesList[i].Values = opt.SeriesList[i].Values[:5]
}
opt.SymbolSize = 4.5
opt.Symbol = SymbolDot
opt.Legend.Symbol = SymbolDot
opt.Title.Show = Ptr(false)
opt.XAxis.Show = Ptr(false)
opt.YAxis[0].Show = Ptr(false)
return opt
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:white\"/><path d=\"M 250 19\nL 280 19\" style=\"stroke-width:3;stroke:rgb(84,112,198);fill:none\"/><circle cx=\"265\" cy=\"19\" r=\"5\" style=\"stroke-width:3;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><text x=\"282\" y=\"25\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1</text><path d=\"M 311 19\nL 341 19\" style=\"stroke-width:3;stroke:rgb(145,204,117);fill:none\"/><circle cx=\"326\" cy=\"19\" r=\"5\" style=\"stroke-width:3;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><text x=\"343\" y=\"25\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">2</text><circle cx=\"10\" cy=\"362\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"155\" cy=\"359\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"300\" cy=\"366\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"445\" cy=\"358\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"590\" cy=\"369\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"10\" cy=\"195\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"155\" cy=\"168\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"300\" cy=\"175\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"445\" cy=\"167\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"590\" cy=\"82\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/></svg>",
},
{
name: "symbol_circle",
makeOptions: func() ScatterChartOption {
opt := makeBasicScatterChartOption()
opt.XAxis.Labels = opt.XAxis.Labels[:5]
for i := range opt.SeriesList {
opt.SeriesList[i].Values = opt.SeriesList[i].Values[:5]
}
opt.SymbolSize = 4.5
opt.Symbol = SymbolCircle
opt.Legend.Symbol = SymbolCircle
opt.Title.Show = Ptr(false)
opt.XAxis.Show = Ptr(false)
opt.YAxis[0].Show = Ptr(false)
return opt
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:white\"/><path d=\"M 250 19\nL 280 19\" style=\"stroke-width:3;stroke:rgb(84,112,198);fill:none\"/><circle cx=\"265\" cy=\"19\" r=\"5\" style=\"stroke-width:3;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><circle cx=\"265\" cy=\"19\" r=\"2\" style=\"stroke-width:3;stroke:white;fill:white\"/><text x=\"282\" y=\"25\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1</text><path d=\"M 311 19\nL 341 19\" style=\"stroke-width:3;stroke:rgb(145,204,117);fill:none\"/><circle cx=\"326\" cy=\"19\" r=\"5\" style=\"stroke-width:3;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/><circle cx=\"326\" cy=\"19\" r=\"2\" style=\"stroke-width:3;stroke:white;fill:white\"/><text x=\"343\" y=\"25\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">2</text><circle cx=\"10\" cy=\"362\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:white\"/><circle cx=\"155\" cy=\"359\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:white\"/><circle cx=\"300\" cy=\"366\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:white\"/><circle cx=\"445\" cy=\"358\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:white\"/><circle cx=\"590\" cy=\"369\" r=\"5\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:white\"/><circle cx=\"10\" cy=\"195\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:white\"/><circle cx=\"155\" cy=\"168\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:white\"/><circle cx=\"300\" cy=\"175\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:white\"/><circle cx=\"445\" cy=\"167\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:white\"/><circle cx=\"590\" cy=\"82\" r=\"5\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:white\"/></svg>",
},
{
name: "symbol_square",
makeOptions: func() ScatterChartOption {
opt := makeBasicScatterChartOption()
opt.XAxis.Labels = opt.XAxis.Labels[:5]
for i := range opt.SeriesList {
opt.SeriesList[i].Values = opt.SeriesList[i].Values[:5]
}
opt.SymbolSize = 4.5
opt.Symbol = SymbolSquare
opt.Legend.Symbol = SymbolSquare
opt.Title.Show = Ptr(false)
opt.XAxis.Show = Ptr(false)
opt.YAxis[0].Show = Ptr(false)
return opt
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:white\"/><path d=\"M 250 13\nL 280 13\nL 280 26\nL 250 26\nL 250 13\" style=\"stroke:none;fill:rgb(84,112,198)\"/><text x=\"282\" y=\"25\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1</text><path d=\"M 311 13\nL 341 13\nL 341 26\nL 311 26\nL 311 13\" style=\"stroke:none;fill:rgb(145,204,117)\"/><text x=\"343\" y=\"25\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">2</text><path d=\"M 5 357\nL 14 357\nL 14 366\nL 5 366\nL 5 357\nM 150 354\nL 159 354\nL 159 363\nL 150 363\nL 150 354\nM 295 361\nL 304 361\nL 304 370\nL 295 370\nL 295 361\nM 440 353\nL 449 353\nL 449 362\nL 440 362\nL 440 353\nM 585 364\nL 594 364\nL 594 373\nL 585 373\nL 585 364\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><path d=\"M 5 190\nL 14 190\nL 14 199\nL 5 199\nL 5 190\nM 150 163\nL 159 163\nL 159 172\nL 150 172\nL 150 163\nM 295 170\nL 304 170\nL 304 179\nL 295 179\nL 295 170\nM 440 162\nL 449 162\nL 449 171\nL 440 171\nL 440 162\nM 585 77\nL 594 77\nL 594 86\nL 585 86\nL 585 77\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/></svg>",
},
{
name: "symbol_diamond",
makeOptions: func() ScatterChartOption {
opt := makeBasicScatterChartOption()
opt.XAxis.Labels = opt.XAxis.Labels[:5]
for i := range opt.SeriesList {
opt.SeriesList[i].Values = opt.SeriesList[i].Values[:5]
}
opt.SymbolSize = 4.5
opt.Symbol = SymbolDiamond
opt.Legend.Symbol = SymbolDiamond
opt.Title.Show = Ptr(false)
opt.XAxis.Show = Ptr(false)
opt.YAxis[0].Show = Ptr(false)
return opt
},
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 600 400\"><path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke:none;fill:white\"/><path d=\"M 255 10\nL 262 20\nL 255 30\nL 248 20\nL 255 10\" style=\"stroke:none;fill:rgb(84,112,198)\"/><text x=\"272\" y=\"25\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">1</text><path d=\"M 306 10\nL 313 20\nL 306 30\nL 299 20\nL 306 10\" style=\"stroke:none;fill:rgb(145,204,117)\"/><text x=\"323\" y=\"25\" style=\"stroke:none;fill:rgb(70,70,70);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">2</text><path d=\"M 10 356\nL 16 362\nL 10 368\nL 4 362\nL 10 356\nM 155 353\nL 161 359\nL 155 365\nL 149 359\nL 155 353\nM 300 360\nL 306 366\nL 300 372\nL 294 366\nL 300 360\nM 445 352\nL 451 358\nL 445 364\nL 439 358\nL 445 352\nM 590 363\nL 596 369\nL 590 375\nL 584 369\nL 590 363\" style=\"stroke-width:1;stroke:rgb(84,112,198);fill:rgb(84,112,198)\"/><path d=\"M 10 189\nL 16 195\nL 10 201\nL 4 195\nL 10 189\nM 155 162\nL 161 168\nL 155 174\nL 149 168\nL 155 162\nM 300 169\nL 306 175\nL 300 181\nL 294 175\nL 300 169\nM 445 161\nL 451 167\nL 445 173\nL 439 167\nL 445 161\nM 590 76\nL 596 82\nL 590 88\nL 584 82\nL 590 76\" style=\"stroke-width:1;stroke:rgb(145,204,117);fill:rgb(145,204,117)\"/></svg>",
},
{
name: "symbol_mixed",
ignore: "size", // result is too big to commit
makeOptions: func() ScatterChartOption {
opt := makeFullScatterChartOption()
opt.XAxis.Labels = opt.XAxis.Labels[:5]
for i := range opt.SeriesList {
opt.SeriesList[i].Values = opt.SeriesList[i].Values[:5]
}
opt.SymbolSize = 4.0
opt.SeriesList[0].Symbol = SymbolCircle
opt.SeriesList[1].Symbol = SymbolSquare
opt.SeriesList[2].Symbol = SymbolDiamond
opt.SeriesList[3].Symbol = SymbolDot
opt.Title.Show = Ptr(false)
opt.XAxis.Show = Ptr(false)
opt.YAxis[0].Show = Ptr(false)
return opt
},
result: "",
},
{
name: "dense_trends",
ignore: "size", // result is too big to commit
makeOptions: func() ScatterChartOption {
opt := makeDenseScatterChartOption()
for i := range opt.SeriesList {
opt.SeriesList[i].TrendLine[0].StrokeSmoothingTension = 0.9 // smooth average line
opt.SeriesList[i].TrendLine[0].Window = 5
c1 := Color{
R: uint8(80 + (20 * i)),
G: uint8(80 + (20 * i)),
B: uint8(80 + (20 * i)),
A: 255,
}
c2 := c1
if i%2 == 0 {
c2.R = 200
} else {
c2.B = 200
}
trendLine1 := SeriesTrendLine{
Type: SeriesTrendTypeCubic,
LineColor: c1,
}
trendLine2 := SeriesTrendLine{
Type: SeriesTrendTypeLinear,
LineColor: c2,
}
opt.SeriesList[i].TrendLine = append(opt.SeriesList[i].TrendLine, trendLine1, trendLine2)
}
// disable extras
opt.XAxis.Show = Ptr(false)
opt.YAxis[0].Show = Ptr(false)
return opt
},
result: "",
},
}
for i, tt := range tests {
if tt.ignore != "" {
continue
}
painterOptions := PainterOptions{
OutputFormat: ChartOutputSVG,
Width: 600,
Height: 400,
}
if !tt.themed {
t.Run(strconv.Itoa(i)+"-"+tt.name, func(t *testing.T) {
p := NewPainter(painterOptions)
validateScatterChartRender(t, p, tt.makeOptions(), tt.result)
})
} else {
theme := GetTheme(ThemeVividDark)
t.Run(strconv.Itoa(i)+"-"+tt.name+"-theme_painter", func(t *testing.T) {
p := NewPainter(painterOptions, PainterThemeOption(theme))
validateScatterChartRender(t, p, tt.makeOptions(), tt.result)
})
t.Run(strconv.Itoa(i)+"-"+tt.name+"-theme_opt", func(t *testing.T) {
p := NewPainter(painterOptions)
opt := tt.makeOptions()
opt.Theme = theme
validateScatterChartRender(t, p, opt, tt.result)
})
}
}
}
func validateScatterChartRender(t *testing.T, p *Painter, opt ScatterChartOption, expectedResult string) {
t.Helper()
err := p.ScatterChart(opt)
require.NoError(t, err)
data, err := p.Bytes()
require.NoError(t, err)
assertEqualSVG(t, expectedResult, data)
}
func TestScatterChartError(t *testing.T) {
t.Parallel()
tests := []struct {
name string
makeOptions func() ScatterChartOption
errorMsgContains string
}{
{
name: "empty_series",
makeOptions: func() ScatterChartOption {
return NewScatterChartOptionWithData([][]float64{})
},
errorMsgContains: "empty series list",
},
}
for i, tt := range tests {
t.Run(strconv.Itoa(i)+"-"+tt.name, func(t *testing.T) {
p := NewPainter(PainterOptions{
OutputFormat: ChartOutputSVG,
Width: 600,
Height: 400,
})
err := p.ScatterChart(tt.makeOptions())
require.Error(t, err)
require.ErrorContains(t, err, tt.errorMsgContains)
})
}
}