forked from iansmith/d3
/
d3.go
470 lines (402 loc) · 13 KB
/
d3.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
package d3
import (
"github.com/gopherjs/gopherjs/js"
"honnef.co/go/js/console"
)
//d3root is the primary d3 object (function) in javascript land.
var d3root = js.Global.Get("d3")
//Selector is a special kind of string used for selecting elements
//from the dom. Roughly, it's a CSS selector.
type Selector string
//TagName is a html tag name, like "div"
type TagName string
//PropertyName is the name of a CSS property like "width"
type PropertyName string
//=================================================================
//Selection is the d3 concept of zero or more selected elements.
type Selection interface {
Append(TagName) Selection
SelectAll(Selector) Selection
Data(js.Object) Selection
Enter() Selection
Style(PropertyName, func(js.Object) string) Selection
StyleS(PropertyName, string) Selection
Text(func(js.Object) string) Selection
TextS(string) Selection
Attr(PropertyName, int64) Selection
AttrF(PropertyName, float64) Selection
AttrS(PropertyName, string) Selection
AttrFunc(PropertyName, func(js.Object) int64) Selection
AttrFuncF(PropertyName, func(js.Object) float64) Selection
AttrFunc2S(PropertyName, func(js.Object, int64) string) Selection
AttrFuncS(PropertyName, func(js.Object) string) Selection
Call(Axis) Selection
}
//selectionImpl is the implementation of Selection.
type selectionImpl struct {
obj js.Object
}
//SelectAll finds all DOM elements that match selector in the current
//selection.
func (self *selectionImpl) SelectAll(n Selector) Selection {
return &selectionImpl{
self.obj.Call("selectAll", string(n)),
}
}
//Data provides a set of data for a data join to work with. The argument
//must be a JS array.
func (self *selectionImpl) Data(arr js.Object) Selection {
return &selectionImpl{
self.obj.Call("data", arr),
}
}
//Enter is the case of adding new elements to a data join.
func (self *selectionImpl) Enter() Selection {
return &selectionImpl{
self.obj.Call("enter"),
}
}
//Append adds elements to the current selection. The parameter is
//a tag name to be added.
func (self *selectionImpl) Append(n TagName) Selection {
return &selectionImpl{
self.obj.Call("append", string(n)),
}
}
//Style modifies the CSS attribute prop of the selection. The function
//is passed each element of the data set to use in computing the value.
func (self *selectionImpl) Style(prop PropertyName, f func(js.Object) string) Selection {
console.Log("calling style", self.obj, prop, f)
return &selectionImpl{
self.obj.Call("style", string(prop), f),
}
}
//StyleConst modifies the CSS attribute prop of the selection to be a
//constant value.
func (self *selectionImpl) StyleS(prop PropertyName, value string) Selection {
return &selectionImpl{
self.obj.Call("style", string(prop), value),
}
}
//Text modifies the text portion of the selected elements to be the return
//values of the function. The function is called for each value in the
//dataset.
func (self *selectionImpl) Text(f func(js.Object) string) Selection {
return &selectionImpl{
self.obj.Call("text", f),
}
}
//TextS modifies the text portion of the selected elements to be
//a constant value.
func (self *selectionImpl) TextS(v string) Selection {
return &selectionImpl{
self.obj.Call("text", v),
}
}
//Attr sets an attribute of the selection to a particular value.
func (self *selectionImpl) Attr(p PropertyName, v int64) Selection {
return &selectionImpl{
self.obj.Call("attr", string(p), v),
}
}
//AttrF sets an attribute of the selection to a particular value.
func (self *selectionImpl) AttrF(p PropertyName, v float64) Selection {
return &selectionImpl{
self.obj.Call("attr", string(p), v),
}
}
//Attr sets an attribute of the selection to a string value.
func (self *selectionImpl) AttrS(p PropertyName, v string) Selection {
return &selectionImpl{
self.obj.Call("attr", string(p), v),
}
}
//AttrFunc2S sets an attribute to a function of two variables with the
//second being the already extracted integer.
func (self *selectionImpl) AttrFunc2S(p PropertyName, v func(js.Object, int64) string) Selection {
return &selectionImpl{
self.obj.Call("attr", string(p), v),
}
}
//AttrFuncS sets an attribute to a function of the data object
func (self *selectionImpl) AttrFuncS(p PropertyName, v func(js.Object) string) Selection {
return &selectionImpl{
self.obj.Call("attr", string(p), v),
}
}
//AttrFunc sets an attribute to a function of one variable that produces an int.
func (self *selectionImpl) AttrFunc(p PropertyName, v func(js.Object) int64) Selection {
return &selectionImpl{
self.obj.Call("attr", string(p), v),
}
}
//AttrFuncF sets an attribute to a function of one variable that produces a float.
func (self *selectionImpl) AttrFuncF(p PropertyName, v func(js.Object) float64) Selection {
return &selectionImpl{
self.obj.Call("attr", string(p), v),
}
}
//Call is a wrapper over the d3 selection call() method. No idea how it works.
func (self *selectionImpl) Call(a Axis) Selection {
s := a.(*axisImpl)
return &selectionImpl{
self.obj.Call("call", s.obj),
}
}
//=================================================================
/*
* Utilities
*/
//Max is d3.max with a function that passes over each object in the array
//supplied as the first argument. If the second function is nil, we assume
//that the array contains (JS) integers.
func Max(v js.Object, fn ExtractorFunc) int64 {
if fn != nil {
return int64(d3root.Call("max", v, fn).Int())
}
return int64(d3root.Call("max", v).Int())
}
//MaxF is d3.max with a function that passes over each object in the array
//supplied as the first argument. If the second function is nil, we assume
//that the array contains (JS) floats.
func MaxF(v js.Object, fn ExtractorFuncF) float64 {
if fn != nil {
return d3root.Call("max", v, fn).Float()
}
return d3root.Call("max", v).Float()
}
//Select is d3.select() and creates a selection from the selector.
func Select(n Selector) Selection {
return &selectionImpl{
d3root.Call("select", string(n)),
}
}
//ScaleLinear is d3.scale.linear and returns a LinearScale
func ScaleLinear() LinearScale {
return &linearScaleImpl{
d3root.Get("scale").Call("linear"),
}
}
//ScaleLinear is d3.scale.ordinal and returns a Ordinal
func ScaleOrdinal() OrdinalScale {
return &ordinalScaleImpl{
d3root.Get("scale").Call("ordinal"),
}
}
//NewAxis creates a new axis object
func NewAxis() Axis {
return &axisImpl{
d3root.Get("svg").Call("axis"),
}
}
//FilterFunc converts "raw" objects to their formatted counterparts.
//Raw version has only string fields, but formatted version should have
//the parsed values. If this func returns nil, that item is ignored.
type FilterFunc func(js.Object) js.Object
//ExtractorFunc is a fun that can pull the int value from an object
type ExtractorFunc func(js.Object) int64
//ExtractorFuncF is a fun that can pull the float value from an object
type ExtractorFuncF func(js.Object) float64
//ExtractorFuncO is a fun that can pull the named (usually ordinal) value from the object
type ExtractorFuncO func(js.Object) js.Object
//TSV loads a tab separated value called filename from the server.
//Each loaded element is passed through filter func and the final
//result is handed to callback.
func TSV(filename string, filter FilterFunc, callback func(js.Object, js.Object)) {
d3root.Call("tsv", filename, filter, callback)
}
//=================================================================
//LinearScale is a wrapper around the d3 concept of the same name.
type LinearScale interface {
Domain([]int64) LinearScale
DomainF([]float64) LinearScale
Range([]int64) LinearScale
Linear(js.Object, ExtractorFunc) int64
LinearF(js.Object, ExtractorFuncF) float64
Invert(js.Object, ExtractorFunc) int64
Func(ExtractorFunc) func(js.Object) int64
FuncF(ExtractorFuncF) func(js.Object) float64
}
//linearScaleImpl is the implementation of LinearScale.
type linearScaleImpl struct {
obj js.Object
}
//Domain sets the domain of the linear domain.
func (self *linearScaleImpl) Domain(d []int64) LinearScale {
return &linearScaleImpl{
self.obj.Call("domain", d),
}
}
//Domain sets the domain of the linear domain.
func (self *linearScaleImpl) DomainF(d []float64) LinearScale {
in := js.Global.Get("Array").New()
for i := 0; i < len(d); i++ {
in.SetIndex(i, d[i])
}
return &linearScaleImpl{
self.obj.Call("domain", in),
}
}
//Range sets the range of the linear domain.
func (self *linearScaleImpl) Range(d []int64) LinearScale {
return &linearScaleImpl{
self.obj.Call("range", d),
}
}
//Linear calls the scale to interpolate a value into its range.
func (self *linearScaleImpl) Linear(obj js.Object, fn ExtractorFunc) int64 {
if fn != nil {
return int64(self.obj.Invoke(fn(obj)).Int())
}
return int64(self.obj.Invoke(obj.Int()).Int())
}
//LinearF calls the scale to interpolate a value into its range and returns
//the results as a float. If the extractor function is specified, it should
//pull the floating point input value out of the provided object.
func (self *linearScaleImpl) LinearF(obj js.Object, fn ExtractorFuncF) float64 {
if fn != nil {
return self.obj.Invoke(fn(obj)).Float()
}
return self.obj.Invoke(obj.Float()).Float()
}
//Invert calls the scale to interpolate a range value into its domain.
func (self *linearScaleImpl) Invert(obj js.Object, fn ExtractorFunc) int64 {
if fn != nil {
return int64(self.obj.Call("invert", fn(obj)).Int())
}
return int64(self.obj.Call("invert", obj.Int()).Int())
}
//Func returns a function wrapper around this scale so it can be used
//in the Go side as a function that can extract the values from the
//objects as integers.
func (self *linearScaleImpl) Func(fn ExtractorFunc) func(js.Object) int64 {
if fn != nil {
return func(obj js.Object) int64 {
return int64(self.obj.Invoke(fn(obj)).Int())
}
}
return func(obj js.Object) int64 {
return int64(self.obj.Invoke(obj.Int()).Int())
}
}
//FuncF returns a function wrapper around this scale so it can be used
//in the Go side as a function that can extract the values from the
//objects as floats.
func (self *linearScaleImpl) FuncF(fn ExtractorFuncF) func(js.Object) float64 {
if fn != nil {
return func(obj js.Object) float64 {
return self.obj.Invoke(fn(obj)).Float()
}
}
return func(obj js.Object) float64 {
return self.obj.Invoke(obj.Float()).Float()
}
}
//=================================================================
//OrdinalScale wraps d3.scale.ordinal
type OrdinalScale interface {
Domain(js.Object) OrdinalScale
RangeBands([]int64) OrdinalScale
RangeBand() int64
RangeBandF() float64
RangeBands3([]int64, float64) OrdinalScale
Ordinal(obj js.Object, fn ExtractorFuncO) int64
}
//ordinalScaleImpl is the implementation of LinearScale.
type ordinalScaleImpl struct {
obj js.Object
}
//Domair should be an array of items.
func (self *ordinalScaleImpl) Domain(obj js.Object) OrdinalScale {
return &ordinalScaleImpl{
self.obj.Call("domain", obj),
}
}
func (self *ordinalScaleImpl) RangeBands(b []int64) OrdinalScale {
return &ordinalScaleImpl{
self.obj.Call("rangeBands", b),
}
}
func (self *ordinalScaleImpl) RangeBand() int64 {
return int64(self.obj.Call("rangeBand").Int())
}
func (self *ordinalScaleImpl) RangeBandF() float64 {
return self.obj.Call("rangeBand").Float()
}
func (self *ordinalScaleImpl) RangeBands3(b []int64, f float64) OrdinalScale {
return &ordinalScaleImpl{
self.obj.Call("rangeBands", b, f),
}
}
//Ordinal calls the scale to interpolate a value into its range. If the
//second function is nil, then we assume the ojbect is already in the ordinal
//domain.
func (self *ordinalScaleImpl) Ordinal(obj js.Object, fn ExtractorFuncO) int64 {
if fn != nil {
return int64(self.obj.Invoke(fn(obj)).Int())
}
return int64(self.obj.Invoke(obj).Int())
}
//=================================================================
type Edge int
const (
BOTTOM = iota
TOP
RIGHT
LEFT
)
func (self Edge) String() string {
switch self {
case BOTTOM:
return "bottom"
case LEFT:
return "left"
case RIGHT:
return "right"
case TOP:
return "top"
}
panic("bad edge value?!?")
}
//Axis is a VERY thin wrapper over d3.svg.axis
type Axis interface {
ScaleO(OrdinalScale) Axis
Scale(LinearScale) Axis
Orient(e Edge) Axis
Ticks(int64, string) Axis
}
type axisImpl struct {
obj js.Object
}
//ScaleO creates an axis, given an already created ordinal scale.
func (self *axisImpl) ScaleO(scale OrdinalScale) Axis {
s := scale.(*ordinalScaleImpl)
return &axisImpl{
self.obj.Call("scale", s.obj),
}
}
//Scale creates an axis, given an already created linear scale.
func (self *axisImpl) Scale(scale LinearScale) Axis {
s := scale.(*linearScaleImpl)
return &axisImpl{
self.obj.Call("scale", s.obj),
}
}
//Orient binds the axis to one of the four edges.
func (self *axisImpl) Orient(e Edge) Axis {
return &axisImpl{
self.obj.Call("orient", e.String()),
}
}
//Ticks changes the way the ticks on the axis look. You can optionally
//pass the 2nd parameter for formatting; use "" for no formatting.
func (self *axisImpl) Ticks(i int64, format string) Axis {
if format == "" {
return &axisImpl{
self.obj.Call("ticks", i),
}
}
return &axisImpl{
self.obj.Call("ticks", i, format),
}
}