vega-encode
Version:
Visual encoding transforms for Vega dataflows.
951 lines (913 loc) • 30.6 kB
JavaScript
import { Transform, ingest, tupleid, stableCompare } from 'vega-dataflow';
import { tickCount, tickFormat, validTicks, tickValues, SymbolLegend, labelFormat, labelValues, GradientLegend, scaleFraction, labelFraction, scale, Sequential, Linear, isContinuous, Time, UTC, Ordinal, scaleImplicit, Log, Sqrt, Pow, Symlog, isLogarithmic, BinOrdinal, isInterpolating, interpolateColors, interpolate, Band, Point, bandSpace, scheme, Threshold, Quantile, Quantize, quantizeInterpolator, interpolateRange, Diverging } from 'vega-scale';
import { inherits, isArray, error, fastmap, falsy, isFunction, constant, peek, one, toSet, isString, zoomLog, zoomPow, zoomSymlog, zoomLinear, stringValue } from 'vega-util';
import { sum, range } from 'd3-array';
import { interpolateRound, interpolate as interpolate$1 } from 'd3-interpolate';
/**
* Generates axis ticks for visualizing a spatial scale.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {Scale} params.scale - The scale to generate ticks for.
* @param {*} [params.count=10] - The approximate number of ticks, or
* desired tick interval, to use.
* @param {Array<*>} [params.values] - The exact tick values to use.
* These must be legal domain values for the provided scale.
* If provided, the count argument is ignored.
* @param {function(*):string} [params.formatSpecifier] - A format specifier
* to use in conjunction with scale.tickFormat. Legal values are
* any valid d3 4.0 format specifier.
* @param {function(*):string} [params.format] - The format function to use.
* If provided, the formatSpecifier argument is ignored.
*/
function AxisTicks(params) {
Transform.call(this, null, params);
}
inherits(AxisTicks, Transform, {
transform(_, pulse) {
if (this.value && !_.modified()) {
return pulse.StopPropagation;
}
var locale = pulse.dataflow.locale(),
out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
ticks = this.value,
scale = _.scale,
tally = _.count == null ? _.values ? _.values.length : 10 : _.count,
count = tickCount(scale, tally, _.minstep),
format = _.format || tickFormat(locale, scale, count, _.formatSpecifier, _.formatType, !!_.values),
values = _.values ? validTicks(scale, _.values, count) : tickValues(scale, count);
if (ticks) out.rem = ticks;
ticks = values.map((value, i) => ingest({
index: i / (values.length - 1 || 1),
value: value,
label: format(value)
}));
if (_.extra && ticks.length) {
// add an extra tick pegged to the initial domain value
// this is used to generate axes with 'binned' domains
ticks.push(ingest({
index: -1,
extra: {
value: ticks[0].value
},
label: ''
}));
}
out.source = ticks;
out.add = ticks;
this.value = ticks;
return out;
}
});
/**
* Joins a set of data elements against a set of visual items.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {function(object): object} [params.item] - An item generator function.
* @param {function(object): *} [params.key] - The key field associating data and visual items.
*/
function DataJoin(params) {
Transform.call(this, null, params);
}
function defaultItemCreate() {
return ingest({});
}
function newMap(key) {
const map = fastmap().test(t => t.exit);
map.lookup = t => map.get(key(t));
return map;
}
inherits(DataJoin, Transform, {
transform(_, pulse) {
var df = pulse.dataflow,
out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
item = _.item || defaultItemCreate,
key = _.key || tupleid,
map = this.value;
// prevent transient (e.g., hover) requests from
// cascading across marks derived from marks
if (isArray(out.encode)) {
out.encode = null;
}
if (map && (_.modified('key') || pulse.modified(key))) {
error('DataJoin does not support modified key function or fields.');
}
if (!map) {
pulse = pulse.addAll();
this.value = map = newMap(key);
}
pulse.visit(pulse.ADD, t => {
const k = key(t);
let x = map.get(k);
if (x) {
if (x.exit) {
map.empty--;
out.add.push(x);
} else {
out.mod.push(x);
}
} else {
x = item(t);
map.set(k, x);
out.add.push(x);
}
x.datum = t;
x.exit = false;
});
pulse.visit(pulse.MOD, t => {
const k = key(t),
x = map.get(k);
if (x) {
x.datum = t;
out.mod.push(x);
}
});
pulse.visit(pulse.REM, t => {
const k = key(t),
x = map.get(k);
if (t === x.datum && !x.exit) {
out.rem.push(x);
x.exit = true;
++map.empty;
}
});
if (pulse.changed(pulse.ADD_MOD)) out.modifies('datum');
if (pulse.clean() || _.clean && map.empty > df.cleanThreshold) {
df.runAfter(map.clean);
}
return out;
}
});
/**
* Invokes encoding functions for visual items.
* @constructor
* @param {object} params - The parameters to the encoding functions. This
* parameter object will be passed through to all invoked encoding functions.
* @param {object} [params.mod=false] - Flag indicating if tuples in the input
* mod set that are unmodified by encoders should be included in the output.
* @param {object} param.encoders - The encoding functions
* @param {function(object, object): boolean} [param.encoders.update] - Update encoding set
* @param {function(object, object): boolean} [param.encoders.enter] - Enter encoding set
* @param {function(object, object): boolean} [param.encoders.exit] - Exit encoding set
*/
function Encode(params) {
Transform.call(this, null, params);
}
inherits(Encode, Transform, {
transform(_, pulse) {
var out = pulse.fork(pulse.ADD_REM),
fmod = _.mod || false,
encoders = _.encoders,
encode = pulse.encode;
// if an array, the encode directive includes additional sets
// that must be defined in order for the primary set to be invoked
// e.g., only run the update set if the hover set is defined
if (isArray(encode)) {
if (out.changed() || encode.every(e => encoders[e])) {
encode = encode[0];
out.encode = null; // consume targeted encode directive
} else {
return pulse.StopPropagation;
}
}
// marshall encoder functions
var reenter = encode === 'enter',
update = encoders.update || falsy,
enter = encoders.enter || falsy,
exit = encoders.exit || falsy,
set = (encode && !reenter ? encoders[encode] : update) || falsy;
if (pulse.changed(pulse.ADD)) {
pulse.visit(pulse.ADD, t => {
enter(t, _);
update(t, _);
});
out.modifies(enter.output);
out.modifies(update.output);
if (set !== falsy && set !== update) {
pulse.visit(pulse.ADD, t => {
set(t, _);
});
out.modifies(set.output);
}
}
if (pulse.changed(pulse.REM) && exit !== falsy) {
pulse.visit(pulse.REM, t => {
exit(t, _);
});
out.modifies(exit.output);
}
if (reenter || set !== falsy) {
const flag = pulse.MOD | (_.modified() ? pulse.REFLOW : 0);
if (reenter) {
pulse.visit(flag, t => {
const mod = enter(t, _) || fmod;
if (set(t, _) || mod) out.mod.push(t);
});
if (out.mod.length) out.modifies(enter.output);
} else {
pulse.visit(flag, t => {
if (set(t, _) || fmod) out.mod.push(t);
});
}
if (out.mod.length) out.modifies(set.output);
}
return out.changed() ? out : pulse.StopPropagation;
}
});
/**
* Generates legend entries for visualizing a scale.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {Scale} params.scale - The scale to generate items for.
* @param {*} [params.count=5] - The approximate number of items, or
* desired tick interval, to use.
* @param {*} [params.limit] - The maximum number of entries to
* include in a symbol legend.
* @param {Array<*>} [params.values] - The exact tick values to use.
* These must be legal domain values for the provided scale.
* If provided, the count argument is ignored.
* @param {string} [params.formatSpecifier] - A format specifier
* to use in conjunction with scale.tickFormat. Legal values are
* any valid D3 format specifier string.
* @param {function(*):string} [params.format] - The format function to use.
* If provided, the formatSpecifier argument is ignored.
*/
function LegendEntries(params) {
Transform.call(this, [], params);
}
inherits(LegendEntries, Transform, {
transform(_, pulse) {
if (this.value != null && !_.modified()) {
return pulse.StopPropagation;
}
var locale = pulse.dataflow.locale(),
out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS),
items = this.value,
type = _.type || SymbolLegend,
scale = _.scale,
limit = +_.limit,
count = tickCount(scale, _.count == null ? 5 : _.count, _.minstep),
lskip = !!_.values || type === SymbolLegend,
format = _.format || labelFormat(locale, scale, count, type, _.formatSpecifier, _.formatType, lskip),
values = _.values || labelValues(scale, count),
domain,
fraction,
size,
offset,
ellipsis;
if (items) out.rem = items;
if (type === SymbolLegend) {
if (limit && values.length > limit) {
pulse.dataflow.warn('Symbol legend count exceeds limit, filtering items.');
items = values.slice(0, limit - 1);
ellipsis = true;
} else {
items = values;
}
if (isFunction(size = _.size)) {
// if first value maps to size zero, remove from list (vega#717)
if (!_.values && scale(items[0]) === 0) {
items = items.slice(1);
}
// compute size offset for legend entries
offset = items.reduce((max, value) => Math.max(max, size(value, _)), 0);
} else {
size = constant(offset = size || 8);
}
items = items.map((value, index) => ingest({
index: index,
label: format(value, index, items),
value: value,
offset: offset,
size: size(value, _)
}));
if (ellipsis) {
ellipsis = values[items.length];
items.push(ingest({
index: items.length,
label: `\u2026${values.length - items.length} entries`,
value: ellipsis,
offset: offset,
size: size(ellipsis, _)
}));
}
} else if (type === GradientLegend) {
domain = scale.domain(), fraction = scaleFraction(scale, domain[0], peek(domain));
// if automatic label generation produces 2 or fewer values,
// use the domain end points instead (fixes vega/vega#1364)
if (values.length < 3 && !_.values && domain[0] !== peek(domain)) {
values = [domain[0], peek(domain)];
}
items = values.map((value, index) => ingest({
index: index,
label: format(value, index, values),
value: value,
perc: fraction(value)
}));
} else {
size = values.length - 1;
fraction = labelFraction(scale);
items = values.map((value, index) => ingest({
index: index,
label: format(value, index, values),
value: value,
perc: index ? fraction(value) : 0,
perc2: index === size ? 1 : fraction(values[index + 1])
}));
}
out.source = items;
out.add = items;
this.value = items;
return out;
}
});
const sourceX = t => t.source.x;
const sourceY = t => t.source.y;
const targetX = t => t.target.x;
const targetY = t => t.target.y;
/**
* Layout paths linking source and target elements.
* @constructor
* @param {object} params - The parameters for this operator.
*/
function LinkPath(params) {
Transform.call(this, {}, params);
}
LinkPath.Definition = {
'type': 'LinkPath',
'metadata': {
'modifies': true
},
'params': [{
'name': 'sourceX',
'type': 'field',
'default': 'source.x'
}, {
'name': 'sourceY',
'type': 'field',
'default': 'source.y'
}, {
'name': 'targetX',
'type': 'field',
'default': 'target.x'
}, {
'name': 'targetY',
'type': 'field',
'default': 'target.y'
}, {
'name': 'orient',
'type': 'enum',
'default': 'vertical',
'values': ['horizontal', 'vertical', 'radial']
}, {
'name': 'shape',
'type': 'enum',
'default': 'line',
'values': ['line', 'arc', 'curve', 'diagonal', 'orthogonal']
}, {
'name': 'require',
'type': 'signal'
}, {
'name': 'as',
'type': 'string',
'default': 'path'
}]
};
inherits(LinkPath, Transform, {
transform(_, pulse) {
var sx = _.sourceX || sourceX,
sy = _.sourceY || sourceY,
tx = _.targetX || targetX,
ty = _.targetY || targetY,
as = _.as || 'path',
orient = _.orient || 'vertical',
shape = _.shape || 'line',
path = Paths.get(shape + '-' + orient) || Paths.get(shape);
if (!path) {
error('LinkPath unsupported type: ' + _.shape + (_.orient ? '-' + _.orient : ''));
}
pulse.visit(pulse.SOURCE, t => {
t[as] = path(sx(t), sy(t), tx(t), ty(t));
});
return pulse.reflow(_.modified()).modifies(as);
}
});
const line = (sx, sy, tx, ty) => 'M' + sx + ',' + sy + 'L' + tx + ',' + ty;
const lineR = (sa, sr, ta, tr) => line(sr * Math.cos(sa), sr * Math.sin(sa), tr * Math.cos(ta), tr * Math.sin(ta));
const arc = (sx, sy, tx, ty) => {
var dx = tx - sx,
dy = ty - sy,
rr = Math.hypot(dx, dy) / 2,
ra = 180 * Math.atan2(dy, dx) / Math.PI;
return 'M' + sx + ',' + sy + 'A' + rr + ',' + rr + ' ' + ra + ' 0 1' + ' ' + tx + ',' + ty;
};
const arcR = (sa, sr, ta, tr) => arc(sr * Math.cos(sa), sr * Math.sin(sa), tr * Math.cos(ta), tr * Math.sin(ta));
const curve = (sx, sy, tx, ty) => {
const dx = tx - sx,
dy = ty - sy,
ix = 0.2 * (dx + dy),
iy = 0.2 * (dy - dx);
return 'M' + sx + ',' + sy + 'C' + (sx + ix) + ',' + (sy + iy) + ' ' + (tx + iy) + ',' + (ty - ix) + ' ' + tx + ',' + ty;
};
const curveR = (sa, sr, ta, tr) => curve(sr * Math.cos(sa), sr * Math.sin(sa), tr * Math.cos(ta), tr * Math.sin(ta));
const orthoX = (sx, sy, tx, ty) => 'M' + sx + ',' + sy + 'V' + ty + 'H' + tx;
const orthoY = (sx, sy, tx, ty) => 'M' + sx + ',' + sy + 'H' + tx + 'V' + ty;
const orthoR = (sa, sr, ta, tr) => {
const sc = Math.cos(sa),
ss = Math.sin(sa),
tc = Math.cos(ta),
ts = Math.sin(ta),
sf = Math.abs(ta - sa) > Math.PI ? ta <= sa : ta > sa;
return 'M' + sr * sc + ',' + sr * ss + 'A' + sr + ',' + sr + ' 0 0,' + (sf ? 1 : 0) + ' ' + sr * tc + ',' + sr * ts + 'L' + tr * tc + ',' + tr * ts;
};
const diagonalX = (sx, sy, tx, ty) => {
const m = (sx + tx) / 2;
return 'M' + sx + ',' + sy + 'C' + m + ',' + sy + ' ' + m + ',' + ty + ' ' + tx + ',' + ty;
};
const diagonalY = (sx, sy, tx, ty) => {
const m = (sy + ty) / 2;
return 'M' + sx + ',' + sy + 'C' + sx + ',' + m + ' ' + tx + ',' + m + ' ' + tx + ',' + ty;
};
const diagonalR = (sa, sr, ta, tr) => {
const sc = Math.cos(sa),
ss = Math.sin(sa),
tc = Math.cos(ta),
ts = Math.sin(ta),
mr = (sr + tr) / 2;
return 'M' + sr * sc + ',' + sr * ss + 'C' + mr * sc + ',' + mr * ss + ' ' + mr * tc + ',' + mr * ts + ' ' + tr * tc + ',' + tr * ts;
};
const Paths = fastmap({
'line': line,
'line-radial': lineR,
'arc': arc,
'arc-radial': arcR,
'curve': curve,
'curve-radial': curveR,
'orthogonal-horizontal': orthoX,
'orthogonal-vertical': orthoY,
'orthogonal-radial': orthoR,
'diagonal-horizontal': diagonalX,
'diagonal-vertical': diagonalY,
'diagonal-radial': diagonalR
});
/**
* Pie and donut chart layout.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {function(object): *} params.field - The value field to size pie segments.
* @param {number} [params.startAngle=0] - The start angle (in radians) of the layout.
* @param {number} [params.endAngle=2π] - The end angle (in radians) of the layout.
* @param {boolean} [params.sort] - Boolean flag for sorting sectors by value.
*/
function Pie(params) {
Transform.call(this, null, params);
}
Pie.Definition = {
'type': 'Pie',
'metadata': {
'modifies': true
},
'params': [{
'name': 'field',
'type': 'field'
}, {
'name': 'startAngle',
'type': 'number',
'default': 0
}, {
'name': 'endAngle',
'type': 'number',
'default': 6.283185307179586
}, {
'name': 'sort',
'type': 'boolean',
'default': false
}, {
'name': 'as',
'type': 'string',
'array': true,
'length': 2,
'default': ['startAngle', 'endAngle']
}]
};
inherits(Pie, Transform, {
transform(_, pulse) {
var as = _.as || ['startAngle', 'endAngle'],
startAngle = as[0],
endAngle = as[1],
field = _.field || one,
start = _.startAngle || 0,
stop = _.endAngle != null ? _.endAngle : 2 * Math.PI,
data = pulse.source,
values = data.map(field),
n = values.length,
a = start,
k = (stop - start) / sum(values),
index = range(n),
i,
t,
v;
if (_.sort) {
index.sort((a, b) => values[a] - values[b]);
}
for (i = 0; i < n; ++i) {
v = values[index[i]];
t = data[index[i]];
t[startAngle] = a;
t[endAngle] = a += v * k;
}
this.value = values;
return pulse.reflow(_.modified()).modifies(as);
}
});
const DEFAULT_COUNT = 5;
function includeZero(scale) {
const type = scale.type;
return !scale.bins && (type === Linear || type === Pow || type === Sqrt);
}
function includePad(type) {
return isContinuous(type) && type !== Sequential;
}
const SKIP = toSet(['set', 'modified', 'clear', 'type', 'scheme', 'schemeExtent', 'schemeCount', 'domain', 'domainMin', 'domainMid', 'domainMax', 'domainRaw', 'domainImplicit', 'nice', 'zero', 'bins', 'range', 'rangeStep', 'round', 'reverse', 'interpolate', 'interpolateGamma']);
/**
* Maintains a scale function mapping data values to visual channels.
* @constructor
* @param {object} params - The parameters for this operator.
*/
function Scale(params) {
Transform.call(this, null, params);
this.modified(true); // always treat as modified
}
inherits(Scale, Transform, {
transform(_, pulse) {
var df = pulse.dataflow,
scale$1 = this.value,
key = scaleKey(_);
if (!scale$1 || key !== scale$1.type) {
this.value = scale$1 = scale(key)();
}
for (key in _) if (!SKIP[key]) {
// padding is a scale property for band/point but not others
if (key === 'padding' && includePad(scale$1.type)) continue;
// invoke scale property setter, raise warning if not found
isFunction(scale$1[key]) ? scale$1[key](_[key]) : df.warn('Unsupported scale property: ' + key);
}
configureRange(scale$1, _, configureBins(scale$1, _, configureDomain(scale$1, _, df)));
return pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS);
}
});
function scaleKey(_) {
var t = _.type,
d = '',
n;
// backwards compatibility pre Vega 5.
if (t === Sequential) return Sequential + '-' + Linear;
if (isContinuousColor(_)) {
n = _.rawDomain ? _.rawDomain.length : _.domain ? _.domain.length + +(_.domainMid != null) : 0;
d = n === 2 ? Sequential + '-' : n === 3 ? Diverging + '-' : '';
}
return (d + t || Linear).toLowerCase();
}
function isContinuousColor(_) {
const t = _.type;
return isContinuous(t) && t !== Time && t !== UTC && (_.scheme || _.range && _.range.length && _.range.every(isString));
}
function configureDomain(scale, _, df) {
// check raw domain, if provided use that and exit early
const raw = rawDomain(scale, _.domainRaw, df);
if (raw > -1) return raw;
var domain = _.domain,
type = scale.type,
zero = _.zero || _.zero === undefined && includeZero(scale),
n,
mid;
if (!domain) return 0;
// adjust domain based on zero, min, max settings
if (zero || _.domainMin != null || _.domainMax != null || _.domainMid != null) {
n = (domain = domain.slice()).length - 1 || 1;
if (zero) {
if (domain[0] > 0) domain[0] = 0;
if (domain[n] < 0) domain[n] = 0;
}
if (_.domainMin != null) domain[0] = _.domainMin;
if (_.domainMax != null) domain[n] = _.domainMax;
if (_.domainMid != null) {
mid = _.domainMid;
const i = mid > domain[n] ? n + 1 : mid < domain[0] ? 0 : n;
if (i !== n) df.warn('Scale domainMid exceeds domain min or max.', mid);
domain.splice(i, 0, mid);
}
}
// adjust continuous domain for minimum pixel padding
if (includePad(type) && _.padding && domain[0] !== peek(domain)) {
domain = padDomain(type, domain, _.range, _.padding, _.exponent, _.constant);
}
// set the scale domain
scale.domain(domainCheck(type, domain, df));
// if ordinal scale domain is defined, prevent implicit
// domain construction as side-effect of scale lookup
if (type === Ordinal) {
scale.unknown(_.domainImplicit ? scaleImplicit : undefined);
}
// perform 'nice' adjustment as requested
if (_.nice && scale.nice) {
scale.nice(_.nice !== true && tickCount(scale, _.nice) || null);
}
// return the cardinality of the domain
return domain.length;
}
function rawDomain(scale, raw, df) {
if (raw) {
scale.domain(domainCheck(scale.type, raw, df));
return raw.length;
} else {
return -1;
}
}
function padDomain(type, domain, range, pad, exponent, constant) {
var span = Math.abs(peek(range) - range[0]),
frac = span / (span - 2 * pad),
d = type === Log ? zoomLog(domain, null, frac) : type === Sqrt ? zoomPow(domain, null, frac, 0.5) : type === Pow ? zoomPow(domain, null, frac, exponent || 1) : type === Symlog ? zoomSymlog(domain, null, frac, constant || 1) : zoomLinear(domain, null, frac);
domain = domain.slice();
domain[0] = d[0];
domain[domain.length - 1] = d[1];
return domain;
}
function domainCheck(type, domain, df) {
if (isLogarithmic(type)) {
// sum signs of domain values
// if all pos or all neg, abs(sum) === domain.length
var s = Math.abs(domain.reduce((s, v) => s + (v < 0 ? -1 : v > 0 ? 1 : 0), 0));
if (s !== domain.length) {
df.warn('Log scale domain includes zero: ' + stringValue(domain));
}
}
return domain;
}
function configureBins(scale, _, count) {
let bins = _.bins;
if (bins && !isArray(bins)) {
// generate bin boundary array
const domain = scale.domain(),
lo = domain[0],
hi = peek(domain),
step = bins.step;
let start = bins.start == null ? lo : bins.start,
stop = bins.stop == null ? hi : bins.stop;
if (!step) error('Scale bins parameter missing step property.');
if (start < lo) start = step * Math.ceil(lo / step);
if (stop > hi) stop = step * Math.floor(hi / step);
bins = range(start, stop + step / 2, step);
}
if (bins) {
// assign bin boundaries to scale instance
scale.bins = bins;
} else if (scale.bins) {
// no current bins, remove bins if previously set
delete scale.bins;
}
// special handling for bin-ordinal scales
if (scale.type === BinOrdinal) {
if (!bins) {
// the domain specifies the bins
scale.bins = scale.domain();
} else if (!_.domain && !_.domainRaw) {
// the bins specify the domain
scale.domain(bins);
count = bins.length;
}
}
// return domain cardinality
return count;
}
function configureRange(scale, _, count) {
var type = scale.type,
round = _.round || false,
range = _.range;
// if range step specified, calculate full range extent
if (_.rangeStep != null) {
range = configureRangeStep(type, _, count);
}
// else if a range scheme is defined, use that
else if (_.scheme) {
range = configureScheme(type, _, count);
if (isFunction(range)) {
if (scale.interpolator) {
return scale.interpolator(range);
} else {
error(`Scale type ${type} does not support interpolating color schemes.`);
}
}
}
// given a range array for an interpolating scale, convert to interpolator
if (range && isInterpolating(type)) {
return scale.interpolator(interpolateColors(flip(range, _.reverse), _.interpolate, _.interpolateGamma));
}
// configure rounding / interpolation
if (range && _.interpolate && scale.interpolate) {
scale.interpolate(interpolate(_.interpolate, _.interpolateGamma));
} else if (isFunction(scale.round)) {
scale.round(round);
} else if (isFunction(scale.rangeRound)) {
scale.interpolate(round ? interpolateRound : interpolate$1);
}
if (range) scale.range(flip(range, _.reverse));
}
function configureRangeStep(type, _, count) {
if (type !== Band && type !== Point) {
error('Only band and point scales support rangeStep.');
}
// calculate full range based on requested step size and padding
var outer = (_.paddingOuter != null ? _.paddingOuter : _.padding) || 0,
inner = type === Point ? 1 : (_.paddingInner != null ? _.paddingInner : _.padding) || 0;
return [0, _.rangeStep * bandSpace(count, inner, outer)];
}
function configureScheme(type, _, count) {
var extent = _.schemeExtent,
name,
scheme$1;
if (isArray(_.scheme)) {
scheme$1 = interpolateColors(_.scheme, _.interpolate, _.interpolateGamma);
} else {
name = _.scheme.toLowerCase();
scheme$1 = scheme(name);
if (!scheme$1) error(`Unrecognized scheme name: ${_.scheme}`);
}
// determine size for potential discrete range
count = type === Threshold ? count + 1 : type === BinOrdinal ? count - 1 : type === Quantile || type === Quantize ? +_.schemeCount || DEFAULT_COUNT : count;
// adjust and/or quantize scheme as appropriate
return isInterpolating(type) ? adjustScheme(scheme$1, extent, _.reverse) : isFunction(scheme$1) ? quantizeInterpolator(adjustScheme(scheme$1, extent), count) : type === Ordinal ? scheme$1 : scheme$1.slice(0, count);
}
function adjustScheme(scheme, extent, reverse) {
return isFunction(scheme) && (extent || reverse) ? interpolateRange(scheme, flip(extent || [0, 1], reverse)) : scheme;
}
function flip(array, reverse) {
return reverse ? array.slice().reverse() : array;
}
/**
* Sorts scenegraph items in the pulse source array.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {function(*,*): number} [params.sort] - A comparator
* function for sorting tuples.
*/
function SortItems(params) {
Transform.call(this, null, params);
}
inherits(SortItems, Transform, {
transform(_, pulse) {
const mod = _.modified('sort') || pulse.changed(pulse.ADD) || pulse.modified(_.sort.fields) || pulse.modified('datum');
if (mod) pulse.source.sort(stableCompare(_.sort));
this.modified(mod);
return pulse;
}
});
const Zero = 'zero',
Center = 'center',
Normalize = 'normalize',
DefOutput = ['y0', 'y1'];
/**
* Stack layout for visualization elements.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {function(object): *} params.field - The value field to stack.
* @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby.
* @param {function(object,object): number} [params.sort] - A comparator for stack sorting.
* @param {string} [offset='zero'] - Stack baseline offset. One of 'zero', 'center', 'normalize'.
*/
function Stack(params) {
Transform.call(this, null, params);
}
Stack.Definition = {
'type': 'Stack',
'metadata': {
'modifies': true
},
'params': [{
'name': 'field',
'type': 'field'
}, {
'name': 'groupby',
'type': 'field',
'array': true
}, {
'name': 'sort',
'type': 'compare'
}, {
'name': 'offset',
'type': 'enum',
'default': Zero,
'values': [Zero, Center, Normalize]
}, {
'name': 'as',
'type': 'string',
'array': true,
'length': 2,
'default': DefOutput
}]
};
inherits(Stack, Transform, {
transform(_, pulse) {
var as = _.as || DefOutput,
y0 = as[0],
y1 = as[1],
sort = stableCompare(_.sort),
field = _.field || one,
stack = _.offset === Center ? stackCenter : _.offset === Normalize ? stackNormalize : stackZero,
groups,
i,
n,
max;
// partition, sum, and sort the stack groups
groups = partition(pulse.source, _.groupby, sort, field);
// compute stack layouts per group
for (i = 0, n = groups.length, max = groups.max; i < n; ++i) {
stack(groups[i], max, field, y0, y1);
}
return pulse.reflow(_.modified()).modifies(as);
}
});
function stackCenter(group, max, field, y0, y1) {
var last = (max - group.sum) / 2,
m = group.length,
j = 0,
t;
for (; j < m; ++j) {
t = group[j];
t[y0] = last;
t[y1] = last += Math.abs(field(t));
}
}
function stackNormalize(group, max, field, y0, y1) {
var scale = 1 / group.sum,
last = 0,
m = group.length,
j = 0,
v = 0,
t;
for (; j < m; ++j) {
t = group[j];
t[y0] = last;
t[y1] = last = scale * (v += Math.abs(field(t)));
}
}
function stackZero(group, max, field, y0, y1) {
var lastPos = 0,
lastNeg = 0,
m = group.length,
j = 0,
v,
t;
for (; j < m; ++j) {
t = group[j];
v = +field(t);
if (v < 0) {
t[y0] = lastNeg;
t[y1] = lastNeg += v;
} else {
t[y0] = lastPos;
t[y1] = lastPos += v;
}
}
}
function partition(data, groupby, sort, field) {
var groups = [],
get = f => f(t),
map,
i,
n,
m,
t,
k,
g,
s,
max;
// partition data points into stack groups
if (groupby == null) {
groups.push(data.slice());
} else {
for (map = {}, i = 0, n = data.length; i < n; ++i) {
t = data[i];
k = groupby.map(get);
g = map[k];
if (!g) {
map[k] = g = [];
groups.push(g);
}
g.push(t);
}
}
// compute sums of groups, sort groups as needed
for (k = 0, max = 0, m = groups.length; k < m; ++k) {
g = groups[k];
for (i = 0, s = 0, n = g.length; i < n; ++i) {
s += Math.abs(field(g[i]));
}
g.sum = s;
if (s > max) max = s;
if (sort) g.sort(sort);
}
groups.max = max;
return groups;
}
export { AxisTicks as axisticks, DataJoin as datajoin, Encode as encode, LegendEntries as legendentries, LinkPath as linkpath, Pie as pie, Scale as scale, SortItems as sortitems, Stack as stack };
//# sourceMappingURL=vega-encode.js.map