vega-dataflow
Version:
Reactive dataflow processing.
92 lines (82 loc) • 3.52 kB
JavaScript
import Operator from '../Operator.js';
import {isChangeSet} from '../ChangeSet.js';
import {constant, extend, isFunction} from 'vega-util';
const SKIP = {skip: true};
/**
* Perform operator updates in response to events. Applies an
* update function to compute a new operator value. If the update function
* returns a {@link ChangeSet}, the operator will be pulsed with those tuple
* changes. Otherwise, the operator value will be updated to the return value.
* @param {EventStream|Operator} source - The event source to react to.
* This argument can be either an EventStream or an Operator.
* @param {Operator|function(object):Operator} target - The operator to update.
* This argument can either be an Operator instance or (if the source
* argument is an EventStream), a function that accepts an event object as
* input and returns an Operator to target.
* @param {function(Parameters,Event): *} [update] - Optional update function
* to compute the new operator value, or a literal value to set. Update
* functions expect to receive a parameter object and event as arguments.
* This function can either return a new operator value or (if the source
* argument is an EventStream) a {@link ChangeSet} instance to pulse
* the target operator with tuple changes.
* @param {object} [params] - The update function parameters.
* @param {object} [options] - Additional options hash. If not overridden,
* updated operators will be skipped by default.
* @param {boolean} [options.skip] - If true, the operator will
* be skipped: it will not be evaluated, but its dependents will be.
* @param {boolean} [options.force] - If true, the operator will
* be re-evaluated even if its value has not changed.
* @return {Dataflow}
*/
export default function(source, target, update, params, options) {
const fn = source instanceof Operator ? onOperator : onStream;
fn(this, source, target, update, params, options);
return this;
}
function onStream(df, stream, target, update, params, options) {
const opt = extend({}, options, SKIP);
let func, op;
if (!isFunction(target)) target = constant(target);
if (update === undefined) {
func = e => df.touch(target(e));
} else if (isFunction(update)) {
op = new Operator(null, update, params, false);
func = e => {
op.evaluate(e);
const t = target(e), v = op.value;
isChangeSet(v) ? df.pulse(t, v, options) : df.update(t, v, opt);
};
} else {
func = e => df.update(target(e), update, opt);
}
stream.apply(func);
}
function onOperator(df, source, target, update, params, options) {
if (update === undefined) {
source.targets().add(target);
} else {
const opt = options || {},
op = new Operator(null, updater(target, update), params, false);
op.modified(opt.force);
op.rank = source.rank; // immediately follow source
source.targets().add(op); // add dependency
if (target) {
op.skip(true); // skip first invocation
op.value = target.value; // initialize value
op.targets().add(target); // chain dependencies
df.connect(target, [op]); // rerank as needed, #1672
}
}
}
function updater(target, update) {
update = isFunction(update) ? update : constant(update);
return target
? function(_, pulse) {
const value = update(_, pulse);
if (!target.skip()) {
target.skip(value !== this.value).value = value;
}
return value;
}
: update;
}