UNPKG

vega-transforms

Version:

Data processing transforms for Vega dataflows.

155 lines (136 loc) 4.01 kB
import Subflow from './Subflow.js'; import {Transform, tupleid} from 'vega-dataflow'; import {fastmap, hasOwnProperty, inherits} from 'vega-util'; /** * Facets a dataflow into a set of subflows based on a key. * @constructor * @param {object} params - The parameters for this operator. * @param {function(Dataflow, string): Operator} params.subflow - A function * that generates a subflow of operators and returns its root operator. * @param {function(object): *} params.key - The key field to facet by. */ export default function Facet(params) { Transform.call(this, {}, params); this._keys = fastmap(); // cache previously calculated key values // keep track of active subflows, use as targets array for listeners // this allows us to limit propagation to only updated subflows const a = this._targets = []; a.active = 0; a.forEach = f => { for (let i=0, n=a.active; i<n; ++i) { f(a[i], i, a); } }; } inherits(Facet, Transform, { activate(flow) { this._targets[this._targets.active++] = flow; }, // parent argument provided by PreFacet subclass subflow(key, flow, pulse, parent) { const flows = this.value; let sf = hasOwnProperty(flows, key) && flows[key], df, p; if (!sf) { p = parent || (p = this._group[key]) && p.tuple; df = pulse.dataflow; sf = new Subflow(pulse.fork(pulse.NO_SOURCE), this); df.add(sf).connect(flow(df, key, p)); flows[key] = sf; this.activate(sf); } else if (sf.value.stamp < pulse.stamp) { sf.init(pulse); this.activate(sf); } return sf; }, clean() { const flows = this.value; let detached = 0; for (const key in flows) { if (flows[key].count === 0) { const detach = flows[key].detachSubflow; if (detach) detach(); delete flows[key]; ++detached; } } // remove inactive targets from the active targets array if (detached) { const active = this._targets.filter(sf => sf && sf.count > 0); this.initTargets(active); } }, initTargets(act) { const a = this._targets, n = a.length, m = act ? act.length : 0; let i = 0; for (; i<m; ++i) { a[i] = act[i]; } for (; i<n && a[i] != null; ++i) { a[i] = null; // ensure old flows can be garbage collected } a.active = m; }, transform(_, pulse) { const df = pulse.dataflow, key = _.key, flow = _.subflow, cache = this._keys, rekey = _.modified('key'), subflow = key => this.subflow(key, flow, pulse); this._group = _.group || {}; this.initTargets(); // reset list of active subflows pulse.visit(pulse.REM, t => { const id = tupleid(t), k = cache.get(id); if (k !== undefined) { cache.delete(id); subflow(k).rem(t); } }); pulse.visit(pulse.ADD, t => { const k = key(t); cache.set(tupleid(t), k); subflow(k).add(t); }); if (rekey || pulse.modified(key.fields)) { pulse.visit(pulse.MOD, t => { const id = tupleid(t), k0 = cache.get(id), k1 = key(t); if (k0 === k1) { subflow(k1).mod(t); } else { cache.set(id, k1); subflow(k0).rem(t); subflow(k1).add(t); } }); } else if (pulse.changed(pulse.MOD)) { pulse.visit(pulse.MOD, t => { subflow(cache.get(tupleid(t))).mod(t); }); } if (rekey) { pulse.visit(pulse.REFLOW, t => { const id = tupleid(t), k0 = cache.get(id), k1 = key(t); if (k0 !== k1) { cache.set(id, k1); subflow(k0).rem(t); subflow(k1).add(t); } }); } if (pulse.clean()) { df.runAfter(() => { this.clean(); cache.clean(); }); } else if (cache.empty > df.cleanThreshold) { df.runAfter(cache.clean); } return pulse; } });