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.

@@ -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 { geoBounds as geoBounds$1, geoCentroid as geoCentroid$1, geoArea as geoArea$1 } from 'd3-geo';
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
- entry = index ? index.value.get(value) : undefined;
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
- data = this.context.data[name],
25
- input = data.input;
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
- target = item.mark.source;
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
- dataName = DataPrefix + data;
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) {// if data set does not exist, there's nothing to track
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
- field = args[1].value,
98
- indexName = IndexPrefix + field;
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) {// TODO: error handling? warning?
114
+ } catch (err) {
115
+ // TODO: error handling? warning?
123
116
  }
124
117
  }
125
118
  }
126
119
 
127
- function getScale(name, ctx) {
128
- let s;
129
- return isFunction(name) ? name : isString(name) ? (s = ctx.scales[name]) && s.value : undefined;
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; // register AST visitors for internal scale functions
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; // resolve scale reference directly to the signal hash argument
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
- r = channel_luminance_value(c.r),
215
- g = channel_luminance_value(c.g),
216
- b = channel_luminance_value(c.b);
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
- } // https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
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
- lum2 = luminance(color2),
223
- lumL = Math.max(lum1, lum2),
224
- lumD = Math.min(lum1, lum2);
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
- data = this.context.data[name],
261
- input = data.input,
262
- stamp = df.stamp();
249
+ data = this.context.data[name],
250
+ input = data.input,
251
+ stamp = df.stamp();
263
252
  let changes = data.changes,
264
- predicate,
265
- key;
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
- dx = t[0].clientX - t[1].clientX,
312
- dy = t[0].clientY - t[1].clientY;
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
- function join(seq, ...args) {
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, ...args) {
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, ...args) {
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, ...args) {
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
- min = stops[0],
387
- max = peek(stops),
388
- fraction = identity;
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
- s = nodes[source],
430
- t = nodes[target];
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
- el = view.container && view.container();
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
- box = new Bounds().set(u[0], u[1], v[0], v[1]),
460
- scene = group || this.context.dataflow.scenegraph().root;
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
- names = array$1(opt.markname);
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
- // event functions
587
- eventPrefix = 'event.vega.',
588
- // event function prefix
589
- thisPrefix = 'this.',
590
- // function context prefix
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
- }; // export code generator
677
+ };
603
678
 
604
- const codeGenerator = codegenExpression(codegenParams); // Build expression function registry
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
- } // Register an expression function
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
- } // register with the functionContext
697
+ }
623
698
 
699
+ // register with the functionContext
700
+ functionContext[name] = fn;
624
701
 
625
- functionContext[name] = fn; // if there is an astVisitor register that, too
702
+ // if there is an astVisitor register that, too
703
+ if (visitor) astVisitors[name] = visitor;
626
704
 
627
- if (visitor) astVisitors[name] = visitor; // if the code generator has already been initialized,
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
- } // register expression functions with ast visitors
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); // register Vega-Lite selection functions
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 = {}; // parse the expression to an abstract syntax tree (ast)
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
- } // analyze ast function calls for dependencies
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
- visit = codegenParams.visitors[name];
750
+ visit = codegenParams.visitors[name];
672
751
  if (visit) visit(name, node.arguments, scope, params);
673
- }); // perform code generation
752
+ });
674
753
 
675
- const gen = codeGenerator(ast); // collect signal dependencies
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
- }); // return generated expression code and dependencies
763
+ });
684
764
 
765
+ // return generated expression code and dependencies
685
766
  return {
686
767
  $expr: extend({
687
768
  code: gen.code