(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-util'), require('vega-dataflow')) : typeof define === 'function' && define.amd ? define(['exports', 'vega-util', 'vega-dataflow'], factory) : (global = global || self, factory(global.vega = {}, global.vega, global.vega)); }(this, (function (exports, vegaUtil, vegaDataflow) { 'use strict'; /** * Parse a serialized dataflow specification. */ function parse(spec) { const ctx = this, operators = spec.operators || []; // parse background if (spec.background) { ctx.background = spec.background; } // parse event configuration if (spec.eventConfig) { ctx.eventConfig = spec.eventConfig; } // parse locale configuration if (spec.locale) { ctx.locale = spec.locale; } // parse operators operators.forEach(entry => ctx.parseOperator(entry)); // parse operator parameters operators.forEach(entry => ctx.parseOperatorParameters(entry)); // parse streams (spec.streams || []).forEach(entry => ctx.parseStream(entry)); // parse updates (spec.updates || []).forEach(entry => ctx.parseUpdate(entry)); return ctx.resolve(); } const Skip = vegaUtil.toSet(['rule']), Swap = vegaUtil.toSet(['group', 'image', 'rect']); function adjustSpatial(encode, marktype) { let code = ''; if (Skip[marktype]) return code; if (encode.x2) { if (encode.x) { if (Swap[marktype]) { code += 'if(o.x>o.x2)$=o.x,o.x=o.x2,o.x2=$;'; } code += 'o.width=o.x2-o.x;'; } else { code += 'o.x=o.x2-(o.width||0);'; } } if (encode.xc) { code += 'o.x=o.xc-(o.width||0)/2;'; } if (encode.y2) { if (encode.y) { if (Swap[marktype]) { code += 'if(o.y>o.y2)$=o.y,o.y=o.y2,o.y2=$;'; } code += 'o.height=o.y2-o.y;'; } else { code += 'o.y=o.y2-(o.height||0);'; } } if (encode.yc) { code += 'o.y=o.yc-(o.height||0)/2;'; } return code; } function canonicalType(type) { return (type + '').toLowerCase(); } function isOperator(type) { return canonicalType(type) === 'operator'; } function isCollect(type) { return canonicalType(type) === 'collect'; } function expression(ctx, args, code) { // wrap code in return statement if expression does not terminate if (code[code.length-1] !== ';') { code = 'return(' + code + ');'; } var fn = Function.apply(null, args.concat(code)); return ctx && ctx.functions ? fn.bind(ctx.functions) : fn; } // generate code for comparing a single field function _compare(u, v, lt, gt) { return `((u = ${u}) < (v = ${v}) || u == null) && v != null ? ${lt} : (u > v || v == null) && u != null ? ${gt} : ((v = v instanceof Date ? +v : v), (u = u instanceof Date ? +u : u)) !== u && v === v ? ${lt} : v !== v && u === u ? ${gt} : `; } var expressionCodegen = { /** * Parse an expression used to update an operator value. */ operator: (ctx, expr) => expression(ctx, ['_'], expr.code), /** * Parse an expression provided as an operator parameter value. */ parameter: (ctx, expr) => expression(ctx, ['datum', '_'], expr.code), /** * Parse an expression applied to an event stream. */ event: (ctx, expr) => expression(ctx, ['event'], expr.code), /** * Parse an expression used to handle an event-driven operator update. */ handler: (ctx, expr) => { const code = `var datum=event.item&&event.item.datum;return ${expr.code};`; return expression(ctx, ['_', 'event'], code); }, /** * Parse an expression that performs visual encoding. */ encode: (ctx, encode) => { const {marktype, channels} = encode; let code = 'var o=item,datum=o.datum,m=0,$;'; for (const name in channels) { const o ='o[' + vegaUtil.stringValue(name) + ']'; code += `$=${channels[name].code};if(${o}!==$)${o}=$,m=1;`; } code += adjustSpatial(channels, marktype); code += 'return m;'; return expression(ctx, ['item', '_'], code); }, /** * Optimized code generators for access and comparison. */ codegen: { get(path) { const ref = `[${path.map(vegaUtil.stringValue).join('][')}]`; const get = Function('_', `return _${ref};`); get.path = ref; return get; }, comparator(fields, orders) { let t; const map = (f, i) => { const o = orders[i]; let u, v; if (f.path) { u = `a${f.path}`; v = `b${f.path}`; } else { (t = t || {})['f'+i] = f; u = `this.f${i}(a)`; v = `this.f${i}(b)`; } return _compare(u, v, -o, o); }; const fn = Function('a', 'b', 'var u, v; return ' + fields.map(map).join('') + '0;'); return t ? fn.bind(t) : fn; } } }; /** * Parse a dataflow operator. */ function parseOperator(spec) { const ctx = this; if (isOperator(spec.type) || !spec.type) { ctx.operator( spec, spec.update ? ctx.operatorExpression(spec.update) : null ); } else { ctx.transform(spec, spec.type); } } /** * Parse and assign operator parameters. */ function parseOperatorParameters(spec) { const ctx = this; if (spec.params) { const op = ctx.get(spec.id); if (!op) vegaUtil.error('Invalid operator id: ' + spec.id); ctx.dataflow.connect(op, op.parameters( ctx.parseParameters(spec.params), spec.react, spec.initonly )); } } /** * Parse a set of operator parameters. */ function parseParameters(spec, params) { params = params || {}; const ctx = this; for (const key in spec) { const value = spec[key]; params[key] = vegaUtil.isArray(value) ? value.map(v => parseParameter(v, ctx, params)) : parseParameter(value, ctx, params); } return params; } /** * Parse a single parameter. */ function parseParameter(spec, ctx, params) { if (!spec || !vegaUtil.isObject(spec)) return spec; for (let i=0, n=PARSERS.length, p; i (_ && _.$tupleid) ? vegaDataflow.tupleid : _); return ctx.fn[k] || (ctx.fn[k] = vegaUtil.compare(c, _.$order, ctx.expr.codegen)); } /** * Resolve an encode operator reference. */ function getEncode(_, ctx) { const spec = _.$encode, encode = {}; for (const name in spec) { const enc = spec[name]; encode[name] = vegaUtil.accessor(ctx.encodeExpression(enc.$expr), enc.$fields); encode[name].output = enc.$output; } return encode; } /** * Resolve a context reference. */ function getContext(_, ctx) { return ctx; } /** * Resolve a recursive subflow specification. */ function getSubflow(_, ctx) { const spec = _.$subflow; return function(dataflow, key, parent) { const subctx = ctx.fork().parse(spec), op = subctx.get(spec.operators[0].id), p = subctx.signals.parent; if (p) p.set(parent); op.detachSubflow = () => ctx.detach(subctx); return op; }; } /** * Resolve a tuple id reference. */ function getTupleId() { return vegaDataflow.tupleid; } /** * Parse an event stream specification. */ function parseStream(spec) { var ctx = this, filter = spec.filter != null ? ctx.eventExpression(spec.filter) : undefined, stream = spec.stream != null ? ctx.get(spec.stream) : undefined, args; if (spec.source) { stream = ctx.events(spec.source, spec.type, filter); } else if (spec.merge) { args = spec.merge.map(_ => ctx.get(_)); stream = args[0].merge.apply(args[0], args.slice(1)); } if (spec.between) { args = spec.between.map(_ => ctx.get(_)); stream = stream.between(args[0], args[1]); } if (spec.filter) { stream = stream.filter(filter); } if (spec.throttle != null) { stream = stream.throttle(+spec.throttle); } if (spec.debounce != null) { stream = stream.debounce(+spec.debounce); } if (stream == null) { vegaUtil.error('Invalid stream definition: ' + JSON.stringify(spec)); } if (spec.consume) stream.consume(true); ctx.stream(spec, stream); } /** * Parse an event-driven operator update. */ function parseUpdate(spec) { var ctx = this, srcid = vegaUtil.isObject(srcid = spec.source) ? srcid.$ref : srcid, source = ctx.get(srcid), target = null, update = spec.update, params = undefined; if (!source) vegaUtil.error('Source not defined: ' + spec.source); target = spec.target && spec.target.$expr ? ctx.eventExpression(spec.target.$expr) : ctx.get(spec.target); if (update && update.$expr) { if (update.$params) { params = ctx.parseParameters(update.$params); } update = ctx.handlerExpression(update.$expr); } ctx.update(spec, source, target, update, params); } var SKIP = {skip: true}; function getState(options) { var ctx = this, state = {}; if (options.signals) { var signals = (state.signals = {}); Object.keys(ctx.signals).forEach(key => { var op = ctx.signals[key]; if (options.signals(key, op)) { signals[key] = op.value; } }); } if (options.data) { var data = (state.data = {}); Object.keys(ctx.data).forEach(key => { var dataset = ctx.data[key]; if (options.data(key, dataset)) { data[key] = dataset.input.value; } }); } if (ctx.subcontext && options.recurse !== false) { state.subcontext = ctx.subcontext.map(ctx => ctx.getState(options)); } return state; } function setState(state) { var ctx = this, df = ctx.dataflow, data = state.data, signals = state.signals; Object.keys(signals || {}).forEach(key => { df.update(ctx.signals[key], signals[key], SKIP); }); Object.keys(data || {}).forEach(key => { df.pulse( ctx.data[key].input, df.changeset().remove(vegaUtil.truthy).insert(data[key]) ); }); (state.subcontext || []).forEach((substate, i) => { var subctx = ctx.subcontext[i]; if (subctx) subctx.setState(substate); }); } /** * Context objects store the current parse state. * Enables lookup of parsed operators, event streams, accessors, etc. * Provides a 'fork' method for creating child contexts for subflows. */ function context(df, transforms, functions, expr) { return new Context(df, transforms, functions, expr); } function Context(df, transforms, functions, expr) { this.dataflow = df; this.transforms = transforms; this.events = df.events.bind(df); this.expr = expr || expressionCodegen, this.signals = {}; this.scales = {}; this.nodes = {}; this.data = {}; this.fn = {}; if (functions) { this.functions = Object.create(functions); this.functions.context = this; } } function Subcontext(ctx) { this.dataflow = ctx.dataflow; this.transforms = ctx.transforms; this.events = ctx.events; this.expr = ctx.expr; this.signals = Object.create(ctx.signals); this.scales = Object.create(ctx.scales); this.nodes = Object.create(ctx.nodes); this.data = Object.create(ctx.data); this.fn = Object.create(ctx.fn); if (ctx.functions) { this.functions = Object.create(ctx.functions); this.functions.context = this; } } Context.prototype = Subcontext.prototype = { fork() { const ctx = new Subcontext(this); (this.subcontext || (this.subcontext = [])).push(ctx); return ctx; }, detach(ctx) { this.subcontext = this.subcontext.filter(c => c !== ctx); // disconnect all nodes in the subcontext // wipe out targets first for better efficiency const keys = Object.keys(ctx.nodes); for (const key of keys) ctx.nodes[key]._targets = null; for (const key of keys) ctx.nodes[key].detach(); ctx.nodes = null; }, get(id) { return this.nodes[id]; }, set(id, node) { return this.nodes[id] = node; }, add(spec, op) { const ctx = this, df = ctx.dataflow, data = spec.value; ctx.set(spec.id, op); if (isCollect(spec.type) && data) { if (data.$ingest) { df.ingest(op, data.$ingest, data.$format); } else if (data.$request) { df.preload(op, data.$request, data.$format); } else { df.pulse(op, df.changeset().insert(data)); } } if (spec.root) { ctx.root = op; } if (spec.parent) { var p = ctx.get(spec.parent.$ref); if (p) { df.connect(p, [op]); op.targets().add(p); } else { (ctx.unresolved = ctx.unresolved || []).push(() => { p = ctx.get(spec.parent.$ref); df.connect(p, [op]); op.targets().add(p); }); } } if (spec.signal) { ctx.signals[spec.signal] = op; } if (spec.scale) { ctx.scales[spec.scale] = op; } if (spec.data) { for (const name in spec.data) { const data = ctx.data[name] || (ctx.data[name] = {}); spec.data[name].forEach(role => data[role] = op); } } }, resolve() { (this.unresolved || []).forEach(fn => fn()); delete this.unresolved; return this; }, operator(spec, update) { this.add(spec, this.dataflow.add(spec.value, update)); }, transform(spec, type) { this.add(spec, this.dataflow.add(this.transforms[canonicalType(type)])); }, stream(spec, stream) { this.set(spec.id, stream); }, update(spec, stream, target, update, params) { this.dataflow.on(stream, target, update, params, spec.options); }, // expression parsing operatorExpression(expr) { return this.expr.operator(this, expr); }, parameterExpression(expr) { return this.expr.parameter(this, expr); }, eventExpression(expr) { return this.expr.event(this, expr); }, handlerExpression(expr) { return this.expr.handler(this, expr); }, encodeExpression(encode) { return this.expr.encode(this, encode); }, // parse methods parse, parseOperator, parseOperatorParameters, parseParameters, parseStream, parseUpdate, // state methods getState, setState }; exports.context = context; Object.defineProperty(exports, '__esModule', { value: true }); })));