vega-functions 5.13.0 → 5.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vega-functions might be problematic. Click here for more details.
- package/LICENSE +1 -1
- package/build/vega-functions.js +300 -382
- package/build/vega-functions.min.js +1 -1
- package/build/vega-functions.min.js.map +1 -1
- package/build/vega-functions.module.js +119 -159
- package/package.json +14 -14
- package/rollup.config.mjs +1 -0
- package/src/functions/lasso.js +57 -59
- package/src/scales.js +14 -5
- package/rollup.config.js +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { truthy, error, hasOwnProperty, isFunction, isString, stringValue, extend, isArray, isObject, field, peek, identity, array as array$1, isBoolean, isDate, isNumber, isRegExp, toBoolean, toDate, toNumber, toString, flush, lerp, pad, span, inrange, truncate, quarter, utcquarter, extent, clampRange, panLinear, panLog, panPow, panSymlog, zoomLinear, zoomLog, zoomPow, zoomSymlog } from 'vega-util';
|
|
2
2
|
import { Literal, codegenExpression, constants, functions, parseExpression, CallExpression } from 'vega-expression';
|
|
3
|
-
import {
|
|
3
|
+
import { isRegisteredScale, bandSpace, scale as scale$1, scaleFraction } from 'vega-scale';
|
|
4
|
+
import { geoArea as geoArea$1, geoBounds as geoBounds$1, geoCentroid as geoCentroid$1 } from 'd3-geo';
|
|
4
5
|
import { rgb, lab, hcl, hsl } from 'd3-color';
|
|
5
6
|
import { isTuple } from 'vega-dataflow';
|
|
6
|
-
import { bandSpace, scale as scale$1, scaleFraction } from 'vega-scale';
|
|
7
7
|
import { Gradient, pathRender, pathParse, Bounds, intersect as intersect$1 } from 'vega-scenegraph';
|
|
8
8
|
import { selectionVisitor, selectionTest, selectionIdTest, selectionResolve, selectionTuples } from 'vega-selections';
|
|
9
9
|
import { random, cumulativeNormal, cumulativeLogNormal, cumulativeUniform, densityNormal, densityLogNormal, densityUniform, quantileNormal, quantileLogNormal, quantileUniform, sampleNormal, sampleLogNormal, sampleUniform } from 'vega-statistics';
|
|
@@ -16,13 +16,13 @@ function data(name) {
|
|
|
16
16
|
}
|
|
17
17
|
function indata(name, field, value) {
|
|
18
18
|
const index = this.context.data[name]['index:' + field],
|
|
19
|
-
|
|
19
|
+
entry = index ? index.value.get(value) : undefined;
|
|
20
20
|
return entry ? entry.count : entry;
|
|
21
21
|
}
|
|
22
22
|
function setdata(name, tuples) {
|
|
23
23
|
const df = this.context.dataflow,
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
data = this.context.data[name],
|
|
25
|
+
input = data.input;
|
|
26
26
|
df.pulse(input, df.changeset().remove(truthy).insert(tuples));
|
|
27
27
|
return 1;
|
|
28
28
|
}
|
|
@@ -30,10 +30,9 @@ function setdata(name, tuples) {
|
|
|
30
30
|
function encode (item, name, retval) {
|
|
31
31
|
if (item) {
|
|
32
32
|
const df = this.context.dataflow,
|
|
33
|
-
|
|
33
|
+
target = item.mark.source;
|
|
34
34
|
df.pulse(target, df.changeset().encode(item, name));
|
|
35
35
|
}
|
|
36
|
-
|
|
37
36
|
return retval !== undefined ? retval : item;
|
|
38
37
|
}
|
|
39
38
|
|
|
@@ -41,14 +40,12 @@ const wrap = method => function (value, spec) {
|
|
|
41
40
|
const locale = this.context.dataflow.locale();
|
|
42
41
|
return locale[method](spec)(value);
|
|
43
42
|
};
|
|
44
|
-
|
|
45
43
|
const format = wrap('format');
|
|
46
44
|
const timeFormat = wrap('timeFormat');
|
|
47
45
|
const utcFormat = wrap('utcFormat');
|
|
48
46
|
const timeParse = wrap('timeParse');
|
|
49
47
|
const utcParse = wrap('utcParse');
|
|
50
48
|
const dateObj = new Date(2000, 0, 1);
|
|
51
|
-
|
|
52
49
|
function time(month, day, specifier) {
|
|
53
50
|
if (!Number.isInteger(month) || !Number.isInteger(day)) return '';
|
|
54
51
|
dateObj.setYear(2000);
|
|
@@ -56,7 +53,6 @@ function time(month, day, specifier) {
|
|
|
56
53
|
dateObj.setDate(day);
|
|
57
54
|
return timeFormat.call(this, dateObj, specifier);
|
|
58
55
|
}
|
|
59
|
-
|
|
60
56
|
function monthFormat(month) {
|
|
61
57
|
return time.call(this, month, 1, '%B');
|
|
62
58
|
}
|
|
@@ -79,14 +75,13 @@ function dataVisitor(name, args, scope, params) {
|
|
|
79
75
|
if (args[0].type !== Literal) {
|
|
80
76
|
error('First argument to data functions must be a string literal.');
|
|
81
77
|
}
|
|
82
|
-
|
|
83
78
|
const data = args[0].value,
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
dataName = DataPrefix + data;
|
|
86
80
|
if (!hasOwnProperty(dataName, params)) {
|
|
87
81
|
try {
|
|
88
82
|
params[dataName] = scope.getData(data).tuplesRef();
|
|
89
|
-
} catch (err) {
|
|
83
|
+
} catch (err) {
|
|
84
|
+
// if data set does not exist, there's nothing to track
|
|
90
85
|
}
|
|
91
86
|
}
|
|
92
87
|
}
|
|
@@ -94,9 +89,8 @@ function indataVisitor(name, args, scope, params) {
|
|
|
94
89
|
if (args[0].type !== Literal) error('First argument to indata must be a string literal.');
|
|
95
90
|
if (args[1].type !== Literal) error('Second argument to indata must be a string literal.');
|
|
96
91
|
const data = args[0].value,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
field = args[1].value,
|
|
93
|
+
indexName = IndexPrefix + field;
|
|
100
94
|
if (!hasOwnProperty(indexName, params)) {
|
|
101
95
|
params[indexName] = scope.getData(data).indataRef(scope, field);
|
|
102
96
|
}
|
|
@@ -112,39 +106,45 @@ function scaleVisitor(name, args, scope, params) {
|
|
|
112
106
|
}
|
|
113
107
|
}
|
|
114
108
|
}
|
|
115
|
-
|
|
116
109
|
function addScaleDependency(scope, params, name) {
|
|
117
110
|
const scaleName = ScalePrefix + name;
|
|
118
|
-
|
|
119
111
|
if (!hasOwnProperty(params, scaleName)) {
|
|
120
112
|
try {
|
|
121
113
|
params[scaleName] = scope.scaleRef(name);
|
|
122
|
-
} catch (err) {
|
|
114
|
+
} catch (err) {
|
|
115
|
+
// TODO: error handling? warning?
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
118
|
}
|
|
126
119
|
|
|
127
|
-
function getScale(
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
function getScale(nameOrFunction, ctx) {
|
|
121
|
+
if (isFunction(nameOrFunction)) {
|
|
122
|
+
return nameOrFunction;
|
|
123
|
+
}
|
|
124
|
+
if (isString(nameOrFunction)) {
|
|
125
|
+
const maybeScale = ctx.scales[nameOrFunction];
|
|
126
|
+
return maybeScale && isRegisteredScale(maybeScale.value) ? maybeScale.value : undefined;
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
130
129
|
}
|
|
131
130
|
function internalScaleFunctions(codegen, fnctx, visitors) {
|
|
132
131
|
// add helper method to the 'this' expression function context
|
|
133
|
-
fnctx.__bandwidth = s => s && s.bandwidth ? s.bandwidth() : 0;
|
|
134
|
-
|
|
132
|
+
fnctx.__bandwidth = s => s && s.bandwidth ? s.bandwidth() : 0;
|
|
135
133
|
|
|
134
|
+
// register AST visitors for internal scale functions
|
|
136
135
|
visitors._bandwidth = scaleVisitor;
|
|
137
136
|
visitors._range = scaleVisitor;
|
|
138
|
-
visitors._scale = scaleVisitor;
|
|
139
|
-
|
|
140
|
-
const ref = arg => '_[' + (arg.type === Literal ? stringValue(ScalePrefix + arg.value) : stringValue(ScalePrefix) + '+' + codegen(arg)) + ']'; // define and return internal scale function code generators
|
|
141
|
-
// these internal functions are called by mark encoders
|
|
137
|
+
visitors._scale = scaleVisitor;
|
|
142
138
|
|
|
139
|
+
// resolve scale reference directly to the signal hash argument
|
|
140
|
+
const ref = arg => '_[' + (arg.type === Literal ? stringValue(ScalePrefix + arg.value) : stringValue(ScalePrefix) + '+' + codegen(arg)) + ']';
|
|
143
141
|
|
|
142
|
+
// define and return internal scale function code generators
|
|
143
|
+
// these internal functions are called by mark encoders
|
|
144
144
|
return {
|
|
145
|
-
_bandwidth: args =>
|
|
146
|
-
_range: args =>
|
|
147
|
-
_scale: args =>
|
|
145
|
+
_bandwidth: args => `this.__bandwidth(${ref(args[0])})`,
|
|
146
|
+
_range: args => `${ref(args[0])}.range()`,
|
|
147
|
+
_scale: args => `${ref(args[0])}(${codegen(args[1])})`
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -160,7 +160,6 @@ function geoMethod(methodName, globalMethod) {
|
|
|
160
160
|
}
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
|
-
|
|
164
163
|
const geoArea = geoMethod('area', geoArea$1);
|
|
165
164
|
const geoBounds = geoMethod('bounds', geoBounds$1);
|
|
166
165
|
const geoCentroid = geoMethod('centroid', geoCentroid$1);
|
|
@@ -173,7 +172,6 @@ function inScope (item) {
|
|
|
173
172
|
value = true;
|
|
174
173
|
break;
|
|
175
174
|
}
|
|
176
|
-
|
|
177
175
|
item = item.mark.group;
|
|
178
176
|
}
|
|
179
177
|
return value;
|
|
@@ -185,10 +183,8 @@ function log(df, method, args) {
|
|
|
185
183
|
} catch (err) {
|
|
186
184
|
df.warn(err);
|
|
187
185
|
}
|
|
188
|
-
|
|
189
186
|
return args[args.length - 1];
|
|
190
187
|
}
|
|
191
|
-
|
|
192
188
|
function warn() {
|
|
193
189
|
return log(this.context.dataflow, 'warn', arguments);
|
|
194
190
|
}
|
|
@@ -199,29 +195,28 @@ function debug() {
|
|
|
199
195
|
return log(this.context.dataflow, 'debug', arguments);
|
|
200
196
|
}
|
|
201
197
|
|
|
198
|
+
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
|
202
199
|
function channel_luminance_value(channelValue) {
|
|
203
200
|
const val = channelValue / 255;
|
|
204
|
-
|
|
205
201
|
if (val <= 0.03928) {
|
|
206
202
|
return val / 12.92;
|
|
207
203
|
}
|
|
208
|
-
|
|
209
204
|
return Math.pow((val + 0.055) / 1.055, 2.4);
|
|
210
205
|
}
|
|
211
|
-
|
|
212
206
|
function luminance(color) {
|
|
213
207
|
const c = rgb(color),
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
208
|
+
r = channel_luminance_value(c.r),
|
|
209
|
+
g = channel_luminance_value(c.g),
|
|
210
|
+
b = channel_luminance_value(c.b);
|
|
217
211
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
218
|
-
}
|
|
212
|
+
}
|
|
219
213
|
|
|
214
|
+
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
|
|
220
215
|
function contrast(color1, color2) {
|
|
221
216
|
const lum1 = luminance(color1),
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
217
|
+
lum2 = luminance(color2),
|
|
218
|
+
lumL = Math.max(lum1, lum2),
|
|
219
|
+
lumD = Math.min(lum1, lum2);
|
|
225
220
|
return (lumL + 0.05) / (lumD + 0.05);
|
|
226
221
|
}
|
|
227
222
|
|
|
@@ -234,41 +229,33 @@ function merge () {
|
|
|
234
229
|
function equal(a, b) {
|
|
235
230
|
return a === b || a !== a && b !== b ? true : isArray(a) ? isArray(b) && a.length === b.length ? equalArray(a, b) : false : isObject(a) && isObject(b) ? equalObject(a, b) : false;
|
|
236
231
|
}
|
|
237
|
-
|
|
238
232
|
function equalArray(a, b) {
|
|
239
233
|
for (let i = 0, n = a.length; i < n; ++i) {
|
|
240
234
|
if (!equal(a[i], b[i])) return false;
|
|
241
235
|
}
|
|
242
|
-
|
|
243
236
|
return true;
|
|
244
237
|
}
|
|
245
|
-
|
|
246
238
|
function equalObject(a, b) {
|
|
247
239
|
for (const key in a) {
|
|
248
240
|
if (!equal(a[key], b[key])) return false;
|
|
249
241
|
}
|
|
250
|
-
|
|
251
242
|
return true;
|
|
252
243
|
}
|
|
253
|
-
|
|
254
244
|
function removePredicate(props) {
|
|
255
245
|
return _ => equalObject(props, _);
|
|
256
246
|
}
|
|
257
|
-
|
|
258
247
|
function modify (name, insert, remove, toggle, modify, values) {
|
|
259
248
|
const df = this.context.dataflow,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
249
|
+
data = this.context.data[name],
|
|
250
|
+
input = data.input,
|
|
251
|
+
stamp = df.stamp();
|
|
263
252
|
let changes = data.changes,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
253
|
+
predicate,
|
|
254
|
+
key;
|
|
267
255
|
if (df._trigger === false || !(input.value.length || insert || toggle)) {
|
|
268
256
|
// nothing to do!
|
|
269
257
|
return 0;
|
|
270
258
|
}
|
|
271
|
-
|
|
272
259
|
if (!changes || changes.stamp < stamp) {
|
|
273
260
|
data.changes = changes = df.changeset();
|
|
274
261
|
changes.stamp = stamp;
|
|
@@ -277,39 +264,33 @@ function modify (name, insert, remove, toggle, modify, values) {
|
|
|
277
264
|
df.pulse(input, changes).run();
|
|
278
265
|
}, true, 1);
|
|
279
266
|
}
|
|
280
|
-
|
|
281
267
|
if (remove) {
|
|
282
268
|
predicate = remove === true ? truthy : isArray(remove) || isTuple(remove) ? remove : removePredicate(remove);
|
|
283
269
|
changes.remove(predicate);
|
|
284
270
|
}
|
|
285
|
-
|
|
286
271
|
if (insert) {
|
|
287
272
|
changes.insert(insert);
|
|
288
273
|
}
|
|
289
|
-
|
|
290
274
|
if (toggle) {
|
|
291
275
|
predicate = removePredicate(toggle);
|
|
292
|
-
|
|
293
276
|
if (input.value.some(predicate)) {
|
|
294
277
|
changes.remove(predicate);
|
|
295
278
|
} else {
|
|
296
279
|
changes.insert(toggle);
|
|
297
280
|
}
|
|
298
281
|
}
|
|
299
|
-
|
|
300
282
|
if (modify) {
|
|
301
283
|
for (key in values) {
|
|
302
284
|
changes.modify(modify, key, values[key]);
|
|
303
285
|
}
|
|
304
286
|
}
|
|
305
|
-
|
|
306
287
|
return 1;
|
|
307
288
|
}
|
|
308
289
|
|
|
309
290
|
function pinchDistance(event) {
|
|
310
291
|
const t = event.touches,
|
|
311
|
-
|
|
312
|
-
|
|
292
|
+
dx = t[0].clientX - t[1].clientX,
|
|
293
|
+
dy = t[0].clientY - t[1].clientY;
|
|
313
294
|
return Math.sqrt(dx * dx + dy * dy);
|
|
314
295
|
}
|
|
315
296
|
function pinchAngle(event) {
|
|
@@ -317,6 +298,7 @@ function pinchAngle(event) {
|
|
|
317
298
|
return Math.atan2(t[0].clientY - t[1].clientY, t[0].clientX - t[1].clientX);
|
|
318
299
|
}
|
|
319
300
|
|
|
301
|
+
// memoize accessor functions
|
|
320
302
|
const accessors = {};
|
|
321
303
|
function pluck (data, name) {
|
|
322
304
|
const accessor = accessors[name] || (accessors[name] = field(name));
|
|
@@ -326,37 +308,31 @@ function pluck (data, name) {
|
|
|
326
308
|
function array(seq) {
|
|
327
309
|
return isArray(seq) || ArrayBuffer.isView(seq) ? seq : null;
|
|
328
310
|
}
|
|
329
|
-
|
|
330
311
|
function sequence(seq) {
|
|
331
312
|
return array(seq) || (isString(seq) ? seq : null);
|
|
332
313
|
}
|
|
333
|
-
|
|
334
314
|
function join(seq) {
|
|
335
315
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
336
316
|
args[_key - 1] = arguments[_key];
|
|
337
317
|
}
|
|
338
|
-
|
|
339
318
|
return array(seq).join(...args);
|
|
340
319
|
}
|
|
341
320
|
function indexof(seq) {
|
|
342
321
|
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
343
322
|
args[_key2 - 1] = arguments[_key2];
|
|
344
323
|
}
|
|
345
|
-
|
|
346
324
|
return sequence(seq).indexOf(...args);
|
|
347
325
|
}
|
|
348
326
|
function lastindexof(seq) {
|
|
349
327
|
for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
|
|
350
328
|
args[_key3 - 1] = arguments[_key3];
|
|
351
329
|
}
|
|
352
|
-
|
|
353
330
|
return sequence(seq).lastIndexOf(...args);
|
|
354
331
|
}
|
|
355
332
|
function slice(seq) {
|
|
356
333
|
for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
|
|
357
334
|
args[_key4 - 1] = arguments[_key4];
|
|
358
335
|
}
|
|
359
|
-
|
|
360
336
|
return sequence(seq).slice(...args);
|
|
361
337
|
}
|
|
362
338
|
function replace(str, pattern, repl) {
|
|
@@ -399,23 +375,20 @@ function scaleGradient (scale, p0, p1, count, group) {
|
|
|
399
375
|
scale = getScale(scale, (group || this).context);
|
|
400
376
|
const gradient = Gradient(p0, p1);
|
|
401
377
|
let stops = scale.domain(),
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
378
|
+
min = stops[0],
|
|
379
|
+
max = peek(stops),
|
|
380
|
+
fraction = identity;
|
|
406
381
|
if (!(max - min)) {
|
|
407
382
|
// expand scale if domain has zero span, fix #1479
|
|
408
383
|
scale = (scale.interpolator ? scale$1('sequential')().interpolator(scale.interpolator()) : scale$1('linear')().interpolate(scale.interpolate()).range(scale.range())).domain([min = 0, max = 1]);
|
|
409
384
|
} else {
|
|
410
385
|
fraction = scaleFraction(scale, min, max);
|
|
411
386
|
}
|
|
412
|
-
|
|
413
387
|
if (scale.ticks) {
|
|
414
388
|
stops = scale.ticks(+count || 15);
|
|
415
389
|
if (min !== stops[0]) stops.unshift(min);
|
|
416
390
|
if (max !== peek(stops)) stops.push(max);
|
|
417
391
|
}
|
|
418
|
-
|
|
419
392
|
stops.forEach(_ => gradient.stop(fraction(_), scale(_)));
|
|
420
393
|
return gradient;
|
|
421
394
|
}
|
|
@@ -434,16 +407,14 @@ function pathShape(path) {
|
|
|
434
407
|
}
|
|
435
408
|
|
|
436
409
|
const datum = d => d.data;
|
|
437
|
-
|
|
438
410
|
function treeNodes(name, context) {
|
|
439
411
|
const tree = data.call(context, name);
|
|
440
412
|
return tree.root && tree.root.lookup || {};
|
|
441
413
|
}
|
|
442
|
-
|
|
443
414
|
function treePath(name, source, target) {
|
|
444
415
|
const nodes = treeNodes(name, this),
|
|
445
|
-
|
|
446
|
-
|
|
416
|
+
s = nodes[source],
|
|
417
|
+
t = nodes[target];
|
|
447
418
|
return s && t ? s.path(t).map(datum) : undefined;
|
|
448
419
|
}
|
|
449
420
|
function treeAncestors(name, node) {
|
|
@@ -452,141 +423,132 @@ function treeAncestors(name, node) {
|
|
|
452
423
|
}
|
|
453
424
|
|
|
454
425
|
const _window = () => typeof window !== 'undefined' && window || null;
|
|
455
|
-
|
|
456
426
|
function screen() {
|
|
457
427
|
const w = _window();
|
|
458
|
-
|
|
459
428
|
return w ? w.screen : {};
|
|
460
429
|
}
|
|
461
430
|
function windowSize() {
|
|
462
431
|
const w = _window();
|
|
463
|
-
|
|
464
432
|
return w ? [w.innerWidth, w.innerHeight] : [undefined, undefined];
|
|
465
433
|
}
|
|
466
434
|
function containerSize() {
|
|
467
435
|
const view = this.context.dataflow,
|
|
468
|
-
|
|
436
|
+
el = view.container && view.container();
|
|
469
437
|
return el ? [el.clientWidth, el.clientHeight] : [undefined, undefined];
|
|
470
438
|
}
|
|
471
439
|
|
|
472
440
|
function intersect (b, opt, group) {
|
|
473
441
|
if (!b) return [];
|
|
474
442
|
const [u, v] = b,
|
|
475
|
-
|
|
476
|
-
|
|
443
|
+
box = new Bounds().set(u[0], u[1], v[0], v[1]),
|
|
444
|
+
scene = group || this.context.dataflow.scenegraph().root;
|
|
477
445
|
return intersect$1(scene, box, filter(opt));
|
|
478
446
|
}
|
|
479
|
-
|
|
480
447
|
function filter(opt) {
|
|
481
448
|
let p = null;
|
|
482
|
-
|
|
483
449
|
if (opt) {
|
|
484
450
|
const types = array$1(opt.marktype),
|
|
485
|
-
|
|
486
|
-
|
|
451
|
+
names = array$1(opt.markname);
|
|
487
452
|
p = _ => (!types.length || types.some(t => _.marktype === t)) && (!names.length || names.some(s => _.name === s));
|
|
488
453
|
}
|
|
489
|
-
|
|
490
454
|
return p;
|
|
491
455
|
}
|
|
492
456
|
|
|
493
457
|
/**
|
|
494
458
|
* Appends a new point to the lasso
|
|
495
|
-
*
|
|
459
|
+
*
|
|
496
460
|
* @param {*} lasso the lasso in pixel space
|
|
497
461
|
* @param {*} x the x coordinate in pixel space
|
|
498
462
|
* @param {*} y the y coordinate in pixel space
|
|
499
463
|
* @param {*} minDist the minimum distance, in pixels, that thenew point needs to be apart from the last point
|
|
500
464
|
* @returns a new array containing the lasso with the new point
|
|
501
465
|
*/
|
|
502
|
-
|
|
503
466
|
function lassoAppend(lasso, x, y) {
|
|
504
467
|
let minDist = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 5;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
if (last === undefined || Math.sqrt((last[0] - x) ** 2 + (last[1] - y) ** 2) > minDist) {
|
|
508
|
-
lasso.push([x, y]);
|
|
509
|
-
return [...lasso];
|
|
510
|
-
}
|
|
468
|
+
lasso = array$1(lasso);
|
|
469
|
+
const last = lasso[lasso.length - 1];
|
|
511
470
|
|
|
512
|
-
|
|
471
|
+
// Add point to lasso if its the first point or distance to last point exceed minDist
|
|
472
|
+
return last === undefined || Math.sqrt((last[0] - x) ** 2 + (last[1] - y) ** 2) > minDist ? [...lasso, [x, y]] : lasso;
|
|
513
473
|
}
|
|
474
|
+
|
|
514
475
|
/**
|
|
515
476
|
* Generates a svg path command which draws a lasso
|
|
516
|
-
*
|
|
477
|
+
*
|
|
517
478
|
* @param {*} lasso the lasso in pixel space in the form [[x,y], [x,y], ...]
|
|
518
479
|
* @returns the svg path command that draws the lasso
|
|
519
480
|
*/
|
|
520
|
-
|
|
521
481
|
function lassoPath(lasso) {
|
|
522
|
-
return (lasso
|
|
482
|
+
return array$1(lasso).reduce((svg, _ref, i) => {
|
|
523
483
|
let [x, y] = _ref;
|
|
524
|
-
return svg += i == 0 ?
|
|
484
|
+
return svg += i == 0 ? `M ${x},${y} ` : i === lasso.length - 1 ? ' Z' : `L ${x},${y} `;
|
|
525
485
|
}, '');
|
|
526
486
|
}
|
|
487
|
+
|
|
527
488
|
/**
|
|
528
489
|
* Inverts the lasso from pixel space to an array of vega scenegraph tuples
|
|
529
|
-
*
|
|
490
|
+
*
|
|
530
491
|
* @param {*} data the dataset
|
|
531
492
|
* @param {*} pixelLasso the lasso in pixel space, [[x,y], [x,y], ...]
|
|
532
493
|
* @param {*} unit the unit where the lasso is defined
|
|
533
|
-
*
|
|
494
|
+
*
|
|
534
495
|
* @returns an array of vega scenegraph tuples
|
|
535
496
|
*/
|
|
536
|
-
|
|
537
497
|
function intersectLasso(markname, pixelLasso, unit) {
|
|
538
498
|
const {
|
|
539
499
|
x,
|
|
540
500
|
y,
|
|
541
501
|
mark
|
|
542
502
|
} = unit;
|
|
543
|
-
const bb = new Bounds().set(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
|
|
503
|
+
const bb = new Bounds().set(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
|
|
544
504
|
|
|
505
|
+
// Get bounding box around lasso
|
|
545
506
|
for (const [px, py] of pixelLasso) {
|
|
546
507
|
if (px < bb.x1) bb.x1 = px;
|
|
547
508
|
if (px > bb.x2) bb.x2 = px;
|
|
548
509
|
if (py < bb.y1) bb.y1 = py;
|
|
549
510
|
if (py > bb.y2) bb.y2 = py;
|
|
550
|
-
}
|
|
551
|
-
|
|
511
|
+
}
|
|
552
512
|
|
|
513
|
+
// Translate bb against unit coordinates
|
|
553
514
|
bb.translate(x, y);
|
|
554
|
-
const intersection = intersect([[bb.x1, bb.y1], [bb.x2, bb.y2]], markname, mark);
|
|
515
|
+
const intersection = intersect([[bb.x1, bb.y1], [bb.x2, bb.y2]], markname, mark);
|
|
555
516
|
|
|
517
|
+
// Check every point against the lasso
|
|
556
518
|
return intersection.filter(tuple => pointInPolygon(tuple.x, tuple.y, pixelLasso));
|
|
557
519
|
}
|
|
520
|
+
|
|
558
521
|
/**
|
|
559
522
|
* Performs a test if a point is inside a polygon based on the idea from
|
|
560
523
|
* https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
|
|
561
|
-
*
|
|
524
|
+
*
|
|
562
525
|
* This method will not need the same start/end point since it wraps around the edges of the array
|
|
563
|
-
*
|
|
526
|
+
*
|
|
564
527
|
* @param {*} test a point to test against
|
|
565
528
|
* @param {*} polygon a polygon in the form [[x,y], [x,y], ...]
|
|
566
529
|
* @returns true if the point lies inside the polygon, false otherwise
|
|
567
530
|
*/
|
|
568
|
-
|
|
569
531
|
function pointInPolygon(testx, testy, polygon) {
|
|
570
532
|
let intersections = 0;
|
|
571
|
-
|
|
572
533
|
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
573
534
|
const [prevX, prevY] = polygon[j];
|
|
574
|
-
const [x, y] = polygon[i];
|
|
535
|
+
const [x, y] = polygon[i];
|
|
575
536
|
|
|
537
|
+
// count intersections
|
|
576
538
|
if (y > testy != prevY > testy && testx < (prevX - x) * (testy - y) / (prevY - y) + x) {
|
|
577
539
|
intersections++;
|
|
578
540
|
}
|
|
579
|
-
}
|
|
580
|
-
|
|
541
|
+
}
|
|
581
542
|
|
|
543
|
+
// point is in polygon if intersection count is odd
|
|
582
544
|
return intersections & 1;
|
|
583
545
|
}
|
|
584
546
|
|
|
547
|
+
// Expression function context object
|
|
585
548
|
const functionContext = {
|
|
586
549
|
random() {
|
|
587
550
|
return random();
|
|
588
551
|
},
|
|
589
|
-
|
|
590
552
|
// override default
|
|
591
553
|
cumulativeNormal,
|
|
592
554
|
cumulativeLogNormal,
|
|
@@ -603,27 +565,21 @@ const functionContext = {
|
|
|
603
565
|
isArray,
|
|
604
566
|
isBoolean,
|
|
605
567
|
isDate,
|
|
606
|
-
|
|
607
568
|
isDefined(_) {
|
|
608
569
|
return _ !== undefined;
|
|
609
570
|
},
|
|
610
|
-
|
|
611
571
|
isNumber,
|
|
612
572
|
isObject,
|
|
613
573
|
isRegExp,
|
|
614
574
|
isString,
|
|
615
575
|
isTuple,
|
|
616
|
-
|
|
617
576
|
isValid(_) {
|
|
618
577
|
return _ != null && _ === _;
|
|
619
578
|
},
|
|
620
|
-
|
|
621
579
|
toBoolean,
|
|
622
|
-
|
|
623
580
|
toDate(_) {
|
|
624
581
|
return toDate(_);
|
|
625
582
|
},
|
|
626
|
-
|
|
627
583
|
// suppress extra arguments
|
|
628
584
|
toNumber,
|
|
629
585
|
toString,
|
|
@@ -672,11 +628,9 @@ const functionContext = {
|
|
|
672
628
|
warn,
|
|
673
629
|
info,
|
|
674
630
|
debug,
|
|
675
|
-
|
|
676
631
|
extent(_) {
|
|
677
632
|
return extent(_);
|
|
678
633
|
},
|
|
679
|
-
|
|
680
634
|
// suppress extra arguments
|
|
681
635
|
inScope,
|
|
682
636
|
intersect,
|
|
@@ -704,54 +658,57 @@ const functionContext = {
|
|
|
704
658
|
intersectLasso
|
|
705
659
|
};
|
|
706
660
|
const eventFunctions = ['view', 'item', 'group', 'xy', 'x', 'y'],
|
|
707
|
-
|
|
708
|
-
eventPrefix = 'event.vega.',
|
|
709
|
-
|
|
710
|
-
thisPrefix = 'this.',
|
|
711
|
-
|
|
712
|
-
astVisitors = {}; // AST visitors for dependency analysis
|
|
713
|
-
// export code generator parameters
|
|
661
|
+
// event functions
|
|
662
|
+
eventPrefix = 'event.vega.',
|
|
663
|
+
// event function prefix
|
|
664
|
+
thisPrefix = 'this.',
|
|
665
|
+
// function context prefix
|
|
666
|
+
astVisitors = {}; // AST visitors for dependency analysis
|
|
714
667
|
|
|
668
|
+
// export code generator parameters
|
|
715
669
|
const codegenParams = {
|
|
716
670
|
forbidden: ['_'],
|
|
717
671
|
allowed: ['datum', 'event', 'item'],
|
|
718
672
|
fieldvar: 'datum',
|
|
719
|
-
globalvar: id =>
|
|
673
|
+
globalvar: id => `_[${stringValue(SignalPrefix + id)}]`,
|
|
720
674
|
functions: buildFunctions,
|
|
721
675
|
constants: constants,
|
|
722
676
|
visitors: astVisitors
|
|
723
|
-
};
|
|
677
|
+
};
|
|
724
678
|
|
|
725
|
-
|
|
679
|
+
// export code generator
|
|
680
|
+
const codeGenerator = codegenExpression(codegenParams);
|
|
726
681
|
|
|
682
|
+
// Build expression function registry
|
|
727
683
|
function buildFunctions(codegen) {
|
|
728
684
|
const fn = functions(codegen);
|
|
729
685
|
eventFunctions.forEach(name => fn[name] = eventPrefix + name);
|
|
730
|
-
|
|
731
686
|
for (const name in functionContext) {
|
|
732
687
|
fn[name] = thisPrefix + name;
|
|
733
688
|
}
|
|
734
|
-
|
|
735
689
|
extend(fn, internalScaleFunctions(codegen, functionContext, astVisitors));
|
|
736
690
|
return fn;
|
|
737
|
-
}
|
|
738
|
-
|
|
691
|
+
}
|
|
739
692
|
|
|
693
|
+
// Register an expression function
|
|
740
694
|
function expressionFunction(name, fn, visitor) {
|
|
741
695
|
if (arguments.length === 1) {
|
|
742
696
|
return functionContext[name];
|
|
743
|
-
}
|
|
697
|
+
}
|
|
744
698
|
|
|
699
|
+
// register with the functionContext
|
|
700
|
+
functionContext[name] = fn;
|
|
745
701
|
|
|
746
|
-
|
|
702
|
+
// if there is an astVisitor register that, too
|
|
703
|
+
if (visitor) astVisitors[name] = visitor;
|
|
747
704
|
|
|
748
|
-
|
|
705
|
+
// if the code generator has already been initialized,
|
|
749
706
|
// we need to also register the function with it
|
|
750
|
-
|
|
751
707
|
if (codeGenerator) codeGenerator.functions[name] = thisPrefix + name;
|
|
752
708
|
return this;
|
|
753
|
-
}
|
|
709
|
+
}
|
|
754
710
|
|
|
711
|
+
// register expression functions with ast visitors
|
|
755
712
|
expressionFunction('bandwidth', bandwidth, scaleVisitor);
|
|
756
713
|
expressionFunction('copy', copy, scaleVisitor);
|
|
757
714
|
expressionFunction('domain', domain, scaleVisitor);
|
|
@@ -766,43 +723,46 @@ expressionFunction('geoShape', geoShape, scaleVisitor);
|
|
|
766
723
|
expressionFunction('indata', indata, indataVisitor);
|
|
767
724
|
expressionFunction('data', data, dataVisitor);
|
|
768
725
|
expressionFunction('treePath', treePath, dataVisitor);
|
|
769
|
-
expressionFunction('treeAncestors', treeAncestors, dataVisitor);
|
|
726
|
+
expressionFunction('treeAncestors', treeAncestors, dataVisitor);
|
|
770
727
|
|
|
728
|
+
// register Vega-Lite selection functions
|
|
771
729
|
expressionFunction('vlSelectionTest', selectionTest, selectionVisitor);
|
|
772
730
|
expressionFunction('vlSelectionIdTest', selectionIdTest, selectionVisitor);
|
|
773
731
|
expressionFunction('vlSelectionResolve', selectionResolve, selectionVisitor);
|
|
774
732
|
expressionFunction('vlSelectionTuples', selectionTuples);
|
|
775
733
|
|
|
776
734
|
function parser (expr, scope) {
|
|
777
|
-
const params = {};
|
|
735
|
+
const params = {};
|
|
778
736
|
|
|
737
|
+
// parse the expression to an abstract syntax tree (ast)
|
|
779
738
|
let ast;
|
|
780
|
-
|
|
781
739
|
try {
|
|
782
740
|
expr = isString(expr) ? expr : stringValue(expr) + '';
|
|
783
741
|
ast = parseExpression(expr);
|
|
784
742
|
} catch (err) {
|
|
785
743
|
error('Expression parse error: ' + expr);
|
|
786
|
-
}
|
|
787
|
-
|
|
744
|
+
}
|
|
788
745
|
|
|
746
|
+
// analyze ast function calls for dependencies
|
|
789
747
|
ast.visit(node => {
|
|
790
748
|
if (node.type !== CallExpression) return;
|
|
791
749
|
const name = node.callee.name,
|
|
792
|
-
|
|
750
|
+
visit = codegenParams.visitors[name];
|
|
793
751
|
if (visit) visit(name, node.arguments, scope, params);
|
|
794
|
-
});
|
|
752
|
+
});
|
|
795
753
|
|
|
796
|
-
|
|
754
|
+
// perform code generation
|
|
755
|
+
const gen = codeGenerator(ast);
|
|
797
756
|
|
|
757
|
+
// collect signal dependencies
|
|
798
758
|
gen.globals.forEach(name => {
|
|
799
759
|
const signalName = SignalPrefix + name;
|
|
800
|
-
|
|
801
760
|
if (!hasOwnProperty(params, signalName) && scope.getSignal(name)) {
|
|
802
761
|
params[signalName] = scope.signalRef(name);
|
|
803
762
|
}
|
|
804
|
-
});
|
|
763
|
+
});
|
|
805
764
|
|
|
765
|
+
// return generated expression code and dependencies
|
|
806
766
|
return {
|
|
807
767
|
$expr: extend({
|
|
808
768
|
code: gen.code
|