vega-functions 5.12.1 → 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 +1184 -137
- package/build/vega-functions.min.js +1 -1
- package/build/vega-functions.min.js.map +1 -1
- package/build/vega-functions.module.js +208 -127
- package/package.json +15 -15
- package/rollup.config.mjs +1 -0
- package/src/codegen.js +12 -3
- package/src/functions/lasso.js +108 -0
- 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,35 +106,41 @@ 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
145
|
_bandwidth: args => `this.__bandwidth(${ref(args[0])})`,
|
|
146
146
|
_range: args => `${ref(args[0])}.range()`,
|
|
@@ -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,21 +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) {
|
|
315
|
+
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
316
|
+
args[_key - 1] = arguments[_key];
|
|
317
|
+
}
|
|
335
318
|
return array(seq).join(...args);
|
|
336
319
|
}
|
|
337
|
-
function indexof(seq
|
|
320
|
+
function indexof(seq) {
|
|
321
|
+
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
322
|
+
args[_key2 - 1] = arguments[_key2];
|
|
323
|
+
}
|
|
338
324
|
return sequence(seq).indexOf(...args);
|
|
339
325
|
}
|
|
340
|
-
function lastindexof(seq
|
|
326
|
+
function lastindexof(seq) {
|
|
327
|
+
for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
|
|
328
|
+
args[_key3 - 1] = arguments[_key3];
|
|
329
|
+
}
|
|
341
330
|
return sequence(seq).lastIndexOf(...args);
|
|
342
331
|
}
|
|
343
|
-
function slice(seq
|
|
332
|
+
function slice(seq) {
|
|
333
|
+
for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
|
|
334
|
+
args[_key4 - 1] = arguments[_key4];
|
|
335
|
+
}
|
|
344
336
|
return sequence(seq).slice(...args);
|
|
345
337
|
}
|
|
346
338
|
function replace(str, pattern, repl) {
|
|
@@ -383,23 +375,20 @@ function scaleGradient (scale, p0, p1, count, group) {
|
|
|
383
375
|
scale = getScale(scale, (group || this).context);
|
|
384
376
|
const gradient = Gradient(p0, p1);
|
|
385
377
|
let stops = scale.domain(),
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
378
|
+
min = stops[0],
|
|
379
|
+
max = peek(stops),
|
|
380
|
+
fraction = identity;
|
|
390
381
|
if (!(max - min)) {
|
|
391
382
|
// expand scale if domain has zero span, fix #1479
|
|
392
383
|
scale = (scale.interpolator ? scale$1('sequential')().interpolator(scale.interpolator()) : scale$1('linear')().interpolate(scale.interpolate()).range(scale.range())).domain([min = 0, max = 1]);
|
|
393
384
|
} else {
|
|
394
385
|
fraction = scaleFraction(scale, min, max);
|
|
395
386
|
}
|
|
396
|
-
|
|
397
387
|
if (scale.ticks) {
|
|
398
388
|
stops = scale.ticks(+count || 15);
|
|
399
389
|
if (min !== stops[0]) stops.unshift(min);
|
|
400
390
|
if (max !== peek(stops)) stops.push(max);
|
|
401
391
|
}
|
|
402
|
-
|
|
403
392
|
stops.forEach(_ => gradient.stop(fraction(_), scale(_)));
|
|
404
393
|
return gradient;
|
|
405
394
|
}
|
|
@@ -418,16 +407,14 @@ function pathShape(path) {
|
|
|
418
407
|
}
|
|
419
408
|
|
|
420
409
|
const datum = d => d.data;
|
|
421
|
-
|
|
422
410
|
function treeNodes(name, context) {
|
|
423
411
|
const tree = data.call(context, name);
|
|
424
412
|
return tree.root && tree.root.lookup || {};
|
|
425
413
|
}
|
|
426
|
-
|
|
427
414
|
function treePath(name, source, target) {
|
|
428
415
|
const nodes = treeNodes(name, this),
|
|
429
|
-
|
|
430
|
-
|
|
416
|
+
s = nodes[source],
|
|
417
|
+
t = nodes[target];
|
|
431
418
|
return s && t ? s.path(t).map(datum) : undefined;
|
|
432
419
|
}
|
|
433
420
|
function treeAncestors(name, node) {
|
|
@@ -436,49 +423,132 @@ function treeAncestors(name, node) {
|
|
|
436
423
|
}
|
|
437
424
|
|
|
438
425
|
const _window = () => typeof window !== 'undefined' && window || null;
|
|
439
|
-
|
|
440
426
|
function screen() {
|
|
441
427
|
const w = _window();
|
|
442
|
-
|
|
443
428
|
return w ? w.screen : {};
|
|
444
429
|
}
|
|
445
430
|
function windowSize() {
|
|
446
431
|
const w = _window();
|
|
447
|
-
|
|
448
432
|
return w ? [w.innerWidth, w.innerHeight] : [undefined, undefined];
|
|
449
433
|
}
|
|
450
434
|
function containerSize() {
|
|
451
435
|
const view = this.context.dataflow,
|
|
452
|
-
|
|
436
|
+
el = view.container && view.container();
|
|
453
437
|
return el ? [el.clientWidth, el.clientHeight] : [undefined, undefined];
|
|
454
438
|
}
|
|
455
439
|
|
|
456
440
|
function intersect (b, opt, group) {
|
|
457
441
|
if (!b) return [];
|
|
458
442
|
const [u, v] = b,
|
|
459
|
-
|
|
460
|
-
|
|
443
|
+
box = new Bounds().set(u[0], u[1], v[0], v[1]),
|
|
444
|
+
scene = group || this.context.dataflow.scenegraph().root;
|
|
461
445
|
return intersect$1(scene, box, filter(opt));
|
|
462
446
|
}
|
|
463
|
-
|
|
464
447
|
function filter(opt) {
|
|
465
448
|
let p = null;
|
|
466
|
-
|
|
467
449
|
if (opt) {
|
|
468
450
|
const types = array$1(opt.marktype),
|
|
469
|
-
|
|
470
|
-
|
|
451
|
+
names = array$1(opt.markname);
|
|
471
452
|
p = _ => (!types.length || types.some(t => _.marktype === t)) && (!names.length || names.some(s => _.name === s));
|
|
472
453
|
}
|
|
473
|
-
|
|
474
454
|
return p;
|
|
475
455
|
}
|
|
476
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Appends a new point to the lasso
|
|
459
|
+
*
|
|
460
|
+
* @param {*} lasso the lasso in pixel space
|
|
461
|
+
* @param {*} x the x coordinate in pixel space
|
|
462
|
+
* @param {*} y the y coordinate in pixel space
|
|
463
|
+
* @param {*} minDist the minimum distance, in pixels, that thenew point needs to be apart from the last point
|
|
464
|
+
* @returns a new array containing the lasso with the new point
|
|
465
|
+
*/
|
|
466
|
+
function lassoAppend(lasso, x, y) {
|
|
467
|
+
let minDist = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 5;
|
|
468
|
+
lasso = array$1(lasso);
|
|
469
|
+
const last = lasso[lasso.length - 1];
|
|
470
|
+
|
|
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;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Generates a svg path command which draws a lasso
|
|
477
|
+
*
|
|
478
|
+
* @param {*} lasso the lasso in pixel space in the form [[x,y], [x,y], ...]
|
|
479
|
+
* @returns the svg path command that draws the lasso
|
|
480
|
+
*/
|
|
481
|
+
function lassoPath(lasso) {
|
|
482
|
+
return array$1(lasso).reduce((svg, _ref, i) => {
|
|
483
|
+
let [x, y] = _ref;
|
|
484
|
+
return svg += i == 0 ? `M ${x},${y} ` : i === lasso.length - 1 ? ' Z' : `L ${x},${y} `;
|
|
485
|
+
}, '');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Inverts the lasso from pixel space to an array of vega scenegraph tuples
|
|
490
|
+
*
|
|
491
|
+
* @param {*} data the dataset
|
|
492
|
+
* @param {*} pixelLasso the lasso in pixel space, [[x,y], [x,y], ...]
|
|
493
|
+
* @param {*} unit the unit where the lasso is defined
|
|
494
|
+
*
|
|
495
|
+
* @returns an array of vega scenegraph tuples
|
|
496
|
+
*/
|
|
497
|
+
function intersectLasso(markname, pixelLasso, unit) {
|
|
498
|
+
const {
|
|
499
|
+
x,
|
|
500
|
+
y,
|
|
501
|
+
mark
|
|
502
|
+
} = unit;
|
|
503
|
+
const bb = new Bounds().set(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
|
|
504
|
+
|
|
505
|
+
// Get bounding box around lasso
|
|
506
|
+
for (const [px, py] of pixelLasso) {
|
|
507
|
+
if (px < bb.x1) bb.x1 = px;
|
|
508
|
+
if (px > bb.x2) bb.x2 = px;
|
|
509
|
+
if (py < bb.y1) bb.y1 = py;
|
|
510
|
+
if (py > bb.y2) bb.y2 = py;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Translate bb against unit coordinates
|
|
514
|
+
bb.translate(x, y);
|
|
515
|
+
const intersection = intersect([[bb.x1, bb.y1], [bb.x2, bb.y2]], markname, mark);
|
|
516
|
+
|
|
517
|
+
// Check every point against the lasso
|
|
518
|
+
return intersection.filter(tuple => pointInPolygon(tuple.x, tuple.y, pixelLasso));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Performs a test if a point is inside a polygon based on the idea from
|
|
523
|
+
* https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
|
|
524
|
+
*
|
|
525
|
+
* This method will not need the same start/end point since it wraps around the edges of the array
|
|
526
|
+
*
|
|
527
|
+
* @param {*} test a point to test against
|
|
528
|
+
* @param {*} polygon a polygon in the form [[x,y], [x,y], ...]
|
|
529
|
+
* @returns true if the point lies inside the polygon, false otherwise
|
|
530
|
+
*/
|
|
531
|
+
function pointInPolygon(testx, testy, polygon) {
|
|
532
|
+
let intersections = 0;
|
|
533
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
534
|
+
const [prevX, prevY] = polygon[j];
|
|
535
|
+
const [x, y] = polygon[i];
|
|
536
|
+
|
|
537
|
+
// count intersections
|
|
538
|
+
if (y > testy != prevY > testy && testx < (prevX - x) * (testy - y) / (prevY - y) + x) {
|
|
539
|
+
intersections++;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// point is in polygon if intersection count is odd
|
|
544
|
+
return intersections & 1;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Expression function context object
|
|
477
548
|
const functionContext = {
|
|
478
549
|
random() {
|
|
479
550
|
return random();
|
|
480
551
|
},
|
|
481
|
-
|
|
482
552
|
// override default
|
|
483
553
|
cumulativeNormal,
|
|
484
554
|
cumulativeLogNormal,
|
|
@@ -495,23 +565,22 @@ const functionContext = {
|
|
|
495
565
|
isArray,
|
|
496
566
|
isBoolean,
|
|
497
567
|
isDate,
|
|
498
|
-
|
|
499
568
|
isDefined(_) {
|
|
500
569
|
return _ !== undefined;
|
|
501
570
|
},
|
|
502
|
-
|
|
503
571
|
isNumber,
|
|
504
572
|
isObject,
|
|
505
573
|
isRegExp,
|
|
506
574
|
isString,
|
|
507
575
|
isTuple,
|
|
508
|
-
|
|
509
576
|
isValid(_) {
|
|
510
577
|
return _ != null && _ === _;
|
|
511
578
|
},
|
|
512
|
-
|
|
513
579
|
toBoolean,
|
|
514
|
-
toDate
|
|
580
|
+
toDate(_) {
|
|
581
|
+
return toDate(_);
|
|
582
|
+
},
|
|
583
|
+
// suppress extra arguments
|
|
515
584
|
toNumber,
|
|
516
585
|
toString,
|
|
517
586
|
indexof,
|
|
@@ -559,7 +628,10 @@ const functionContext = {
|
|
|
559
628
|
warn,
|
|
560
629
|
info,
|
|
561
630
|
debug,
|
|
562
|
-
extent
|
|
631
|
+
extent(_) {
|
|
632
|
+
return extent(_);
|
|
633
|
+
},
|
|
634
|
+
// suppress extra arguments
|
|
563
635
|
inScope,
|
|
564
636
|
intersect,
|
|
565
637
|
clampRange,
|
|
@@ -580,17 +652,20 @@ const functionContext = {
|
|
|
580
652
|
zoomPow,
|
|
581
653
|
zoomSymlog,
|
|
582
654
|
encode,
|
|
583
|
-
modify
|
|
655
|
+
modify,
|
|
656
|
+
lassoAppend,
|
|
657
|
+
lassoPath,
|
|
658
|
+
intersectLasso
|
|
584
659
|
};
|
|
585
660
|
const eventFunctions = ['view', 'item', 'group', 'xy', 'x', 'y'],
|
|
586
|
-
|
|
587
|
-
eventPrefix = 'event.vega.',
|
|
588
|
-
|
|
589
|
-
thisPrefix = 'this.',
|
|
590
|
-
|
|
591
|
-
astVisitors = {}; // AST visitors for dependency analysis
|
|
592
|
-
// 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
|
|
593
667
|
|
|
668
|
+
// export code generator parameters
|
|
594
669
|
const codegenParams = {
|
|
595
670
|
forbidden: ['_'],
|
|
596
671
|
allowed: ['datum', 'event', 'item'],
|
|
@@ -599,38 +674,41 @@ const codegenParams = {
|
|
|
599
674
|
functions: buildFunctions,
|
|
600
675
|
constants: constants,
|
|
601
676
|
visitors: astVisitors
|
|
602
|
-
};
|
|
677
|
+
};
|
|
603
678
|
|
|
604
|
-
|
|
679
|
+
// export code generator
|
|
680
|
+
const codeGenerator = codegenExpression(codegenParams);
|
|
605
681
|
|
|
682
|
+
// Build expression function registry
|
|
606
683
|
function buildFunctions(codegen) {
|
|
607
684
|
const fn = functions(codegen);
|
|
608
685
|
eventFunctions.forEach(name => fn[name] = eventPrefix + name);
|
|
609
|
-
|
|
610
686
|
for (const name in functionContext) {
|
|
611
687
|
fn[name] = thisPrefix + name;
|
|
612
688
|
}
|
|
613
|
-
|
|
614
689
|
extend(fn, internalScaleFunctions(codegen, functionContext, astVisitors));
|
|
615
690
|
return fn;
|
|
616
|
-
}
|
|
617
|
-
|
|
691
|
+
}
|
|
618
692
|
|
|
693
|
+
// Register an expression function
|
|
619
694
|
function expressionFunction(name, fn, visitor) {
|
|
620
695
|
if (arguments.length === 1) {
|
|
621
696
|
return functionContext[name];
|
|
622
|
-
}
|
|
697
|
+
}
|
|
623
698
|
|
|
699
|
+
// register with the functionContext
|
|
700
|
+
functionContext[name] = fn;
|
|
624
701
|
|
|
625
|
-
|
|
702
|
+
// if there is an astVisitor register that, too
|
|
703
|
+
if (visitor) astVisitors[name] = visitor;
|
|
626
704
|
|
|
627
|
-
|
|
705
|
+
// if the code generator has already been initialized,
|
|
628
706
|
// we need to also register the function with it
|
|
629
|
-
|
|
630
707
|
if (codeGenerator) codeGenerator.functions[name] = thisPrefix + name;
|
|
631
708
|
return this;
|
|
632
|
-
}
|
|
709
|
+
}
|
|
633
710
|
|
|
711
|
+
// register expression functions with ast visitors
|
|
634
712
|
expressionFunction('bandwidth', bandwidth, scaleVisitor);
|
|
635
713
|
expressionFunction('copy', copy, scaleVisitor);
|
|
636
714
|
expressionFunction('domain', domain, scaleVisitor);
|
|
@@ -645,43 +723,46 @@ expressionFunction('geoShape', geoShape, scaleVisitor);
|
|
|
645
723
|
expressionFunction('indata', indata, indataVisitor);
|
|
646
724
|
expressionFunction('data', data, dataVisitor);
|
|
647
725
|
expressionFunction('treePath', treePath, dataVisitor);
|
|
648
|
-
expressionFunction('treeAncestors', treeAncestors, dataVisitor);
|
|
726
|
+
expressionFunction('treeAncestors', treeAncestors, dataVisitor);
|
|
649
727
|
|
|
728
|
+
// register Vega-Lite selection functions
|
|
650
729
|
expressionFunction('vlSelectionTest', selectionTest, selectionVisitor);
|
|
651
730
|
expressionFunction('vlSelectionIdTest', selectionIdTest, selectionVisitor);
|
|
652
731
|
expressionFunction('vlSelectionResolve', selectionResolve, selectionVisitor);
|
|
653
732
|
expressionFunction('vlSelectionTuples', selectionTuples);
|
|
654
733
|
|
|
655
734
|
function parser (expr, scope) {
|
|
656
|
-
const params = {};
|
|
735
|
+
const params = {};
|
|
657
736
|
|
|
737
|
+
// parse the expression to an abstract syntax tree (ast)
|
|
658
738
|
let ast;
|
|
659
|
-
|
|
660
739
|
try {
|
|
661
740
|
expr = isString(expr) ? expr : stringValue(expr) + '';
|
|
662
741
|
ast = parseExpression(expr);
|
|
663
742
|
} catch (err) {
|
|
664
743
|
error('Expression parse error: ' + expr);
|
|
665
|
-
}
|
|
666
|
-
|
|
744
|
+
}
|
|
667
745
|
|
|
746
|
+
// analyze ast function calls for dependencies
|
|
668
747
|
ast.visit(node => {
|
|
669
748
|
if (node.type !== CallExpression) return;
|
|
670
749
|
const name = node.callee.name,
|
|
671
|
-
|
|
750
|
+
visit = codegenParams.visitors[name];
|
|
672
751
|
if (visit) visit(name, node.arguments, scope, params);
|
|
673
|
-
});
|
|
752
|
+
});
|
|
674
753
|
|
|
675
|
-
|
|
754
|
+
// perform code generation
|
|
755
|
+
const gen = codeGenerator(ast);
|
|
676
756
|
|
|
757
|
+
// collect signal dependencies
|
|
677
758
|
gen.globals.forEach(name => {
|
|
678
759
|
const signalName = SignalPrefix + name;
|
|
679
|
-
|
|
680
760
|
if (!hasOwnProperty(params, signalName) && scope.getSignal(name)) {
|
|
681
761
|
params[signalName] = scope.signalRef(name);
|
|
682
762
|
}
|
|
683
|
-
});
|
|
763
|
+
});
|
|
684
764
|
|
|
765
|
+
// return generated expression code and dependencies
|
|
685
766
|
return {
|
|
686
767
|
$expr: extend({
|
|
687
768
|
code: gen.code
|