vega-functions
Version:
Custom functions for the Vega expression language.
1,511 lines (1,450 loc) • 59.4 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-util'), require('vega-expression'), require('vega-scale'), require('vega-dataflow'), require('vega-scenegraph'), require('vega-selections'), require('vega-statistics'), require('vega-time')) :
typeof define === 'function' && define.amd ? define(['exports', 'vega-util', 'vega-expression', 'vega-scale', 'vega-dataflow', 'vega-scenegraph', 'vega-selections', 'vega-statistics', 'vega-time'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vega = {}, global.vega, global.vega, global.vega, global.vega, global.vega, global.vega, global.vega, global.vega));
})(this, (function (exports, vegaUtil, vegaExpression, vegaScale, vegaDataflow, vegaScenegraph, vegaSelections, vegaStatistics, vegaTime) { 'use strict';
function data(name) {
const data = this.context.data[name];
return data ? data.values.value : [];
}
function indata(name, field, value) {
const index = this.context.data[name]['index:' + field],
entry = index ? index.value.get(value) : undefined;
return entry ? entry.count : entry;
}
function setdata(name, tuples) {
const df = this.context.dataflow,
data = this.context.data[name],
input = data.input;
df.pulse(input, df.changeset().remove(vegaUtil.truthy).insert(tuples));
return 1;
}
function encode (item, name, retval) {
if (item) {
const df = this.context.dataflow,
target = item.mark.source;
df.pulse(target, df.changeset().encode(item, name));
}
return retval !== undefined ? retval : item;
}
const wrap = method => function (value, spec) {
const locale = this.context.dataflow.locale();
return value === null ? 'null' : locale[method](spec)(value);
};
const format = wrap('format');
const timeFormat = wrap('timeFormat');
const utcFormat = wrap('utcFormat');
const timeParse = wrap('timeParse');
const utcParse = wrap('utcParse');
const dateObj = new Date(2000, 0, 1);
function time(month, day, specifier) {
if (!Number.isInteger(month) || !Number.isInteger(day)) return '';
dateObj.setYear(2000);
dateObj.setMonth(month);
dateObj.setDate(day);
return timeFormat.call(this, dateObj, specifier);
}
function monthFormat(month) {
return time.call(this, month, 1, '%B');
}
function monthAbbrevFormat(month) {
return time.call(this, month, 1, '%b');
}
function dayFormat(day) {
return time.call(this, 0, 2 + day, '%A');
}
function dayAbbrevFormat(day) {
return time.call(this, 0, 2 + day, '%a');
}
const DataPrefix = ':';
const IndexPrefix = '@';
const ScalePrefix = '%';
const SignalPrefix = '$';
function dataVisitor(name, args, scope, params) {
if (args[0].type !== vegaExpression.Literal) {
vegaUtil.error('First argument to data functions must be a string literal.');
}
const data = args[0].value,
dataName = DataPrefix + data;
if (!vegaUtil.hasOwnProperty(dataName, params)) {
try {
params[dataName] = scope.getData(data).tuplesRef();
} catch (err) {
// if data set does not exist, there's nothing to track
}
}
}
function indataVisitor(name, args, scope, params) {
if (args[0].type !== vegaExpression.Literal) vegaUtil.error('First argument to indata must be a string literal.');
if (args[1].type !== vegaExpression.Literal) vegaUtil.error('Second argument to indata must be a string literal.');
const data = args[0].value,
field = args[1].value,
indexName = IndexPrefix + field;
if (!vegaUtil.hasOwnProperty(indexName, params)) {
params[indexName] = scope.getData(data).indataRef(scope, field);
}
}
function scaleVisitor(name, args, scope, params) {
if (args[0].type === vegaExpression.Literal) {
// add scale dependency
addScaleDependency(scope, params, args[0].value);
} else {
// indirect scale lookup; add all scales as parameters
for (name in scope.scales) {
addScaleDependency(scope, params, name);
}
}
}
function addScaleDependency(scope, params, name) {
const scaleName = ScalePrefix + name;
if (!vegaUtil.hasOwnProperty(params, scaleName)) {
try {
params[scaleName] = scope.scaleRef(name);
} catch (err) {
// TODO: error handling? warning?
}
}
}
/**
* nameOrFunction must be a string or function that was registered.
* Return undefined if scale is not recognized.
*/
function getScale(nameOrFunction, ctx) {
if (vegaUtil.isString(nameOrFunction)) {
const maybeScale = ctx.scales[nameOrFunction];
return maybeScale && vegaScale.isRegisteredScale(maybeScale.value) ? maybeScale.value : undefined;
} else if (vegaUtil.isFunction(nameOrFunction)) {
return vegaScale.isRegisteredScale(nameOrFunction) ? nameOrFunction : undefined;
}
return undefined;
}
function internalScaleFunctions(codegen, fnctx, visitors) {
// add helper method to the 'this' expression function context
fnctx.__bandwidth = s => s && s.bandwidth ? s.bandwidth() : 0;
// register AST visitors for internal scale functions
visitors._bandwidth = scaleVisitor;
visitors._range = scaleVisitor;
visitors._scale = scaleVisitor;
// resolve scale reference directly to the signal hash argument
const ref = arg => '_[' + (arg.type === vegaExpression.Literal ? vegaUtil.stringValue(ScalePrefix + arg.value) : vegaUtil.stringValue(ScalePrefix) + '+' + codegen(arg)) + ']';
// define and return internal scale function code generators
// these internal functions are called by mark encoders
return {
_bandwidth: args => `this.__bandwidth(${ref(args[0])})`,
_range: args => `${ref(args[0])}.range()`,
_scale: args => `${ref(args[0])}(${codegen(args[1])})`
};
}
// https://github.com/python/cpython/blob/a74eea238f5baba15797e2e8b570d153bc8690a7/Modules/mathmodule.c#L1423
class Adder {
constructor() {
this._partials = new Float64Array(32);
this._n = 0;
}
add(x) {
const p = this._partials;
let i = 0;
for (let j = 0; j < this._n && j < 32; j++) {
const y = p[j],
hi = x + y,
lo = Math.abs(x) < Math.abs(y) ? x - (hi - y) : y - (hi - x);
if (lo) p[i++] = lo;
x = hi;
}
p[i] = x;
this._n = i + 1;
return this;
}
valueOf() {
const p = this._partials;
let n = this._n,
x,
y,
lo,
hi = 0;
if (n > 0) {
hi = p[--n];
while (n > 0) {
x = hi;
y = p[--n];
hi = x + y;
lo = y - (hi - x);
if (lo) break;
}
if (n > 0 && (lo < 0 && p[n - 1] < 0 || lo > 0 && p[n - 1] > 0)) {
y = lo * 2;
x = hi + y;
if (y == x - hi) hi = x;
}
}
return hi;
}
}
function range$2(start, stop, step) {
start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;
var i = -1,
n = Math.max(0, Math.ceil((stop - start) / step)) | 0,
range = new Array(n);
while (++i < n) {
range[i] = start + i * step;
}
return range;
}
var epsilon = 1e-6;
var epsilon2 = 1e-12;
var pi = Math.PI;
var halfPi = pi / 2;
var quarterPi = pi / 4;
var tau = pi * 2;
var degrees$1 = 180 / pi;
var radians$1 = pi / 180;
var abs = Math.abs;
var atan2 = Math.atan2;
var cos = Math.cos;
var hypot = Math.hypot;
var sin = Math.sin;
var sqrt = Math.sqrt;
function asin(x) {
return x > 1 ? halfPi : x < -1 ? -halfPi : Math.asin(x);
}
function noop() {}
function streamGeometry(geometry, stream) {
if (geometry && streamGeometryType.hasOwnProperty(geometry.type)) {
streamGeometryType[geometry.type](geometry, stream);
}
}
var streamObjectType = {
Feature: function (object, stream) {
streamGeometry(object.geometry, stream);
},
FeatureCollection: function (object, stream) {
var features = object.features,
i = -1,
n = features.length;
while (++i < n) streamGeometry(features[i].geometry, stream);
}
};
var streamGeometryType = {
Sphere: function (object, stream) {
stream.sphere();
},
Point: function (object, stream) {
object = object.coordinates;
stream.point(object[0], object[1], object[2]);
},
MultiPoint: function (object, stream) {
var coordinates = object.coordinates,
i = -1,
n = coordinates.length;
while (++i < n) object = coordinates[i], stream.point(object[0], object[1], object[2]);
},
LineString: function (object, stream) {
streamLine(object.coordinates, stream, 0);
},
MultiLineString: function (object, stream) {
var coordinates = object.coordinates,
i = -1,
n = coordinates.length;
while (++i < n) streamLine(coordinates[i], stream, 0);
},
Polygon: function (object, stream) {
streamPolygon(object.coordinates, stream);
},
MultiPolygon: function (object, stream) {
var coordinates = object.coordinates,
i = -1,
n = coordinates.length;
while (++i < n) streamPolygon(coordinates[i], stream);
},
GeometryCollection: function (object, stream) {
var geometries = object.geometries,
i = -1,
n = geometries.length;
while (++i < n) streamGeometry(geometries[i], stream);
}
};
function streamLine(coordinates, stream, closed) {
var i = -1,
n = coordinates.length - closed,
coordinate;
stream.lineStart();
while (++i < n) coordinate = coordinates[i], stream.point(coordinate[0], coordinate[1], coordinate[2]);
stream.lineEnd();
}
function streamPolygon(coordinates, stream) {
var i = -1,
n = coordinates.length;
stream.polygonStart();
while (++i < n) streamLine(coordinates[i], stream, 1);
stream.polygonEnd();
}
function geoStream (object, stream) {
if (object && streamObjectType.hasOwnProperty(object.type)) {
streamObjectType[object.type](object, stream);
} else {
streamGeometry(object, stream);
}
}
var areaRingSum = new Adder();
// hello?
var areaSum = new Adder(),
lambda00$2,
phi00$2,
lambda0$1,
cosPhi0,
sinPhi0;
var areaStream = {
point: noop,
lineStart: noop,
lineEnd: noop,
polygonStart: function () {
areaRingSum = new Adder();
areaStream.lineStart = areaRingStart;
areaStream.lineEnd = areaRingEnd;
},
polygonEnd: function () {
var areaRing = +areaRingSum;
areaSum.add(areaRing < 0 ? tau + areaRing : areaRing);
this.lineStart = this.lineEnd = this.point = noop;
},
sphere: function () {
areaSum.add(tau);
}
};
function areaRingStart() {
areaStream.point = areaPointFirst;
}
function areaRingEnd() {
areaPoint(lambda00$2, phi00$2);
}
function areaPointFirst(lambda, phi) {
areaStream.point = areaPoint;
lambda00$2 = lambda, phi00$2 = phi;
lambda *= radians$1, phi *= radians$1;
lambda0$1 = lambda, cosPhi0 = cos(phi = phi / 2 + quarterPi), sinPhi0 = sin(phi);
}
function areaPoint(lambda, phi) {
lambda *= radians$1, phi *= radians$1;
phi = phi / 2 + quarterPi; // half the angular distance from south pole
// Spherical excess E for a spherical triangle with vertices: south pole,
// previous point, current point. Uses a formula derived from Cagnoli’s
// theorem. See Todhunter, Spherical Trig. (1871), Sec. 103, Eq. (2).
var dLambda = lambda - lambda0$1,
sdLambda = dLambda >= 0 ? 1 : -1,
adLambda = sdLambda * dLambda,
cosPhi = cos(phi),
sinPhi = sin(phi),
k = sinPhi0 * sinPhi,
u = cosPhi0 * cosPhi + k * cos(adLambda),
v = k * sdLambda * sin(adLambda);
areaRingSum.add(atan2(v, u));
// Advance the previous points.
lambda0$1 = lambda, cosPhi0 = cosPhi, sinPhi0 = sinPhi;
}
function area (object) {
areaSum = new Adder();
geoStream(object, areaStream);
return areaSum * 2;
}
function spherical(cartesian) {
return [atan2(cartesian[1], cartesian[0]), asin(cartesian[2])];
}
function cartesian(spherical) {
var lambda = spherical[0],
phi = spherical[1],
cosPhi = cos(phi);
return [cosPhi * cos(lambda), cosPhi * sin(lambda), sin(phi)];
}
function cartesianCross(a, b) {
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
}
// TODO return d
function cartesianNormalizeInPlace(d) {
var l = sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
d[0] /= l, d[1] /= l, d[2] /= l;
}
var lambda0, phi0, lambda1, phi1,
// bounds
lambda2,
// previous lambda-coordinate
lambda00$1, phi00$1,
// first point
p0,
// previous 3D point
deltaSum, ranges, range$1;
var boundsStream = {
point: boundsPoint,
lineStart: boundsLineStart,
lineEnd: boundsLineEnd,
polygonStart: function () {
boundsStream.point = boundsRingPoint;
boundsStream.lineStart = boundsRingStart;
boundsStream.lineEnd = boundsRingEnd;
deltaSum = new Adder();
areaStream.polygonStart();
},
polygonEnd: function () {
areaStream.polygonEnd();
boundsStream.point = boundsPoint;
boundsStream.lineStart = boundsLineStart;
boundsStream.lineEnd = boundsLineEnd;
if (areaRingSum < 0) lambda0 = -(lambda1 = 180), phi0 = -(phi1 = 90);else if (deltaSum > epsilon) phi1 = 90;else if (deltaSum < -1e-6) phi0 = -90;
range$1[0] = lambda0, range$1[1] = lambda1;
},
sphere: function () {
lambda0 = -(lambda1 = 180), phi0 = -(phi1 = 90);
}
};
function boundsPoint(lambda, phi) {
ranges.push(range$1 = [lambda0 = lambda, lambda1 = lambda]);
if (phi < phi0) phi0 = phi;
if (phi > phi1) phi1 = phi;
}
function linePoint(lambda, phi) {
var p = cartesian([lambda * radians$1, phi * radians$1]);
if (p0) {
var normal = cartesianCross(p0, p),
equatorial = [normal[1], -normal[0], 0],
inflection = cartesianCross(equatorial, normal);
cartesianNormalizeInPlace(inflection);
inflection = spherical(inflection);
var delta = lambda - lambda2,
sign = delta > 0 ? 1 : -1,
lambdai = inflection[0] * degrees$1 * sign,
phii,
antimeridian = abs(delta) > 180;
if (antimeridian ^ (sign * lambda2 < lambdai && lambdai < sign * lambda)) {
phii = inflection[1] * degrees$1;
if (phii > phi1) phi1 = phii;
} else if (lambdai = (lambdai + 360) % 360 - 180, antimeridian ^ (sign * lambda2 < lambdai && lambdai < sign * lambda)) {
phii = -inflection[1] * degrees$1;
if (phii < phi0) phi0 = phii;
} else {
if (phi < phi0) phi0 = phi;
if (phi > phi1) phi1 = phi;
}
if (antimeridian) {
if (lambda < lambda2) {
if (angle(lambda0, lambda) > angle(lambda0, lambda1)) lambda1 = lambda;
} else {
if (angle(lambda, lambda1) > angle(lambda0, lambda1)) lambda0 = lambda;
}
} else {
if (lambda1 >= lambda0) {
if (lambda < lambda0) lambda0 = lambda;
if (lambda > lambda1) lambda1 = lambda;
} else {
if (lambda > lambda2) {
if (angle(lambda0, lambda) > angle(lambda0, lambda1)) lambda1 = lambda;
} else {
if (angle(lambda, lambda1) > angle(lambda0, lambda1)) lambda0 = lambda;
}
}
}
} else {
ranges.push(range$1 = [lambda0 = lambda, lambda1 = lambda]);
}
if (phi < phi0) phi0 = phi;
if (phi > phi1) phi1 = phi;
p0 = p, lambda2 = lambda;
}
function boundsLineStart() {
boundsStream.point = linePoint;
}
function boundsLineEnd() {
range$1[0] = lambda0, range$1[1] = lambda1;
boundsStream.point = boundsPoint;
p0 = null;
}
function boundsRingPoint(lambda, phi) {
if (p0) {
var delta = lambda - lambda2;
deltaSum.add(abs(delta) > 180 ? delta + (delta > 0 ? 360 : -360) : delta);
} else {
lambda00$1 = lambda, phi00$1 = phi;
}
areaStream.point(lambda, phi);
linePoint(lambda, phi);
}
function boundsRingStart() {
areaStream.lineStart();
}
function boundsRingEnd() {
boundsRingPoint(lambda00$1, phi00$1);
areaStream.lineEnd();
if (abs(deltaSum) > epsilon) lambda0 = -(lambda1 = 180);
range$1[0] = lambda0, range$1[1] = lambda1;
p0 = null;
}
// Finds the left-right distance between two longitudes.
// This is almost the same as (lambda1 - lambda0 + 360°) % 360°, except that we want
// the distance between ±180° to be 360°.
function angle(lambda0, lambda1) {
return (lambda1 -= lambda0) < 0 ? lambda1 + 360 : lambda1;
}
function rangeCompare(a, b) {
return a[0] - b[0];
}
function rangeContains(range, x) {
return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
}
function bounds (feature) {
var i, n, a, b, merged, deltaMax, delta;
phi1 = lambda1 = -(lambda0 = phi0 = Infinity);
ranges = [];
geoStream(feature, boundsStream);
// First, sort ranges by their minimum longitudes.
if (n = ranges.length) {
ranges.sort(rangeCompare);
// Then, merge any ranges that overlap.
for (i = 1, a = ranges[0], merged = [a]; i < n; ++i) {
b = ranges[i];
if (rangeContains(a, b[0]) || rangeContains(a, b[1])) {
if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
} else {
merged.push(a = b);
}
}
// Finally, find the largest gap between the merged ranges.
// The final bounding box will be the inverse of this gap.
for (deltaMax = -Infinity, n = merged.length - 1, i = 0, a = merged[n]; i <= n; a = b, ++i) {
b = merged[i];
if ((delta = angle(a[1], b[0])) > deltaMax) deltaMax = delta, lambda0 = b[0], lambda1 = a[1];
}
}
ranges = range$1 = null;
return lambda0 === Infinity || phi0 === Infinity ? [[NaN, NaN], [NaN, NaN]] : [[lambda0, phi0], [lambda1, phi1]];
}
var W0, W1, X0, Y0, Z0, X1, Y1, Z1, X2, Y2, Z2, lambda00, phi00,
// first point
x0, y0, z0; // previous point
var centroidStream = {
sphere: noop,
point: centroidPoint,
lineStart: centroidLineStart,
lineEnd: centroidLineEnd,
polygonStart: function () {
centroidStream.lineStart = centroidRingStart;
centroidStream.lineEnd = centroidRingEnd;
},
polygonEnd: function () {
centroidStream.lineStart = centroidLineStart;
centroidStream.lineEnd = centroidLineEnd;
}
};
// Arithmetic mean of Cartesian vectors.
function centroidPoint(lambda, phi) {
lambda *= radians$1, phi *= radians$1;
var cosPhi = cos(phi);
centroidPointCartesian(cosPhi * cos(lambda), cosPhi * sin(lambda), sin(phi));
}
function centroidPointCartesian(x, y, z) {
++W0;
X0 += (x - X0) / W0;
Y0 += (y - Y0) / W0;
Z0 += (z - Z0) / W0;
}
function centroidLineStart() {
centroidStream.point = centroidLinePointFirst;
}
function centroidLinePointFirst(lambda, phi) {
lambda *= radians$1, phi *= radians$1;
var cosPhi = cos(phi);
x0 = cosPhi * cos(lambda);
y0 = cosPhi * sin(lambda);
z0 = sin(phi);
centroidStream.point = centroidLinePoint;
centroidPointCartesian(x0, y0, z0);
}
function centroidLinePoint(lambda, phi) {
lambda *= radians$1, phi *= radians$1;
var cosPhi = cos(phi),
x = cosPhi * cos(lambda),
y = cosPhi * sin(lambda),
z = sin(phi),
w = atan2(sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
W1 += w;
X1 += w * (x0 + (x0 = x));
Y1 += w * (y0 + (y0 = y));
Z1 += w * (z0 + (z0 = z));
centroidPointCartesian(x0, y0, z0);
}
function centroidLineEnd() {
centroidStream.point = centroidPoint;
}
// See J. E. Brock, The Inertia Tensor for a Spherical Triangle,
// J. Applied Mechanics 42, 239 (1975).
function centroidRingStart() {
centroidStream.point = centroidRingPointFirst;
}
function centroidRingEnd() {
centroidRingPoint(lambda00, phi00);
centroidStream.point = centroidPoint;
}
function centroidRingPointFirst(lambda, phi) {
lambda00 = lambda, phi00 = phi;
lambda *= radians$1, phi *= radians$1;
centroidStream.point = centroidRingPoint;
var cosPhi = cos(phi);
x0 = cosPhi * cos(lambda);
y0 = cosPhi * sin(lambda);
z0 = sin(phi);
centroidPointCartesian(x0, y0, z0);
}
function centroidRingPoint(lambda, phi) {
lambda *= radians$1, phi *= radians$1;
var cosPhi = cos(phi),
x = cosPhi * cos(lambda),
y = cosPhi * sin(lambda),
z = sin(phi),
cx = y0 * z - z0 * y,
cy = z0 * x - x0 * z,
cz = x0 * y - y0 * x,
m = hypot(cx, cy, cz),
w = asin(m),
// line weight = angle
v = m && -w / m; // area weight multiplier
X2.add(v * cx);
Y2.add(v * cy);
Z2.add(v * cz);
W1 += w;
X1 += w * (x0 + (x0 = x));
Y1 += w * (y0 + (y0 = y));
Z1 += w * (z0 + (z0 = z));
centroidPointCartesian(x0, y0, z0);
}
function centroid (object) {
W0 = W1 = X0 = Y0 = Z0 = X1 = Y1 = Z1 = 0;
X2 = new Adder();
Y2 = new Adder();
Z2 = new Adder();
geoStream(object, centroidStream);
var x = +X2,
y = +Y2,
z = +Z2,
m = hypot(x, y, z);
// If the area-weighted ccentroid is undefined, fall back to length-weighted ccentroid.
if (m < epsilon2) {
x = X1, y = Y1, z = Z1;
// If the feature has zero length, fall back to arithmetic mean of point vectors.
if (W1 < epsilon) x = X0, y = Y0, z = Z0;
m = hypot(x, y, z);
// If the feature still has an undefined ccentroid, then return.
if (m < epsilon2) return [NaN, NaN];
}
return [atan2(y, x) * degrees$1, asin(z / m) * degrees$1];
}
function geoMethod(methodName, globalMethod) {
return function (projection, geojson, group) {
if (projection) {
// projection defined, use it
const p = getScale(projection, (group || this).context);
return p && p.path[methodName](geojson);
} else {
// projection undefined, use global method
return globalMethod(geojson);
}
};
}
const geoArea = geoMethod('area', area);
const geoBounds = geoMethod('bounds', bounds);
const geoCentroid = geoMethod('centroid', centroid);
function geoScale(projection, group) {
const p = getScale(projection, (group || this).context);
return p && p.scale();
}
function inScope (item) {
const group = this.context.group;
let value = false;
if (group) while (item) {
if (item === group) {
value = true;
break;
}
item = item.mark.group;
}
return value;
}
function log(df, method, args) {
try {
df[method].apply(df, ['EXPRESSION'].concat([].slice.call(args)));
} catch (err) {
df.warn(err);
}
return args[args.length - 1];
}
function warn() {
return log(this.context.dataflow, 'warn', arguments);
}
function info() {
return log(this.context.dataflow, 'info', arguments);
}
function debug() {
return log(this.context.dataflow, 'debug', arguments);
}
function define (constructor, factory, prototype) {
constructor.prototype = factory.prototype = prototype;
prototype.constructor = constructor;
}
function extend(parent, definition) {
var prototype = Object.create(parent.prototype);
for (var key in definition) prototype[key] = definition[key];
return prototype;
}
function Color() {}
var darker = 0.7;
var brighter = 1 / darker;
var reI = "\\s*([+-]?\\d+)\\s*",
reN = "\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)\\s*",
reP = "\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)%\\s*",
reHex = /^#([0-9a-f]{3,8})$/,
reRgbInteger = new RegExp(`^rgb\\(${reI},${reI},${reI}\\)$`),
reRgbPercent = new RegExp(`^rgb\\(${reP},${reP},${reP}\\)$`),
reRgbaInteger = new RegExp(`^rgba\\(${reI},${reI},${reI},${reN}\\)$`),
reRgbaPercent = new RegExp(`^rgba\\(${reP},${reP},${reP},${reN}\\)$`),
reHslPercent = new RegExp(`^hsl\\(${reN},${reP},${reP}\\)$`),
reHslaPercent = new RegExp(`^hsla\\(${reN},${reP},${reP},${reN}\\)$`);
var named = {
aliceblue: 0xf0f8ff,
antiquewhite: 0xfaebd7,
aqua: 0x00ffff,
aquamarine: 0x7fffd4,
azure: 0xf0ffff,
beige: 0xf5f5dc,
bisque: 0xffe4c4,
black: 0x000000,
blanchedalmond: 0xffebcd,
blue: 0x0000ff,
blueviolet: 0x8a2be2,
brown: 0xa52a2a,
burlywood: 0xdeb887,
cadetblue: 0x5f9ea0,
chartreuse: 0x7fff00,
chocolate: 0xd2691e,
coral: 0xff7f50,
cornflowerblue: 0x6495ed,
cornsilk: 0xfff8dc,
crimson: 0xdc143c,
cyan: 0x00ffff,
darkblue: 0x00008b,
darkcyan: 0x008b8b,
darkgoldenrod: 0xb8860b,
darkgray: 0xa9a9a9,
darkgreen: 0x006400,
darkgrey: 0xa9a9a9,
darkkhaki: 0xbdb76b,
darkmagenta: 0x8b008b,
darkolivegreen: 0x556b2f,
darkorange: 0xff8c00,
darkorchid: 0x9932cc,
darkred: 0x8b0000,
darksalmon: 0xe9967a,
darkseagreen: 0x8fbc8f,
darkslateblue: 0x483d8b,
darkslategray: 0x2f4f4f,
darkslategrey: 0x2f4f4f,
darkturquoise: 0x00ced1,
darkviolet: 0x9400d3,
deeppink: 0xff1493,
deepskyblue: 0x00bfff,
dimgray: 0x696969,
dimgrey: 0x696969,
dodgerblue: 0x1e90ff,
firebrick: 0xb22222,
floralwhite: 0xfffaf0,
forestgreen: 0x228b22,
fuchsia: 0xff00ff,
gainsboro: 0xdcdcdc,
ghostwhite: 0xf8f8ff,
gold: 0xffd700,
goldenrod: 0xdaa520,
gray: 0x808080,
green: 0x008000,
greenyellow: 0xadff2f,
grey: 0x808080,
honeydew: 0xf0fff0,
hotpink: 0xff69b4,
indianred: 0xcd5c5c,
indigo: 0x4b0082,
ivory: 0xfffff0,
khaki: 0xf0e68c,
lavender: 0xe6e6fa,
lavenderblush: 0xfff0f5,
lawngreen: 0x7cfc00,
lemonchiffon: 0xfffacd,
lightblue: 0xadd8e6,
lightcoral: 0xf08080,
lightcyan: 0xe0ffff,
lightgoldenrodyellow: 0xfafad2,
lightgray: 0xd3d3d3,
lightgreen: 0x90ee90,
lightgrey: 0xd3d3d3,
lightpink: 0xffb6c1,
lightsalmon: 0xffa07a,
lightseagreen: 0x20b2aa,
lightskyblue: 0x87cefa,
lightslategray: 0x778899,
lightslategrey: 0x778899,
lightsteelblue: 0xb0c4de,
lightyellow: 0xffffe0,
lime: 0x00ff00,
limegreen: 0x32cd32,
linen: 0xfaf0e6,
magenta: 0xff00ff,
maroon: 0x800000,
mediumaquamarine: 0x66cdaa,
mediumblue: 0x0000cd,
mediumorchid: 0xba55d3,
mediumpurple: 0x9370db,
mediumseagreen: 0x3cb371,
mediumslateblue: 0x7b68ee,
mediumspringgreen: 0x00fa9a,
mediumturquoise: 0x48d1cc,
mediumvioletred: 0xc71585,
midnightblue: 0x191970,
mintcream: 0xf5fffa,
mistyrose: 0xffe4e1,
moccasin: 0xffe4b5,
navajowhite: 0xffdead,
navy: 0x000080,
oldlace: 0xfdf5e6,
olive: 0x808000,
olivedrab: 0x6b8e23,
orange: 0xffa500,
orangered: 0xff4500,
orchid: 0xda70d6,
palegoldenrod: 0xeee8aa,
palegreen: 0x98fb98,
paleturquoise: 0xafeeee,
palevioletred: 0xdb7093,
papayawhip: 0xffefd5,
peachpuff: 0xffdab9,
peru: 0xcd853f,
pink: 0xffc0cb,
plum: 0xdda0dd,
powderblue: 0xb0e0e6,
purple: 0x800080,
rebeccapurple: 0x663399,
red: 0xff0000,
rosybrown: 0xbc8f8f,
royalblue: 0x4169e1,
saddlebrown: 0x8b4513,
salmon: 0xfa8072,
sandybrown: 0xf4a460,
seagreen: 0x2e8b57,
seashell: 0xfff5ee,
sienna: 0xa0522d,
silver: 0xc0c0c0,
skyblue: 0x87ceeb,
slateblue: 0x6a5acd,
slategray: 0x708090,
slategrey: 0x708090,
snow: 0xfffafa,
springgreen: 0x00ff7f,
steelblue: 0x4682b4,
tan: 0xd2b48c,
teal: 0x008080,
thistle: 0xd8bfd8,
tomato: 0xff6347,
turquoise: 0x40e0d0,
violet: 0xee82ee,
wheat: 0xf5deb3,
white: 0xffffff,
whitesmoke: 0xf5f5f5,
yellow: 0xffff00,
yellowgreen: 0x9acd32
};
define(Color, color, {
copy(channels) {
return Object.assign(new this.constructor(), this, channels);
},
displayable() {
return this.rgb().displayable();
},
hex: color_formatHex,
// Deprecated! Use color.formatHex.
formatHex: color_formatHex,
formatHex8: color_formatHex8,
formatHsl: color_formatHsl,
formatRgb: color_formatRgb,
toString: color_formatRgb
});
function color_formatHex() {
return this.rgb().formatHex();
}
function color_formatHex8() {
return this.rgb().formatHex8();
}
function color_formatHsl() {
return hslConvert(this).formatHsl();
}
function color_formatRgb() {
return this.rgb().formatRgb();
}
function color(format) {
var m, l;
format = (format + "").trim().toLowerCase();
return (m = reHex.exec(format)) ? (l = m[1].length, m = parseInt(m[1], 16), l === 6 ? rgbn(m) // #ff0000
: l === 3 ? new Rgb(m >> 8 & 0xf | m >> 4 & 0xf0, m >> 4 & 0xf | m & 0xf0, (m & 0xf) << 4 | m & 0xf, 1) // #f00
: l === 8 ? rgba(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000
: l === 4 ? rgba(m >> 12 & 0xf | m >> 8 & 0xf0, m >> 8 & 0xf | m >> 4 & 0xf0, m >> 4 & 0xf | m & 0xf0, ((m & 0xf) << 4 | m & 0xf) / 0xff) // #f000
: null // invalid hex
) : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0)
: (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%)
: (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1)
: (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1)
: (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%)
: (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1)
: named.hasOwnProperty(format) ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins
: format === "transparent" ? new Rgb(NaN, NaN, NaN, 0) : null;
}
function rgbn(n) {
return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1);
}
function rgba(r, g, b, a) {
if (a <= 0) r = g = b = NaN;
return new Rgb(r, g, b, a);
}
function rgbConvert(o) {
if (!(o instanceof Color)) o = color(o);
if (!o) return new Rgb();
o = o.rgb();
return new Rgb(o.r, o.g, o.b, o.opacity);
}
function rgb(r, g, b, opacity) {
return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity);
}
function Rgb(r, g, b, opacity) {
this.r = +r;
this.g = +g;
this.b = +b;
this.opacity = +opacity;
}
define(Rgb, rgb, extend(Color, {
brighter(k) {
k = k == null ? brighter : Math.pow(brighter, k);
return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
},
darker(k) {
k = k == null ? darker : Math.pow(darker, k);
return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
},
rgb() {
return this;
},
clamp() {
return new Rgb(clampi(this.r), clampi(this.g), clampi(this.b), clampa(this.opacity));
},
displayable() {
return -0.5 <= this.r && this.r < 255.5 && -0.5 <= this.g && this.g < 255.5 && -0.5 <= this.b && this.b < 255.5 && 0 <= this.opacity && this.opacity <= 1;
},
hex: rgb_formatHex,
// Deprecated! Use color.formatHex.
formatHex: rgb_formatHex,
formatHex8: rgb_formatHex8,
formatRgb: rgb_formatRgb,
toString: rgb_formatRgb
}));
function rgb_formatHex() {
return `#${hex(this.r)}${hex(this.g)}${hex(this.b)}`;
}
function rgb_formatHex8() {
return `#${hex(this.r)}${hex(this.g)}${hex(this.b)}${hex((isNaN(this.opacity) ? 1 : this.opacity) * 255)}`;
}
function rgb_formatRgb() {
const a = clampa(this.opacity);
return `${a === 1 ? "rgb(" : "rgba("}${clampi(this.r)}, ${clampi(this.g)}, ${clampi(this.b)}${a === 1 ? ")" : `, ${a})`}`;
}
function clampa(opacity) {
return isNaN(opacity) ? 1 : Math.max(0, Math.min(1, opacity));
}
function clampi(value) {
return Math.max(0, Math.min(255, Math.round(value) || 0));
}
function hex(value) {
value = clampi(value);
return (value < 16 ? "0" : "") + value.toString(16);
}
function hsla(h, s, l, a) {
if (a <= 0) h = s = l = NaN;else if (l <= 0 || l >= 1) h = s = NaN;else if (s <= 0) h = NaN;
return new Hsl(h, s, l, a);
}
function hslConvert(o) {
if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity);
if (!(o instanceof Color)) o = color(o);
if (!o) return new Hsl();
if (o instanceof Hsl) return o;
o = o.rgb();
var r = o.r / 255,
g = o.g / 255,
b = o.b / 255,
min = Math.min(r, g, b),
max = Math.max(r, g, b),
h = NaN,
s = max - min,
l = (max + min) / 2;
if (s) {
if (r === max) h = (g - b) / s + (g < b) * 6;else if (g === max) h = (b - r) / s + 2;else h = (r - g) / s + 4;
s /= l < 0.5 ? max + min : 2 - max - min;
h *= 60;
} else {
s = l > 0 && l < 1 ? 0 : h;
}
return new Hsl(h, s, l, o.opacity);
}
function hsl(h, s, l, opacity) {
return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity);
}
function Hsl(h, s, l, opacity) {
this.h = +h;
this.s = +s;
this.l = +l;
this.opacity = +opacity;
}
define(Hsl, hsl, extend(Color, {
brighter(k) {
k = k == null ? brighter : Math.pow(brighter, k);
return new Hsl(this.h, this.s, this.l * k, this.opacity);
},
darker(k) {
k = k == null ? darker : Math.pow(darker, k);
return new Hsl(this.h, this.s, this.l * k, this.opacity);
},
rgb() {
var h = this.h % 360 + (this.h < 0) * 360,
s = isNaN(h) || isNaN(this.s) ? 0 : this.s,
l = this.l,
m2 = l + (l < 0.5 ? l : 1 - l) * s,
m1 = 2 * l - m2;
return new Rgb(hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2), hsl2rgb(h, m1, m2), hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2), this.opacity);
},
clamp() {
return new Hsl(clamph(this.h), clampt(this.s), clampt(this.l), clampa(this.opacity));
},
displayable() {
return (0 <= this.s && this.s <= 1 || isNaN(this.s)) && 0 <= this.l && this.l <= 1 && 0 <= this.opacity && this.opacity <= 1;
},
formatHsl() {
const a = clampa(this.opacity);
return `${a === 1 ? "hsl(" : "hsla("}${clamph(this.h)}, ${clampt(this.s) * 100}%, ${clampt(this.l) * 100}%${a === 1 ? ")" : `, ${a})`}`;
}
}));
function clamph(value) {
value = (value || 0) % 360;
return value < 0 ? value + 360 : value;
}
function clampt(value) {
return Math.max(0, Math.min(1, value || 0));
}
/* From FvD 13.37, CSS Color Module Level 3 */
function hsl2rgb(h, m1, m2) {
return (h < 60 ? m1 + (m2 - m1) * h / 60 : h < 180 ? m2 : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60 : m1) * 255;
}
const radians = Math.PI / 180;
const degrees = 180 / Math.PI;
// https://observablehq.com/@mbostock/lab-and-rgb
const K = 18,
Xn = 0.96422,
Yn = 1,
Zn = 0.82521,
t0 = 4 / 29,
t1 = 6 / 29,
t2 = 3 * t1 * t1,
t3 = t1 * t1 * t1;
function labConvert(o) {
if (o instanceof Lab) return new Lab(o.l, o.a, o.b, o.opacity);
if (o instanceof Hcl) return hcl2lab(o);
if (!(o instanceof Rgb)) o = rgbConvert(o);
var r = rgb2lrgb(o.r),
g = rgb2lrgb(o.g),
b = rgb2lrgb(o.b),
y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn),
x,
z;
if (r === g && g === b) x = z = y;else {
x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn);
z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn);
}
return new Lab(116 * y - 16, 500 * (x - y), 200 * (y - z), o.opacity);
}
function lab(l, a, b, opacity) {
return arguments.length === 1 ? labConvert(l) : new Lab(l, a, b, opacity == null ? 1 : opacity);
}
function Lab(l, a, b, opacity) {
this.l = +l;
this.a = +a;
this.b = +b;
this.opacity = +opacity;
}
define(Lab, lab, extend(Color, {
brighter(k) {
return new Lab(this.l + K * (k == null ? 1 : k), this.a, this.b, this.opacity);
},
darker(k) {
return new Lab(this.l - K * (k == null ? 1 : k), this.a, this.b, this.opacity);
},
rgb() {
var y = (this.l + 16) / 116,
x = isNaN(this.a) ? y : y + this.a / 500,
z = isNaN(this.b) ? y : y - this.b / 200;
x = Xn * lab2xyz(x);
y = Yn * lab2xyz(y);
z = Zn * lab2xyz(z);
return new Rgb(lrgb2rgb(3.1338561 * x - 1.6168667 * y - 0.4906146 * z), lrgb2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z), lrgb2rgb(0.0719453 * x - 0.2289914 * y + 1.4052427 * z), this.opacity);
}
}));
function xyz2lab(t) {
return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0;
}
function lab2xyz(t) {
return t > t1 ? t * t * t : t2 * (t - t0);
}
function lrgb2rgb(x) {
return 255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055);
}
function rgb2lrgb(x) {
return (x /= 255) <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
}
function hclConvert(o) {
if (o instanceof Hcl) return new Hcl(o.h, o.c, o.l, o.opacity);
if (!(o instanceof Lab)) o = labConvert(o);
if (o.a === 0 && o.b === 0) return new Hcl(NaN, 0 < o.l && o.l < 100 ? 0 : NaN, o.l, o.opacity);
var h = Math.atan2(o.b, o.a) * degrees;
return new Hcl(h < 0 ? h + 360 : h, Math.sqrt(o.a * o.a + o.b * o.b), o.l, o.opacity);
}
function hcl(h, c, l, opacity) {
return arguments.length === 1 ? hclConvert(h) : new Hcl(h, c, l, opacity == null ? 1 : opacity);
}
function Hcl(h, c, l, opacity) {
this.h = +h;
this.c = +c;
this.l = +l;
this.opacity = +opacity;
}
function hcl2lab(o) {
if (isNaN(o.h)) return new Lab(o.l, 0, 0, o.opacity);
var h = o.h * radians;
return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity);
}
define(Hcl, hcl, extend(Color, {
brighter(k) {
return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity);
},
darker(k) {
return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity);
},
rgb() {
return hcl2lab(this).rgb();
}
}));
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
function channel_luminance_value(channelValue) {
const val = channelValue / 255;
if (val <= 0.03928) {
return val / 12.92;
}
return Math.pow((val + 0.055) / 1.055, 2.4);
}
function luminance(color) {
const c = rgb(color),
r = channel_luminance_value(c.r),
g = channel_luminance_value(c.g),
b = channel_luminance_value(c.b);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
function contrast(color1, color2) {
const lum1 = luminance(color1),
lum2 = luminance(color2),
lumL = Math.max(lum1, lum2),
lumD = Math.min(lum1, lum2);
return (lumL + 0.05) / (lumD + 0.05);
}
function merge () {
const args = [].slice.call(arguments);
args.unshift({});
return vegaUtil.extend(...args);
}
function equal(a, b) {
return a === b || a !== a && b !== b ? true : vegaUtil.isArray(a) ? vegaUtil.isArray(b) && a.length === b.length ? equalArray(a, b) : false : vegaUtil.isObject(a) && vegaUtil.isObject(b) ? equalObject(a, b) : false;
}
function equalArray(a, b) {
for (let i = 0, n = a.length; i < n; ++i) {
if (!equal(a[i], b[i])) return false;
}
return true;
}
function equalObject(a, b) {
for (const key in a) {
if (!equal(a[key], b[key])) return false;
}
return true;
}
function removePredicate(props) {
return _ => equalObject(props, _);
}
function modify (name, insert, remove, toggle, modify, values) {
const df = this.context.dataflow,
data = this.context.data[name],
input = data.input,
stamp = df.stamp();
let changes = data.changes,
predicate,
key;
if (df._trigger === false || !(input.value.length || insert || toggle)) {
// nothing to do!
return 0;
}
if (!changes || changes.stamp < stamp) {
data.changes = changes = df.changeset();
changes.stamp = stamp;
df.runAfter(() => {
data.modified = true;
df.pulse(input, changes).run();
}, true, 1);
}
if (remove) {
predicate = remove === true ? vegaUtil.truthy : vegaUtil.isArray(remove) || vegaDataflow.isTuple(remove) ? remove : removePredicate(remove);
changes.remove(predicate);
}
if (insert) {
changes.insert(insert);
}
if (toggle) {
predicate = removePredicate(toggle);
if (input.value.some(predicate)) {
changes.remove(predicate);
} else {
changes.insert(toggle);
}
}
if (modify) {
for (key in values) {
changes.modify(modify, key, values[key]);
}
}
return 1;
}
function pinchDistance(event) {
const t = event.touches,
dx = t[0].clientX - t[1].clientX,
dy = t[0].clientY - t[1].clientY;
return Math.hypot(dx, dy);
}
function pinchAngle(event) {
const t = event.touches;
return Math.atan2(t[0].clientY - t[1].clientY, t[0].clientX - t[1].clientX);
}
// memoize accessor functions
const accessors = {};
function pluck (data, name) {
const accessor = accessors[name] || (accessors[name] = vegaUtil.field(name));
return vegaUtil.isArray(data) ? data.map(accessor) : accessor(data);
}
function array(seq) {
return vegaUtil.isArray(seq) || ArrayBuffer.isView(seq) ? seq : null;
}
function sequence(seq) {
return array(seq) || (vegaUtil.isString(seq) ? seq : null);
}
function join(seq, ...args) {
return array(seq).join(...args);
}
function indexof(seq, ...args) {
return sequence(seq).indexOf(...args);
}
function lastindexof(seq, ...args) {
return sequence(seq).lastIndexOf(...args);
}
function slice(seq, ...args) {
return sequence(seq).slice(...args);
}
function replace(str, pattern, repl) {
if (vegaUtil.isFunction(repl)) vegaUtil.error('Function argument passed to replace.');
if (!vegaUtil.isString(pattern) && !vegaUtil.isRegExp(pattern)) vegaUtil.error('Please pass a string or RegExp argument to replace.');
return String(str).replace(pattern, repl);
}
function reverse(seq) {
return array(seq).slice().reverse();
}
function sort(seq) {
return array(seq).slice().sort(vegaUtil.ascending);
}
function bandspace(count, paddingInner, paddingOuter) {
return vegaScale.bandSpace(count || 0, paddingInner || 0, paddingOuter || 0);
}
function bandwidth(name, group) {
const s = getScale(name, (group || this).context);
return s && s.bandwidth ? s.bandwidth() : 0;
}
function copy(name, group) {
const s = getScale(name, (group || this).context);
return s ? s.copy() : undefined;
}
function domain(name, group) {
const s = getScale(name, (group || this).context);
return s ? s.domain() : [];
}
function invert(name, range, group) {
const s = getScale(name, (group || this).context);
return !s ? undefined : vegaUtil.isArray(range) ? (s.invertRange || s.invert)(range) : (s.invert || s.invertExtent)(range);
}
function range(name, group) {
const s = getScale(name, (group || this).context);
return s && s.range ? s.range() : [];
}
function scale(name, value, group) {
const s = getScale(name, (group || this).context);
return s ? s(value) : undefined;
}
function scaleGradient (scale, p0, p1, count, group) {
scale = getScale(scale, (group || this).context);
const gradient = vegaScenegraph.Gradient(p0, p1);
let stops = scale.domain(),
min = stops[0],
max = vegaUtil.peek(stops),
fraction = vegaUtil.identity;
if (!(max - min)) {
// expand scale if domain has zero span, fix #1479
scale = (scale.interpolator ? vegaScale.scale('sequential')().interpolator(scale.interpolator()) : vegaScale.scale('linear')().interpolate(scale.interpolate()).range(scale.range())).domain([min = 0, max = 1]);
} else {
fraction = vegaScale.scaleFraction(scale, min, max);
}
if (scale.ticks) {
stops = scale.ticks(+count || 15);
if (min !== stops[0]) stops.unshift(min);
if (max !== vegaUtil.peek(stops)) stops.push(max);
}
stops.forEach(_ => gradient.stop(fraction(_), scale(_)));
return gradient;
}
function geoShape(projection, geojson, group) {
const p = getScale(projection, (group || this).context);
return function (context) {
return p ? p.path.context(context)(geojson) : '';
};
}
function pathShape(path) {
let p = null;
return function (context) {
return context ? vegaScenegraph.pathRender(context, p = p || vegaScenegraph.pathParse(path)) : path;
};
}
const datum = d => d.data;
function treeNodes(name, context) {
const tree = data.call(context, name);
return tree.root && tree.root.lookup || {};
}
function treePath(name, source, target) {
const nodes = treeNodes(name, this),
s = nodes[source],
t = nodes[target];
return s && t ? s.path(t).map(datum) : undefined;
}
function treeAncestors(name, node) {
const n = treeNodes(name, this)[node];
return n ? n.ancestors().map(datum) : undefined;
}
const _window = () => typeof window !== 'undefined' && window || null;
function screen() {
const w = _window();
return w ? w.screen : {};
}
function windowSize() {
const w = _window();
return w ? [w.innerWidth, w.innerHeight] : [undefined, undefined];
}
function containerSize() {
const view = this.context.dataflow,
el = view.container && view.container();
return el ? [el.clientWidth, el.clientHeight] : [undefined, undefined];
}
function intersect (b, opt, group) {
if (!b) return [];
const [u, v] = b,
box = new vegaScenegraph.Bounds().set(u[0], u[1], v[0], v[1]),
scene = group || this.context.dataflow.scenegraph().root;
return vegaScenegraph.intersect(scene, box, filter(opt));
}
function filter(opt) {
let p = null;
if (opt) {
const types = vegaUtil.array(opt.marktype),
names = vegaUtil.array(opt.markname);
p = _ => (!types.length || types.some(t => _.marktype === t)) && (!names.length || names.some(s => _.name === s));
}
return p;
}
/**
* Appends a new point to the lasso
*
* @param {*} lasso the lasso in pixel space
* @param {*} x the x coordinate in pixel space
* @param {*} y the y coordinate in pixel space
* @param {*} minDist the minimum distance, in pixels, that thenew point needs to be apart from the last point
* @returns a new array containing the lasso with the new point
*/
function lassoAppend(lasso, x, y, minDist = 5) {
lasso = vegaUtil.array(lasso);
const last = lasso[lasso.length - 1];
// Add point to lasso if its the first point or distance to last point exceed minDist
return last === undefined || Math.hypot(last[0] - x, last[1] - y) > minDist ? [...lasso, [x, y]] : lasso;
}
/**
* Generates a svg path command which draws a lasso
*
* @param {*} lasso the lasso in pixel space in the form [[x,y], [x,y], ...]
* @returns the svg path command that draws the lasso
*/
function lassoPath(lasso) {
return vegaUtil.array(lasso).reduce((svg, [x, y], i) => {
return svg += i == 0 ? `M ${x},${y} ` : i === lasso.length - 1 ? ' Z' : `L ${x},${y} `;
}, '');
}
/**
* Inverts the lasso from pixel space to an array of vega scenegraph tuples
*
* @param {*} data the dataset
* @param {*} pixelLasso the lasso in pixel space, [[x,y], [x,y], ...]
* @param {*} unit the unit where the lasso is defined
*
* @returns an array of vega scenegraph tuples
*/
function intersectLasso(markname, pixelLasso, unit) {
const {
x,
y,
mark
} = unit;
const bb = new vegaScenegraph.Bounds().set(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
// Get bounding box around lasso
for (const [px, py] of pixelLasso) {
if (px < bb.x1) bb.x1 = px;
if (px > bb.x2) bb.x2 = px;
if (py < bb.y1) bb.y1 = py;
if (py > bb.y2) bb.y2 = py;
}
// Translate bb against unit coordinates
bb.translate(x, y);
const intersection = intersect([[bb.x1, bb.y1], [bb.x2, bb.y2]], markname, mark);
// Check every point against the lasso
return intersection.filter(tuple => pointInPolygon(tuple.x, tuple.y, pixelLasso));
}
/**
* Performs a test if a point is inside a polygon based on the idea from
* https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
*
* This method will not need the same start/end point since it wraps around the edges of the array
*
* @param {*} test a point to test against
* @param {*} polygon a polygon in the form [[x,y], [x,y], ...]
* @returns true if the point lies inside the polygon, false otherwise
*/
function pointInPolygon(testx, testy, polygon) {
let intersections = 0;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const [prevX, prevY] = polygon[j];
const [x, y] = polygon[i];
// count intersections
if (y > testy != prevY > test