UNPKG

118 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-util'), require('vega-dataflow'), require('vega-statistics'), require('vega-time')) :
3 typeof define === 'function' && define.amd ? define(['exports', 'vega-util', 'vega-dataflow', 'vega-statistics', 'vega-time'], factory) :
4 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vega = {}, global.vega, global.vega, global.vega, global.vega));
5})(this, (function (exports, vegaUtil, vegaDataflow, vegaStatistics, vegaTime) { 'use strict';
6
7 function multikey(f) {
8 return x => {
9 const n = f.length;
10 let i = 1,
11 k = String(f[0](x));
12 for (; i < n; ++i) {
13 k += '|' + f[i](x);
14 }
15 return k;
16 };
17 }
18 function groupkey(fields) {
19 return !fields || !fields.length ? function () {
20 return '';
21 } : fields.length === 1 ? fields[0] : multikey(fields);
22 }
23
24 function measureName(op, field, as) {
25 return as || op + (!field ? '' : '_' + field);
26 }
27 const noop = () => {};
28 const base_op = {
29 init: noop,
30 add: noop,
31 rem: noop,
32 idx: 0
33 };
34 const AggregateOps = {
35 values: {
36 init: m => m.cell.store = true,
37 value: m => m.cell.data.values(),
38 idx: -1
39 },
40 count: {
41 value: m => m.cell.num
42 },
43 __count__: {
44 value: m => m.missing + m.valid
45 },
46 missing: {
47 value: m => m.missing
48 },
49 valid: {
50 value: m => m.valid
51 },
52 sum: {
53 init: m => m.sum = 0,
54 value: m => m.sum,
55 add: (m, v) => m.sum += +v,
56 rem: (m, v) => m.sum -= v
57 },
58 product: {
59 init: m => m.product = 1,
60 value: m => m.valid ? m.product : undefined,
61 add: (m, v) => m.product *= v,
62 rem: (m, v) => m.product /= v
63 },
64 mean: {
65 init: m => m.mean = 0,
66 value: m => m.valid ? m.mean : undefined,
67 add: (m, v) => (m.mean_d = v - m.mean, m.mean += m.mean_d / m.valid),
68 rem: (m, v) => (m.mean_d = v - m.mean, m.mean -= m.valid ? m.mean_d / m.valid : m.mean)
69 },
70 average: {
71 value: m => m.valid ? m.mean : undefined,
72 req: ['mean'],
73 idx: 1
74 },
75 variance: {
76 init: m => m.dev = 0,
77 value: m => m.valid > 1 ? m.dev / (m.valid - 1) : undefined,
78 add: (m, v) => m.dev += m.mean_d * (v - m.mean),
79 rem: (m, v) => m.dev -= m.mean_d * (v - m.mean),
80 req: ['mean'],
81 idx: 1
82 },
83 variancep: {
84 value: m => m.valid > 1 ? m.dev / m.valid : undefined,
85 req: ['variance'],
86 idx: 2
87 },
88 stdev: {
89 value: m => m.valid > 1 ? Math.sqrt(m.dev / (m.valid - 1)) : undefined,
90 req: ['variance'],
91 idx: 2
92 },
93 stdevp: {
94 value: m => m.valid > 1 ? Math.sqrt(m.dev / m.valid) : undefined,
95 req: ['variance'],
96 idx: 2
97 },
98 stderr: {
99 value: m => m.valid > 1 ? Math.sqrt(m.dev / (m.valid * (m.valid - 1))) : undefined,
100 req: ['variance'],
101 idx: 2
102 },
103 distinct: {
104 value: m => m.cell.data.distinct(m.get),
105 req: ['values'],
106 idx: 3
107 },
108 ci0: {
109 value: m => m.cell.data.ci0(m.get),
110 req: ['values'],
111 idx: 3
112 },
113 ci1: {
114 value: m => m.cell.data.ci1(m.get),
115 req: ['values'],
116 idx: 3
117 },
118 median: {
119 value: m => m.cell.data.q2(m.get),
120 req: ['values'],
121 idx: 3
122 },
123 q1: {
124 value: m => m.cell.data.q1(m.get),
125 req: ['values'],
126 idx: 3
127 },
128 q3: {
129 value: m => m.cell.data.q3(m.get),
130 req: ['values'],
131 idx: 3
132 },
133 min: {
134 init: m => m.min = undefined,
135 value: m => m.min = Number.isNaN(m.min) ? m.cell.data.min(m.get) : m.min,
136 add: (m, v) => {
137 if (v < m.min || m.min === undefined) m.min = v;
138 },
139 rem: (m, v) => {
140 if (v <= m.min) m.min = NaN;
141 },
142 req: ['values'],
143 idx: 4
144 },
145 max: {
146 init: m => m.max = undefined,
147 value: m => m.max = Number.isNaN(m.max) ? m.cell.data.max(m.get) : m.max,
148 add: (m, v) => {
149 if (v > m.max || m.max === undefined) m.max = v;
150 },
151 rem: (m, v) => {
152 if (v >= m.max) m.max = NaN;
153 },
154 req: ['values'],
155 idx: 4
156 },
157 argmin: {
158 init: m => m.argmin = undefined,
159 value: m => m.argmin || m.cell.data.argmin(m.get),
160 add: (m, v, t) => {
161 if (v < m.min) m.argmin = t;
162 },
163 rem: (m, v) => {
164 if (v <= m.min) m.argmin = undefined;
165 },
166 req: ['min', 'values'],
167 idx: 3
168 },
169 argmax: {
170 init: m => m.argmax = undefined,
171 value: m => m.argmax || m.cell.data.argmax(m.get),
172 add: (m, v, t) => {
173 if (v > m.max) m.argmax = t;
174 },
175 rem: (m, v) => {
176 if (v >= m.max) m.argmax = undefined;
177 },
178 req: ['max', 'values'],
179 idx: 3
180 }
181 };
182 const ValidAggregateOps = Object.keys(AggregateOps).filter(d => d !== '__count__');
183 function measure(key, value) {
184 return out => vegaUtil.extend({
185 name: key,
186 out: out || key
187 }, base_op, value);
188 }
189 [...ValidAggregateOps, '__count__'].forEach(key => {
190 AggregateOps[key] = measure(key, AggregateOps[key]);
191 });
192 function createMeasure(op, name) {
193 return AggregateOps[op](name);
194 }
195 function compareIndex(a, b) {
196 return a.idx - b.idx;
197 }
198 function resolve(agg) {
199 const map = {};
200 agg.forEach(a => map[a.name] = a);
201 const getreqs = a => {
202 if (!a.req) return;
203 a.req.forEach(key => {
204 if (!map[key]) getreqs(map[key] = AggregateOps[key]());
205 });
206 };
207 agg.forEach(getreqs);
208 return Object.values(map).sort(compareIndex);
209 }
210 function init() {
211 this.valid = 0;
212 this.missing = 0;
213 this._ops.forEach(op => op.init(this));
214 }
215 function add(v, t) {
216 if (v == null || v === '') {
217 ++this.missing;
218 return;
219 }
220 if (v !== v) return;
221 ++this.valid;
222 this._ops.forEach(op => op.add(this, v, t));
223 }
224 function rem(v, t) {
225 if (v == null || v === '') {
226 --this.missing;
227 return;
228 }
229 if (v !== v) return;
230 --this.valid;
231 this._ops.forEach(op => op.rem(this, v, t));
232 }
233 function set(t) {
234 this._out.forEach(op => t[op.out] = op.value(this));
235 return t;
236 }
237 function compileMeasures(agg, field) {
238 const get = field || vegaUtil.identity,
239 ops = resolve(agg),
240 out = agg.slice().sort(compareIndex);
241 function ctr(cell) {
242 this._ops = ops;
243 this._out = out;
244 this.cell = cell;
245 this.init();
246 }
247 ctr.prototype.init = init;
248 ctr.prototype.add = add;
249 ctr.prototype.rem = rem;
250 ctr.prototype.set = set;
251 ctr.prototype.get = get;
252 ctr.fields = agg.map(op => op.out);
253 return ctr;
254 }
255
256 function TupleStore(key) {
257 this._key = key ? vegaUtil.field(key) : vegaDataflow.tupleid;
258 this.reset();
259 }
260 const prototype$1 = TupleStore.prototype;
261 prototype$1.reset = function () {
262 this._add = [];
263 this._rem = [];
264 this._ext = null;
265 this._get = null;
266 this._q = null;
267 };
268 prototype$1.add = function (v) {
269 this._add.push(v);
270 };
271 prototype$1.rem = function (v) {
272 this._rem.push(v);
273 };
274 prototype$1.values = function () {
275 this._get = null;
276 if (this._rem.length === 0) return this._add;
277 const a = this._add,
278 r = this._rem,
279 k = this._key,
280 n = a.length,
281 m = r.length,
282 x = Array(n - m),
283 map = {};
284 let i, j, v;
285
286 // use unique key field to clear removed values
287 for (i = 0; i < m; ++i) {
288 map[k(r[i])] = 1;
289 }
290 for (i = 0, j = 0; i < n; ++i) {
291 if (map[k(v = a[i])]) {
292 map[k(v)] = 0;
293 } else {
294 x[j++] = v;
295 }
296 }
297 this._rem = [];
298 return this._add = x;
299 };
300
301 // memoizing statistics methods
302
303 prototype$1.distinct = function (get) {
304 const v = this.values(),
305 map = {};
306 let n = v.length,
307 count = 0,
308 s;
309 while (--n >= 0) {
310 s = get(v[n]) + '';
311 if (!vegaUtil.hasOwnProperty(map, s)) {
312 map[s] = 1;
313 ++count;
314 }
315 }
316 return count;
317 };
318 prototype$1.extent = function (get) {
319 if (this._get !== get || !this._ext) {
320 const v = this.values(),
321 i = vegaUtil.extentIndex(v, get);
322 this._ext = [v[i[0]], v[i[1]]];
323 this._get = get;
324 }
325 return this._ext;
326 };
327 prototype$1.argmin = function (get) {
328 return this.extent(get)[0] || {};
329 };
330 prototype$1.argmax = function (get) {
331 return this.extent(get)[1] || {};
332 };
333 prototype$1.min = function (get) {
334 const m = this.extent(get)[0];
335 return m != null ? get(m) : undefined;
336 };
337 prototype$1.max = function (get) {
338 const m = this.extent(get)[1];
339 return m != null ? get(m) : undefined;
340 };
341 prototype$1.quartile = function (get) {
342 if (this._get !== get || !this._q) {
343 this._q = vegaStatistics.quartiles(this.values(), get);
344 this._get = get;
345 }
346 return this._q;
347 };
348 prototype$1.q1 = function (get) {
349 return this.quartile(get)[0];
350 };
351 prototype$1.q2 = function (get) {
352 return this.quartile(get)[1];
353 };
354 prototype$1.q3 = function (get) {
355 return this.quartile(get)[2];
356 };
357 prototype$1.ci = function (get) {
358 if (this._get !== get || !this._ci) {
359 this._ci = vegaStatistics.bootstrapCI(this.values(), 1000, 0.05, get);
360 this._get = get;
361 }
362 return this._ci;
363 };
364 prototype$1.ci0 = function (get) {
365 return this.ci(get)[0];
366 };
367 prototype$1.ci1 = function (get) {
368 return this.ci(get)[1];
369 };
370
371 /**
372 * Group-by aggregation operator.
373 * @constructor
374 * @param {object} params - The parameters for this operator.
375 * @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby.
376 * @param {Array<function(object): *>} [params.fields] - An array of accessors to aggregate.
377 * @param {Array<string>} [params.ops] - An array of strings indicating aggregation operations.
378 * @param {Array<string>} [params.as] - An array of output field names for aggregated values.
379 * @param {boolean} [params.cross=false] - A flag indicating that the full
380 * cross-product of groupby values should be generated, including empty cells.
381 * If true, the drop parameter is ignored and empty cells are retained.
382 * @param {boolean} [params.drop=true] - A flag indicating if empty cells should be removed.
383 */
384 function Aggregate(params) {
385 vegaDataflow.Transform.call(this, null, params);
386 this._adds = []; // array of added output tuples
387 this._mods = []; // array of modified output tuples
388 this._alen = 0; // number of active added tuples
389 this._mlen = 0; // number of active modified tuples
390 this._drop = true; // should empty aggregation cells be removed
391 this._cross = false; // produce full cross-product of group-by values
392
393 this._dims = []; // group-by dimension accessors
394 this._dnames = []; // group-by dimension names
395
396 this._measures = []; // collection of aggregation monoids
397 this._countOnly = false; // flag indicating only count aggregation
398 this._counts = null; // collection of count fields
399 this._prev = null; // previous aggregation cells
400
401 this._inputs = null; // array of dependent input tuple field names
402 this._outputs = null; // array of output tuple field names
403 }
404
405 Aggregate.Definition = {
406 'type': 'Aggregate',
407 'metadata': {
408 'generates': true,
409 'changes': true
410 },
411 'params': [{
412 'name': 'groupby',
413 'type': 'field',
414 'array': true
415 }, {
416 'name': 'ops',
417 'type': 'enum',
418 'array': true,
419 'values': ValidAggregateOps
420 }, {
421 'name': 'fields',
422 'type': 'field',
423 'null': true,
424 'array': true
425 }, {
426 'name': 'as',
427 'type': 'string',
428 'null': true,
429 'array': true
430 }, {
431 'name': 'drop',
432 'type': 'boolean',
433 'default': true
434 }, {
435 'name': 'cross',
436 'type': 'boolean',
437 'default': false
438 }, {
439 'name': 'key',
440 'type': 'field'
441 }]
442 };
443 vegaUtil.inherits(Aggregate, vegaDataflow.Transform, {
444 transform(_, pulse) {
445 const aggr = this,
446 out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
447 mod = _.modified();
448 aggr.stamp = out.stamp;
449 if (aggr.value && (mod || pulse.modified(aggr._inputs, true))) {
450 aggr._prev = aggr.value;
451 aggr.value = mod ? aggr.init(_) : {};
452 pulse.visit(pulse.SOURCE, t => aggr.add(t));
453 } else {
454 aggr.value = aggr.value || aggr.init(_);
455 pulse.visit(pulse.REM, t => aggr.rem(t));
456 pulse.visit(pulse.ADD, t => aggr.add(t));
457 }
458
459 // Indicate output fields and return aggregate tuples.
460 out.modifies(aggr._outputs);
461
462 // Should empty cells be dropped?
463 aggr._drop = _.drop !== false;
464
465 // If domain cross-product requested, generate empty cells as needed
466 // and ensure that empty cells are not dropped
467 if (_.cross && aggr._dims.length > 1) {
468 aggr._drop = false;
469 aggr.cross();
470 }
471 if (pulse.clean() && aggr._drop) {
472 out.clean(true).runAfter(() => this.clean());
473 }
474 return aggr.changes(out);
475 },
476 cross() {
477 const aggr = this,
478 curr = aggr.value,
479 dims = aggr._dnames,
480 vals = dims.map(() => ({})),
481 n = dims.length;
482
483 // collect all group-by domain values
484 function collect(cells) {
485 let key, i, t, v;
486 for (key in cells) {
487 t = cells[key].tuple;
488 for (i = 0; i < n; ++i) {
489 vals[i][v = t[dims[i]]] = v;
490 }
491 }
492 }
493 collect(aggr._prev);
494 collect(curr);
495
496 // iterate over key cross-product, create cells as needed
497 function generate(base, tuple, index) {
498 const name = dims[index],
499 v = vals[index++];
500 for (const k in v) {
501 const key = base ? base + '|' + k : k;
502 tuple[name] = v[k];
503 if (index < n) generate(key, tuple, index);else if (!curr[key]) aggr.cell(key, tuple);
504 }
505 }
506 generate('', {}, 0);
507 },
508 init(_) {
509 // initialize input and output fields
510 const inputs = this._inputs = [],
511 outputs = this._outputs = [],
512 inputMap = {};
513 function inputVisit(get) {
514 const fields = vegaUtil.array(vegaUtil.accessorFields(get)),
515 n = fields.length;
516 let i = 0,
517 f;
518 for (; i < n; ++i) {
519 if (!inputMap[f = fields[i]]) {
520 inputMap[f] = 1;
521 inputs.push(f);
522 }
523 }
524 }
525
526 // initialize group-by dimensions
527 this._dims = vegaUtil.array(_.groupby);
528 this._dnames = this._dims.map(d => {
529 const dname = vegaUtil.accessorName(d);
530 inputVisit(d);
531 outputs.push(dname);
532 return dname;
533 });
534 this.cellkey = _.key ? _.key : groupkey(this._dims);
535
536 // initialize aggregate measures
537 this._countOnly = true;
538 this._counts = [];
539 this._measures = [];
540 const fields = _.fields || [null],
541 ops = _.ops || ['count'],
542 as = _.as || [],
543 n = fields.length,
544 map = {};
545 let field, op, m, mname, outname, i;
546 if (n !== ops.length) {
547 vegaUtil.error('Unmatched number of fields and aggregate ops.');
548 }
549 for (i = 0; i < n; ++i) {
550 field = fields[i];
551 op = ops[i];
552 if (field == null && op !== 'count') {
553 vegaUtil.error('Null aggregate field specified.');
554 }
555 mname = vegaUtil.accessorName(field);
556 outname = measureName(op, mname, as[i]);
557 outputs.push(outname);
558 if (op === 'count') {
559 this._counts.push(outname);
560 continue;
561 }
562 m = map[mname];
563 if (!m) {
564 inputVisit(field);
565 m = map[mname] = [];
566 m.field = field;
567 this._measures.push(m);
568 }
569 if (op !== 'count') this._countOnly = false;
570 m.push(createMeasure(op, outname));
571 }
572 this._measures = this._measures.map(m => compileMeasures(m, m.field));
573 return {}; // aggregation cells (this.value)
574 },
575
576 // -- Cell Management -----
577
578 cellkey: groupkey(),
579 cell(key, t) {
580 let cell = this.value[key];
581 if (!cell) {
582 cell = this.value[key] = this.newcell(key, t);
583 this._adds[this._alen++] = cell;
584 } else if (cell.num === 0 && this._drop && cell.stamp < this.stamp) {
585 cell.stamp = this.stamp;
586 this._adds[this._alen++] = cell;
587 } else if (cell.stamp < this.stamp) {
588 cell.stamp = this.stamp;
589 this._mods[this._mlen++] = cell;
590 }
591 return cell;
592 },
593 newcell(key, t) {
594 const cell = {
595 key: key,
596 num: 0,
597 agg: null,
598 tuple: this.newtuple(t, this._prev && this._prev[key]),
599 stamp: this.stamp,
600 store: false
601 };
602 if (!this._countOnly) {
603 const measures = this._measures,
604 n = measures.length;
605 cell.agg = Array(n);
606 for (let i = 0; i < n; ++i) {
607 cell.agg[i] = new measures[i](cell);
608 }
609 }
610 if (cell.store) {
611 cell.data = new TupleStore();
612 }
613 return cell;
614 },
615 newtuple(t, p) {
616 const names = this._dnames,
617 dims = this._dims,
618 n = dims.length,
619 x = {};
620 for (let i = 0; i < n; ++i) {
621 x[names[i]] = dims[i](t);
622 }
623 return p ? vegaDataflow.replace(p.tuple, x) : vegaDataflow.ingest(x);
624 },
625 clean() {
626 const cells = this.value;
627 for (const key in cells) {
628 if (cells[key].num === 0) {
629 delete cells[key];
630 }
631 }
632 },
633 // -- Process Tuples -----
634
635 add(t) {
636 const key = this.cellkey(t),
637 cell = this.cell(key, t);
638 cell.num += 1;
639 if (this._countOnly) return;
640 if (cell.store) cell.data.add(t);
641 const agg = cell.agg;
642 for (let i = 0, n = agg.length; i < n; ++i) {
643 agg[i].add(agg[i].get(t), t);
644 }
645 },
646 rem(t) {
647 const key = this.cellkey(t),
648 cell = this.cell(key, t);
649 cell.num -= 1;
650 if (this._countOnly) return;
651 if (cell.store) cell.data.rem(t);
652 const agg = cell.agg;
653 for (let i = 0, n = agg.length; i < n; ++i) {
654 agg[i].rem(agg[i].get(t), t);
655 }
656 },
657 celltuple(cell) {
658 const tuple = cell.tuple,
659 counts = this._counts;
660
661 // consolidate stored values
662 if (cell.store) {
663 cell.data.values();
664 }
665
666 // update tuple properties
667 for (let i = 0, n = counts.length; i < n; ++i) {
668 tuple[counts[i]] = cell.num;
669 }
670 if (!this._countOnly) {
671 const agg = cell.agg;
672 for (let i = 0, n = agg.length; i < n; ++i) {
673 agg[i].set(tuple);
674 }
675 }
676 return tuple;
677 },
678 changes(out) {
679 const adds = this._adds,
680 mods = this._mods,
681 prev = this._prev,
682 drop = this._drop,
683 add = out.add,
684 rem = out.rem,
685 mod = out.mod;
686 let cell, key, i, n;
687 if (prev) for (key in prev) {
688 cell = prev[key];
689 if (!drop || cell.num) rem.push(cell.tuple);
690 }
691 for (i = 0, n = this._alen; i < n; ++i) {
692 add.push(this.celltuple(adds[i]));
693 adds[i] = null; // for garbage collection
694 }
695
696 for (i = 0, n = this._mlen; i < n; ++i) {
697 cell = mods[i];
698 (cell.num === 0 && drop ? rem : mod).push(this.celltuple(cell));
699 mods[i] = null; // for garbage collection
700 }
701
702 this._alen = this._mlen = 0; // reset list of active cells
703 this._prev = null;
704 return out;
705 }
706 });
707
708 // epsilon bias to offset floating point error (#1737)
709 const EPSILON$1 = 1e-14;
710
711 /**
712 * Generates a binning function for discretizing data.
713 * @constructor
714 * @param {object} params - The parameters for this operator. The
715 * provided values should be valid options for the {@link bin} function.
716 * @param {function(object): *} params.field - The data field to bin.
717 */
718 function Bin(params) {
719 vegaDataflow.Transform.call(this, null, params);
720 }
721 Bin.Definition = {
722 'type': 'Bin',
723 'metadata': {
724 'modifies': true
725 },
726 'params': [{
727 'name': 'field',
728 'type': 'field',
729 'required': true
730 }, {
731 'name': 'interval',
732 'type': 'boolean',
733 'default': true
734 }, {
735 'name': 'anchor',
736 'type': 'number'
737 }, {
738 'name': 'maxbins',
739 'type': 'number',
740 'default': 20
741 }, {
742 'name': 'base',
743 'type': 'number',
744 'default': 10
745 }, {
746 'name': 'divide',
747 'type': 'number',
748 'array': true,
749 'default': [5, 2]
750 }, {
751 'name': 'extent',
752 'type': 'number',
753 'array': true,
754 'length': 2,
755 'required': true
756 }, {
757 'name': 'span',
758 'type': 'number'
759 }, {
760 'name': 'step',
761 'type': 'number'
762 }, {
763 'name': 'steps',
764 'type': 'number',
765 'array': true
766 }, {
767 'name': 'minstep',
768 'type': 'number',
769 'default': 0
770 }, {
771 'name': 'nice',
772 'type': 'boolean',
773 'default': true
774 }, {
775 'name': 'name',
776 'type': 'string'
777 }, {
778 'name': 'as',
779 'type': 'string',
780 'array': true,
781 'length': 2,
782 'default': ['bin0', 'bin1']
783 }]
784 };
785 vegaUtil.inherits(Bin, vegaDataflow.Transform, {
786 transform(_, pulse) {
787 const band = _.interval !== false,
788 bins = this._bins(_),
789 start = bins.start,
790 step = bins.step,
791 as = _.as || ['bin0', 'bin1'],
792 b0 = as[0],
793 b1 = as[1];
794 let flag;
795 if (_.modified()) {
796 pulse = pulse.reflow(true);
797 flag = pulse.SOURCE;
798 } else {
799 flag = pulse.modified(vegaUtil.accessorFields(_.field)) ? pulse.ADD_MOD : pulse.ADD;
800 }
801 pulse.visit(flag, band ? t => {
802 const v = bins(t);
803 // minimum bin value (inclusive)
804 t[b0] = v;
805 // maximum bin value (exclusive)
806 // use convoluted math for better floating point agreement
807 // see https://github.com/vega/vega/issues/830
808 // infinite values propagate through this formula! #2227
809 t[b1] = v == null ? null : start + step * (1 + (v - start) / step);
810 } : t => t[b0] = bins(t));
811 return pulse.modifies(band ? as : b0);
812 },
813 _bins(_) {
814 if (this.value && !_.modified()) {
815 return this.value;
816 }
817 const field = _.field,
818 bins = vegaStatistics.bin(_),
819 step = bins.step;
820 let start = bins.start,
821 stop = start + Math.ceil((bins.stop - start) / step) * step,
822 a,
823 d;
824 if ((a = _.anchor) != null) {
825 d = a - (start + step * Math.floor((a - start) / step));
826 start += d;
827 stop += d;
828 }
829 const f = function (t) {
830 let v = vegaUtil.toNumber(field(t));
831 return v == null ? null : v < start ? -Infinity : v > stop ? +Infinity : (v = Math.max(start, Math.min(v, stop - step)), start + step * Math.floor(EPSILON$1 + (v - start) / step));
832 };
833 f.start = start;
834 f.stop = bins.stop;
835 f.step = step;
836 return this.value = vegaUtil.accessor(f, vegaUtil.accessorFields(field), _.name || 'bin_' + vegaUtil.accessorName(field));
837 }
838 });
839
840 function SortedList (idFunc, source, input) {
841 const $ = idFunc;
842 let data = source || [],
843 add = input || [],
844 rem = {},
845 cnt = 0;
846 return {
847 add: t => add.push(t),
848 remove: t => rem[$(t)] = ++cnt,
849 size: () => data.length,
850 data: (compare, resort) => {
851 if (cnt) {
852 data = data.filter(t => !rem[$(t)]);
853 rem = {};
854 cnt = 0;
855 }
856 if (resort && compare) {
857 data.sort(compare);
858 }
859 if (add.length) {
860 data = compare ? vegaUtil.merge(compare, data, add.sort(compare)) : data.concat(add);
861 add = [];
862 }
863 return data;
864 }
865 };
866 }
867
868 /**
869 * Collects all data tuples that pass through this operator.
870 * @constructor
871 * @param {object} params - The parameters for this operator.
872 * @param {function(*,*): number} [params.sort] - An optional
873 * comparator function for additionally sorting the collected tuples.
874 */
875 function Collect(params) {
876 vegaDataflow.Transform.call(this, [], params);
877 }
878 Collect.Definition = {
879 'type': 'Collect',
880 'metadata': {
881 'source': true
882 },
883 'params': [{
884 'name': 'sort',
885 'type': 'compare'
886 }]
887 };
888 vegaUtil.inherits(Collect, vegaDataflow.Transform, {
889 transform(_, pulse) {
890 const out = pulse.fork(pulse.ALL),
891 list = SortedList(vegaDataflow.tupleid, this.value, out.materialize(out.ADD).add),
892 sort = _.sort,
893 mod = pulse.changed() || sort && (_.modified('sort') || pulse.modified(sort.fields));
894 out.visit(out.REM, list.remove);
895 this.modified(mod);
896 this.value = out.source = list.data(vegaDataflow.stableCompare(sort), mod);
897
898 // propagate tree root if defined
899 if (pulse.source && pulse.source.root) {
900 this.value.root = pulse.source.root;
901 }
902 return out;
903 }
904 });
905
906 /**
907 * Generates a comparator function.
908 * @constructor
909 * @param {object} params - The parameters for this operator.
910 * @param {Array<string|function>} params.fields - The fields to compare.
911 * @param {Array<string>} [params.orders] - The sort orders.
912 * Each entry should be one of "ascending" (default) or "descending".
913 */
914 function Compare(params) {
915 vegaDataflow.Operator.call(this, null, update$5, params);
916 }
917 vegaUtil.inherits(Compare, vegaDataflow.Operator);
918 function update$5(_) {
919 return this.value && !_.modified() ? this.value : vegaUtil.compare(_.fields, _.orders);
920 }
921
922 /**
923 * Count regexp-defined pattern occurrences in a text field.
924 * @constructor
925 * @param {object} params - The parameters for this operator.
926 * @param {function(object): *} params.field - An accessor for the text field.
927 * @param {string} [params.pattern] - RegExp string defining the text pattern.
928 * @param {string} [params.case] - One of 'lower', 'upper' or null (mixed) case.
929 * @param {string} [params.stopwords] - RegExp string of words to ignore.
930 */
931 function CountPattern(params) {
932 vegaDataflow.Transform.call(this, null, params);
933 }
934 CountPattern.Definition = {
935 'type': 'CountPattern',
936 'metadata': {
937 'generates': true,
938 'changes': true
939 },
940 'params': [{
941 'name': 'field',
942 'type': 'field',
943 'required': true
944 }, {
945 'name': 'case',
946 'type': 'enum',
947 'values': ['upper', 'lower', 'mixed'],
948 'default': 'mixed'
949 }, {
950 'name': 'pattern',
951 'type': 'string',
952 'default': '[\\w"]+'
953 }, {
954 'name': 'stopwords',
955 'type': 'string',
956 'default': ''
957 }, {
958 'name': 'as',
959 'type': 'string',
960 'array': true,
961 'length': 2,
962 'default': ['text', 'count']
963 }]
964 };
965 function tokenize(text, tcase, match) {
966 switch (tcase) {
967 case 'upper':
968 text = text.toUpperCase();
969 break;
970 case 'lower':
971 text = text.toLowerCase();
972 break;
973 }
974 return text.match(match);
975 }
976 vegaUtil.inherits(CountPattern, vegaDataflow.Transform, {
977 transform(_, pulse) {
978 const process = update => tuple => {
979 var tokens = tokenize(get(tuple), _.case, match) || [],
980 t;
981 for (var i = 0, n = tokens.length; i < n; ++i) {
982 if (!stop.test(t = tokens[i])) update(t);
983 }
984 };
985 const init = this._parameterCheck(_, pulse),
986 counts = this._counts,
987 match = this._match,
988 stop = this._stop,
989 get = _.field,
990 as = _.as || ['text', 'count'],
991 add = process(t => counts[t] = 1 + (counts[t] || 0)),
992 rem = process(t => counts[t] -= 1);
993 if (init) {
994 pulse.visit(pulse.SOURCE, add);
995 } else {
996 pulse.visit(pulse.ADD, add);
997 pulse.visit(pulse.REM, rem);
998 }
999 return this._finish(pulse, as); // generate output tuples
1000 },
1001
1002 _parameterCheck(_, pulse) {
1003 let init = false;
1004 if (_.modified('stopwords') || !this._stop) {
1005 this._stop = new RegExp('^' + (_.stopwords || '') + '$', 'i');
1006 init = true;
1007 }
1008 if (_.modified('pattern') || !this._match) {
1009 this._match = new RegExp(_.pattern || '[\\w\']+', 'g');
1010 init = true;
1011 }
1012 if (_.modified('field') || pulse.modified(_.field.fields)) {
1013 init = true;
1014 }
1015 if (init) this._counts = {};
1016 return init;
1017 },
1018 _finish(pulse, as) {
1019 const counts = this._counts,
1020 tuples = this._tuples || (this._tuples = {}),
1021 text = as[0],
1022 count = as[1],
1023 out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS);
1024 let w, t, c;
1025 for (w in counts) {
1026 t = tuples[w];
1027 c = counts[w] || 0;
1028 if (!t && c) {
1029 tuples[w] = t = vegaDataflow.ingest({});
1030 t[text] = w;
1031 t[count] = c;
1032 out.add.push(t);
1033 } else if (c === 0) {
1034 if (t) out.rem.push(t);
1035 counts[w] = null;
1036 tuples[w] = null;
1037 } else if (t[count] !== c) {
1038 t[count] = c;
1039 out.mod.push(t);
1040 }
1041 }
1042 return out.modifies(as);
1043 }
1044 });
1045
1046 /**
1047 * Perform a cross-product of a tuple stream with itself.
1048 * @constructor
1049 * @param {object} params - The parameters for this operator.
1050 * @param {function(object):boolean} [params.filter] - An optional filter
1051 * function for selectively including tuples in the cross product.
1052 * @param {Array<string>} [params.as] - The names of the output fields.
1053 */
1054 function Cross(params) {
1055 vegaDataflow.Transform.call(this, null, params);
1056 }
1057 Cross.Definition = {
1058 'type': 'Cross',
1059 'metadata': {
1060 'generates': true
1061 },
1062 'params': [{
1063 'name': 'filter',
1064 'type': 'expr'
1065 }, {
1066 'name': 'as',
1067 'type': 'string',
1068 'array': true,
1069 'length': 2,
1070 'default': ['a', 'b']
1071 }]
1072 };
1073 vegaUtil.inherits(Cross, vegaDataflow.Transform, {
1074 transform(_, pulse) {
1075 const out = pulse.fork(pulse.NO_SOURCE),
1076 as = _.as || ['a', 'b'],
1077 a = as[0],
1078 b = as[1],
1079 reset = !this.value || pulse.changed(pulse.ADD_REM) || _.modified('as') || _.modified('filter');
1080 let data = this.value;
1081 if (reset) {
1082 if (data) out.rem = data;
1083 data = pulse.materialize(pulse.SOURCE).source;
1084 out.add = this.value = cross(data, a, b, _.filter || vegaUtil.truthy);
1085 } else {
1086 out.mod = data;
1087 }
1088 out.source = this.value;
1089 return out.modifies(as);
1090 }
1091 });
1092 function cross(input, a, b, filter) {
1093 var data = [],
1094 t = {},
1095 n = input.length,
1096 i = 0,
1097 j,
1098 left;
1099 for (; i < n; ++i) {
1100 t[a] = left = input[i];
1101 for (j = 0; j < n; ++j) {
1102 t[b] = input[j];
1103 if (filter(t)) {
1104 data.push(vegaDataflow.ingest(t));
1105 t = {};
1106 t[a] = left;
1107 }
1108 }
1109 }
1110 return data;
1111 }
1112
1113 const Distributions = {
1114 kde: vegaStatistics.randomKDE,
1115 mixture: vegaStatistics.randomMixture,
1116 normal: vegaStatistics.randomNormal,
1117 lognormal: vegaStatistics.randomLogNormal,
1118 uniform: vegaStatistics.randomUniform
1119 };
1120 const DISTRIBUTIONS = 'distributions',
1121 FUNCTION = 'function',
1122 FIELD = 'field';
1123
1124 /**
1125 * Parse a parameter object for a probability distribution.
1126 * @param {object} def - The distribution parameter object.
1127 * @param {function():Array<object>} - A method for requesting
1128 * source data. Used for distributions (such as KDE) that
1129 * require sample data points. This method will only be
1130 * invoked if the 'from' parameter for a target data source
1131 * is not provided. Typically this method returns backing
1132 * source data for a Pulse object.
1133 * @return {object} - The output distribution object.
1134 */
1135 function parse(def, data) {
1136 const func = def[FUNCTION];
1137 if (!vegaUtil.hasOwnProperty(Distributions, func)) {
1138 vegaUtil.error('Unknown distribution function: ' + func);
1139 }
1140 const d = Distributions[func]();
1141 for (const name in def) {
1142 // if data field, extract values
1143 if (name === FIELD) {
1144 d.data((def.from || data()).map(def[name]));
1145 }
1146
1147 // if distribution mixture, recurse to parse each definition
1148 else if (name === DISTRIBUTIONS) {
1149 d[name](def[name].map(_ => parse(_, data)));
1150 }
1151
1152 // otherwise, simply set the parameter
1153 else if (typeof d[name] === FUNCTION) {
1154 d[name](def[name]);
1155 }
1156 }
1157 return d;
1158 }
1159
1160 /**
1161 * Grid sample points for a probability density. Given a distribution and
1162 * a sampling extent, will generate points suitable for plotting either
1163 * PDF (probability density function) or CDF (cumulative distribution
1164 * function) curves.
1165 * @constructor
1166 * @param {object} params - The parameters for this operator.
1167 * @param {object} params.distribution - The probability distribution. This
1168 * is an object parameter dependent on the distribution type.
1169 * @param {string} [params.method='pdf'] - The distribution method to sample.
1170 * One of 'pdf' or 'cdf'.
1171 * @param {Array<number>} [params.extent] - The [min, max] extent over which
1172 * to sample the distribution. This argument is required in most cases, but
1173 * can be omitted if the distribution (e.g., 'kde') supports a 'data' method
1174 * that returns numerical sample points from which the extent can be deduced.
1175 * @param {number} [params.minsteps=25] - The minimum number of curve samples
1176 * for plotting the density.
1177 * @param {number} [params.maxsteps=200] - The maximum number of curve samples
1178 * for plotting the density.
1179 * @param {number} [params.steps] - The exact number of curve samples for
1180 * plotting the density. If specified, overrides both minsteps and maxsteps
1181 * to set an exact number of uniform samples. Useful in conjunction with
1182 * a fixed extent to ensure consistent sample points for stacked densities.
1183 */
1184 function Density(params) {
1185 vegaDataflow.Transform.call(this, null, params);
1186 }
1187 const distributions = [{
1188 'key': {
1189 'function': 'normal'
1190 },
1191 'params': [{
1192 'name': 'mean',
1193 'type': 'number',
1194 'default': 0
1195 }, {
1196 'name': 'stdev',
1197 'type': 'number',
1198 'default': 1
1199 }]
1200 }, {
1201 'key': {
1202 'function': 'lognormal'
1203 },
1204 'params': [{
1205 'name': 'mean',
1206 'type': 'number',
1207 'default': 0
1208 }, {
1209 'name': 'stdev',
1210 'type': 'number',
1211 'default': 1
1212 }]
1213 }, {
1214 'key': {
1215 'function': 'uniform'
1216 },
1217 'params': [{
1218 'name': 'min',
1219 'type': 'number',
1220 'default': 0
1221 }, {
1222 'name': 'max',
1223 'type': 'number',
1224 'default': 1
1225 }]
1226 }, {
1227 'key': {
1228 'function': 'kde'
1229 },
1230 'params': [{
1231 'name': 'field',
1232 'type': 'field',
1233 'required': true
1234 }, {
1235 'name': 'from',
1236 'type': 'data'
1237 }, {
1238 'name': 'bandwidth',
1239 'type': 'number',
1240 'default': 0
1241 }]
1242 }];
1243 const mixture = {
1244 'key': {
1245 'function': 'mixture'
1246 },
1247 'params': [{
1248 'name': 'distributions',
1249 'type': 'param',
1250 'array': true,
1251 'params': distributions
1252 }, {
1253 'name': 'weights',
1254 'type': 'number',
1255 'array': true
1256 }]
1257 };
1258 Density.Definition = {
1259 'type': 'Density',
1260 'metadata': {
1261 'generates': true
1262 },
1263 'params': [{
1264 'name': 'extent',
1265 'type': 'number',
1266 'array': true,
1267 'length': 2
1268 }, {
1269 'name': 'steps',
1270 'type': 'number'
1271 }, {
1272 'name': 'minsteps',
1273 'type': 'number',
1274 'default': 25
1275 }, {
1276 'name': 'maxsteps',
1277 'type': 'number',
1278 'default': 200
1279 }, {
1280 'name': 'method',
1281 'type': 'string',
1282 'default': 'pdf',
1283 'values': ['pdf', 'cdf']
1284 }, {
1285 'name': 'distribution',
1286 'type': 'param',
1287 'params': distributions.concat(mixture)
1288 }, {
1289 'name': 'as',
1290 'type': 'string',
1291 'array': true,
1292 'default': ['value', 'density']
1293 }]
1294 };
1295 vegaUtil.inherits(Density, vegaDataflow.Transform, {
1296 transform(_, pulse) {
1297 const out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS);
1298 if (!this.value || pulse.changed() || _.modified()) {
1299 const dist = parse(_.distribution, source(pulse)),
1300 minsteps = _.steps || _.minsteps || 25,
1301 maxsteps = _.steps || _.maxsteps || 200;
1302 let method = _.method || 'pdf';
1303 if (method !== 'pdf' && method !== 'cdf') {
1304 vegaUtil.error('Invalid density method: ' + method);
1305 }
1306 if (!_.extent && !dist.data) {
1307 vegaUtil.error('Missing density extent parameter.');
1308 }
1309 method = dist[method];
1310 const as = _.as || ['value', 'density'],
1311 domain = _.extent || vegaUtil.extent(dist.data()),
1312 values = vegaStatistics.sampleCurve(method, domain, minsteps, maxsteps).map(v => {
1313 const tuple = {};
1314 tuple[as[0]] = v[0];
1315 tuple[as[1]] = v[1];
1316 return vegaDataflow.ingest(tuple);
1317 });
1318 if (this.value) out.rem = this.value;
1319 this.value = out.add = out.source = values;
1320 }
1321 return out;
1322 }
1323 });
1324 function source(pulse) {
1325 return () => pulse.materialize(pulse.SOURCE).source;
1326 }
1327
1328 // use either provided alias or accessor field name
1329 function fieldNames(fields, as) {
1330 if (!fields) return null;
1331 return fields.map((f, i) => as[i] || vegaUtil.accessorName(f));
1332 }
1333 function partition$1(data, groupby, field) {
1334 const groups = [],
1335 get = f => f(t);
1336 let map, i, n, t, k, g;
1337
1338 // partition data points into groups
1339 if (groupby == null) {
1340 groups.push(data.map(field));
1341 } else {
1342 for (map = {}, i = 0, n = data.length; i < n; ++i) {
1343 t = data[i];
1344 k = groupby.map(get);
1345 g = map[k];
1346 if (!g) {
1347 map[k] = g = [];
1348 g.dims = k;
1349 groups.push(g);
1350 }
1351 g.push(field(t));
1352 }
1353 }
1354 return groups;
1355 }
1356
1357 const Output = 'bin';
1358
1359 /**
1360 * Dot density binning for dot plot construction.
1361 * Based on Leland Wilkinson, Dot Plots, The American Statistician, 1999.
1362 * https://www.cs.uic.edu/~wilkinson/Publications/dotplots.pdf
1363 * @constructor
1364 * @param {object} params - The parameters for this operator.
1365 * @param {function(object): *} params.field - The value field to bin.
1366 * @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby.
1367 * @param {number} [params.step] - The step size (bin width) within which dots should be
1368 * stacked. Defaults to 1/30 of the extent of the data *field*.
1369 * @param {boolean} [params.smooth=false] - A boolean flag indicating if dot density
1370 * stacks should be smoothed to reduce variance.
1371 */
1372 function DotBin(params) {
1373 vegaDataflow.Transform.call(this, null, params);
1374 }
1375 DotBin.Definition = {
1376 'type': 'DotBin',
1377 'metadata': {
1378 'modifies': true
1379 },
1380 'params': [{
1381 'name': 'field',
1382 'type': 'field',
1383 'required': true
1384 }, {
1385 'name': 'groupby',
1386 'type': 'field',
1387 'array': true
1388 }, {
1389 'name': 'step',
1390 'type': 'number'
1391 }, {
1392 'name': 'smooth',
1393 'type': 'boolean',
1394 'default': false
1395 }, {
1396 'name': 'as',
1397 'type': 'string',
1398 'default': Output
1399 }]
1400 };
1401 const autostep = (data, field) => vegaUtil.span(vegaUtil.extent(data, field)) / 30;
1402 vegaUtil.inherits(DotBin, vegaDataflow.Transform, {
1403 transform(_, pulse) {
1404 if (this.value && !(_.modified() || pulse.changed())) {
1405 return pulse; // early exit
1406 }
1407
1408 const source = pulse.materialize(pulse.SOURCE).source,
1409 groups = partition$1(pulse.source, _.groupby, vegaUtil.identity),
1410 smooth = _.smooth || false,
1411 field = _.field,
1412 step = _.step || autostep(source, field),
1413 sort = vegaDataflow.stableCompare((a, b) => field(a) - field(b)),
1414 as = _.as || Output,
1415 n = groups.length;
1416
1417 // compute dotplot bins per group
1418 let min = Infinity,
1419 max = -Infinity,
1420 i = 0,
1421 j;
1422 for (; i < n; ++i) {
1423 const g = groups[i].sort(sort);
1424 j = -1;
1425 for (const v of vegaStatistics.dotbin(g, step, smooth, field)) {
1426 if (v < min) min = v;
1427 if (v > max) max = v;
1428 g[++j][as] = v;
1429 }
1430 }
1431 this.value = {
1432 start: min,
1433 stop: max,
1434 step: step
1435 };
1436 return pulse.reflow(true).modifies(as);
1437 }
1438 });
1439
1440 /**
1441 * Wraps an expression function with access to external parameters.
1442 * @constructor
1443 * @param {object} params - The parameters for this operator.
1444 * @param {function} params.expr - The expression function. The
1445 * function should accept both a datum and a parameter object.
1446 * This operator's value will be a new function that wraps the
1447 * expression function with access to this operator's parameters.
1448 */
1449 function Expression(params) {
1450 vegaDataflow.Operator.call(this, null, update$4, params);
1451 this.modified(true);
1452 }
1453 vegaUtil.inherits(Expression, vegaDataflow.Operator);
1454 function update$4(_) {
1455 const expr = _.expr;
1456 return this.value && !_.modified('expr') ? this.value : vegaUtil.accessor(datum => expr(datum, _), vegaUtil.accessorFields(expr), vegaUtil.accessorName(expr));
1457 }
1458
1459 /**
1460 * Computes extents (min/max) for a data field.
1461 * @constructor
1462 * @param {object} params - The parameters for this operator.
1463 * @param {function(object): *} params.field - The field over which to compute extends.
1464 */
1465 function Extent(params) {
1466 vegaDataflow.Transform.call(this, [undefined, undefined], params);
1467 }
1468 Extent.Definition = {
1469 'type': 'Extent',
1470 'metadata': {},
1471 'params': [{
1472 'name': 'field',
1473 'type': 'field',
1474 'required': true
1475 }]
1476 };
1477 vegaUtil.inherits(Extent, vegaDataflow.Transform, {
1478 transform(_, pulse) {
1479 const extent = this.value,
1480 field = _.field,
1481 mod = pulse.changed() || pulse.modified(field.fields) || _.modified('field');
1482 let min = extent[0],
1483 max = extent[1];
1484 if (mod || min == null) {
1485 min = +Infinity;
1486 max = -Infinity;
1487 }
1488 pulse.visit(mod ? pulse.SOURCE : pulse.ADD, t => {
1489 const v = vegaUtil.toNumber(field(t));
1490 if (v != null) {
1491 // NaNs will fail all comparisons!
1492 if (v < min) min = v;
1493 if (v > max) max = v;
1494 }
1495 });
1496 if (!Number.isFinite(min) || !Number.isFinite(max)) {
1497 let name = vegaUtil.accessorName(field);
1498 if (name) name = ` for field "${name}"`;
1499 pulse.dataflow.warn(`Infinite extent${name}: [${min}, ${max}]`);
1500 min = max = undefined;
1501 }
1502 this.value = [min, max];
1503 }
1504 });
1505
1506 /**
1507 * Provides a bridge between a parent transform and a target subflow that
1508 * consumes only a subset of the tuples that pass through the parent.
1509 * @constructor
1510 * @param {Pulse} pulse - A pulse to use as the value of this operator.
1511 * @param {Transform} parent - The parent transform (typically a Facet instance).
1512 */
1513 function Subflow(pulse, parent) {
1514 vegaDataflow.Operator.call(this, pulse);
1515 this.parent = parent;
1516 this.count = 0;
1517 }
1518 vegaUtil.inherits(Subflow, vegaDataflow.Operator, {
1519 /**
1520 * Routes pulses from this subflow to a target transform.
1521 * @param {Transform} target - A transform that receives the subflow of tuples.
1522 */
1523 connect(target) {
1524 this.detachSubflow = target.detachSubflow;
1525 this.targets().add(target);
1526 return target.source = this;
1527 },
1528 /**
1529 * Add an 'add' tuple to the subflow pulse.
1530 * @param {Tuple} t - The tuple being added.
1531 */
1532 add(t) {
1533 this.count += 1;
1534 this.value.add.push(t);
1535 },
1536 /**
1537 * Add a 'rem' tuple to the subflow pulse.
1538 * @param {Tuple} t - The tuple being removed.
1539 */
1540 rem(t) {
1541 this.count -= 1;
1542 this.value.rem.push(t);
1543 },
1544 /**
1545 * Add a 'mod' tuple to the subflow pulse.
1546 * @param {Tuple} t - The tuple being modified.
1547 */
1548 mod(t) {
1549 this.value.mod.push(t);
1550 },
1551 /**
1552 * Re-initialize this operator's pulse value.
1553 * @param {Pulse} pulse - The pulse to copy from.
1554 * @see Pulse.init
1555 */
1556 init(pulse) {
1557 this.value.init(pulse, pulse.NO_SOURCE);
1558 },
1559 /**
1560 * Evaluate this operator. This method overrides the
1561 * default behavior to simply return the contained pulse value.
1562 * @return {Pulse}
1563 */
1564 evaluate() {
1565 // assert: this.value.stamp === pulse.stamp
1566 return this.value;
1567 }
1568 });
1569
1570 /**
1571 * Facets a dataflow into a set of subflows based on a key.
1572 * @constructor
1573 * @param {object} params - The parameters for this operator.
1574 * @param {function(Dataflow, string): Operator} params.subflow - A function
1575 * that generates a subflow of operators and returns its root operator.
1576 * @param {function(object): *} params.key - The key field to facet by.
1577 */
1578 function Facet(params) {
1579 vegaDataflow.Transform.call(this, {}, params);
1580 this._keys = vegaUtil.fastmap(); // cache previously calculated key values
1581
1582 // keep track of active subflows, use as targets array for listeners
1583 // this allows us to limit propagation to only updated subflows
1584 const a = this._targets = [];
1585 a.active = 0;
1586 a.forEach = f => {
1587 for (let i = 0, n = a.active; i < n; ++i) {
1588 f(a[i], i, a);
1589 }
1590 };
1591 }
1592 vegaUtil.inherits(Facet, vegaDataflow.Transform, {
1593 activate(flow) {
1594 this._targets[this._targets.active++] = flow;
1595 },
1596 // parent argument provided by PreFacet subclass
1597 subflow(key, flow, pulse, parent) {
1598 const flows = this.value;
1599 let sf = vegaUtil.hasOwnProperty(flows, key) && flows[key],
1600 df,
1601 p;
1602 if (!sf) {
1603 p = parent || (p = this._group[key]) && p.tuple;
1604 df = pulse.dataflow;
1605 sf = new Subflow(pulse.fork(pulse.NO_SOURCE), this);
1606 df.add(sf).connect(flow(df, key, p));
1607 flows[key] = sf;
1608 this.activate(sf);
1609 } else if (sf.value.stamp < pulse.stamp) {
1610 sf.init(pulse);
1611 this.activate(sf);
1612 }
1613 return sf;
1614 },
1615 clean() {
1616 const flows = this.value;
1617 let detached = 0;
1618 for (const key in flows) {
1619 if (flows[key].count === 0) {
1620 const detach = flows[key].detachSubflow;
1621 if (detach) detach();
1622 delete flows[key];
1623 ++detached;
1624 }
1625 }
1626
1627 // remove inactive targets from the active targets array
1628 if (detached) {
1629 const active = this._targets.filter(sf => sf && sf.count > 0);
1630 this.initTargets(active);
1631 }
1632 },
1633 initTargets(act) {
1634 const a = this._targets,
1635 n = a.length,
1636 m = act ? act.length : 0;
1637 let i = 0;
1638 for (; i < m; ++i) {
1639 a[i] = act[i];
1640 }
1641 for (; i < n && a[i] != null; ++i) {
1642 a[i] = null; // ensure old flows can be garbage collected
1643 }
1644
1645 a.active = m;
1646 },
1647 transform(_, pulse) {
1648 const df = pulse.dataflow,
1649 key = _.key,
1650 flow = _.subflow,
1651 cache = this._keys,
1652 rekey = _.modified('key'),
1653 subflow = key => this.subflow(key, flow, pulse);
1654 this._group = _.group || {};
1655 this.initTargets(); // reset list of active subflows
1656
1657 pulse.visit(pulse.REM, t => {
1658 const id = vegaDataflow.tupleid(t),
1659 k = cache.get(id);
1660 if (k !== undefined) {
1661 cache.delete(id);
1662 subflow(k).rem(t);
1663 }
1664 });
1665 pulse.visit(pulse.ADD, t => {
1666 const k = key(t);
1667 cache.set(vegaDataflow.tupleid(t), k);
1668 subflow(k).add(t);
1669 });
1670 if (rekey || pulse.modified(key.fields)) {
1671 pulse.visit(pulse.MOD, t => {
1672 const id = vegaDataflow.tupleid(t),
1673 k0 = cache.get(id),
1674 k1 = key(t);
1675 if (k0 === k1) {
1676 subflow(k1).mod(t);
1677 } else {
1678 cache.set(id, k1);
1679 subflow(k0).rem(t);
1680 subflow(k1).add(t);
1681 }
1682 });
1683 } else if (pulse.changed(pulse.MOD)) {
1684 pulse.visit(pulse.MOD, t => {
1685 subflow(cache.get(vegaDataflow.tupleid(t))).mod(t);
1686 });
1687 }
1688 if (rekey) {
1689 pulse.visit(pulse.REFLOW, t => {
1690 const id = vegaDataflow.tupleid(t),
1691 k0 = cache.get(id),
1692 k1 = key(t);
1693 if (k0 !== k1) {
1694 cache.set(id, k1);
1695 subflow(k0).rem(t);
1696 subflow(k1).add(t);
1697 }
1698 });
1699 }
1700 if (pulse.clean()) {
1701 df.runAfter(() => {
1702 this.clean();
1703 cache.clean();
1704 });
1705 } else if (cache.empty > df.cleanThreshold) {
1706 df.runAfter(cache.clean);
1707 }
1708 return pulse;
1709 }
1710 });
1711
1712 /**
1713 * Generates one or more field accessor functions.
1714 * If the 'name' parameter is an array, an array of field accessors
1715 * will be created and the 'as' parameter will be ignored.
1716 * @constructor
1717 * @param {object} params - The parameters for this operator.
1718 * @param {string} params.name - The field name(s) to access.
1719 * @param {string} params.as - The accessor function name.
1720 */
1721 function Field(params) {
1722 vegaDataflow.Operator.call(this, null, update$3, params);
1723 }
1724 vegaUtil.inherits(Field, vegaDataflow.Operator);
1725 function update$3(_) {
1726 return this.value && !_.modified() ? this.value : vegaUtil.isArray(_.name) ? vegaUtil.array(_.name).map(f => vegaUtil.field(f)) : vegaUtil.field(_.name, _.as);
1727 }
1728
1729 /**
1730 * Filters data tuples according to a predicate function.
1731 * @constructor
1732 * @param {object} params - The parameters for this operator.
1733 * @param {function(object): *} params.expr - The predicate expression function
1734 * that determines a tuple's filter status. Truthy values pass the filter.
1735 */
1736 function Filter(params) {
1737 vegaDataflow.Transform.call(this, vegaUtil.fastmap(), params);
1738 }
1739 Filter.Definition = {
1740 'type': 'Filter',
1741 'metadata': {
1742 'changes': true
1743 },
1744 'params': [{
1745 'name': 'expr',
1746 'type': 'expr',
1747 'required': true
1748 }]
1749 };
1750 vegaUtil.inherits(Filter, vegaDataflow.Transform, {
1751 transform(_, pulse) {
1752 const df = pulse.dataflow,
1753 cache = this.value,
1754 // cache ids of filtered tuples
1755 output = pulse.fork(),
1756 add = output.add,
1757 rem = output.rem,
1758 mod = output.mod,
1759 test = _.expr;
1760 let isMod = true;
1761 pulse.visit(pulse.REM, t => {
1762 const id = vegaDataflow.tupleid(t);
1763 if (!cache.has(id)) rem.push(t);else cache.delete(id);
1764 });
1765 pulse.visit(pulse.ADD, t => {
1766 if (test(t, _)) add.push(t);else cache.set(vegaDataflow.tupleid(t), 1);
1767 });
1768 function revisit(t) {
1769 const id = vegaDataflow.tupleid(t),
1770 b = test(t, _),
1771 s = cache.get(id);
1772 if (b && s) {
1773 cache.delete(id);
1774 add.push(t);
1775 } else if (!b && !s) {
1776 cache.set(id, 1);
1777 rem.push(t);
1778 } else if (isMod && b && !s) {
1779 mod.push(t);
1780 }
1781 }
1782 pulse.visit(pulse.MOD, revisit);
1783 if (_.modified()) {
1784 isMod = false;
1785 pulse.visit(pulse.REFLOW, revisit);
1786 }
1787 if (cache.empty > df.cleanThreshold) df.runAfter(cache.clean);
1788 return output;
1789 }
1790 });
1791
1792 /**
1793 * Flattens array-typed field values into new data objects.
1794 * If multiple fields are specified, they are treated as parallel arrays,
1795 * with output values included for each matching index (or null if missing).
1796 * @constructor
1797 * @param {object} params - The parameters for this operator.
1798 * @param {Array<function(object): *>} params.fields - An array of field
1799 * accessors for the tuple fields that should be flattened.
1800 * @param {string} [params.index] - Optional output field name for index
1801 * value. If unspecified, no index field is included in the output.
1802 * @param {Array<string>} [params.as] - Output field names for flattened
1803 * array fields. Any unspecified fields will use the field name provided
1804 * by the fields accessors.
1805 */
1806 function Flatten(params) {
1807 vegaDataflow.Transform.call(this, [], params);
1808 }
1809 Flatten.Definition = {
1810 'type': 'Flatten',
1811 'metadata': {
1812 'generates': true
1813 },
1814 'params': [{
1815 'name': 'fields',
1816 'type': 'field',
1817 'array': true,
1818 'required': true
1819 }, {
1820 'name': 'index',
1821 'type': 'string'
1822 }, {
1823 'name': 'as',
1824 'type': 'string',
1825 'array': true
1826 }]
1827 };
1828 vegaUtil.inherits(Flatten, vegaDataflow.Transform, {
1829 transform(_, pulse) {
1830 const out = pulse.fork(pulse.NO_SOURCE),
1831 fields = _.fields,
1832 as = fieldNames(fields, _.as || []),
1833 index = _.index || null,
1834 m = as.length;
1835
1836 // remove any previous results
1837 out.rem = this.value;
1838
1839 // generate flattened tuples
1840 pulse.visit(pulse.SOURCE, t => {
1841 const arrays = fields.map(f => f(t)),
1842 maxlen = arrays.reduce((l, a) => Math.max(l, a.length), 0);
1843 let i = 0,
1844 j,
1845 d,
1846 v;
1847 for (; i < maxlen; ++i) {
1848 d = vegaDataflow.derive(t);
1849 for (j = 0; j < m; ++j) {
1850 d[as[j]] = (v = arrays[j][i]) == null ? null : v;
1851 }
1852 if (index) {
1853 d[index] = i;
1854 }
1855 out.add.push(d);
1856 }
1857 });
1858 this.value = out.source = out.add;
1859 if (index) out.modifies(index);
1860 return out.modifies(as);
1861 }
1862 });
1863
1864 /**
1865 * Folds one more tuple fields into multiple tuples in which the field
1866 * name and values are available under new 'key' and 'value' fields.
1867 * @constructor
1868 * @param {object} params - The parameters for this operator.
1869 * @param {function(object): *} params.fields - An array of field accessors
1870 * for the tuple fields that should be folded.
1871 * @param {Array<string>} [params.as] - Output field names for folded key
1872 * and value fields, defaults to ['key', 'value'].
1873 */
1874 function Fold(params) {
1875 vegaDataflow.Transform.call(this, [], params);
1876 }
1877 Fold.Definition = {
1878 'type': 'Fold',
1879 'metadata': {
1880 'generates': true
1881 },
1882 'params': [{
1883 'name': 'fields',
1884 'type': 'field',
1885 'array': true,
1886 'required': true
1887 }, {
1888 'name': 'as',
1889 'type': 'string',
1890 'array': true,
1891 'length': 2,
1892 'default': ['key', 'value']
1893 }]
1894 };
1895 vegaUtil.inherits(Fold, vegaDataflow.Transform, {
1896 transform(_, pulse) {
1897 const out = pulse.fork(pulse.NO_SOURCE),
1898 fields = _.fields,
1899 fnames = fields.map(vegaUtil.accessorName),
1900 as = _.as || ['key', 'value'],
1901 k = as[0],
1902 v = as[1],
1903 n = fields.length;
1904 out.rem = this.value;
1905 pulse.visit(pulse.SOURCE, t => {
1906 for (let i = 0, d; i < n; ++i) {
1907 d = vegaDataflow.derive(t);
1908 d[k] = fnames[i];
1909 d[v] = fields[i](t);
1910 out.add.push(d);
1911 }
1912 });
1913 this.value = out.source = out.add;
1914 return out.modifies(as);
1915 }
1916 });
1917
1918 /**
1919 * Invokes a function for each data tuple and saves the results as a new field.
1920 * @constructor
1921 * @param {object} params - The parameters for this operator.
1922 * @param {function(object): *} params.expr - The formula function to invoke for each tuple.
1923 * @param {string} params.as - The field name under which to save the result.
1924 * @param {boolean} [params.initonly=false] - If true, the formula is applied to
1925 * added tuples only, and does not update in response to modifications.
1926 */
1927 function Formula(params) {
1928 vegaDataflow.Transform.call(this, null, params);
1929 }
1930 Formula.Definition = {
1931 'type': 'Formula',
1932 'metadata': {
1933 'modifies': true
1934 },
1935 'params': [{
1936 'name': 'expr',
1937 'type': 'expr',
1938 'required': true
1939 }, {
1940 'name': 'as',
1941 'type': 'string',
1942 'required': true
1943 }, {
1944 'name': 'initonly',
1945 'type': 'boolean'
1946 }]
1947 };
1948 vegaUtil.inherits(Formula, vegaDataflow.Transform, {
1949 transform(_, pulse) {
1950 const func = _.expr,
1951 as = _.as,
1952 mod = _.modified(),
1953 flag = _.initonly ? pulse.ADD : mod ? pulse.SOURCE : pulse.modified(func.fields) || pulse.modified(as) ? pulse.ADD_MOD : pulse.ADD;
1954 if (mod) {
1955 // parameters updated, need to reflow
1956 pulse = pulse.materialize().reflow(true);
1957 }
1958 if (!_.initonly) {
1959 pulse.modifies(as);
1960 }
1961 return pulse.visit(flag, t => t[as] = func(t, _));
1962 }
1963 });
1964
1965 /**
1966 * Generates data tuples using a provided generator function.
1967 * @constructor
1968 * @param {object} params - The parameters for this operator.
1969 * @param {function(Parameters): object} params.generator - A tuple generator
1970 * function. This function is given the operator parameters as input.
1971 * Changes to any additional parameters will not trigger re-calculation
1972 * of previously generated tuples. Only future tuples are affected.
1973 * @param {number} params.size - The number of tuples to produce.
1974 */
1975 function Generate(params) {
1976 vegaDataflow.Transform.call(this, [], params);
1977 }
1978 vegaUtil.inherits(Generate, vegaDataflow.Transform, {
1979 transform(_, pulse) {
1980 const out = pulse.fork(pulse.ALL),
1981 gen = _.generator;
1982 let data = this.value,
1983 num = _.size - data.length,
1984 add,
1985 rem,
1986 t;
1987 if (num > 0) {
1988 // need more tuples, generate and add
1989 for (add = []; --num >= 0;) {
1990 add.push(t = vegaDataflow.ingest(gen(_)));
1991 data.push(t);
1992 }
1993 out.add = out.add.length ? out.materialize(out.ADD).add.concat(add) : add;
1994 } else {
1995 // need fewer tuples, remove
1996 rem = data.slice(0, -num);
1997 out.rem = out.rem.length ? out.materialize(out.REM).rem.concat(rem) : rem;
1998 data = data.slice(-num);
1999 }
2000 out.source = this.value = data;
2001 return out;
2002 }
2003 });
2004
2005 function ascending(a, b) {
2006 return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
2007 }
2008
2009 function descending(a, b) {
2010 return a == null || b == null ? NaN : b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
2011 }
2012
2013 function bisector(f) {
2014 let compare1, compare2, delta;
2015
2016 // If an accessor is specified, promote it to a comparator. In this case we
2017 // can test whether the search value is (self-) comparable. We can’t do this
2018 // for a comparator (except for specific, known comparators) because we can’t
2019 // tell if the comparator is symmetric, and an asymmetric comparator can’t be
2020 // used to test whether a single value is comparable.
2021 if (f.length !== 2) {
2022 compare1 = ascending;
2023 compare2 = (d, x) => ascending(f(d), x);
2024 delta = (d, x) => f(d) - x;
2025 } else {
2026 compare1 = f === ascending || f === descending ? f : zero;
2027 compare2 = f;
2028 delta = f;
2029 }
2030 function left(a, x, lo = 0, hi = a.length) {
2031 if (lo < hi) {
2032 if (compare1(x, x) !== 0) return hi;
2033 do {
2034 const mid = lo + hi >>> 1;
2035 if (compare2(a[mid], x) < 0) lo = mid + 1;else hi = mid;
2036 } while (lo < hi);
2037 }
2038 return lo;
2039 }
2040 function right(a, x, lo = 0, hi = a.length) {
2041 if (lo < hi) {
2042 if (compare1(x, x) !== 0) return hi;
2043 do {
2044 const mid = lo + hi >>> 1;
2045 if (compare2(a[mid], x) <= 0) lo = mid + 1;else hi = mid;
2046 } while (lo < hi);
2047 }
2048 return lo;
2049 }
2050 function center(a, x, lo = 0, hi = a.length) {
2051 const i = left(a, x, lo, hi - 1);
2052 return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i;
2053 }
2054 return {
2055 left,
2056 center,
2057 right
2058 };
2059 }
2060 function zero() {
2061 return 0;
2062 }
2063
2064 function* numbers(values, valueof) {
2065 if (valueof === undefined) {
2066 for (let value of values) {
2067 if (value != null && (value = +value) >= value) {
2068 yield value;
2069 }
2070 }
2071 } else {
2072 let index = -1;
2073 for (let value of values) {
2074 if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) {
2075 yield value;
2076 }
2077 }
2078 }
2079 }
2080
2081 function compareDefined(compare = ascending) {
2082 if (compare === ascending) return ascendingDefined;
2083 if (typeof compare !== "function") throw new TypeError("compare is not a function");
2084 return (a, b) => {
2085 const x = compare(a, b);
2086 if (x || x === 0) return x;
2087 return (compare(b, b) === 0) - (compare(a, a) === 0);
2088 };
2089 }
2090 function ascendingDefined(a, b) {
2091 return (a == null || !(a >= a)) - (b == null || !(b >= b)) || (a < b ? -1 : a > b ? 1 : 0);
2092 }
2093
2094 function max(values, valueof) {
2095 let max;
2096 if (valueof === undefined) {
2097 for (const value of values) {
2098 if (value != null && (max < value || max === undefined && value >= value)) {
2099 max = value;
2100 }
2101 }
2102 } else {
2103 let index = -1;
2104 for (let value of values) {
2105 if ((value = valueof(value, ++index, values)) != null && (max < value || max === undefined && value >= value)) {
2106 max = value;
2107 }
2108 }
2109 }
2110 return max;
2111 }
2112
2113 function min(values, valueof) {
2114 let min;
2115 if (valueof === undefined) {
2116 for (const value of values) {
2117 if (value != null && (min > value || min === undefined && value >= value)) {
2118 min = value;
2119 }
2120 }
2121 } else {
2122 let index = -1;
2123 for (let value of values) {
2124 if ((value = valueof(value, ++index, values)) != null && (min > value || min === undefined && value >= value)) {
2125 min = value;
2126 }
2127 }
2128 }
2129 return min;
2130 }
2131
2132 // Based on https://github.com/mourner/quickselect
2133 // ISC license, Copyright 2018 Vladimir Agafonkin.
2134 function quickselect(array, k, left = 0, right = Infinity, compare) {
2135 k = Math.floor(k);
2136 left = Math.floor(Math.max(0, left));
2137 right = Math.floor(Math.min(array.length - 1, right));
2138 if (!(left <= k && k <= right)) return array;
2139 compare = compare === undefined ? ascendingDefined : compareDefined(compare);
2140 while (right > left) {
2141 if (right - left > 600) {
2142 const n = right - left + 1;
2143 const m = k - left + 1;
2144 const z = Math.log(n);
2145 const s = 0.5 * Math.exp(2 * z / 3);
2146 const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
2147 const newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
2148 const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
2149 quickselect(array, k, newLeft, newRight, compare);
2150 }
2151 const t = array[k];
2152 let i = left;
2153 let j = right;
2154 swap(array, left, k);
2155 if (compare(array[right], t) > 0) swap(array, left, right);
2156 while (i < j) {
2157 swap(array, i, j), ++i, --j;
2158 while (compare(array[i], t) < 0) ++i;
2159 while (compare(array[j], t) > 0) --j;
2160 }
2161 if (compare(array[left], t) === 0) swap(array, left, j);else ++j, swap(array, j, right);
2162 if (j <= k) left = j + 1;
2163 if (k <= j) right = j - 1;
2164 }
2165 return array;
2166 }
2167 function swap(array, i, j) {
2168 const t = array[i];
2169 array[i] = array[j];
2170 array[j] = t;
2171 }
2172
2173 function quantile(values, p, valueof) {
2174 values = Float64Array.from(numbers(values, valueof));
2175 if (!(n = values.length) || isNaN(p = +p)) return;
2176 if (p <= 0 || n < 2) return min(values);
2177 if (p >= 1) return max(values);
2178 var n,
2179 i = (n - 1) * p,
2180 i0 = Math.floor(i),
2181 value0 = max(quickselect(values, i0).subarray(0, i0 + 1)),
2182 value1 = min(values.subarray(i0 + 1));
2183 return value0 + (value1 - value0) * (i - i0);
2184 }
2185
2186 function mean(values, valueof) {
2187 let count = 0;
2188 let sum = 0;
2189 if (valueof === undefined) {
2190 for (let value of values) {
2191 if (value != null && (value = +value) >= value) {
2192 ++count, sum += value;
2193 }
2194 }
2195 } else {
2196 let index = -1;
2197 for (let value of values) {
2198 if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) {
2199 ++count, sum += value;
2200 }
2201 }
2202 }
2203 if (count) return sum / count;
2204 }
2205
2206 function median(values, valueof) {
2207 return quantile(values, 0.5, valueof);
2208 }
2209
2210 function range(start, stop, step) {
2211 start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;
2212 var i = -1,
2213 n = Math.max(0, Math.ceil((stop - start) / step)) | 0,
2214 range = new Array(n);
2215 while (++i < n) {
2216 range[i] = start + i * step;
2217 }
2218 return range;
2219 }
2220
2221 const Methods = {
2222 value: 'value',
2223 median: median,
2224 mean: mean,
2225 min: min,
2226 max: max
2227 };
2228 const Empty = [];
2229
2230 /**
2231 * Impute missing values.
2232 * @constructor
2233 * @param {object} params - The parameters for this operator.
2234 * @param {function(object): *} params.field - The value field to impute.
2235 * @param {Array<function(object): *>} [params.groupby] - An array of
2236 * accessors to determine series within which to perform imputation.
2237 * @param {function(object): *} params.key - An accessor for a key value.
2238 * Each key value should be unique within a group. New tuples will be
2239 * imputed for any key values that are not found within a group.
2240 * @param {Array<*>} [params.keyvals] - Optional array of required key
2241 * values. New tuples will be imputed for any key values that are not
2242 * found within a group. In addition, these values will be automatically
2243 * augmented with the key values observed in the input data.
2244 * @param {string} [method='value'] - The imputation method to use. One of
2245 * 'value', 'mean', 'median', 'max', 'min'.
2246 * @param {*} [value=0] - The constant value to use for imputation
2247 * when using method 'value'.
2248 */
2249 function Impute(params) {
2250 vegaDataflow.Transform.call(this, [], params);
2251 }
2252 Impute.Definition = {
2253 'type': 'Impute',
2254 'metadata': {
2255 'changes': true
2256 },
2257 'params': [{
2258 'name': 'field',
2259 'type': 'field',
2260 'required': true
2261 }, {
2262 'name': 'key',
2263 'type': 'field',
2264 'required': true
2265 }, {
2266 'name': 'keyvals',
2267 'array': true
2268 }, {
2269 'name': 'groupby',
2270 'type': 'field',
2271 'array': true
2272 }, {
2273 'name': 'method',
2274 'type': 'enum',
2275 'default': 'value',
2276 'values': ['value', 'mean', 'median', 'max', 'min']
2277 }, {
2278 'name': 'value',
2279 'default': 0
2280 }]
2281 };
2282 function getValue(_) {
2283 var m = _.method || Methods.value,
2284 v;
2285 if (Methods[m] == null) {
2286 vegaUtil.error('Unrecognized imputation method: ' + m);
2287 } else if (m === Methods.value) {
2288 v = _.value !== undefined ? _.value : 0;
2289 return () => v;
2290 } else {
2291 return Methods[m];
2292 }
2293 }
2294 function getField(_) {
2295 const f = _.field;
2296 return t => t ? f(t) : NaN;
2297 }
2298 vegaUtil.inherits(Impute, vegaDataflow.Transform, {
2299 transform(_, pulse) {
2300 var out = pulse.fork(pulse.ALL),
2301 impute = getValue(_),
2302 field = getField(_),
2303 fName = vegaUtil.accessorName(_.field),
2304 kName = vegaUtil.accessorName(_.key),
2305 gNames = (_.groupby || []).map(vegaUtil.accessorName),
2306 groups = partition(pulse.source, _.groupby, _.key, _.keyvals),
2307 curr = [],
2308 prev = this.value,
2309 m = groups.domain.length,
2310 group,
2311 value,
2312 gVals,
2313 kVal,
2314 g,
2315 i,
2316 j,
2317 l,
2318 n,
2319 t;
2320 for (g = 0, l = groups.length; g < l; ++g) {
2321 group = groups[g];
2322 gVals = group.values;
2323 value = NaN;
2324
2325 // add tuples for missing values
2326 for (j = 0; j < m; ++j) {
2327 if (group[j] != null) continue;
2328 kVal = groups.domain[j];
2329 t = {
2330 _impute: true
2331 };
2332 for (i = 0, n = gVals.length; i < n; ++i) t[gNames[i]] = gVals[i];
2333 t[kName] = kVal;
2334 t[fName] = Number.isNaN(value) ? value = impute(group, field) : value;
2335 curr.push(vegaDataflow.ingest(t));
2336 }
2337 }
2338
2339 // update pulse with imputed tuples
2340 if (curr.length) out.add = out.materialize(out.ADD).add.concat(curr);
2341 if (prev.length) out.rem = out.materialize(out.REM).rem.concat(prev);
2342 this.value = curr;
2343 return out;
2344 }
2345 });
2346 function partition(data, groupby, key, keyvals) {
2347 var get = f => f(t),
2348 groups = [],
2349 domain = keyvals ? keyvals.slice() : [],
2350 kMap = {},
2351 gMap = {},
2352 gVals,
2353 gKey,
2354 group,
2355 i,
2356 j,
2357 k,
2358 n,
2359 t;
2360 domain.forEach((k, i) => kMap[k] = i + 1);
2361 for (i = 0, n = data.length; i < n; ++i) {
2362 t = data[i];
2363 k = key(t);
2364 j = kMap[k] || (kMap[k] = domain.push(k));
2365 gKey = (gVals = groupby ? groupby.map(get) : Empty) + '';
2366 if (!(group = gMap[gKey])) {
2367 group = gMap[gKey] = [];
2368 groups.push(group);
2369 group.values = gVals;
2370 }
2371 group[j - 1] = t;
2372 }
2373 groups.domain = domain;
2374 return groups;
2375 }
2376
2377 /**
2378 * Extend input tuples with aggregate values.
2379 * Calcuates aggregate values and joins them with the input stream.
2380 * @constructor
2381 */
2382 function JoinAggregate(params) {
2383 Aggregate.call(this, params);
2384 }
2385 JoinAggregate.Definition = {
2386 'type': 'JoinAggregate',
2387 'metadata': {
2388 'modifies': true
2389 },
2390 'params': [{
2391 'name': 'groupby',
2392 'type': 'field',
2393 'array': true
2394 }, {
2395 'name': 'fields',
2396 'type': 'field',
2397 'null': true,
2398 'array': true
2399 }, {
2400 'name': 'ops',
2401 'type': 'enum',
2402 'array': true,
2403 'values': ValidAggregateOps
2404 }, {
2405 'name': 'as',
2406 'type': 'string',
2407 'null': true,
2408 'array': true
2409 }, {
2410 'name': 'key',
2411 'type': 'field'
2412 }]
2413 };
2414 vegaUtil.inherits(JoinAggregate, Aggregate, {
2415 transform(_, pulse) {
2416 const aggr = this,
2417 mod = _.modified();
2418 let cells;
2419
2420 // process all input tuples to calculate aggregates
2421 if (aggr.value && (mod || pulse.modified(aggr._inputs, true))) {
2422 cells = aggr.value = mod ? aggr.init(_) : {};
2423 pulse.visit(pulse.SOURCE, t => aggr.add(t));
2424 } else {
2425 cells = aggr.value = aggr.value || this.init(_);
2426 pulse.visit(pulse.REM, t => aggr.rem(t));
2427 pulse.visit(pulse.ADD, t => aggr.add(t));
2428 }
2429
2430 // update aggregation cells
2431 aggr.changes();
2432
2433 // write aggregate values to input tuples
2434 pulse.visit(pulse.SOURCE, t => {
2435 vegaUtil.extend(t, cells[aggr.cellkey(t)].tuple);
2436 });
2437 return pulse.reflow(mod).modifies(this._outputs);
2438 },
2439 changes() {
2440 const adds = this._adds,
2441 mods = this._mods;
2442 let i, n;
2443 for (i = 0, n = this._alen; i < n; ++i) {
2444 this.celltuple(adds[i]);
2445 adds[i] = null; // for garbage collection
2446 }
2447
2448 for (i = 0, n = this._mlen; i < n; ++i) {
2449 this.celltuple(mods[i]);
2450 mods[i] = null; // for garbage collection
2451 }
2452
2453 this._alen = this._mlen = 0; // reset list of active cells
2454 }
2455 });
2456
2457 /**
2458 * Compute kernel density estimates (KDE) for one or more data groups.
2459 * @constructor
2460 * @param {object} params - The parameters for this operator.
2461 * @param {Array<function(object): *>} [params.groupby] - An array of accessors
2462 * to groupby.
2463 * @param {function(object): *} params.field - An accessor for the data field
2464 * to estimate.
2465 * @param {number} [params.bandwidth=0] - The KDE kernel bandwidth.
2466 * If zero or unspecified, the bandwidth is automatically determined.
2467 * @param {boolean} [params.counts=false] - A boolean flag indicating if the
2468 * output values should be probability estimates (false, default) or
2469 * smoothed counts (true).
2470 * @param {string} [params.cumulative=false] - A boolean flag indicating if a
2471 * density (false) or cumulative distribution (true) should be generated.
2472 * @param {Array<number>} [params.extent] - The domain extent over which to
2473 * plot the density. If unspecified, the [min, max] data extent is used.
2474 * @param {string} [params.resolve='independent'] - Indicates how parameters for
2475 * multiple densities should be resolved. If "independent" (the default), each
2476 * density may have its own domain extent and dynamic number of curve sample
2477 * steps. If "shared", the KDE transform will ensure that all densities are
2478 * defined over a shared domain and curve steps, enabling stacking.
2479 * @param {number} [params.minsteps=25] - The minimum number of curve samples
2480 * for plotting the density.
2481 * @param {number} [params.maxsteps=200] - The maximum number of curve samples
2482 * for plotting the density.
2483 * @param {number} [params.steps] - The exact number of curve samples for
2484 * plotting the density. If specified, overrides both minsteps and maxsteps
2485 * to set an exact number of uniform samples. Useful in conjunction with
2486 * a fixed extent to ensure consistent sample points for stacked densities.
2487 */
2488 function KDE(params) {
2489 vegaDataflow.Transform.call(this, null, params);
2490 }
2491 KDE.Definition = {
2492 'type': 'KDE',
2493 'metadata': {
2494 'generates': true
2495 },
2496 'params': [{
2497 'name': 'groupby',
2498 'type': 'field',
2499 'array': true
2500 }, {
2501 'name': 'field',
2502 'type': 'field',
2503 'required': true
2504 }, {
2505 'name': 'cumulative',
2506 'type': 'boolean',
2507 'default': false
2508 }, {
2509 'name': 'counts',
2510 'type': 'boolean',
2511 'default': false
2512 }, {
2513 'name': 'bandwidth',
2514 'type': 'number',
2515 'default': 0
2516 }, {
2517 'name': 'extent',
2518 'type': 'number',
2519 'array': true,
2520 'length': 2
2521 }, {
2522 'name': 'resolve',
2523 'type': 'enum',
2524 'values': ['shared', 'independent'],
2525 'default': 'independent'
2526 }, {
2527 'name': 'steps',
2528 'type': 'number'
2529 }, {
2530 'name': 'minsteps',
2531 'type': 'number',
2532 'default': 25
2533 }, {
2534 'name': 'maxsteps',
2535 'type': 'number',
2536 'default': 200
2537 }, {
2538 'name': 'as',
2539 'type': 'string',
2540 'array': true,
2541 'default': ['value', 'density']
2542 }]
2543 };
2544 vegaUtil.inherits(KDE, vegaDataflow.Transform, {
2545 transform(_, pulse) {
2546 const out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS);
2547 if (!this.value || pulse.changed() || _.modified()) {
2548 const source = pulse.materialize(pulse.SOURCE).source,
2549 groups = partition$1(source, _.groupby, _.field),
2550 names = (_.groupby || []).map(vegaUtil.accessorName),
2551 bandwidth = _.bandwidth,
2552 method = _.cumulative ? 'cdf' : 'pdf',
2553 as = _.as || ['value', 'density'],
2554 values = [];
2555 let domain = _.extent,
2556 minsteps = _.steps || _.minsteps || 25,
2557 maxsteps = _.steps || _.maxsteps || 200;
2558 if (method !== 'pdf' && method !== 'cdf') {
2559 vegaUtil.error('Invalid density method: ' + method);
2560 }
2561 if (_.resolve === 'shared') {
2562 if (!domain) domain = vegaUtil.extent(source, _.field);
2563 minsteps = maxsteps = _.steps || maxsteps;
2564 }
2565 groups.forEach(g => {
2566 const density = vegaStatistics.randomKDE(g, bandwidth)[method],
2567 scale = _.counts ? g.length : 1,
2568 local = domain || vegaUtil.extent(g);
2569 vegaStatistics.sampleCurve(density, local, minsteps, maxsteps).forEach(v => {
2570 const t = {};
2571 for (let i = 0; i < names.length; ++i) {
2572 t[names[i]] = g.dims[i];
2573 }
2574 t[as[0]] = v[0];
2575 t[as[1]] = v[1] * scale;
2576 values.push(vegaDataflow.ingest(t));
2577 });
2578 });
2579 if (this.value) out.rem = this.value;
2580 this.value = out.add = out.source = values;
2581 }
2582 return out;
2583 }
2584 });
2585
2586 /**
2587 * Generates a key function.
2588 * @constructor
2589 * @param {object} params - The parameters for this operator.
2590 * @param {Array<string>} params.fields - The field name(s) for the key function.
2591 * @param {boolean} params.flat - A boolean flag indicating if the field names
2592 * should be treated as flat property names, side-stepping nested field
2593 * lookups normally indicated by dot or bracket notation.
2594 */
2595 function Key(params) {
2596 vegaDataflow.Operator.call(this, null, update$2, params);
2597 }
2598 vegaUtil.inherits(Key, vegaDataflow.Operator);
2599 function update$2(_) {
2600 return this.value && !_.modified() ? this.value : vegaUtil.key(_.fields, _.flat);
2601 }
2602
2603 /**
2604 * Load and parse data from an external source. Marshalls parameter
2605 * values and then invokes the Dataflow request method.
2606 * @constructor
2607 * @param {object} params - The parameters for this operator.
2608 * @param {string} params.url - The URL to load from.
2609 * @param {object} params.format - The data format options.
2610 */
2611 function Load(params) {
2612 vegaDataflow.Transform.call(this, [], params);
2613 this._pending = null;
2614 }
2615 vegaUtil.inherits(Load, vegaDataflow.Transform, {
2616 transform(_, pulse) {
2617 const df = pulse.dataflow;
2618 if (this._pending) {
2619 // update state and return pulse
2620 return output(this, pulse, this._pending);
2621 }
2622 if (stop(_)) return pulse.StopPropagation;
2623 if (_.values) {
2624 // parse and ingest values, return output pulse
2625 return output(this, pulse, df.parse(_.values, _.format));
2626 } else if (_.async) {
2627 // return promise for non-blocking async loading
2628 const p = df.request(_.url, _.format).then(res => {
2629 this._pending = vegaUtil.array(res.data);
2630 return df => df.touch(this);
2631 });
2632 return {
2633 async: p
2634 };
2635 } else {
2636 // return promise for synchronous loading
2637 return df.request(_.url, _.format).then(res => output(this, pulse, vegaUtil.array(res.data)));
2638 }
2639 }
2640 });
2641 function stop(_) {
2642 return _.modified('async') && !(_.modified('values') || _.modified('url') || _.modified('format'));
2643 }
2644 function output(op, pulse, data) {
2645 data.forEach(vegaDataflow.ingest);
2646 const out = pulse.fork(pulse.NO_FIELDS & pulse.NO_SOURCE);
2647 out.rem = op.value;
2648 op.value = out.source = out.add = data;
2649 op._pending = null;
2650 if (out.rem.length) out.clean(true);
2651 return out;
2652 }
2653
2654 /**
2655 * Extend tuples by joining them with values from a lookup table.
2656 * @constructor
2657 * @param {object} params - The parameters for this operator.
2658 * @param {Map} params.index - The lookup table map.
2659 * @param {Array<function(object): *} params.fields - The fields to lookup.
2660 * @param {Array<string>} params.as - Output field names for each lookup value.
2661 * @param {*} [params.default] - A default value to use if lookup fails.
2662 */
2663 function Lookup(params) {
2664 vegaDataflow.Transform.call(this, {}, params);
2665 }
2666 Lookup.Definition = {
2667 'type': 'Lookup',
2668 'metadata': {
2669 'modifies': true
2670 },
2671 'params': [{
2672 'name': 'index',
2673 'type': 'index',
2674 'params': [{
2675 'name': 'from',
2676 'type': 'data',
2677 'required': true
2678 }, {
2679 'name': 'key',
2680 'type': 'field',
2681 'required': true
2682 }]
2683 }, {
2684 'name': 'values',
2685 'type': 'field',
2686 'array': true
2687 }, {
2688 'name': 'fields',
2689 'type': 'field',
2690 'array': true,
2691 'required': true
2692 }, {
2693 'name': 'as',
2694 'type': 'string',
2695 'array': true
2696 }, {
2697 'name': 'default',
2698 'default': null
2699 }]
2700 };
2701 vegaUtil.inherits(Lookup, vegaDataflow.Transform, {
2702 transform(_, pulse) {
2703 const keys = _.fields,
2704 index = _.index,
2705 values = _.values,
2706 defaultValue = _.default == null ? null : _.default,
2707 reset = _.modified(),
2708 n = keys.length;
2709 let flag = reset ? pulse.SOURCE : pulse.ADD,
2710 out = pulse,
2711 as = _.as,
2712 set,
2713 m,
2714 mods;
2715 if (values) {
2716 m = values.length;
2717 if (n > 1 && !as) {
2718 vegaUtil.error('Multi-field lookup requires explicit "as" parameter.');
2719 }
2720 if (as && as.length !== n * m) {
2721 vegaUtil.error('The "as" parameter has too few output field names.');
2722 }
2723 as = as || values.map(vegaUtil.accessorName);
2724 set = function (t) {
2725 for (var i = 0, k = 0, j, v; i < n; ++i) {
2726 v = index.get(keys[i](t));
2727 if (v == null) for (j = 0; j < m; ++j, ++k) t[as[k]] = defaultValue;else for (j = 0; j < m; ++j, ++k) t[as[k]] = values[j](v);
2728 }
2729 };
2730 } else {
2731 if (!as) {
2732 vegaUtil.error('Missing output field names.');
2733 }
2734 set = function (t) {
2735 for (var i = 0, v; i < n; ++i) {
2736 v = index.get(keys[i](t));
2737 t[as[i]] = v == null ? defaultValue : v;
2738 }
2739 };
2740 }
2741 if (reset) {
2742 out = pulse.reflow(true);
2743 } else {
2744 mods = keys.some(k => pulse.modified(k.fields));
2745 flag |= mods ? pulse.MOD : 0;
2746 }
2747 pulse.visit(flag, set);
2748 return out.modifies(as);
2749 }
2750 });
2751
2752 /**
2753 * Computes global min/max extents over a collection of extents.
2754 * @constructor
2755 * @param {object} params - The parameters for this operator.
2756 * @param {Array<Array<number>>} params.extents - The input extents.
2757 */
2758 function MultiExtent(params) {
2759 vegaDataflow.Operator.call(this, null, update$1, params);
2760 }
2761 vegaUtil.inherits(MultiExtent, vegaDataflow.Operator);
2762 function update$1(_) {
2763 if (this.value && !_.modified()) {
2764 return this.value;
2765 }
2766 const ext = _.extents,
2767 n = ext.length;
2768 let min = +Infinity,
2769 max = -Infinity,
2770 i,
2771 e;
2772 for (i = 0; i < n; ++i) {
2773 e = ext[i];
2774 if (e[0] < min) min = e[0];
2775 if (e[1] > max) max = e[1];
2776 }
2777 return [min, max];
2778 }
2779
2780 /**
2781 * Merge a collection of value arrays.
2782 * @constructor
2783 * @param {object} params - The parameters for this operator.
2784 * @param {Array<Array<*>>} params.values - The input value arrrays.
2785 */
2786 function MultiValues(params) {
2787 vegaDataflow.Operator.call(this, null, update, params);
2788 }
2789 vegaUtil.inherits(MultiValues, vegaDataflow.Operator);
2790 function update(_) {
2791 return this.value && !_.modified() ? this.value : _.values.reduce((data, _) => data.concat(_), []);
2792 }
2793
2794 /**
2795 * Operator whose value is simply its parameter hash. This operator is
2796 * useful for enabling reactive updates to values of nested objects.
2797 * @constructor
2798 * @param {object} params - The parameters for this operator.
2799 */
2800 function Params(params) {
2801 vegaDataflow.Transform.call(this, null, params);
2802 }
2803 vegaUtil.inherits(Params, vegaDataflow.Transform, {
2804 transform(_, pulse) {
2805 this.modified(_.modified());
2806 this.value = _;
2807 return pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS); // do not pass tuples
2808 }
2809 });
2810
2811 /**
2812 * Aggregate and pivot selected field values to become new fields.
2813 * This operator is useful to construction cross-tabulations.
2814 * @constructor
2815 * @param {Array<function(object): *>} [params.groupby] - An array of accessors
2816 * to groupby. These fields act just like groupby fields of an Aggregate transform.
2817 * @param {function(object): *} params.field - The field to pivot on. The unique
2818 * values of this field become new field names in the output stream.
2819 * @param {function(object): *} params.value - The field to populate pivoted fields.
2820 * The aggregate values of this field become the values of the new pivoted fields.
2821 * @param {string} [params.op] - The aggregation operation for the value field,
2822 * applied per cell in the output stream. The default is "sum".
2823 * @param {number} [params.limit] - An optional parameter indicating the maximum
2824 * number of pivoted fields to generate. The pivoted field names are sorted in
2825 * ascending order prior to enforcing the limit.
2826 */
2827 function Pivot(params) {
2828 Aggregate.call(this, params);
2829 }
2830 Pivot.Definition = {
2831 'type': 'Pivot',
2832 'metadata': {
2833 'generates': true,
2834 'changes': true
2835 },
2836 'params': [{
2837 'name': 'groupby',
2838 'type': 'field',
2839 'array': true
2840 }, {
2841 'name': 'field',
2842 'type': 'field',
2843 'required': true
2844 }, {
2845 'name': 'value',
2846 'type': 'field',
2847 'required': true
2848 }, {
2849 'name': 'op',
2850 'type': 'enum',
2851 'values': ValidAggregateOps,
2852 'default': 'sum'
2853 }, {
2854 'name': 'limit',
2855 'type': 'number',
2856 'default': 0
2857 }, {
2858 'name': 'key',
2859 'type': 'field'
2860 }]
2861 };
2862 vegaUtil.inherits(Pivot, Aggregate, {
2863 _transform: Aggregate.prototype.transform,
2864 transform(_, pulse) {
2865 return this._transform(aggregateParams(_, pulse), pulse);
2866 }
2867 });
2868
2869 // Shoehorn a pivot transform into an aggregate transform!
2870 // First collect all unique pivot field values.
2871 // Then generate aggregate fields for each output pivot field.
2872 function aggregateParams(_, pulse) {
2873 const key = _.field,
2874 value = _.value,
2875 op = (_.op === 'count' ? '__count__' : _.op) || 'sum',
2876 fields = vegaUtil.accessorFields(key).concat(vegaUtil.accessorFields(value)),
2877 keys = pivotKeys(key, _.limit || 0, pulse);
2878
2879 // if data stream content changes, pivot fields may change
2880 // flag parameter modification to ensure re-initialization
2881 if (pulse.changed()) _.set('__pivot__', null, null, true);
2882 return {
2883 key: _.key,
2884 groupby: _.groupby,
2885 ops: keys.map(() => op),
2886 fields: keys.map(k => get(k, key, value, fields)),
2887 as: keys.map(k => k + ''),
2888 modified: _.modified.bind(_)
2889 };
2890 }
2891
2892 // Generate aggregate field accessor.
2893 // Output NaN for non-existent values; aggregator will ignore!
2894 function get(k, key, value, fields) {
2895 return vegaUtil.accessor(d => key(d) === k ? value(d) : NaN, fields, k + '');
2896 }
2897
2898 // Collect (and optionally limit) all unique pivot values.
2899 function pivotKeys(key, limit, pulse) {
2900 const map = {},
2901 list = [];
2902 pulse.visit(pulse.SOURCE, t => {
2903 const k = key(t);
2904 if (!map[k]) {
2905 map[k] = 1;
2906 list.push(k);
2907 }
2908 });
2909 list.sort(vegaUtil.ascending);
2910 return limit ? list.slice(0, limit) : list;
2911 }
2912
2913 /**
2914 * Partitions pre-faceted data into tuple subflows.
2915 * @constructor
2916 * @param {object} params - The parameters for this operator.
2917 * @param {function(Dataflow, string): Operator} params.subflow - A function
2918 * that generates a subflow of operators and returns its root operator.
2919 * @param {function(object): Array<object>} params.field - The field
2920 * accessor for an array of subflow tuple objects.
2921 */
2922 function PreFacet(params) {
2923 Facet.call(this, params);
2924 }
2925 vegaUtil.inherits(PreFacet, Facet, {
2926 transform(_, pulse) {
2927 const flow = _.subflow,
2928 field = _.field,
2929 subflow = t => this.subflow(vegaDataflow.tupleid(t), flow, pulse, t);
2930 if (_.modified('field') || field && pulse.modified(vegaUtil.accessorFields(field))) {
2931 vegaUtil.error('PreFacet does not support field modification.');
2932 }
2933 this.initTargets(); // reset list of active subflows
2934
2935 if (field) {
2936 pulse.visit(pulse.MOD, t => {
2937 const sf = subflow(t);
2938 field(t).forEach(_ => sf.mod(_));
2939 });
2940 pulse.visit(pulse.ADD, t => {
2941 const sf = subflow(t);
2942 field(t).forEach(_ => sf.add(vegaDataflow.ingest(_)));
2943 });
2944 pulse.visit(pulse.REM, t => {
2945 const sf = subflow(t);
2946 field(t).forEach(_ => sf.rem(_));
2947 });
2948 } else {
2949 pulse.visit(pulse.MOD, t => subflow(t).mod(t));
2950 pulse.visit(pulse.ADD, t => subflow(t).add(t));
2951 pulse.visit(pulse.REM, t => subflow(t).rem(t));
2952 }
2953 if (pulse.clean()) {
2954 pulse.runAfter(() => this.clean());
2955 }
2956 return pulse;
2957 }
2958 });
2959
2960 /**
2961 * Performs a relational projection, copying selected fields from source
2962 * tuples to a new set of derived tuples.
2963 * @constructor
2964 * @param {object} params - The parameters for this operator.
2965 * @param {Array<function(object): *} params.fields - The fields to project,
2966 * as an array of field accessors. If unspecified, all fields will be
2967 * copied with names unchanged.
2968 * @param {Array<string>} [params.as] - Output field names for each projected
2969 * field. Any unspecified fields will use the field name provided by
2970 * the field accessor.
2971 */
2972 function Project(params) {
2973 vegaDataflow.Transform.call(this, null, params);
2974 }
2975 Project.Definition = {
2976 'type': 'Project',
2977 'metadata': {
2978 'generates': true,
2979 'changes': true
2980 },
2981 'params': [{
2982 'name': 'fields',
2983 'type': 'field',
2984 'array': true
2985 }, {
2986 'name': 'as',
2987 'type': 'string',
2988 'null': true,
2989 'array': true
2990 }]
2991 };
2992 vegaUtil.inherits(Project, vegaDataflow.Transform, {
2993 transform(_, pulse) {
2994 const out = pulse.fork(pulse.NO_SOURCE),
2995 fields = _.fields,
2996 as = fieldNames(_.fields, _.as || []),
2997 derive = fields ? (s, t) => project(s, t, fields, as) : vegaDataflow.rederive;
2998 let lut;
2999 if (this.value) {
3000 lut = this.value;
3001 } else {
3002 pulse = pulse.addAll();
3003 lut = this.value = {};
3004 }
3005 pulse.visit(pulse.REM, t => {
3006 const id = vegaDataflow.tupleid(t);
3007 out.rem.push(lut[id]);
3008 lut[id] = null;
3009 });
3010 pulse.visit(pulse.ADD, t => {
3011 const dt = derive(t, vegaDataflow.ingest({}));
3012 lut[vegaDataflow.tupleid(t)] = dt;
3013 out.add.push(dt);
3014 });
3015 pulse.visit(pulse.MOD, t => {
3016 out.mod.push(derive(t, lut[vegaDataflow.tupleid(t)]));
3017 });
3018 return out;
3019 }
3020 });
3021 function project(s, t, fields, as) {
3022 for (let i = 0, n = fields.length; i < n; ++i) {
3023 t[as[i]] = fields[i](s);
3024 }
3025 return t;
3026 }
3027
3028 /**
3029 * Proxy the value of another operator as a pure signal value.
3030 * Ensures no tuples are propagated.
3031 * @constructor
3032 * @param {object} params - The parameters for this operator.
3033 * @param {*} params.value - The value to proxy, becomes the value of this operator.
3034 */
3035 function Proxy(params) {
3036 vegaDataflow.Transform.call(this, null, params);
3037 }
3038 vegaUtil.inherits(Proxy, vegaDataflow.Transform, {
3039 transform(_, pulse) {
3040 this.value = _.value;
3041 return _.modified('value') ? pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS) : pulse.StopPropagation;
3042 }
3043 });
3044
3045 /**
3046 * Generates sample quantile values from an input data stream.
3047 * @constructor
3048 * @param {object} params - The parameters for this operator.
3049 * @param {function(object): *} params.field - An accessor for the data field
3050 * over which to calculate quantile values.
3051 * @param {Array<function(object): *>} [params.groupby] - An array of accessors
3052 * to groupby.
3053 * @param {Array<number>} [params.probs] - An array of probabilities in
3054 * the range (0, 1) for which to compute quantile values. If not specified,
3055 * the *step* parameter will be used.
3056 * @param {Array<number>} [params.step=0.01] - A probability step size for
3057 * sampling quantile values. All values from one-half the step size up to
3058 * 1 (exclusive) will be sampled. This parameter is only used if the
3059 * *quantiles* parameter is not provided.
3060 */
3061 function Quantile(params) {
3062 vegaDataflow.Transform.call(this, null, params);
3063 }
3064 Quantile.Definition = {
3065 'type': 'Quantile',
3066 'metadata': {
3067 'generates': true,
3068 'changes': true
3069 },
3070 'params': [{
3071 'name': 'groupby',
3072 'type': 'field',
3073 'array': true
3074 }, {
3075 'name': 'field',
3076 'type': 'field',
3077 'required': true
3078 }, {
3079 'name': 'probs',
3080 'type': 'number',
3081 'array': true
3082 }, {
3083 'name': 'step',
3084 'type': 'number',
3085 'default': 0.01
3086 }, {
3087 'name': 'as',
3088 'type': 'string',
3089 'array': true,
3090 'default': ['prob', 'value']
3091 }]
3092 };
3093 const EPSILON = 1e-14;
3094 vegaUtil.inherits(Quantile, vegaDataflow.Transform, {
3095 transform(_, pulse) {
3096 const out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
3097 as = _.as || ['prob', 'value'];
3098 if (this.value && !_.modified() && !pulse.changed()) {
3099 out.source = this.value;
3100 return out;
3101 }
3102 const source = pulse.materialize(pulse.SOURCE).source,
3103 groups = partition$1(source, _.groupby, _.field),
3104 names = (_.groupby || []).map(vegaUtil.accessorName),
3105 values = [],
3106 step = _.step || 0.01,
3107 p = _.probs || range(step / 2, 1 - EPSILON, step),
3108 n = p.length;
3109 groups.forEach(g => {
3110 const q = vegaStatistics.quantiles(g, p);
3111 for (let i = 0; i < n; ++i) {
3112 const t = {};
3113 for (let i = 0; i < names.length; ++i) {
3114 t[names[i]] = g.dims[i];
3115 }
3116 t[as[0]] = p[i];
3117 t[as[1]] = q[i];
3118 values.push(vegaDataflow.ingest(t));
3119 }
3120 });
3121 if (this.value) out.rem = this.value;
3122 this.value = out.add = out.source = values;
3123 return out;
3124 }
3125 });
3126
3127 /**
3128 * Relays a data stream between data processing pipelines.
3129 * If the derive parameter is set, this transform will create derived
3130 * copies of observed tuples. This provides derived data streams in which
3131 * modifications to the tuples do not pollute an upstream data source.
3132 * @param {object} params - The parameters for this operator.
3133 * @param {number} [params.derive=false] - Boolean flag indicating if
3134 * the transform should make derived copies of incoming tuples.
3135 * @constructor
3136 */
3137 function Relay(params) {
3138 vegaDataflow.Transform.call(this, null, params);
3139 }
3140 vegaUtil.inherits(Relay, vegaDataflow.Transform, {
3141 transform(_, pulse) {
3142 let out, lut;
3143 if (this.value) {
3144 lut = this.value;
3145 } else {
3146 out = pulse = pulse.addAll();
3147 lut = this.value = {};
3148 }
3149 if (_.derive) {
3150 out = pulse.fork(pulse.NO_SOURCE);
3151 pulse.visit(pulse.REM, t => {
3152 const id = vegaDataflow.tupleid(t);
3153 out.rem.push(lut[id]);
3154 lut[id] = null;
3155 });
3156 pulse.visit(pulse.ADD, t => {
3157 const dt = vegaDataflow.derive(t);
3158 lut[vegaDataflow.tupleid(t)] = dt;
3159 out.add.push(dt);
3160 });
3161 pulse.visit(pulse.MOD, t => {
3162 const dt = lut[vegaDataflow.tupleid(t)];
3163 for (const k in t) {
3164 dt[k] = t[k];
3165 // down stream writes may overwrite re-derived tuples
3166 // conservatively mark all source fields as modified
3167 out.modifies(k);
3168 }
3169 out.mod.push(dt);
3170 });
3171 }
3172 return out;
3173 }
3174 });
3175
3176 /**
3177 * Samples tuples passing through this operator.
3178 * Uses reservoir sampling to maintain a representative sample.
3179 * @constructor
3180 * @param {object} params - The parameters for this operator.
3181 * @param {number} [params.size=1000] - The maximum number of samples.
3182 */
3183 function Sample(params) {
3184 vegaDataflow.Transform.call(this, [], params);
3185 this.count = 0;
3186 }
3187 Sample.Definition = {
3188 'type': 'Sample',
3189 'metadata': {},
3190 'params': [{
3191 'name': 'size',
3192 'type': 'number',
3193 'default': 1000
3194 }]
3195 };
3196 vegaUtil.inherits(Sample, vegaDataflow.Transform, {
3197 transform(_, pulse) {
3198 const out = pulse.fork(pulse.NO_SOURCE),
3199 mod = _.modified('size'),
3200 num = _.size,
3201 map = this.value.reduce((m, t) => (m[vegaDataflow.tupleid(t)] = 1, m), {});
3202 let res = this.value,
3203 cnt = this.count,
3204 cap = 0;
3205
3206 // sample reservoir update function
3207 function update(t) {
3208 let p, idx;
3209 if (res.length < num) {
3210 res.push(t);
3211 } else {
3212 idx = ~~((cnt + 1) * vegaStatistics.random());
3213 if (idx < res.length && idx >= cap) {
3214 p = res[idx];
3215 if (map[vegaDataflow.tupleid(p)]) out.rem.push(p); // eviction
3216 res[idx] = t;
3217 }
3218 }
3219 ++cnt;
3220 }
3221 if (pulse.rem.length) {
3222 // find all tuples that should be removed, add to output
3223 pulse.visit(pulse.REM, t => {
3224 const id = vegaDataflow.tupleid(t);
3225 if (map[id]) {
3226 map[id] = -1;
3227 out.rem.push(t);
3228 }
3229 --cnt;
3230 });
3231
3232 // filter removed tuples out of the sample reservoir
3233 res = res.filter(t => map[vegaDataflow.tupleid(t)] !== -1);
3234 }
3235 if ((pulse.rem.length || mod) && res.length < num && pulse.source) {
3236 // replenish sample if backing data source is available
3237 cap = cnt = res.length;
3238 pulse.visit(pulse.SOURCE, t => {
3239 // update, but skip previously sampled tuples
3240 if (!map[vegaDataflow.tupleid(t)]) update(t);
3241 });
3242 cap = -1;
3243 }
3244 if (mod && res.length > num) {
3245 const n = res.length - num;
3246 for (let i = 0; i < n; ++i) {
3247 map[vegaDataflow.tupleid(res[i])] = -1;
3248 out.rem.push(res[i]);
3249 }
3250 res = res.slice(n);
3251 }
3252 if (pulse.mod.length) {
3253 // propagate modified tuples in the sample reservoir
3254 pulse.visit(pulse.MOD, t => {
3255 if (map[vegaDataflow.tupleid(t)]) out.mod.push(t);
3256 });
3257 }
3258 if (pulse.add.length) {
3259 // update sample reservoir
3260 pulse.visit(pulse.ADD, update);
3261 }
3262 if (pulse.add.length || cap < 0) {
3263 // output newly added tuples
3264 out.add = res.filter(t => !map[vegaDataflow.tupleid(t)]);
3265 }
3266 this.count = cnt;
3267 this.value = out.source = res;
3268 return out;
3269 }
3270 });
3271
3272 /**
3273 * Generates data tuples for a specified sequence range of numbers.
3274 * @constructor
3275 * @param {object} params - The parameters for this operator.
3276 * @param {number} params.start - The first number in the sequence.
3277 * @param {number} params.stop - The last number (exclusive) in the sequence.
3278 * @param {number} [params.step=1] - The step size between numbers in the sequence.
3279 */
3280 function Sequence(params) {
3281 vegaDataflow.Transform.call(this, null, params);
3282 }
3283 Sequence.Definition = {
3284 'type': 'Sequence',
3285 'metadata': {
3286 'generates': true,
3287 'changes': true
3288 },
3289 'params': [{
3290 'name': 'start',
3291 'type': 'number',
3292 'required': true
3293 }, {
3294 'name': 'stop',
3295 'type': 'number',
3296 'required': true
3297 }, {
3298 'name': 'step',
3299 'type': 'number',
3300 'default': 1
3301 }, {
3302 'name': 'as',
3303 'type': 'string',
3304 'default': 'data'
3305 }]
3306 };
3307 vegaUtil.inherits(Sequence, vegaDataflow.Transform, {
3308 transform(_, pulse) {
3309 if (this.value && !_.modified()) return;
3310 const out = pulse.materialize().fork(pulse.MOD),
3311 as = _.as || 'data';
3312 out.rem = this.value ? pulse.rem.concat(this.value) : pulse.rem;
3313 this.value = range(_.start, _.stop, _.step || 1).map(v => {
3314 const t = {};
3315 t[as] = v;
3316 return vegaDataflow.ingest(t);
3317 });
3318 out.add = pulse.add.concat(this.value);
3319 return out;
3320 }
3321 });
3322
3323 /**
3324 * Propagates a new pulse without any tuples so long as the input
3325 * pulse contains some added, removed or modified tuples.
3326 * @param {object} params - The parameters for this operator.
3327 * @constructor
3328 */
3329 function Sieve(params) {
3330 vegaDataflow.Transform.call(this, null, params);
3331 this.modified(true); // always treat as modified
3332 }
3333
3334 vegaUtil.inherits(Sieve, vegaDataflow.Transform, {
3335 transform(_, pulse) {
3336 this.value = pulse.source;
3337 return pulse.changed() ? pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS) : pulse.StopPropagation;
3338 }
3339 });
3340
3341 /**
3342 * Discretize dates to specific time units.
3343 * @constructor
3344 * @param {object} params - The parameters for this operator.
3345 * @param {function(object): *} params.field - The data field containing date/time values.
3346 */
3347 function TimeUnit(params) {
3348 vegaDataflow.Transform.call(this, null, params);
3349 }
3350 const OUTPUT = ['unit0', 'unit1'];
3351 TimeUnit.Definition = {
3352 'type': 'TimeUnit',
3353 'metadata': {
3354 'modifies': true
3355 },
3356 'params': [{
3357 'name': 'field',
3358 'type': 'field',
3359 'required': true
3360 }, {
3361 'name': 'interval',
3362 'type': 'boolean',
3363 'default': true
3364 }, {
3365 'name': 'units',
3366 'type': 'enum',
3367 'values': vegaTime.TIME_UNITS,
3368 'array': true
3369 }, {
3370 'name': 'step',
3371 'type': 'number',
3372 'default': 1
3373 }, {
3374 'name': 'maxbins',
3375 'type': 'number',
3376 'default': 40
3377 }, {
3378 'name': 'extent',
3379 'type': 'date',
3380 'array': true
3381 }, {
3382 'name': 'timezone',
3383 'type': 'enum',
3384 'default': 'local',
3385 'values': ['local', 'utc']
3386 }, {
3387 'name': 'as',
3388 'type': 'string',
3389 'array': true,
3390 'length': 2,
3391 'default': OUTPUT
3392 }]
3393 };
3394 vegaUtil.inherits(TimeUnit, vegaDataflow.Transform, {
3395 transform(_, pulse) {
3396 const field = _.field,
3397 band = _.interval !== false,
3398 utc = _.timezone === 'utc',
3399 floor = this._floor(_, pulse),
3400 offset = (utc ? vegaTime.utcInterval : vegaTime.timeInterval)(floor.unit).offset,
3401 as = _.as || OUTPUT,
3402 u0 = as[0],
3403 u1 = as[1],
3404 step = floor.step;
3405 let min = floor.start || Infinity,
3406 max = floor.stop || -Infinity,
3407 flag = pulse.ADD;
3408 if (_.modified() || pulse.changed(pulse.REM) || pulse.modified(vegaUtil.accessorFields(field))) {
3409 pulse = pulse.reflow(true);
3410 flag = pulse.SOURCE;
3411 min = Infinity;
3412 max = -Infinity;
3413 }
3414 pulse.visit(flag, t => {
3415 const v = field(t);
3416 let a, b;
3417 if (v == null) {
3418 t[u0] = null;
3419 if (band) t[u1] = null;
3420 } else {
3421 t[u0] = a = b = floor(v);
3422 if (band) t[u1] = b = offset(a, step);
3423 if (a < min) min = a;
3424 if (b > max) max = b;
3425 }
3426 });
3427 floor.start = min;
3428 floor.stop = max;
3429 return pulse.modifies(band ? as : u0);
3430 },
3431 _floor(_, pulse) {
3432 const utc = _.timezone === 'utc';
3433
3434 // get parameters
3435 const {
3436 units,
3437 step
3438 } = _.units ? {
3439 units: _.units,
3440 step: _.step || 1
3441 } : vegaTime.timeBin({
3442 extent: _.extent || vegaUtil.extent(pulse.materialize(pulse.SOURCE).source, _.field),
3443 maxbins: _.maxbins
3444 });
3445
3446 // check / standardize time units
3447 const tunits = vegaTime.timeUnits(units),
3448 prev = this.value || {},
3449 floor = (utc ? vegaTime.utcFloor : vegaTime.timeFloor)(tunits, step);
3450 floor.unit = vegaUtil.peek(tunits);
3451 floor.units = tunits;
3452 floor.step = step;
3453 floor.start = prev.start;
3454 floor.stop = prev.stop;
3455 return this.value = floor;
3456 }
3457 });
3458
3459 /**
3460 * An index that maps from unique, string-coerced, field values to tuples.
3461 * Assumes that the field serves as a unique key with no duplicate values.
3462 * @constructor
3463 * @param {object} params - The parameters for this operator.
3464 * @param {function(object): *} params.field - The field accessor to index.
3465 */
3466 function TupleIndex(params) {
3467 vegaDataflow.Transform.call(this, vegaUtil.fastmap(), params);
3468 }
3469 vegaUtil.inherits(TupleIndex, vegaDataflow.Transform, {
3470 transform(_, pulse) {
3471 const df = pulse.dataflow,
3472 field = _.field,
3473 index = this.value,
3474 set = t => index.set(field(t), t);
3475 let mod = true;
3476 if (_.modified('field') || pulse.modified(field.fields)) {
3477 index.clear();
3478 pulse.visit(pulse.SOURCE, set);
3479 } else if (pulse.changed()) {
3480 pulse.visit(pulse.REM, t => index.delete(field(t)));
3481 pulse.visit(pulse.ADD, set);
3482 } else {
3483 mod = false;
3484 }
3485 this.modified(mod);
3486 if (index.empty > df.cleanThreshold) df.runAfter(index.clean);
3487 return pulse.fork();
3488 }
3489 });
3490
3491 /**
3492 * Extracts an array of values. Assumes the source data has already been
3493 * reduced as needed (e.g., by an upstream Aggregate transform).
3494 * @constructor
3495 * @param {object} params - The parameters for this operator.
3496 * @param {function(object): *} params.field - The domain field to extract.
3497 * @param {function(*,*): number} [params.sort] - An optional
3498 * comparator function for sorting the values. The comparator will be
3499 * applied to backing tuples prior to value extraction.
3500 */
3501 function Values(params) {
3502 vegaDataflow.Transform.call(this, null, params);
3503 }
3504 vegaUtil.inherits(Values, vegaDataflow.Transform, {
3505 transform(_, pulse) {
3506 const run = !this.value || _.modified('field') || _.modified('sort') || pulse.changed() || _.sort && pulse.modified(_.sort.fields);
3507 if (run) {
3508 this.value = (_.sort ? pulse.source.slice().sort(vegaDataflow.stableCompare(_.sort)) : pulse.source).map(_.field);
3509 }
3510 }
3511 });
3512
3513 function WindowOp(op, field, param, as) {
3514 const fn = WindowOps[op](field, param);
3515 return {
3516 init: fn.init || vegaUtil.zero,
3517 update: function (w, t) {
3518 t[as] = fn.next(w);
3519 }
3520 };
3521 }
3522 const WindowOps = {
3523 row_number: function () {
3524 return {
3525 next: w => w.index + 1
3526 };
3527 },
3528 rank: function () {
3529 let rank;
3530 return {
3531 init: () => rank = 1,
3532 next: w => {
3533 const i = w.index,
3534 data = w.data;
3535 return i && w.compare(data[i - 1], data[i]) ? rank = i + 1 : rank;
3536 }
3537 };
3538 },
3539 dense_rank: function () {
3540 let drank;
3541 return {
3542 init: () => drank = 1,
3543 next: w => {
3544 const i = w.index,
3545 d = w.data;
3546 return i && w.compare(d[i - 1], d[i]) ? ++drank : drank;
3547 }
3548 };
3549 },
3550 percent_rank: function () {
3551 const rank = WindowOps.rank(),
3552 next = rank.next;
3553 return {
3554 init: rank.init,
3555 next: w => (next(w) - 1) / (w.data.length - 1)
3556 };
3557 },
3558 cume_dist: function () {
3559 let cume;
3560 return {
3561 init: () => cume = 0,
3562 next: w => {
3563 const d = w.data,
3564 c = w.compare;
3565 let i = w.index;
3566 if (cume < i) {
3567 while (i + 1 < d.length && !c(d[i], d[i + 1])) ++i;
3568 cume = i;
3569 }
3570 return (1 + cume) / d.length;
3571 }
3572 };
3573 },
3574 ntile: function (field, num) {
3575 num = +num;
3576 if (!(num > 0)) vegaUtil.error('ntile num must be greater than zero.');
3577 const cume = WindowOps.cume_dist(),
3578 next = cume.next;
3579 return {
3580 init: cume.init,
3581 next: w => Math.ceil(num * next(w))
3582 };
3583 },
3584 lag: function (field, offset) {
3585 offset = +offset || 1;
3586 return {
3587 next: w => {
3588 const i = w.index - offset;
3589 return i >= 0 ? field(w.data[i]) : null;
3590 }
3591 };
3592 },
3593 lead: function (field, offset) {
3594 offset = +offset || 1;
3595 return {
3596 next: w => {
3597 const i = w.index + offset,
3598 d = w.data;
3599 return i < d.length ? field(d[i]) : null;
3600 }
3601 };
3602 },
3603 first_value: function (field) {
3604 return {
3605 next: w => field(w.data[w.i0])
3606 };
3607 },
3608 last_value: function (field) {
3609 return {
3610 next: w => field(w.data[w.i1 - 1])
3611 };
3612 },
3613 nth_value: function (field, nth) {
3614 nth = +nth;
3615 if (!(nth > 0)) vegaUtil.error('nth_value nth must be greater than zero.');
3616 return {
3617 next: w => {
3618 const i = w.i0 + (nth - 1);
3619 return i < w.i1 ? field(w.data[i]) : null;
3620 }
3621 };
3622 },
3623 prev_value: function (field) {
3624 let prev;
3625 return {
3626 init: () => prev = null,
3627 next: w => {
3628 const v = field(w.data[w.index]);
3629 return v != null ? prev = v : prev;
3630 }
3631 };
3632 },
3633 next_value: function (field) {
3634 let v, i;
3635 return {
3636 init: () => (v = null, i = -1),
3637 next: w => {
3638 const d = w.data;
3639 return w.index <= i ? v : (i = find(field, d, w.index)) < 0 ? (i = d.length, v = null) : v = field(d[i]);
3640 }
3641 };
3642 }
3643 };
3644 function find(field, data, index) {
3645 for (let n = data.length; index < n; ++index) {
3646 const v = field(data[index]);
3647 if (v != null) return index;
3648 }
3649 return -1;
3650 }
3651 const ValidWindowOps = Object.keys(WindowOps);
3652
3653 function WindowState(_) {
3654 const ops = vegaUtil.array(_.ops),
3655 fields = vegaUtil.array(_.fields),
3656 params = vegaUtil.array(_.params),
3657 as = vegaUtil.array(_.as),
3658 outputs = this.outputs = [],
3659 windows = this.windows = [],
3660 inputs = {},
3661 map = {},
3662 counts = [],
3663 measures = [];
3664 let countOnly = true;
3665 function visitInputs(f) {
3666 vegaUtil.array(vegaUtil.accessorFields(f)).forEach(_ => inputs[_] = 1);
3667 }
3668 visitInputs(_.sort);
3669 ops.forEach((op, i) => {
3670 const field = fields[i],
3671 mname = vegaUtil.accessorName(field),
3672 name = measureName(op, mname, as[i]);
3673 visitInputs(field);
3674 outputs.push(name);
3675
3676 // Window operation
3677 if (vegaUtil.hasOwnProperty(WindowOps, op)) {
3678 windows.push(WindowOp(op, fields[i], params[i], name));
3679 }
3680
3681 // Aggregate operation
3682 else {
3683 if (field == null && op !== 'count') {
3684 vegaUtil.error('Null aggregate field specified.');
3685 }
3686 if (op === 'count') {
3687 counts.push(name);
3688 return;
3689 }
3690 countOnly = false;
3691 let m = map[mname];
3692 if (!m) {
3693 m = map[mname] = [];
3694 m.field = field;
3695 measures.push(m);
3696 }
3697 m.push(createMeasure(op, name));
3698 }
3699 });
3700 if (counts.length || measures.length) {
3701 this.cell = cell(measures, counts, countOnly);
3702 }
3703 this.inputs = Object.keys(inputs);
3704 }
3705 const prototype = WindowState.prototype;
3706 prototype.init = function () {
3707 this.windows.forEach(_ => _.init());
3708 if (this.cell) this.cell.init();
3709 };
3710 prototype.update = function (w, t) {
3711 const cell = this.cell,
3712 wind = this.windows,
3713 data = w.data,
3714 m = wind && wind.length;
3715 let j;
3716 if (cell) {
3717 for (j = w.p0; j < w.i0; ++j) cell.rem(data[j]);
3718 for (j = w.p1; j < w.i1; ++j) cell.add(data[j]);
3719 cell.set(t);
3720 }
3721 for (j = 0; j < m; ++j) wind[j].update(w, t);
3722 };
3723 function cell(measures, counts, countOnly) {
3724 measures = measures.map(m => compileMeasures(m, m.field));
3725 const cell = {
3726 num: 0,
3727 agg: null,
3728 store: false,
3729 count: counts
3730 };
3731 if (!countOnly) {
3732 var n = measures.length,
3733 a = cell.agg = Array(n),
3734 i = 0;
3735 for (; i < n; ++i) a[i] = new measures[i](cell);
3736 }
3737 if (cell.store) {
3738 var store = cell.data = new TupleStore();
3739 }
3740 cell.add = function (t) {
3741 cell.num += 1;
3742 if (countOnly) return;
3743 if (store) store.add(t);
3744 for (let i = 0; i < n; ++i) {
3745 a[i].add(a[i].get(t), t);
3746 }
3747 };
3748 cell.rem = function (t) {
3749 cell.num -= 1;
3750 if (countOnly) return;
3751 if (store) store.rem(t);
3752 for (let i = 0; i < n; ++i) {
3753 a[i].rem(a[i].get(t), t);
3754 }
3755 };
3756 cell.set = function (t) {
3757 let i, n;
3758
3759 // consolidate stored values
3760 if (store) store.values();
3761
3762 // update tuple properties
3763 for (i = 0, n = counts.length; i < n; ++i) t[counts[i]] = cell.num;
3764 if (!countOnly) for (i = 0, n = a.length; i < n; ++i) a[i].set(t);
3765 };
3766 cell.init = function () {
3767 cell.num = 0;
3768 if (store) store.reset();
3769 for (let i = 0; i < n; ++i) a[i].init();
3770 };
3771 return cell;
3772 }
3773
3774 /**
3775 * Perform window calculations and write results to the input stream.
3776 * @constructor
3777 * @param {object} params - The parameters for this operator.
3778 * @param {function(*,*): number} [params.sort] - A comparator function for sorting tuples within a window.
3779 * @param {Array<function(object): *>} [params.groupby] - An array of accessors by which to partition tuples into separate windows.
3780 * @param {Array<string>} params.ops - An array of strings indicating window operations to perform.
3781 * @param {Array<function(object): *>} [params.fields] - An array of accessors
3782 * for data fields to use as inputs to window operations.
3783 * @param {Array<*>} [params.params] - An array of parameter values for window operations.
3784 * @param {Array<string>} [params.as] - An array of output field names for window operations.
3785 * @param {Array<number>} [params.frame] - Window frame definition as two-element array.
3786 * @param {boolean} [params.ignorePeers=false] - If true, base window frame boundaries on row
3787 * number alone, ignoring peers with identical sort values. If false (default),
3788 * the window boundaries will be adjusted to include peer values.
3789 */
3790 function Window(params) {
3791 vegaDataflow.Transform.call(this, {}, params);
3792 this._mlen = 0;
3793 this._mods = [];
3794 }
3795 Window.Definition = {
3796 'type': 'Window',
3797 'metadata': {
3798 'modifies': true
3799 },
3800 'params': [{
3801 'name': 'sort',
3802 'type': 'compare'
3803 }, {
3804 'name': 'groupby',
3805 'type': 'field',
3806 'array': true
3807 }, {
3808 'name': 'ops',
3809 'type': 'enum',
3810 'array': true,
3811 'values': ValidWindowOps.concat(ValidAggregateOps)
3812 }, {
3813 'name': 'params',
3814 'type': 'number',
3815 'null': true,
3816 'array': true
3817 }, {
3818 'name': 'fields',
3819 'type': 'field',
3820 'null': true,
3821 'array': true
3822 }, {
3823 'name': 'as',
3824 'type': 'string',
3825 'null': true,
3826 'array': true
3827 }, {
3828 'name': 'frame',
3829 'type': 'number',
3830 'null': true,
3831 'array': true,
3832 'length': 2,
3833 'default': [null, 0]
3834 }, {
3835 'name': 'ignorePeers',
3836 'type': 'boolean',
3837 'default': false
3838 }]
3839 };
3840 vegaUtil.inherits(Window, vegaDataflow.Transform, {
3841 transform(_, pulse) {
3842 this.stamp = pulse.stamp;
3843 const mod = _.modified(),
3844 cmp = vegaDataflow.stableCompare(_.sort),
3845 key = groupkey(_.groupby),
3846 group = t => this.group(key(t));
3847
3848 // initialize window state
3849 let state = this.state;
3850 if (!state || mod) {
3851 state = this.state = new WindowState(_);
3852 }
3853
3854 // partition input tuples
3855 if (mod || pulse.modified(state.inputs)) {
3856 this.value = {};
3857 pulse.visit(pulse.SOURCE, t => group(t).add(t));
3858 } else {
3859 pulse.visit(pulse.REM, t => group(t).remove(t));
3860 pulse.visit(pulse.ADD, t => group(t).add(t));
3861 }
3862
3863 // perform window calculations for each modified partition
3864 for (let i = 0, n = this._mlen; i < n; ++i) {
3865 processPartition(this._mods[i], state, cmp, _);
3866 }
3867 this._mlen = 0;
3868 this._mods = [];
3869
3870 // TODO don't reflow everything?
3871 return pulse.reflow(mod).modifies(state.outputs);
3872 },
3873 group(key) {
3874 let group = this.value[key];
3875 if (!group) {
3876 group = this.value[key] = SortedList(vegaDataflow.tupleid);
3877 group.stamp = -1;
3878 }
3879 if (group.stamp < this.stamp) {
3880 group.stamp = this.stamp;
3881 this._mods[this._mlen++] = group;
3882 }
3883 return group;
3884 }
3885 });
3886 function processPartition(list, state, cmp, _) {
3887 const sort = _.sort,
3888 range = sort && !_.ignorePeers,
3889 frame = _.frame || [null, 0],
3890 data = list.data(cmp),
3891 // use cmp for stable sort
3892 n = data.length,
3893 b = range ? bisector(sort) : null,
3894 w = {
3895 i0: 0,
3896 i1: 0,
3897 p0: 0,
3898 p1: 0,
3899 index: 0,
3900 data: data,
3901 compare: sort || vegaUtil.constant(-1)
3902 };
3903 state.init();
3904 for (let i = 0; i < n; ++i) {
3905 setWindow(w, frame, i, n);
3906 if (range) adjustRange(w, b);
3907 state.update(w, data[i]);
3908 }
3909 }
3910 function setWindow(w, f, i, n) {
3911 w.p0 = w.i0;
3912 w.p1 = w.i1;
3913 w.i0 = f[0] == null ? 0 : Math.max(0, i - Math.abs(f[0]));
3914 w.i1 = f[1] == null ? n : Math.min(n, i + Math.abs(f[1]) + 1);
3915 w.index = i;
3916 }
3917
3918 // if frame type is 'range', adjust window for peer values
3919 function adjustRange(w, bisect) {
3920 const r0 = w.i0,
3921 r1 = w.i1 - 1,
3922 c = w.compare,
3923 d = w.data,
3924 n = d.length - 1;
3925 if (r0 > 0 && !c(d[r0], d[r0 - 1])) w.i0 = bisect.left(d, d[r0]);
3926 if (r1 < n && !c(d[r1], d[r1 + 1])) w.i1 = bisect.right(d, d[r1]);
3927 }
3928
3929 exports.aggregate = Aggregate;
3930 exports.bin = Bin;
3931 exports.collect = Collect;
3932 exports.compare = Compare;
3933 exports.countpattern = CountPattern;
3934 exports.cross = Cross;
3935 exports.density = Density;
3936 exports.dotbin = DotBin;
3937 exports.expression = Expression;
3938 exports.extent = Extent;
3939 exports.facet = Facet;
3940 exports.field = Field;
3941 exports.filter = Filter;
3942 exports.flatten = Flatten;
3943 exports.fold = Fold;
3944 exports.formula = Formula;
3945 exports.generate = Generate;
3946 exports.impute = Impute;
3947 exports.joinaggregate = JoinAggregate;
3948 exports.kde = KDE;
3949 exports.key = Key;
3950 exports.load = Load;
3951 exports.lookup = Lookup;
3952 exports.multiextent = MultiExtent;
3953 exports.multivalues = MultiValues;
3954 exports.params = Params;
3955 exports.pivot = Pivot;
3956 exports.prefacet = PreFacet;
3957 exports.project = Project;
3958 exports.proxy = Proxy;
3959 exports.quantile = Quantile;
3960 exports.relay = Relay;
3961 exports.sample = Sample;
3962 exports.sequence = Sequence;
3963 exports.sieve = Sieve;
3964 exports.subflow = Subflow;
3965 exports.timeunit = TimeUnit;
3966 exports.tupleindex = TupleIndex;
3967 exports.values = Values;
3968 exports.window = Window;
3969
3970}));