UNPKG

vega-parser

Version:

Parse Vega specifications to runtime dataflows.

133 lines (115 loc) 4.24 kB
import parseAutosize from './autosize.js'; import parsePadding from './padding.js'; import parseEncode from './encode.js'; import parseSignal from './signal.js'; import parseSpec from './scope.js'; import {extendEncode} from './encode/util.js'; import {GroupMark} from './marks/marktypes.js'; import {FrameRole} from './marks/roles.js'; import {operator, ref, value} from '../util.js'; import DataScope from '../DataScope.js'; import {Bound, Collect, Encode, Render, Sieve, ViewLayout} from '../transforms.js'; import {array, extend, hasOwnProperty} from 'vega-util'; const rootEncode = spec => extendEncode( { enter: { x: {value: 0}, y: {value: 0} }, update: { width: {signal: 'width'}, height: {signal: 'height'} } }, spec ); export default function parseView(spec, scope) { const config = scope.config; // add scenegraph root const root = ref(scope.root = scope.add(operator())); // parse top-level signal definitions const signals = collectSignals(spec, config); signals.forEach(_ => parseSignal(_, scope)); // assign description, event, legend, and locale configuration scope.description = spec.description || config.description; scope.eventConfig = config.events; scope.legends = scope.objectProperty(config.legend && config.legend.layout); scope.locale = config.locale; // store root group item const input = scope.add(Collect()); // encode root group item const encode = scope.add(Encode(parseEncode( rootEncode(spec.encode), GroupMark, FrameRole, spec.style, scope, {pulse: ref(input)} ))); // perform view layout const parent = scope.add(ViewLayout({ layout: scope.objectProperty(spec.layout), legends: scope.legends, autosize: scope.signalRef('autosize'), mark: root, pulse: ref(encode) })); scope.operators.pop(); // parse remainder of specification scope.pushState(ref(encode), ref(parent), null); parseSpec(spec, scope, signals); scope.operators.push(parent); // bound / render / sieve root item let op = scope.add(Bound({mark: root, pulse: ref(parent)})); op = scope.add(Render({pulse: ref(op)})); op = scope.add(Sieve({pulse: ref(op)})); // track metadata for root item scope.addData('root', new DataScope(scope, input, input, op)); return scope; } function signalObject(name, value) { return value && value.signal ? { name, update: value.signal } : { name, value }; } /** * Collect top-level signals, merging values as needed. Signals * defined in the config signals arrays are added only if that * signal is not explicitly defined in the specification. * Built-in signals (autosize, background, padding, width, height) * receive special treatment. They are initialized using the * top-level spec property, or, if undefined in the spec, using * the corresponding top-level config property. If this property * is a signal reference object, the signal expression maps to the * signal 'update' property. If the spec's top-level signal array * contains an entry that matches a built-in signal, that entry * will be merged with the built-in specification, potentially * overwriting existing 'value' or 'update' properties. */ function collectSignals(spec, config) { const _ = name => value(spec[name], config[name]), signals = [ signalObject('background', _('background')), signalObject('autosize', parseAutosize(_('autosize'))), signalObject('padding', parsePadding(_('padding'))), signalObject('width', _('width') || 0), signalObject('height', _('height') || 0) ], pre = signals.reduce((p, s) => (p[s.name] = s, p), {}), map = {}; // add spec signal array array(spec.signals).forEach(s => { if (hasOwnProperty(pre, s.name)) { // merge if built-in signal s = extend(pre[s.name], s); } else { // otherwise add to signal list signals.push(s); } map[s.name] = s; }); // add config signal array array(config.signals).forEach(s => { if (!hasOwnProperty(map, s.name) && !hasOwnProperty(pre, s.name)) { // add to signal list if not already defined signals.push(s); } }); return signals; }