(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-util'), require('vega-functions'), require('vega-event-selector'), require('vega-scale'), require('vega-dataflow')) : typeof define === 'function' && define.amd ? define(['exports', 'vega-util', 'vega-functions', 'vega-event-selector', 'vega-scale', 'vega-dataflow'], factory) : (global = global || self, factory(global.vega = {}, global.vega, global.vega, global.vega, global.vega, global.vega)); }(this, (function (exports, vegaUtil, vegaFunctions, vegaEventSelector, vegaScale, vegaDataflow) { 'use strict'; function parseAutosize(spec) { return vegaUtil.isObject(spec) ? spec : {type: spec || 'pad'}; } const number = _ => +_ || 0; const paddingObject = _ => ({top: _, bottom: _, left: _, right: _}); function parsePadding(spec) { return !vegaUtil.isObject(spec) ? paddingObject(number(spec)) : spec.signal ? spec : { top: number(spec.top), bottom: number(spec.bottom), left: number(spec.left), right: number(spec.right) }; } const encoder = _ => vegaUtil.isObject(_) && !vegaUtil.isArray(_) ? vegaUtil.extend({}, _) : {value: _}; function addEncode(object, name, value, set) { if (value != null) { // Always assign signal to update, even if the signal is from the enter block if (vegaUtil.isObject(value) && !vegaUtil.isArray(value)) { object.update[name] = value; } else { object[set || 'enter'][name] = {value: value}; } return 1; } else { return 0; } } function addEncoders(object, enter, update) { for (const name in enter) { addEncode(object, name, enter[name]); } for (const name in update) { addEncode(object, name, update[name], 'update'); } } function extendEncode(encode, extra, skip) { for (const name in extra) { if (skip && vegaUtil.hasOwnProperty(skip, name)) continue; encode[name] = vegaUtil.extend(encode[name] || {}, extra[name]); } return encode; } function has(key, encode) { return encode && ( (encode.enter && encode.enter[key]) || (encode.update && encode.update[key]) ); } var MarkRole = 'mark'; var FrameRole = 'frame'; var ScopeRole = 'scope'; var AxisRole = 'axis'; var AxisDomainRole = 'axis-domain'; var AxisGridRole = 'axis-grid'; var AxisLabelRole = 'axis-label'; var AxisTickRole = 'axis-tick'; var AxisTitleRole = 'axis-title'; var LegendRole = 'legend'; var LegendBandRole = 'legend-band'; var LegendEntryRole = 'legend-entry'; var LegendGradientRole = 'legend-gradient'; var LegendLabelRole = 'legend-label'; var LegendSymbolRole = 'legend-symbol'; var LegendTitleRole = 'legend-title'; var TitleRole = 'title'; var TitleTextRole = 'title-text'; var TitleSubtitleRole = 'title-subtitle'; function applyDefaults(encode, type, role, style, config) { const defaults = {}, enter = {}; let update, key, skip, props; // if text mark, apply global lineBreak settings (#2370) key = 'lineBreak'; if (type === 'text' && config[key] != null && !has(key, encode)) { applyDefault(defaults, key, config[key]); } // ignore legend and axis roles if (role == 'legend' || String(role).startsWith('axis')) { role = null; } // resolve mark config props = role === FrameRole ? config.group : (role === MarkRole) ? vegaUtil.extend({}, config.mark, config[type]) : null; for (key in props) { // do not apply defaults if relevant fields are defined skip = has(key, encode) || (key === 'fill' || key === 'stroke') && (has('fill', encode) || has('stroke', encode)); if (!skip) applyDefault(defaults, key, props[key]); } // resolve styles, apply with increasing precedence vegaUtil.array(style).forEach(name => { const props = config.style && config.style[name]; for (const key in props) { if (!has(key, encode)) { applyDefault(defaults, key, props[key]); } } }); encode = vegaUtil.extend({}, encode); // defensive copy for (key in defaults) { props = defaults[key]; if (props.signal) { (update = update || {})[key] = props; } else { enter[key] = props; } } encode.enter = vegaUtil.extend(enter, encode.enter); if (update) encode.update = vegaUtil.extend(update, encode.update); return encode; } function applyDefault(defaults, key, value) { defaults[key] = value && value.signal ? {signal: value.signal} : {value: value}; } const scaleRef = scale => vegaUtil.isString(scale) ? vegaUtil.stringValue(scale) : scale.signal ? `(${scale.signal})` : field(scale); function entry(enc) { if (enc.gradient != null) { return gradient(enc); } let value = enc.signal ? `(${enc.signal})` : enc.color ? color(enc.color) : enc.field != null ? field(enc.field) : enc.value !== undefined ? vegaUtil.stringValue(enc.value) : undefined; if (enc.scale != null) { value = scale(enc, value); } if (value === undefined) { value = null; } if (enc.exponent != null) { value = `pow(${value},${property(enc.exponent)})`; } if (enc.mult != null) { value += `*${property(enc.mult)}`; } if (enc.offset != null) { value += `+${property(enc.offset)}`; } if (enc.round) { value = `round(${value})`; } return value; } const _color = (type, x, y, z) => `(${type}(${[x, y, z].map(entry).join(',')})+'')`; function color(enc) { return (enc.c) ? _color('hcl', enc.h, enc.c, enc.l) : (enc.h || enc.s) ? _color('hsl', enc.h, enc.s, enc.l) : (enc.l || enc.a) ? _color('lab', enc.l, enc.a, enc.b) : (enc.r || enc.g || enc.b) ? _color('rgb', enc.r, enc.g, enc.b) : null; } function gradient(enc) { // map undefined to null; expression lang does not allow undefined const args = [enc.start, enc.stop, enc.count] .map(_ => _ == null ? null : vegaUtil.stringValue(_)); // trim null inputs from the end while (args.length && vegaUtil.peek(args) == null) args.pop(); args.unshift(scaleRef(enc.gradient)); return `gradient(${args.join(',')})`; } function property(property) { return vegaUtil.isObject(property) ? '(' + entry(property) + ')' : property; } function field(ref) { return resolveField(vegaUtil.isObject(ref) ? ref : {datum: ref}); } function resolveField(ref) { let object, level, field; if (ref.signal) { object = 'datum'; field = ref.signal; } else if (ref.group || ref.parent) { level = Math.max(1, ref.level || 1); object = 'item'; while (level-- > 0) { object += '.mark.group'; } if (ref.parent) { field = ref.parent; object += '.datum'; } else { field = ref.group; } } else if (ref.datum) { object = 'datum'; field = ref.datum; } else { vegaUtil.error('Invalid field reference: ' + vegaUtil.stringValue(ref)); } if (!ref.signal) { field = vegaUtil.isString(field) ? vegaUtil.splitAccessPath(field).map(vegaUtil.stringValue).join('][') : resolveField(field); } return object + '[' + field + ']'; } function scale(enc, value) { const scale = scaleRef(enc.scale); if (enc.range != null) { // pull value from scale range value = `lerp(_range(${scale}), ${+enc.range})`; } else { // run value through scale and/or pull scale bandwidth if (value !== undefined) value = `_scale(${scale}, ${value})`; if (enc.band) { value = (value ? value + '+' : '') + `_bandwidth(${scale})` + (+enc.band === 1 ? '' : '*' + property(enc.band)); if (enc.extra) { // include logic to handle extraneous elements value = `(datum.extra ? _scale(${scale}, datum.extra.value) : ${value})`; } } if (value == null) value = '0'; } return value; } function rule(enc) { let code = ''; enc.forEach(rule => { const value = entry(rule); code += rule.test ? `(${rule.test})?${value}:` : value; }); // if no else clause, terminate with null (#1366) if (vegaUtil.peek(code) === ':') { code += 'null'; } return code; } function parseEncode(encode, type, role, style, scope, params) { const enc = {}; params = params || {}; params.encoders = {$encode: enc}; encode = applyDefaults(encode, type, role, style, scope.config); for (const key in encode) { enc[key] = parseBlock(encode[key], type, params, scope); } return params; } function parseBlock(block, marktype, params, scope) { const channels = {}, fields = {}; for (const name in block) { if (block[name] != null) { // skip any null entries channels[name] = parse(expr(block[name]), scope, params, fields); } } return { $expr: {marktype, channels}, $fields: Object.keys(fields), $output: Object.keys(block) }; } function expr(enc) { return vegaUtil.isArray(enc) ? rule(enc) : entry(enc); } function parse(code, scope, params, fields) { const expr = vegaFunctions.parseExpression(code, scope); expr.$fields.forEach(name => fields[name] = 1); vegaUtil.extend(params, expr.$params); return expr.$expr; } var OUTER = 'outer', OUTER_INVALID = ['value', 'update', 'init', 'react', 'bind']; function outerError(prefix, name) { vegaUtil.error(prefix + ' for "outer" push: ' + vegaUtil.stringValue(name)); } function parseSignal(signal, scope) { var name = signal.name; if (signal.push === OUTER) { // signal must already be defined, raise error if not if (!scope.signals[name]) outerError('No prior signal definition', name); // signal push must not use properties reserved for standard definition OUTER_INVALID.forEach(function(prop) { if (signal[prop] !== undefined) outerError('Invalid property ', prop); }); } else { // define a new signal in the current scope var op = scope.addSignal(name, signal.value); if (signal.react === false) op.react = false; if (signal.bind) scope.addBinding(name, signal.bind); } } function Entry(type, value, params, parent) { this.id = -1; this.type = type; this.value = value; this.params = params; if (parent) this.parent = parent; } function entry$1(type, value, params, parent) { return new Entry(type, value, params, parent); } function operator(value, params) { return entry$1('operator', value, params); } // ----- function ref(op) { var ref = {$ref: op.id}; // if operator not yet registered, cache ref to resolve later if (op.id < 0) (op.refs = op.refs || []).push(ref); return ref; } function fieldRef(field, name) { return name ? {$field: field, $name: name} : {$field: field}; } var keyFieldRef = fieldRef('key'); function compareRef(fields, orders) { return {$compare: fields, $order: orders}; } function keyRef(fields, flat) { var ref = {$key: fields}; if (flat) ref.$flat = true; return ref; } // ----- var Ascending = 'ascending'; var Descending = 'descending'; function sortKey(sort) { return !vegaUtil.isObject(sort) ? '' : (sort.order === Descending ? '-' : '+') + aggrField(sort.op, sort.field); } function aggrField(op, field) { return (op && op.signal ? '$' + op.signal : op || '') + (op && field ? '_' : '') + (field && field.signal ? '$' + field.signal : field || ''); } // ----- var Scope = 'scope'; var View = 'view'; function isSignal(_) { return _ && _.signal; } function isExpr(_) { return _ && _.expr; } function hasSignal(_) { if (isSignal(_)) return true; if (vegaUtil.isObject(_)) for (var key in _) { if (hasSignal(_[key])) return true; } return false; } function value(specValue, defaultValue) { return specValue != null ? specValue : defaultValue; } function deref(v) { return v && v.signal || v; } var Timer = 'timer'; function parseStream(stream, scope) { var method = stream.merge ? mergeStream : stream.stream ? nestedStream : stream.type ? eventStream : vegaUtil.error('Invalid stream specification: ' + vegaUtil.stringValue(stream)); return method(stream, scope); } function eventSource(source) { return source === Scope ? View : (source || View); } function mergeStream(stream, scope) { var list = stream.merge.map(s => parseStream(s, scope)), entry = streamParameters({merge: list}, stream, scope); return scope.addStream(entry).id; } function nestedStream(stream, scope) { var id = parseStream(stream.stream, scope), entry = streamParameters({stream: id}, stream, scope); return scope.addStream(entry).id; } function eventStream(stream, scope) { var id, entry; if (stream.type === Timer) { id = scope.event(Timer, stream.throttle); stream = {between: stream.between, filter: stream.filter}; } else { id = scope.event(eventSource(stream.source), stream.type); } entry = streamParameters({stream: id}, stream, scope); return Object.keys(entry).length === 1 ? id : scope.addStream(entry).id; } function streamParameters(entry, stream, scope) { var param = stream.between; if (param) { if (param.length !== 2) { vegaUtil.error('Stream "between" parameter must have 2 entries: ' + vegaUtil.stringValue(stream)); } entry.between = [ parseStream(param[0], scope), parseStream(param[1], scope) ]; } param = stream.filter ? [].concat(stream.filter) : []; if (stream.marktype || stream.markname || stream.markrole) { // add filter for mark type, name and/or role param.push(filterMark(stream.marktype, stream.markname, stream.markrole)); } if (stream.source === Scope) { // add filter to limit events from sub-scope only param.push('inScope(event.item)'); } if (param.length) { entry.filter = vegaFunctions.parseExpression('(' + param.join(')&&(') + ')', scope).$expr; } if ((param = stream.throttle) != null) { entry.throttle = +param; } if ((param = stream.debounce) != null) { entry.debounce = +param; } if (stream.consume) { entry.consume = true; } return entry; } function filterMark(type, name, role) { var item = 'event.item'; return item + (type && type !== '*' ? '&&' + item + '.mark.marktype===\'' + type + '\'' : '') + (role ? '&&' + item + '.mark.role===\'' + role + '\'' : '') + (name ? '&&' + item + '.mark.name===\'' + name + '\'' : ''); } // bypass expression parser for internal operator references const OP_VALUE_EXPR = { code: '_.$value', ast: {type: 'Identifier', value: 'value'} }; function parseUpdate(spec, scope, target) { var events = spec.events, update = spec.update, encode = spec.encode, sources = [], entry = {target: target}; if (!events) { vegaUtil.error('Signal update missing events specification.'); } // interpret as an event selector string if (vegaUtil.isString(events)) { events = vegaEventSelector.selector(events, scope.isSubscope() ? Scope : View); } // separate event streams from signal updates events = vegaUtil.array(events) .filter(s => s.signal || s.scale ? (sources.push(s), 0) : 1); // merge internal operator listeners if (sources.length > 1) { sources = [mergeSources(sources)]; } // merge event streams, include as source if (events.length) { sources.push(events.length > 1 ? {merge: events} : events[0]); } if (encode != null) { if (update) vegaUtil.error('Signal encode and update are mutually exclusive.'); update = 'encode(item(),' + vegaUtil.stringValue(encode) + ')'; } // resolve update value entry.update = vegaUtil.isString(update) ? vegaFunctions.parseExpression(update, scope) : update.expr != null ? vegaFunctions.parseExpression(update.expr, scope) : update.value != null ? update.value : update.signal != null ? { $expr: OP_VALUE_EXPR, $params: {$value: scope.signalRef(update.signal)} } : vegaUtil.error('Invalid signal update specification.'); if (spec.force) { entry.options = {force: true}; } sources.forEach(function(source) { scope.addUpdate(vegaUtil.extend(streamSource(source, scope), entry)); }); } function streamSource(stream, scope) { return { source: stream.signal ? scope.signalRef(stream.signal) : stream.scale ? scope.scaleRef(stream.scale) : parseStream(stream, scope) }; } function mergeSources(sources) { return { signal: '[' + sources.map(s => s.scale ? 'scale("' + s.scale + '")' : s.signal) + ']' }; } function parseSignalUpdates(signal, scope) { var op = scope.getSignal(signal.name), expr = signal.update; if (signal.init) { if (expr) { vegaUtil.error('Signals can not include both init and update expressions.'); } else { expr = signal.init; op.initonly = true; } } if (expr) { expr = vegaFunctions.parseExpression(expr, scope); op.update = expr.$expr; op.params = expr.$params; } if (signal.on) { signal.on.forEach(function(_) { parseUpdate(_, scope, op.id); }); } } const transform = name => (params, value, parent) => entry$1(name, value, params || undefined, parent); var Aggregate = transform('aggregate'); var AxisTicks = transform('axisticks'); var Bound = transform('bound'); var Collect = transform('collect'); var Compare = transform('compare'); var DataJoin = transform('datajoin'); var Encode = transform('encode'); var Expression = transform('expression'); var Facet = transform('facet'); var Field = transform('field'); var Key = transform('key'); var LegendEntries = transform('legendentries'); var Load = transform('load'); var Mark = transform('mark'); var MultiExtent = transform('multiextent'); var MultiValues = transform('multivalues'); var Overlap = transform('overlap'); var Params = transform('params'); var PreFacet = transform('prefacet'); var Projection = transform('projection'); var Proxy = transform('proxy'); var Relay = transform('relay'); var Render = transform('render'); var Scale = transform('scale'); var Sieve = transform('sieve'); var SortItems = transform('sortitems'); var ViewLayout = transform('viewlayout'); var Values = transform('values'); var FIELD_REF_ID = 0; var MULTIDOMAIN_SORT_OPS = {min: 'min', max: 'max', count: 'sum'}; function initScale(spec, scope) { var type = spec.type || 'linear'; if (!vegaScale.isValidScaleType(type)) { vegaUtil.error('Unrecognized scale type: ' + vegaUtil.stringValue(type)); } scope.addScale(spec.name, { type: type, domain: undefined }); } function parseScale(spec, scope) { var params = scope.getScale(spec.name).params, key; params.domain = parseScaleDomain(spec.domain, spec, scope); if (spec.range != null) { params.range = parseScaleRange(spec, scope, params); } if (spec.interpolate != null) { parseScaleInterpolate(spec.interpolate, params); } if (spec.nice != null) { params.nice = parseScaleNice(spec.nice); } if (spec.bins != null) { params.bins = parseScaleBins(spec.bins, scope); } for (key in spec) { if (vegaUtil.hasOwnProperty(params, key) || key === 'name') continue; params[key] = parseLiteral(spec[key], scope); } } function parseLiteral(v, scope) { return !vegaUtil.isObject(v) ? v : v.signal ? scope.signalRef(v.signal) : vegaUtil.error('Unsupported object: ' + vegaUtil.stringValue(v)); } function parseArray(v, scope) { return v.signal ? scope.signalRef(v.signal) : v.map(v => parseLiteral(v, scope)); } function dataLookupError(name) { vegaUtil.error('Can not find data set: ' + vegaUtil.stringValue(name)); } // -- SCALE DOMAIN ---- function parseScaleDomain(domain, spec, scope) { if (!domain) { if (spec.domainMin != null || spec.domainMax != null) { vegaUtil.error('No scale domain defined for domainMin/domainMax to override.'); } return; // default domain } return domain.signal ? scope.signalRef(domain.signal) : (vegaUtil.isArray(domain) ? explicitDomain : domain.fields ? multipleDomain : singularDomain)(domain, spec, scope); } function explicitDomain(domain, spec, scope) { return domain.map(function(v) { return parseLiteral(v, scope); }); } function singularDomain(domain, spec, scope) { var data = scope.getData(domain.data); if (!data) dataLookupError(domain.data); return vegaScale.isDiscrete(spec.type) ? data.valuesRef(scope, domain.field, parseSort(domain.sort, false)) : vegaScale.isQuantile(spec.type) ? data.domainRef(scope, domain.field) : data.extentRef(scope, domain.field); } function multipleDomain(domain, spec, scope) { var data = domain.data, fields = domain.fields.reduce(function(dom, d) { d = vegaUtil.isString(d) ? {data: data, field: d} : (vegaUtil.isArray(d) || d.signal) ? fieldRef$1(d, scope) : d; dom.push(d); return dom; }, []); return (vegaScale.isDiscrete(spec.type) ? ordinalMultipleDomain : vegaScale.isQuantile(spec.type) ? quantileMultipleDomain : numericMultipleDomain)(domain, scope, fields); } function fieldRef$1(data, scope) { var name = '_:vega:_' + (FIELD_REF_ID++), coll = Collect({}); if (vegaUtil.isArray(data)) { coll.value = {$ingest: data}; } else if (data.signal) { var code = 'setdata(' + vegaUtil.stringValue(name) + ',' + data.signal + ')'; coll.params.input = scope.signalRef(code); } scope.addDataPipeline(name, [coll, Sieve({})]); return {data: name, field: 'data'}; } function ordinalMultipleDomain(domain, scope, fields) { var sort = parseSort(domain.sort, true), counts, p, a, c, v; // get value counts for each domain field counts = fields.map(function(f) { var data = scope.getData(f.data); if (!data) dataLookupError(f.data); return data.countsRef(scope, f.field, sort); }); // aggregate the results from each domain field p = {groupby: keyFieldRef, pulse: counts}; if (sort) { a = sort.op || 'count'; v = sort.field ? aggrField(a, sort.field) : 'count'; p.ops = [MULTIDOMAIN_SORT_OPS[a]]; p.fields = [scope.fieldRef(v)]; p.as = [v]; } a = scope.add(Aggregate(p)); // collect aggregate output c = scope.add(Collect({pulse: ref(a)})); // extract values for combined domain v = scope.add(Values({ field: keyFieldRef, sort: scope.sortRef(sort), pulse: ref(c) })); return ref(v); } function parseSort(sort, multidomain) { if (sort) { if (!sort.field && !sort.op) { if (vegaUtil.isObject(sort)) sort.field = 'key'; else sort = {field: 'key'}; } else if (!sort.field && sort.op !== 'count') { vegaUtil.error('No field provided for sort aggregate op: ' + sort.op); } else if (multidomain && sort.field) { if (sort.op && !MULTIDOMAIN_SORT_OPS[sort.op]) { vegaUtil.error('Multiple domain scales can not be sorted using ' + sort.op); } } } return sort; } function quantileMultipleDomain(domain, scope, fields) { // get value arrays for each domain field var values = fields.map(function(f) { var data = scope.getData(f.data); if (!data) dataLookupError(f.data); return data.domainRef(scope, f.field); }); // combine value arrays return ref(scope.add(MultiValues({values: values}))); } function numericMultipleDomain(domain, scope, fields) { // get extents for each domain field var extents = fields.map(function(f) { var data = scope.getData(f.data); if (!data) dataLookupError(f.data); return data.extentRef(scope, f.field); }); // combine extents return ref(scope.add(MultiExtent({extents: extents}))); } // -- SCALE BINS ----- function parseScaleBins(v, scope) { return v.signal || vegaUtil.isArray(v) ? parseArray(v, scope) : scope.objectProperty(v); } // -- SCALE NICE ----- function parseScaleNice(nice) { return vegaUtil.isObject(nice) ? { interval: parseLiteral(nice.interval), step: parseLiteral(nice.step) } : parseLiteral(nice); } // -- SCALE INTERPOLATION ----- function parseScaleInterpolate(interpolate, params) { params.interpolate = parseLiteral(interpolate.type || interpolate); if (interpolate.gamma != null) { params.interpolateGamma = parseLiteral(interpolate.gamma); } } // -- SCALE RANGE ----- function parseScaleRange(spec, scope, params) { var range = spec.range, config = scope.config.range; if (range.signal) { return scope.signalRef(range.signal); } else if (vegaUtil.isString(range)) { if (config && vegaUtil.hasOwnProperty(config, range)) { spec = vegaUtil.extend({}, spec, {range: config[range]}); return parseScaleRange(spec, scope, params); } else if (range === 'width') { range = [0, {signal: 'width'}]; } else if (range === 'height') { range = vegaScale.isDiscrete(spec.type) ? [0, {signal: 'height'}] : [{signal: 'height'}, 0]; } else { vegaUtil.error('Unrecognized scale range value: ' + vegaUtil.stringValue(range)); } } else if (range.scheme) { params.scheme = vegaUtil.isArray(range.scheme) ? parseArray(range.scheme, scope) : parseLiteral(range.scheme, scope); if (range.extent) params.schemeExtent = parseArray(range.extent, scope); if (range.count) params.schemeCount = parseLiteral(range.count, scope); return; } else if (range.step) { params.rangeStep = parseLiteral(range.step, scope); return; } else if (vegaScale.isDiscrete(spec.type) && !vegaUtil.isArray(range)) { return parseScaleDomain(range, spec, scope); } else if (!vegaUtil.isArray(range)) { vegaUtil.error('Unsupported range type: ' + vegaUtil.stringValue(range)); } return range.map(v => (vegaUtil.isArray(v) ? parseArray : parseLiteral)(v, scope)); } function parseProjection(proj, scope) { var config = scope.config.projection || {}, params = {}; for (var name in proj) { if (name === 'name') continue; params[name] = parseParameter(proj[name], name, scope); } // apply projection defaults from config for (name in config) { if (params[name] == null) { params[name] = parseParameter(config[name], name, scope); } } scope.addProjection(proj.name, params); } function parseParameter(_, name, scope) { return vegaUtil.isArray(_) ? _.map(function(_) { return parseParameter(_, name, scope); }) : !vegaUtil.isObject(_) ? _ : _.signal ? scope.signalRef(_.signal) : name === 'fit' ? _ : vegaUtil.error('Unsupported parameter object: ' + vegaUtil.stringValue(_)); } const Top = 'top'; const Left = 'left'; const Right = 'right'; const Bottom = 'bottom'; const Center = 'center'; const Vertical = 'vertical'; const Start = 'start'; const Middle = 'middle'; const End = 'end'; const Index = 'index'; const Label = 'label'; const Offset = 'offset'; const Perc = 'perc'; const Perc2 = 'perc2'; const Value = 'value'; const GuideLabelStyle = 'guide-label'; const GuideTitleStyle = 'guide-title'; const GroupTitleStyle = 'group-title'; const GroupSubtitleStyle = 'group-subtitle'; const Symbols = 'symbol'; const Gradient = 'gradient'; const Discrete = 'discrete'; const Size = 'size'; const Shape = 'shape'; const Fill = 'fill'; const Stroke = 'stroke'; const StrokeWidth = 'strokeWidth'; const StrokeDash = 'strokeDash'; const Opacity = 'opacity'; // Encoding channels supported by legends // In priority order of 'canonical' scale const LegendScales = [ Size, Shape, Fill, Stroke, StrokeWidth, StrokeDash, Opacity ]; const Skip = { name: 1, style: 1, interactive: 1 }; const zero = {value: 0}; const one = {value: 1}; var GroupMark = 'group'; var RectMark = 'rect'; var RuleMark = 'rule'; var SymbolMark = 'symbol'; var TextMark = 'text'; function guideGroup(mark) { mark.type = GroupMark; mark.interactive = mark.interactive || false; return mark; } function lookup(spec, config) { const _ = (name, dflt) => value(spec[name], value(config[name], dflt)); _.isVertical = s => Vertical === value( spec.direction, config.direction || (s ? config.symbolDirection : config.gradientDirection) ); _.gradientLength = () => value( spec.gradientLength, config.gradientLength || config.gradientWidth ); _.gradientThickness = () => value( spec.gradientThickness, config.gradientThickness || config.gradientHeight ); _.entryColumns = () => value( spec.columns, value(config.columns, +_.isVertical(true)) ); return _; } function getEncoding(name, encode) { var v = encode && ( (encode.update && encode.update[name]) || (encode.enter && encode.enter[name]) ); return v && v.signal ? v : v ? v.value : null; } function getStyle(name, scope, style) { var s = scope.config.style[style]; return s && s[name]; } function anchorExpr(s, e, m) { return `item.anchor === '${Start}' ? ${s} : item.anchor === '${End}' ? ${e} : ${m}`; } const alignExpr = anchorExpr( vegaUtil.stringValue(Left), vegaUtil.stringValue(Right), vegaUtil.stringValue(Center) ); function tickBand(_) { let v = _('tickBand'), offset = _('tickOffset'), band, extra; if (!v) { // if no tick band entry, fall back on other properties band = _('bandPosition'); extra = _('tickExtra'); } else if (v.signal) { // if signal, augment code to interpret values band = {signal: `(${v.signal}) === 'extent' ? 1 : 0.5`}; extra = {signal: `(${v.signal}) === 'extent'`}; if (!vegaUtil.isObject(offset)) { offset = {signal: `(${v.signal}) === 'extent' ? 0 : ${offset}`}; } } else if (v === 'extent') { // if constant, simply set values band = 1; extra = true; offset = 0; } else { band = 0.5; extra = false; } return {extra, band, offset}; } function extendOffset(value, offset) { return !offset ? value : !value ? offset : !vegaUtil.isObject(value) ? { value, offset } : Object.assign({}, value, { offset: extendOffset(value.offset, offset) }); } function guideMark(mark, extras) { if (extras) { mark.name = extras.name; mark.style = extras.style || mark.style; mark.interactive = !!extras.interactive; mark.encode = extendEncode(mark.encode, extras, Skip); } else { mark.interactive = false; } return mark; } function legendGradient(spec, scale, config, userEncode) { var _ = lookup(spec, config), vertical = _.isVertical(), thickness = _.gradientThickness(), length = _.gradientLength(), encode, enter, start, stop, width, height; if (vertical) { start = [0, 1]; stop = [0, 0]; width = thickness; height = length; } else { start = [0, 0]; stop = [1, 0]; width = length; height = thickness; } encode = { enter: enter = { opacity: zero, x: zero, y: zero, width: encoder(width), height: encoder(height) }, update: vegaUtil.extend({}, enter, { opacity: one, fill: {gradient: scale, start: start, stop: stop} }), exit: { opacity: zero } }; addEncoders(encode, { stroke: _('gradientStrokeColor'), strokeWidth: _('gradientStrokeWidth') }, { // update opacity: _('gradientOpacity') }); return guideMark({ type: RectMark, role: LegendGradientRole, encode }, userEncode); } function legendGradientDiscrete(spec, scale, config, userEncode, dataRef) { var _ = lookup(spec, config), vertical = _.isVertical(), thickness = _.gradientThickness(), length = _.gradientLength(), encode, enter, u, v, uu, vv, adjust = ''; vertical ? (u = 'y', uu = 'y2', v = 'x', vv = 'width', adjust = '1-') : (u = 'x', uu = 'x2', v = 'y', vv = 'height'); enter = { opacity: zero, fill: {scale: scale, field: Value} }; enter[u] = {signal: adjust + 'datum.' + Perc, mult: length}; enter[v] = zero; enter[uu] = {signal: adjust + 'datum.' + Perc2, mult: length}; enter[vv] = encoder(thickness); encode = { enter: enter, update: vegaUtil.extend({}, enter, {opacity: one}), exit: {opacity: zero} }; addEncoders(encode, { stroke: _('gradientStrokeColor'), strokeWidth: _('gradientStrokeWidth') }, { // update opacity: _('gradientOpacity') }); return guideMark({ type: RectMark, role: LegendBandRole, key: Value, from: dataRef, encode }, userEncode); } const alignExpr$1 = `datum.${Perc}<=0?"${Left}":datum.${Perc}>=1?"${Right}":"${Center}"`, baselineExpr = `datum.${Perc}<=0?"${Bottom}":datum.${Perc}>=1?"${Top}":"${Middle}"`; function legendGradientLabels(spec, config, userEncode, dataRef) { var _ = lookup(spec, config), vertical = _.isVertical(), thickness = encoder(_.gradientThickness()), length = _.gradientLength(), overlap = _('labelOverlap'), encode, enter, update, u, v, adjust = ''; encode = { enter: enter = { opacity: zero }, update: update = { opacity: one, text: {field: Label} }, exit: { opacity: zero } }; addEncoders(encode, { fill: _('labelColor'), fillOpacity: _('labelOpacity'), font: _('labelFont'), fontSize: _('labelFontSize'), fontStyle: _('labelFontStyle'), fontWeight: _('labelFontWeight'), limit: value(spec.labelLimit, config.gradientLabelLimit) }); if (vertical) { enter.align = {value: 'left'}; enter.baseline = update.baseline = {signal: baselineExpr}; u = 'y'; v = 'x'; adjust = '1-'; } else { enter.align = update.align = {signal: alignExpr$1}; enter.baseline = {value: 'top'}; u = 'x'; v = 'y'; } enter[u] = update[u] = {signal: adjust + 'datum.' + Perc, mult: length}; enter[v] = update[v] = thickness; thickness.offset = value(spec.labelOffset, config.gradientLabelOffset) || 0; overlap = overlap ? { separation: _('labelSeparation'), method: overlap, order: 'datum.' + Index } : undefined; // type, role, style, key, dataRef, encode, extras return guideMark({ type: TextMark, role: LegendLabelRole, style: GuideLabelStyle, key: Value, from: dataRef, encode, overlap }, userEncode); } // userEncode is top-level, includes entries, symbols, labels function legendSymbolGroups(spec, config, userEncode, dataRef, columns) { var _ = lookup(spec, config), entries = userEncode.entries, interactive = !!(entries && entries.interactive), name = entries ? entries.name : undefined, height = _('clipHeight'), symbolOffset = _('symbolOffset'), valueRef = {data: 'value'}, encode = {}, xSignal = `(${columns}) ? datum.${Offset} : datum.${Size}`, yEncode = height ? encoder(height) : {field: Size}, index = `datum.${Index}`, ncols = `max(1, ${columns})`, enter, update, labelOffset, symbols, labels, nrows, sort; yEncode.mult = 0.5; // -- LEGEND SYMBOLS -- encode = { enter: enter = { opacity: zero, x: {signal: xSignal, mult: 0.5, offset: symbolOffset}, y: yEncode }, update: update = { opacity: one, x: enter.x, y: enter.y }, exit: { opacity: zero } }; var baseFill = null, baseStroke = null; if (!spec.fill) { baseFill = config.symbolBaseFillColor; baseStroke = config.symbolBaseStrokeColor; } addEncoders(encode, { fill: _('symbolFillColor', baseFill), shape: _('symbolType'), size: _('symbolSize'), stroke: _('symbolStrokeColor', baseStroke), strokeDash: _('symbolDash'), strokeDashOffset: _('symbolDashOffset'), strokeWidth: _('symbolStrokeWidth') }, { // update opacity: _('symbolOpacity') }); LegendScales.forEach(function(scale) { if (spec[scale]) { update[scale] = enter[scale] = {scale: spec[scale], field: Value}; } }); symbols = guideMark({ type: SymbolMark, role: LegendSymbolRole, key: Value, from: valueRef, clip: height ? true : undefined, encode }, userEncode.symbols); // -- LEGEND LABELS -- labelOffset = encoder(symbolOffset); labelOffset.offset = _('labelOffset'); encode = { enter: enter = { opacity: zero, x: {signal: xSignal, offset: labelOffset}, y: yEncode }, update: update = { opacity: one, text: {field: Label}, x: enter.x, y: enter.y }, exit: { opacity: zero } }; addEncoders(encode, { align: _('labelAlign'), baseline: _('labelBaseline'), fill: _('labelColor'), fillOpacity: _('labelOpacity'), font: _('labelFont'), fontSize: _('labelFontSize'), fontStyle: _('labelFontStyle'), fontWeight: _('labelFontWeight'), limit: _('labelLimit') }); labels = guideMark({ type: TextMark, role: LegendLabelRole, style: GuideLabelStyle, key: Value, from: valueRef, encode }, userEncode.labels); // -- LEGEND ENTRY GROUPS -- encode = { enter: { noBound: {value: !height}, // ignore width/height in bounds calc width: zero, height: height ? encoder(height) : zero, opacity: zero }, exit: {opacity: zero}, update: update = { opacity: one, row: {signal: null}, column: {signal: null} } }; // annotate and sort groups to ensure correct ordering if (_.isVertical(true)) { nrows = `ceil(item.mark.items.length / ${ncols})`; update.row.signal = `${index}%${nrows}`; update.column.signal = `floor(${index} / ${nrows})`; sort = {field: ['row', index]}; } else { update.row.signal = `floor(${index} / ${ncols})`; update.column.signal = `${index} % ${ncols}`; sort = {field: index}; } // handle zero column case (implies infinite columns) update.column.signal = `(${columns})?${update.column.signal}:${index}`; // facet legend entries into sub-groups dataRef = {facet: {data: dataRef, name: 'value', groupby: Index}}; return guideGroup({ role: ScopeRole, from: dataRef, encode: extendEncode(encode, entries, Skip), marks: [symbols, labels], name, interactive, sort }); } function legendSymbolLayout(spec, config) { const _ = lookup(spec, config); // layout parameters for legend entries return { align: _('gridAlign'), columns: _.entryColumns(), center: { row: true, column: false }, padding: { row: _('rowPadding'), column: _('columnPadding') } }; } // expression logic for align, anchor, angle, and baseline calculation const isL = 'item.orient === "left"', isR = 'item.orient === "right"', isLR = `(${isL} || ${isR})`, isVG = `datum.vgrad && ${isLR}`, baseline = anchorExpr('"top"', '"bottom"', '"middle"'), alignFlip = anchorExpr('"right"', '"left"', '"center"'), exprAlign = `datum.vgrad && ${isR} ? (${alignFlip}) : (${isLR} && !(datum.vgrad && ${isL})) ? "left" : ${alignExpr}`, exprAnchor = `item._anchor || (${isLR} ? "middle" : "start")`, exprAngle = `${isVG} ? (${isL} ? -90 : 90) : 0`, exprBaseline = `${isLR} ? (datum.vgrad ? (${isR} ? "bottom" : "top") : ${baseline}) : "top"`; function legendTitle(spec, config, userEncode, dataRef) { var _ = lookup(spec, config), encode; encode = { enter: {opacity: zero}, update: { opacity: one, x: {field: {group: 'padding'}}, y: {field: {group: 'padding'}} }, exit: {opacity: zero} }; addEncoders(encode, { orient: _('titleOrient'), _anchor: _('titleAnchor'), anchor: {signal: exprAnchor}, angle: {signal: exprAngle}, align: {signal: exprAlign}, baseline: {signal: exprBaseline}, text: spec.title, fill: _('titleColor'), fillOpacity: _('titleOpacity'), font: _('titleFont'), fontSize: _('titleFontSize'), fontStyle: _('titleFontStyle'), fontWeight: _('titleFontWeight'), limit: _('titleLimit'), lineHeight: _('titleLineHeight') }, { // require update align: _('titleAlign'), baseline: _('titleBaseline') }); return guideMark({ type: TextMark, role: LegendTitleRole, style: GuideTitleStyle, from: dataRef, encode }, userEncode); } function clip(clip, scope) { var expr; if (vegaUtil.isObject(clip)) { if (clip.signal) { expr = clip.signal; } else if (clip.path) { expr = 'pathShape(' + param(clip.path) + ')'; } else if (clip.sphere) { expr = 'geoShape(' + param(clip.sphere) + ', {type: "Sphere"})'; } } return expr ? scope.signalRef(expr) : !!clip; } function param(value) { return vegaUtil.isObject(value) && value.signal ? value.signal : vegaUtil.stringValue(value); } function getRole(spec) { var role = spec.role || ''; return (!role.indexOf('axis') || !role.indexOf('legend') || !role.indexOf('title')) ? role : spec.type === GroupMark ? ScopeRole : (role || MarkRole); } function definition(spec) { return { marktype: spec.type, name: spec.name || undefined, role: spec.role || getRole(spec), zindex: +spec.zindex || undefined, aria: spec.aria, description: spec.description }; } function interactive(spec, scope) { return spec && spec.signal ? scope.signalRef(spec.signal) : spec === false ? false : true; } /** * Parse a data transform specification. */ function parseTransform(spec, scope) { var def = vegaDataflow.definition(spec.type); if (!def) vegaUtil.error('Unrecognized transform type: ' + vegaUtil.stringValue(spec.type)); var t = entry$1(def.type.toLowerCase(), null, parseParameters(def, spec, scope)); if (spec.signal) scope.addSignal(spec.signal, scope.proxy(t)); t.metadata = def.metadata || {}; return t; } /** * Parse all parameters of a data transform. */ function parseParameters(def, spec, scope) { var params = {}, pdef, i, n; for (i=0, n=def.params.length; i spec[s] ? (scales[s] = spec[s], scale = scale || spec[s]) : 0 ); if (!scale) vegaUtil.error('Missing valid scale for legend.'); // resolve legend type (symbol, gradient, or discrete gradient) type = legendType(spec, scope.scaleType(scale)); // single-element data source for legend group datum = { title: spec.title != null, scales: scales, type: type, vgrad: type !== 'symbol' && _.isVertical() }; dataRef = ref(scope.add(Collect(null, [datum]))); // encoding properties for legend group legendEncode = extendEncode( buildLegendEncode(_, spec, config), legendEncode, Skip ); // encoding properties for legend entry sub-group entryEncode = {enter: {x: {value: 0}, y: {value: 0}}}; // data source for legend values entryRef = ref(scope.add(LegendEntries(params = { type: type, scale: scope.scaleRef(scale), count: scope.objectProperty(_('tickCount')), limit: scope.property(_('symbolLimit')), values: scope.objectProperty(spec.values), minstep: scope.property(spec.tickMinStep), formatType: scope.property(spec.formatType), formatSpecifier: scope.property(spec.format) }))); // continuous gradient legend if (type === Gradient) { children = [ legendGradient(spec, scale, config, encode.gradient), legendGradientLabels(spec, config, encode.labels, entryRef) ]; // adjust default tick count based on the gradient length params.count = params.count || scope.signalRef( `max(2,2*floor((${deref(_.gradientLength())})/100))` ); } // discrete gradient legend else if (type === Discrete) { children = [ legendGradientDiscrete(spec, scale, config, encode.gradient, entryRef), legendGradientLabels(spec, config, encode.labels, entryRef) ]; } // symbol legend else { // determine legend symbol group layout entryLayout = legendSymbolLayout(spec, config); children = [ legendSymbolGroups(spec, config, encode, entryRef, deref(entryLayout.columns)) ]; // pass symbol size information to legend entry generator params.size = sizeExpression(spec, scope, children[0].marks); } // generate legend marks children = [ guideGroup({ role: LegendEntryRole, from: dataRef, encode: entryEncode, marks: children, layout: entryLayout, interactive }) ]; // include legend title if defined if (datum.title) { children.push(legendTitle(spec, config, encode.title, dataRef)); } // parse legend specification return parseMark( guideGroup({ role: LegendRole, from: dataRef, encode: legendEncode, marks: children, aria: _('aria'), description: _('description'), zindex: _('zindex'), name, interactive, style }), scope ); } function legendType(spec, scaleType) { var type = spec.type || Symbols; if (!spec.type && scaleCount(spec) === 1 && (spec.fill || spec.stroke)) { type = vegaScale.isContinuous(scaleType) ? Gradient : vegaScale.isDiscretizing(scaleType) ? Discrete : Symbols; } return type !== Gradient ? type : vegaScale.isDiscretizing(scaleType) ? Discrete : Gradient; } function scaleCount(spec) { return LegendScales.reduce(function(count, type) { return count + (spec[type] ? 1 : 0); }, 0); } function buildLegendEncode(_, spec, config) { var encode = {enter: {}, update: {}}; addEncoders(encode, { orient: _('orient'), offset: _('offset'), padding: _('padding'), titlePadding: _('titlePadding'), cornerRadius: _('cornerRadius'), fill: _('fillColor'), stroke: _('strokeColor'), strokeWidth: config.strokeWidth, strokeDash: config.strokeDash, x: _('legendX'), y: _('legendY'), // accessibility support format: spec.format, formatType: spec.formatType }); return encode; } function sizeExpression(spec, scope, marks) { var size = deref(getChannel('size', spec, marks)), strokeWidth = deref(getChannel('strokeWidth', spec, marks)), fontSize = deref(getFontSize(marks[1].encode, scope, GuideLabelStyle)); return vegaFunctions.parseExpression( `max(ceil(sqrt(${size})+${strokeWidth}),${fontSize})`, scope ); } function getChannel(name, spec, marks) { return spec[name] ? `scale("${spec[name]}",datum)` : getEncoding(name, marks[0].encode); } function getFontSize(encode, scope, style) { return getEncoding('fontSize', encode) || getStyle('fontSize', scope, style); } const angleExpr = `item.orient==="${Left}"?-90:item.orient==="${Right}"?90:0`; function parseTitle(spec, scope) { spec = vegaUtil.isString(spec) ? {text: spec} : spec; var _ = lookup(spec, scope.config.title), encode = spec.encode || {}, userEncode = encode.group || {}, name = userEncode.name || undefined, interactive = userEncode.interactive, style = userEncode.style, children = [], datum, dataRef; // single-element data source for group title datum = {}; dataRef = ref(scope.add(Collect(null, [datum]))); // include title text children.push(buildTitle(spec, _, titleEncode(spec), dataRef)); // include subtitle text if (spec.subtitle) { children.push(buildSubTitle(spec, _, encode.subtitle, dataRef)); } // parse title specification return parseMark( guideGroup({ role: TitleRole, from: dataRef, encode: groupEncode(_, userEncode), marks: children, aria: _('aria'), description: _('description'), zindex: _('zindex'), name, interactive, style }), scope ); } // provide backwards-compatibility for title custom encode; // the top-level encode block has been *deprecated*. function titleEncode(spec) { const encode = spec.encode; return (encode && encode.title) || vegaUtil.extend({ name: spec.name, interactive: spec.interactive, style: spec.style }, encode); } function groupEncode(_, userEncode) { var encode = {enter: {}, update: {}}; addEncoders(encode, { orient: _('orient'), anchor: _('anchor'), align: {signal: alignExpr}, angle: {signal: angleExpr}, limit: _('limit'), frame: _('frame'), offset: _('offset') || 0, padding: _('subtitlePadding') }); return extendEncode(encode, userEncode, Skip); } function buildTitle(spec, _, userEncode, dataRef) { var zero = {value: 0}, text = spec.text, encode = { enter: {opacity: zero}, update: {opacity: {value: 1}}, exit: {opacity: zero} }; addEncoders(encode, { text: text, align: {signal: 'item.mark.group.align'}, angle: {signal: 'item.mark.group.angle'}, limit: {signal: 'item.mark.group.limit'}, baseline: 'top', dx: _('dx'), dy: _('dy'), fill: _('color'), font: _('font'), fontSize: _('fontSize'), fontStyle: _('fontStyle'), fontWeight: _('fontWeight'), lineHeight: _('lineHeight') }, { // update align: _('align'), angle: _('angle'), baseline: _('baseline') }); return guideMark({ type: TextMark, role: TitleTextRole, style: GroupTitleStyle, from: dataRef, encode }, userEncode); } function buildSubTitle(spec, _, userEncode, dataRef) { var zero = {value: 0}, text = spec.subtitle, encode = { enter: {opacity: zero}, update: {opacity: {value: 1}}, exit: {opacity: zero} }; addEncoders(encode, { text: text, align: {signal: 'item.mark.group.align'}, angle: {signal: 'item.mark.group.angle'}, limit: {signal: 'item.mark.group.limit'}, baseline: 'top', dx: _('dx'), dy: _('dy'), fill: _('subtitleColor'), font: _('subtitleFont'), fontSize: _('subtitleFontSize'), fontStyle: _('subtitleFontStyle'), fontWeight: _('subtitleFontWeight'), lineHeight: _('subtitleLineHeight') }, { // update align: _('align'), angle: _('angle'), baseline: _('baseline') }); return guideMark({ type: TextMark, role: TitleSubtitleRole, style: GroupSubtitleStyle, from: dataRef, encode }, userEncode); } function parseData$1(data, scope) { var transforms = []; if (data.transform) { data.transform.forEach(function(tx) { transforms.push(parseTransform(tx, scope)); }); } if (data.on) { data.on.forEach(function(on) { parseTrigger(on, scope, data.name); }); } scope.addDataPipeline(data.name, analyze(data, scope, transforms)); } /** * Analyze a data pipeline, add needed operators. */ function analyze(data, scope, ops) { var output = [], source = null, modify = false, generate = false, upstream, i, n, t, m; if (data.values) { // hard-wired input data set if (hasSignal(data.values) || hasSignal(data.format)) { // if either values or format has signal, use dynamic loader output.push(load(scope, data)); output.push(source = collect()); } else { // otherwise, ingest upon dataflow init output.push(source = collect({ $ingest: data.values, $format: data.format })); } } else if (data.url) { // load data from external source if (hasSignal(data.url) || hasSignal(data.format)) { // if either url or format has signal, use dynamic loader output.push(load(scope, data)); output.push(source = collect()); } else { // otherwise, request load upon dataflow init output.push(source = collect({ $request: data.url, $format: data.format })); } } else if (data.source) { // derives from one or more other data sets source = upstream = vegaUtil.array(data.source).map(function(d) { return ref(scope.getData(d).output); }); output.push(null); // populate later } // scan data transforms, add collectors as needed for (i=0, n=ops.length; i orient === Bottom || orient === Top; // get sign coefficient based on axis orient const getSign = (orient, a, b) => isSignal(orient) ? ifLeftTopExpr(orient.signal, a, b) : orient === Left || orient === Top ? a : b; // condition on axis x-direction const ifX = (orient, a, b) => isSignal(orient) ? ifXEnc(orient.signal, a, b) : isX(orient) ? a : b; // condition on axis y-direction const ifY = (orient, a, b) => isSignal(orient) ? ifYEnc(orient.signal, a, b) : isX(orient) ? b : a; const ifTop = (orient, a, b) => isSignal(orient) ? ifTopExpr(orient.signal, a, b) : orient === Top ? {value: a} : {value: b}; const ifRight = (orient, a, b) => isSignal(orient) ? ifRightExpr(orient.signal, a, b) : orient === Right ? {value: a} : {value: b}; const ifXEnc = ($orient, a, b) => ifEnc( `${$orient} === '${Top}' || ${$orient} === '${Bottom}'`, a, b ); const ifYEnc = ($orient, a, b) => ifEnc( `${$orient} !== '${Top}' && ${$orient} !== '${Bottom}'`, a, b ); const ifLeftTopExpr = ($orient, a, b) => ifExpr( `${$orient} === '${Left}' || ${$orient} === '${Top}'`, a, b ); const ifTopExpr = ($orient, a, b) => ifExpr( `${$orient} === '${Top}'`, a, b ); const ifRightExpr = ($orient, a, b) => ifExpr( `${$orient} === '${Right}'`, a, b ); const ifEnc = (test, a, b) => { // ensure inputs are encoder objects (or null) a = a != null ? encoder(a) : a; b = b != null ? encoder(b) : b; if (isSimple(a) && isSimple(b)) { // if possible generate simple signal expression a = a ? (a.signal || vegaUtil.stringValue(a.value)) : null; b = b ? (b.signal || vegaUtil.stringValue(b.value)) : null; return {signal: `${test} ? (${a}) : (${b})`}; } else { // otherwise generate rule set return [vegaUtil.extend({test}, a)].concat(b || []); } }; const isSimple = enc => ( enc == null || Object.keys(enc).length === 1 ); const ifExpr = (test, a, b) => ({ signal: `${test} ? (${toExpr(a)}) : (${toExpr(b)})` }); const ifOrient = ($orient, t, b, l, r) => ({ signal: (l != null ? `${$orient} === '${Left}' ? (${toExpr(l)}) : ` : '') + (b != null ? `${$orient} === '${Bottom}' ? (${toExpr(b)}) : ` : '') + (r != null ? `${$orient} === '${Right}' ? (${toExpr(r)}) : ` : '') + (t != null ? `${$orient} === '${Top}' ? (${toExpr(t)}) : ` : '') + '(null)' }); const toExpr = v => isSignal(v) ? v.signal : v == null ? null : vegaUtil.stringValue(v); const mult = (sign, value) => value === 0 ? 0 : isSignal(sign) ? {signal: `(${sign.signal}) * ${value}`} : {value: sign * value}; const patch = (value, base) => { const s = value.signal; return s && s.endsWith('(null)') ? {signal: s.slice(0, -6) + base.signal} : value; }; function fallback(prop, config, axisConfig, style) { let styleProp; if (config && vegaUtil.hasOwnProperty(config, prop)) { return config[prop]; } else if (vegaUtil.hasOwnProperty(axisConfig, prop)) { return axisConfig[prop]; } else if (prop.startsWith('title')) { switch (prop) { case 'titleColor': styleProp = 'fill'; break; case 'titleFont': case 'titleFontSize': case 'titleFontWeight': styleProp = prop[5].toLowerCase() + prop.slice(6); } return style[GuideTitleStyle][styleProp]; } else if (prop.startsWith('label')) { switch (prop) { case 'labelColor': styleProp = 'fill'; break; case 'labelFont': case 'labelFontSize': styleProp = prop[5].toLowerCase() + prop.slice(6); } return style[GuideLabelStyle][styleProp]; } return null; } function keys(objects) { const map = {}; for (const obj of objects) { if (!obj) continue; for (const key in obj) map[key] = 1; } return Object.keys(map); } function axisConfig(spec, scope) { var config = scope.config, style = config.style, axis = config.axis, band = scope.scaleType(spec.scale) === 'band' && config.axisBand, orient = spec.orient, xy, or, key; if (isSignal(orient)) { const xyKeys = keys([ config.axisX, config.axisY ]), orientKeys = keys([ config.axisTop, config.axisBottom, config.axisLeft, config.axisRight ]); xy = {}; for (key of xyKeys) { xy[key] = ifX( orient, fallback(key, config.axisX, axis, style), fallback(key, config.axisY, axis, style) ); } or = {}; for (key of orientKeys) { or[key] = ifOrient( orient.signal, fallback(key, config.axisTop, axis, style), fallback(key, config.axisBottom, axis, style), fallback(key, config.axisLeft, axis, style), fallback(key, config.axisRight, axis, style) ); } } else { xy = (orient === Top || orient === Bottom) ? config.axisX : config.axisY; or = config['axis' + orient[0].toUpperCase() + orient.slice(1)]; } var result = (xy || or || band) ? vegaUtil.extend({}, axis, xy, or, band) : axis; return result; } function axisDomain(spec, config, userEncode, dataRef) { var _ = lookup(spec, config), orient = spec.orient, encode, enter, update; encode = { enter: enter = {opacity: zero}, update: update = {opacity: one}, exit: {opacity: zero} }; addEncoders(encode, { stroke: _('domainColor'), strokeCap: _('domainCap'), strokeDash: _('domainDash'), strokeDashOffset: _('domainDashOffset'), strokeWidth: _('domainWidth'), strokeOpacity: _('domainOpacity') }); const pos0 = position(spec, 0); const pos1 = position(spec, 1); enter.x = update.x = ifX(orient, pos0, zero); enter.x2 = update.x2 = ifX(orient, pos1); enter.y = update.y = ifY(orient, pos0, zero); enter.y2 = update.y2 = ifY(orient, pos1); return guideMark({ type: RuleMark, role: AxisDomainRole, from: dataRef, encode }, userEncode); } function position(spec, pos) { return {scale: spec.scale, range: pos}; } function axisGrid(spec, config, userEncode, dataRef, band) { var _ = lookup(spec, config), orient = spec.orient, vscale = spec.gridScale, sign = getSign(orient, 1, -1), offset = offsetValue(spec.offset, sign), encode, enter, exit, update, tickPos, gridStart, gridEnd, sz; encode = { enter: enter = {opacity: zero}, update: update = {opacity: one}, exit: exit = {opacity: zero} }; addEncoders(encode, { stroke: _('gridColor'), strokeCap: _('gridCap'), strokeDash: _('gridDash'), strokeDashOffset: _('gridDashOffset'), strokeOpacity: _('gridOpacity'), strokeWidth: _('gridWidth') }); tickPos = { scale: spec.scale, field: Value, band: band.band, extra: band.extra, offset: band.offset, round: _('tickRound') }; sz = ifX(orient, {signal: 'height'}, {signal: 'width'}); gridStart = vscale ? {scale: vscale, range: 0, mult: sign, offset: offset} : {value: 0, offset: offset}; gridEnd = vscale ? {scale: vscale, range: 1, mult: sign, offset: offset} : vegaUtil.extend(sz, {mult: sign, offset: offset}); enter.x = update.x = ifX(orient, tickPos, gridStart); enter.y = update.y = ifY(orient, tickPos, gridStart); enter.x2 = update.x2 = ifY(orient, gridEnd); enter.y2 = update.y2 = ifX(orient, gridEnd); exit.x = ifX(orient, tickPos); exit.y = ifY(orient, tickPos); return guideMark({ type: RuleMark, role: AxisGridRole, key: Value, from: dataRef, encode }, userEncode); } function offsetValue(offset, sign) { if (sign === 1) ; else if (!vegaUtil.isObject(offset)) { offset = isSignal(sign) ? {signal: `(${sign.signal}) * (${offset || 0})`} : sign * (offset || 0); } else { let entry = offset = vegaUtil.extend({}, offset); while (entry.mult != null) { if (!vegaUtil.isObject(entry.mult)) { entry.mult = isSignal(sign) // no offset if sign === 1 ? {signal: `(${entry.mult}) * (${sign.signal})`} : entry.mult * sign; return offset; } else { entry = entry.mult = vegaUtil.extend({}, entry.mult); } } entry.mult = sign; } return offset; } function axisTicks(spec, config, userEncode, dataRef, size, band) { var _ = lookup(spec, config), orient = spec.orient, sign = getSign(orient, -1, 1), encode, enter, exit, update, tickSize, tickPos; encode = { enter: enter = {opacity: zero}, update: update = {opacity: one}, exit: exit = {opacity: zero} }; addEncoders(encode, { stroke: _('tickColor'), strokeCap: _('tickCap'), strokeDash: _('tickDash'), strokeDashOffset: _('tickDashOffset'), strokeOpacity: _('tickOpacity'), strokeWidth: _('tickWidth') }); tickSize = encoder(size); tickSize.mult = sign; tickPos = { scale: spec.scale, field: Value, band: band.band, extra: band.extra, offset: band.offset, round: _('tickRound') }; update.y = enter.y = ifX(orient, zero, tickPos); update.y2 = enter.y2 = ifX(orient, tickSize); exit.x = ifX(orient, tickPos); update.x = enter.x = ifY(orient, zero, tickPos); update.x2 = enter.x2 = ifY(orient, tickSize); exit.y = ifY(orient, tickPos); return guideMark({ type: RuleMark, role: AxisTickRole, key: Value, from: dataRef, encode }, userEncode); } function flushExpr(scale, threshold, a, b, c) { return { signal: 'flush(range("' + scale + '"), ' + 'scale("' + scale + '", datum.value), ' + threshold + ',' + a + ',' + b + ',' + c + ')' }; } function axisLabels(spec, config, userEncode, dataRef, size, band) { var _ = lookup(spec, config), orient = spec.orient, scale = spec.scale, sign = getSign(orient, -1, 1), flush = deref(_('labelFlush')), flushOffset = deref(_('labelFlushOffset')), flushOn = flush === 0 || !!flush, labelAlign = _('labelAlign'), labelBaseline = _('labelBaseline'), encode, enter, update, tickSize, tickPos, align, baseline, bound, overlap, offsetExpr; tickSize = encoder(size); tickSize.mult = sign; tickSize.offset = encoder(_('labelPadding') || 0); tickSize.offset.mult = sign; tickPos = { scale: scale, field: Value, band: 0.5, offset: extendOffset(band.offset, _('labelOffset')) }; align = ifX(orient, flushOn ? flushExpr(scale, flush, '"left"', '"right"', '"center"') : {value: 'center'}, ifRight(orient, 'left', 'right') ); baseline = ifX(orient, ifTop(orient, 'bottom', 'top'), flushOn ? flushExpr(scale, flush, '"top"', '"bottom"', '"middle"') : {value: 'middle'} ); offsetExpr = flushExpr(scale, flush, `-(${flushOffset})`, flushOffset, 0); flushOn = flushOn && flushOffset; enter = { opacity: zero, x: ifX(orient, tickPos, tickSize), y: ifY(orient, tickPos, tickSize) }; encode = { enter: enter, update: update = { opacity: one, text: {field: Label}, x: enter.x, y: enter.y, align, baseline }, exit: { opacity: zero, x: enter.x, y: enter.y } }; addEncoders(encode, { dx: !labelAlign && flushOn ? ifX(orient, offsetExpr) : null, dy: !labelBaseline && flushOn ? ifY(orient, offsetExpr) : null }); addEncoders(encode, { angle: _('labelAngle'), fill: _('labelColor'), fillOpacity: _('labelOpacity'), font: _('labelFont'), fontSize: _('labelFontSize'), fontWeight: _('labelFontWeight'), fontStyle: _('labelFontStyle'), limit: _('labelLimit'), lineHeight: _('labelLineHeight') }, { align: labelAlign, baseline: labelBaseline }); bound = _('labelBound'); overlap = _('labelOverlap'); // if overlap method or bound defined, request label overlap removal overlap = overlap || bound ? { separation: _('labelSeparation'), method: overlap, order: 'datum.index', bound: bound ? {scale, orient, tolerance: bound} : null } : undefined; if (update.align !== align) { update.align = patch(update.align, align); } if (update.baseline !== baseline) { update.baseline = patch(update.baseline, baseline); } return guideMark({ type: TextMark, role: AxisLabelRole, style: GuideLabelStyle, key: Value, from: dataRef, encode, overlap }, userEncode); } function axisTitle(spec, config, userEncode, dataRef) { var _ = lookup(spec, config), orient = spec.orient, sign = getSign(orient, -1, 1), encode, enter, update, titlePos; encode = { enter: enter = { opacity: zero, anchor: encoder(_('titleAnchor', null)), align: {signal: alignExpr} }, update: update = vegaUtil.extend({}, enter, { opacity: one, text: encoder(spec.title) }), exit: { opacity: zero } }; titlePos = { signal: `lerp(range("${spec.scale}"), ${anchorExpr(0, 1, 0.5)})` }; update.x = ifX(orient, titlePos); update.y = ifY(orient, titlePos); enter.angle = ifX(orient, zero, mult(sign, 90)); enter.baseline = ifX(orient, ifTop(orient, Bottom, Top), {value: Bottom}); update.angle = enter.angle; update.baseline = enter.baseline; addEncoders(encode, { fill: _('titleColor'), fillOpacity: _('titleOpacity'), font: _('titleFont'), fontSize: _('titleFontSize'), fontStyle: _('titleFontStyle'), fontWeight: _('titleFontWeight'), limit: _('titleLimit'), lineHeight: _('titleLineHeight') }, { // require update align: _('titleAlign'), angle: _('titleAngle'), baseline: _('titleBaseline') }); autoLayout(_, orient, encode, userEncode); encode.update.align = patch(encode.update.align, enter.align); encode.update.angle = patch(encode.update.angle, enter.angle); encode.update.baseline = patch(encode.update.baseline, enter.baseline); return guideMark({ type: TextMark, role: AxisTitleRole, style: GuideTitleStyle, from: dataRef, encode }, userEncode); } function autoLayout(_, orient, encode, userEncode) { const auto = (value, dim) => value != null ? (encode.update[dim] = patch(encoder(value), encode.update[dim]), false) : !has(dim, userEncode) ? true : false; const autoY = auto(_('titleX'), 'x'), autoX = auto(_('titleY'), 'y'); encode.enter.auto = autoX === autoY ? encoder(autoX) : ifX(orient, encoder(autoX), encoder(autoY)); } function parseAxis(spec, scope) { var config = axisConfig(spec, scope), encode = spec.encode || {}, axisEncode = encode.axis || {}, name = axisEncode.name || undefined, interactive = axisEncode.interactive, style = axisEncode.style, _ = lookup(spec, config), band = tickBand(_), datum, dataRef, ticksRef, size, children; // single-element data source for axis group datum = { scale: spec.scale, ticks: !!_('ticks'), labels: !!_('labels'), grid: !!_('grid'), domain: !!_('domain'), title: spec.title != null }; dataRef = ref(scope.add(Collect({}, [datum]))); // encoding properties for axis group item axisEncode = extendEncode( buildAxisEncode(_, spec), axisEncode, Skip ); // data source for axis ticks ticksRef = ref(scope.add(AxisTicks({ scale: scope.scaleRef(spec.scale), extra: scope.property(band.extra), count: scope.objectProperty(spec.tickCount), values: scope.objectProperty(spec.values), minstep: scope.property(spec.tickMinStep), formatType: scope.property(spec.formatType), formatSpecifier: scope.property(spec.format) }))); // generate axis marks children = []; // include axis gridlines if requested if (datum.grid) { children.push(axisGrid(spec, config, encode.grid, ticksRef, band)); } // include axis ticks if requested if (datum.ticks) { size = _('tickSize'); children.push(axisTicks(spec, config, encode.ticks, ticksRef, size, band)); } // include axis labels if requested if (datum.labels) { size = datum.ticks ? size : 0; children.push(axisLabels(spec, config, encode.labels, ticksRef, size, band)); } // include axis domain path if requested if (datum.domain) { children.push(axisDomain(spec, config, encode.domain, dataRef)); } // include axis title if defined if (datum.title) { children.push(axisTitle(spec, config, encode.title, dataRef)); } // parse axis specification return parseMark( guideGroup({ role: AxisRole, from: dataRef, encode: axisEncode, marks: children, aria: _('aria'), description: _('description'), zindex: _('zindex'), name, interactive, style }), scope ); } function buildAxisEncode(_, spec) { var encode = {enter: {}, update: {}}; addEncoders(encode, { orient: _('orient'), offset: _('offset') || 0, position: value(spec.position, 0), titlePadding: _('titlePadding'), minExtent: _('minExtent'), maxExtent: _('maxExtent'), range: {signal: `abs(span(range("${spec.scale}")))`}, translate: _('translate'), // accessibility support format: spec.format, formatType: spec.formatType }); return encode; } function parseScope(spec, scope, preprocessed) { var signals = vegaUtil.array(spec.signals), scales = vegaUtil.array(spec.scales); // parse signal definitions, if not already preprocessed if (!preprocessed) signals.forEach(_ => parseSignal(_, scope)); // parse cartographic projection definitions vegaUtil.array(spec.projections).forEach(_ => parseProjection(_, scope)); // initialize scale references scales.forEach(_ => initScale(_, scope)); // parse data sources vegaUtil.array(spec.data).forEach(_ => parseData$1(_, scope)); // parse scale definitions scales.forEach(_ => parseScale(_, scope)); // parse signal updates (preprocessed || signals).forEach(_ => parseSignalUpdates(_, scope)); // parse axis definitions vegaUtil.array(spec.axes).forEach(_ => parseAxis(_, scope)); // parse mark definitions vegaUtil.array(spec.marks).forEach(_ => parseMark(_, scope)); // parse legend definitions vegaUtil.array(spec.legends).forEach(_ => parseLegend(_, scope)); // parse title, if defined if (spec.title) parseTitle(spec.title, scope); // parse collected lambda (anonymous) expressions scope.parseLambdas(); return scope; } const rootEncode = spec => extendEncode( { enter: { x: {value: 0}, y: {value: 0} }, update: { width: {signal: 'width'}, height: {signal: 'height'} } }, spec ); 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); parseScope(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 vegaUtil.array(spec.signals).forEach(s => { if (vegaUtil.hasOwnProperty(pre, s.name)) { // merge if built-in signal s = vegaUtil.extend(pre[s.name], s); } else { // otherwise add to signal list signals.push(s); } map[s.name] = s; }); // add config signal array vegaUtil.array(config.signals).forEach(s => { if (!vegaUtil.hasOwnProperty(map, s.name) && !vegaUtil.hasOwnProperty(pre, s.name)) { // add to signal list if not already defined signals.push(s); } }); return signals; } function Scope$1(config, options) { this.config = config || {}; this.options = options || {}; this.bindings = []; this.field = {}; this.signals = {}; this.lambdas = {}; this.scales = {}; this.events = {}; this.data = {}; this.streams = []; this.updates = []; this.operators = []; this.eventConfig = null; this.locale = null; this._id = 0; this._subid = 0; this._nextsub = [0]; this._parent = []; this._encode = []; this._lookup = []; this._markpath = []; } function Subscope(scope) { this.config = scope.config; this.options = scope.options; this.legends = scope.legends; this.field = Object.create(scope.field); this.signals = Object.create(scope.signals); this.lambdas = Object.create(scope.lambdas); this.scales = Object.create(scope.scales); this.events = Object.create(scope.events); this.data = Object.create(scope.data); this.streams = []; this.updates = []; this.operators = []; this._id = 0; this._subid = ++scope._nextsub[0]; this._nextsub = scope._nextsub; this._parent = scope._parent.slice(); this._encode = scope._encode.slice(); this._lookup = scope._lookup.slice(); this._markpath = scope._markpath; } var prototype$1 = Scope$1.prototype = Subscope.prototype; // ---- prototype$1.parse = function(spec) { return parseScope(spec, this); }; prototype$1.fork = function() { return new Subscope(this); }; prototype$1.isSubscope = function() { return this._subid > 0; }; prototype$1.toRuntime = function() { this.finish(); return { description: this.description, operators: this.operators, streams: this.streams, updates: this.updates, bindings: this.bindings, eventConfig: this.eventConfig, locale: this.locale }; }; prototype$1.id = function() { return (this._subid ? this._subid + ':' : 0) + this._id++; }; prototype$1.add = function(op) { this.operators.push(op); op.id = this.id(); // if pre-registration references exist, resolve them now if (op.refs) { op.refs.forEach(function(ref) { ref.$ref = op.id; }); op.refs = null; } return op; }; prototype$1.proxy = function(op) { var vref = op instanceof Entry ? ref(op) : op; return this.add(Proxy({value: vref})); }; prototype$1.addStream = function(stream) { this.streams.push(stream); stream.id = this.id(); return stream; }; prototype$1.addUpdate = function(update) { this.updates.push(update); return update; }; // Apply metadata prototype$1.finish = function() { var name, ds; // annotate root if (this.root) this.root.root = true; // annotate signals for (name in this.signals) { this.signals[name].signal = name; } // annotate scales for (name in this.scales) { this.scales[name].scale = name; } // annotate data sets function annotate(op, name, type) { var data, list; if (op) { data = op.data || (op.data = {}); list = data[name] || (data[name] = []); list.push(type); } } for (name in this.data) { ds = this.data[name]; annotate(ds.input, name, 'input'); annotate(ds.output, name, 'output'); annotate(ds.values, name, 'values'); for (var field in ds.index) { annotate(ds.index[field], name, 'index:' + field); } } return this; }; // ---- prototype$1.pushState = function(encode, parent, lookup) { this._encode.push(ref(this.add(Sieve({pulse: encode})))); this._parent.push(parent); this._lookup.push(lookup ? ref(this.proxy(lookup)) : null); this._markpath.push(-1); }; prototype$1.popState = function() { this._encode.pop(); this._parent.pop(); this._lookup.pop(); this._markpath.pop(); }; prototype$1.parent = function() { return vegaUtil.peek(this._parent); }; prototype$1.encode = function() { return vegaUtil.peek(this._encode); }; prototype$1.lookup = function() { return vegaUtil.peek(this._lookup); }; prototype$1.markpath = function() { var p = this._markpath; return ++p[p.length-1]; }; // ---- prototype$1.fieldRef = function(field, name) { if (vegaUtil.isString(field)) return fieldRef(field, name); if (!field.signal) { vegaUtil.error('Unsupported field reference: ' + vegaUtil.stringValue(field)); } var s = field.signal, f = this.field[s], params; if (!f) { params = {name: this.signalRef(s)}; if (name) params.as = name; this.field[s] = f = ref(this.add(Field(params))); } return f; }; prototype$1.compareRef = function(cmp) { function check(_) { if (isSignal(_)) { signal = true; return scope.signalRef(_.signal); } else if (isExpr(_)) { signal = true; return scope.exprRef(_.expr); } else { return _; } } var scope = this, signal = false, fields = vegaUtil.array(cmp.field).map(check), orders = vegaUtil.array(cmp.order).map(check); return signal ? ref(this.add(Compare({fields: fields, orders: orders}))) : compareRef(fields, orders); }; prototype$1.keyRef = function(fields, flat) { function check(_) { if (isSignal(_)) { signal = true; return ref(sig[_.signal]); } else { return _; } } var sig = this.signals, signal = false; fields = vegaUtil.array(fields).map(check); return signal ? ref(this.add(Key({fields: fields, flat: flat}))) : keyRef(fields, flat); }; prototype$1.sortRef = function(sort) { if (!sort) return sort; // including id ensures stable sorting var a = aggrField(sort.op, sort.field), o = sort.order || Ascending; return o.signal ? ref(this.add(Compare({ fields: a, orders: this.signalRef(o.signal) }))) : compareRef(a, o); }; // ---- prototype$1.event = function(source, type) { var key = source + ':' + type; if (!this.events[key]) { var id = this.id(); this.streams.push({ id: id, source: source, type: type }); this.events[key] = id; } return this.events[key]; }; // ---- prototype$1.hasOwnSignal = function(name) { return vegaUtil.hasOwnProperty(this.signals, name); }; prototype$1.addSignal = function(name, value) { if (this.hasOwnSignal(name)) { vegaUtil.error('Duplicate signal name: ' + vegaUtil.stringValue(name)); } var op = value instanceof Entry ? value : this.add(operator(value)); return this.signals[name] = op; }; prototype$1.getSignal = function(name) { if (!this.signals[name]) { vegaUtil.error('Unrecognized signal name: ' + vegaUtil.stringValue(name)); } return this.signals[name]; }; prototype$1.signalRef = function(s) { if (this.signals[s]) { return ref(this.signals[s]); } else if (!vegaUtil.hasOwnProperty(this.lambdas, s)) { this.lambdas[s] = this.add(operator(null)); } return ref(this.lambdas[s]); }; prototype$1.parseLambdas = function() { var code = Object.keys(this.lambdas); for (var i=0, n=code.length; i 0 ? ',' : '') + (vegaUtil.isObject(value) ? (value.signal || propertyLambda(value)) : vegaUtil.stringValue(value)); } return code + ']'; } function objectLambda(obj) { var code = '{', i = 0, key, value; for (key in obj) { value = obj[key]; code += (++i > 1 ? ',' : '') + vegaUtil.stringValue(key) + ':' + (vegaUtil.isObject(value) ? (value.signal || propertyLambda(value)) : vegaUtil.stringValue(value)); } return code + '}'; } prototype$1.exprRef = function(code, name) { var params = {expr: vegaFunctions.parseExpression(code, this)}; if (name) params.expr.$name = name; return ref(this.add(Expression(params))); }; prototype$1.addBinding = function(name, bind) { if (!this.bindings) { vegaUtil.error('Nested signals do not support binding: ' + vegaUtil.stringValue(name)); } this.bindings.push(vegaUtil.extend({signal: name}, bind)); }; // ---- prototype$1.addScaleProj = function(name, transform) { if (vegaUtil.hasOwnProperty(this.scales, name)) { vegaUtil.error('Duplicate scale or projection name: ' + vegaUtil.stringValue(name)); } this.scales[name] = this.add(transform); }; prototype$1.addScale = function(name, params) { this.addScaleProj(name, Scale(params)); }; prototype$1.addProjection = function(name, params) { this.addScaleProj(name, Projection(params)); }; prototype$1.getScale = function(name) { if (!this.scales[name]) { vegaUtil.error('Unrecognized scale name: ' + vegaUtil.stringValue(name)); } return this.scales[name]; }; prototype$1.projectionRef = prototype$1.scaleRef = function(name) { return ref(this.getScale(name)); }; prototype$1.projectionType = prototype$1.scaleType = function(name) { return this.getScale(name).params.type; }; // ---- prototype$1.addData = function(name, dataScope) { if (vegaUtil.hasOwnProperty(this.data, name)) { vegaUtil.error('Duplicate data set name: ' + vegaUtil.stringValue(name)); } return (this.data[name] = dataScope); }; prototype$1.getData = function(name) { if (!this.data[name]) { vegaUtil.error('Undefined data set name: ' + vegaUtil.stringValue(name)); } return this.data[name]; }; prototype$1.addDataPipeline = function(name, entries) { if (vegaUtil.hasOwnProperty(this.data, name)) { vegaUtil.error('Duplicate data set name: ' + vegaUtil.stringValue(name)); } return this.addData(name, DataScope.fromEntries(this, entries)); }; /** * Standard configuration defaults for Vega specification parsing. * Users can provide their own (sub-)set of these default values * by passing in a config object to the top-level parse method. */ function defaults() { const defaultFont = 'sans-serif', defaultSymbolSize = 30, defaultStrokeWidth = 2, defaultColor = '#4c78a8', black = '#000', gray = '#888', lightGray = '#ddd'; return { // default visualization description description: 'Vega visualization', // default padding around visualization padding: 0, // default for automatic sizing; options: 'none', 'pad', 'fit' // or provide an object (e.g., {'type': 'pad', 'resize': true}) autosize: 'pad', // default view background color // covers the entire view component background: null, // default event handling configuration // preventDefault for view-sourced event types except 'wheel' events: { defaults: {allow: ['wheel']} }, // defaults for top-level group marks // accepts mark properties (fill, stroke, etc) // covers the data rectangle within group width/height group: null, // defaults for basic mark types // each subset accepts mark properties (fill, stroke, etc) mark: null, arc: { fill: defaultColor }, area: { fill: defaultColor }, image: null, line: { stroke: defaultColor, strokeWidth: defaultStrokeWidth }, path: { stroke: defaultColor }, rect: { fill: defaultColor }, rule: { stroke: black }, shape: { stroke: defaultColor }, symbol: { fill: defaultColor, size: 64 }, text: { fill: black, font: defaultFont, fontSize: 11 }, trail: { fill: defaultColor, size: defaultStrokeWidth }, // style definitions style: { // axis & legend labels 'guide-label': { fill: black, font: defaultFont, fontSize: 10 }, // axis & legend titles 'guide-title': { fill: black, font: defaultFont, fontSize: 11, fontWeight: 'bold' }, // headers, including chart title 'group-title': { fill: black, font: defaultFont, fontSize: 13, fontWeight: 'bold' }, // chart subtitle 'group-subtitle': { fill: black, font: defaultFont, fontSize: 12 }, // defaults for styled point marks in Vega-Lite point: { size: defaultSymbolSize, strokeWidth: defaultStrokeWidth, shape: 'circle' }, circle: { size: defaultSymbolSize, strokeWidth: defaultStrokeWidth }, square: { size: defaultSymbolSize, strokeWidth: defaultStrokeWidth, shape: 'square' }, // defaults for styled group marks in Vega-Lite cell: { fill: 'transparent', stroke: lightGray } }, // defaults for title title: { orient: 'top', anchor: 'middle', offset: 4, subtitlePadding: 3 }, // defaults for axes axis: { minExtent: 0, maxExtent: 200, bandPosition: 0.5, domain: true, domainWidth: 1, domainColor: gray, grid: false, gridWidth: 1, gridColor: lightGray, labels: true, labelAngle: 0, labelLimit: 180, labelOffset: 0, labelPadding: 2, ticks: true, tickColor: gray, tickOffset: 0, tickRound: true, tickSize: 5, tickWidth: 1, titlePadding: 4 }, // correction for centering bias axisBand: { tickOffset: -0.5 }, // defaults for cartographic projection projection: { type: 'mercator' }, // defaults for legends legend: { orient: 'right', padding: 0, gridAlign: 'each', columnPadding: 10, rowPadding: 2, symbolDirection: 'vertical', gradientDirection: 'vertical', gradientLength: 200, gradientThickness: 16, gradientStrokeColor: lightGray, gradientStrokeWidth: 0, gradientLabelOffset: 2, labelAlign: 'left', labelBaseline: 'middle', labelLimit: 160, labelOffset: 4, labelOverlap: true, symbolLimit: 30, symbolType: 'circle', symbolSize: 100, symbolOffset: 0, symbolStrokeWidth: 1.5, symbolBaseFillColor: 'transparent', symbolBaseStrokeColor: gray, titleLimit: 180, titleOrient: 'top', titlePadding: 5, layout: { offset: 18, direction: 'horizontal', left: { direction: 'vertical' }, right: { direction: 'vertical' } } }, // defaults for scale ranges range: { category: { scheme: 'tableau10' }, ordinal: { scheme: 'blues' }, heatmap: { scheme: 'yellowgreenblue' }, ramp: { scheme: 'blues' }, diverging: { scheme: 'blueorange', extent: [1, 0] }, symbol: [ 'circle', 'square', 'triangle-up', 'cross', 'diamond', 'triangle-right', 'triangle-down', 'triangle-left' ] } }; } function parse$1(spec, config, options) { if (!vegaUtil.isObject(spec)) { vegaUtil.error('Input Vega specification must be an object.'); } config = vegaUtil.mergeConfig(defaults(), config, spec.config); return parseView(spec, new Scope$1(config, options)).toRuntime(); } exports.AxisDomainRole = AxisDomainRole; exports.AxisGridRole = AxisGridRole; exports.AxisLabelRole = AxisLabelRole; exports.AxisRole = AxisRole; exports.AxisTickRole = AxisTickRole; exports.AxisTitleRole = AxisTitleRole; exports.DataScope = DataScope; exports.FrameRole = FrameRole; exports.LegendEntryRole = LegendEntryRole; exports.LegendLabelRole = LegendLabelRole; exports.LegendRole = LegendRole; exports.LegendSymbolRole = LegendSymbolRole; exports.LegendTitleRole = LegendTitleRole; exports.MarkRole = MarkRole; exports.Scope = Scope$1; exports.ScopeRole = ScopeRole; exports.config = defaults; exports.parse = parse$1; exports.signal = parseSignal; exports.signalUpdates = parseSignalUpdates; exports.stream = parseStream; Object.defineProperty(exports, '__esModule', { value: true }); })));