vega
Version:
The Vega visualization grammar.
1,561 lines (1,517 loc) • 925 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-dsv'), require('topojson-client'), require('d3-array'), require('d3-format'), require('d3-time'), require('d3-time-format'), require('d3-shape'), require('d3-path'), require('d3-scale'), require('d3-interpolate'), require('d3-geo'), require('d3-color'), require('d3-force'), require('d3-hierarchy'), require('d3-delaunay'), require('d3-timer')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-dsv', 'topojson-client', 'd3-array', 'd3-format', 'd3-time', 'd3-time-format', 'd3-shape', 'd3-path', 'd3-scale', 'd3-interpolate', 'd3-geo', 'd3-color', 'd3-force', 'd3-hierarchy', 'd3-delaunay', 'd3-timer'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vega = {}, global.d3, global.topojson, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3));
})(this, (function (exports, d3Dsv, topojsonClient, d3Array, d3Format, d3Time, d3TimeFormat, d3Shape, d3Path, $$2, $$1, d3Geo, d3Color, d3Force, d3Hierarchy, d3Delaunay, d3Timer) { 'use strict';
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var $__namespace = /*#__PURE__*/_interopNamespaceDefault($$2);
var $$1__namespace = /*#__PURE__*/_interopNamespaceDefault($$1);
function accessor(fn, fields, name) {
fn.fields = fields || [];
fn.fname = name;
return fn;
}
function accessorName(fn) {
return fn == null ? null : fn.fname;
}
function accessorFields(fn) {
return fn == null ? null : fn.fields;
}
function getter$1(path) {
return path.length === 1 ? get1(path[0]) : getN(path);
}
const get1 = field => function (obj) {
return obj[field];
};
const getN = path => {
const len = path.length;
return function (obj) {
for (let i = 0; i < len; ++i) {
obj = obj[path[i]];
}
return obj;
};
};
function error(message) {
throw Error(message);
}
function splitAccessPath(p) {
const path = [],
n = p.length;
let q = null,
b = 0,
s = '',
i,
j,
c;
p = p + '';
function push() {
path.push(s + p.substring(i, j));
s = '';
i = j + 1;
}
for (i = j = 0; j < n; ++j) {
c = p[j];
if (c === '\\') {
s += p.substring(i, j++);
i = j;
} else if (c === q) {
push();
q = null;
b = -1;
} else if (q) {
continue;
} else if (i === b && c === '"') {
i = j + 1;
q = c;
} else if (i === b && c === "'") {
i = j + 1;
q = c;
} else if (c === '.' && !b) {
if (j > i) {
push();
} else {
i = j + 1;
}
} else if (c === '[') {
if (j > i) push();
b = i = j + 1;
} else if (c === ']') {
if (!b) error('Access path missing open bracket: ' + p);
if (b > 0) push();
b = 0;
i = j + 1;
}
}
if (b) error('Access path missing closing bracket: ' + p);
if (q) error('Access path missing closing quote: ' + p);
if (j > i) {
j++;
push();
}
return path;
}
function field$1(field, name, opt) {
const path = splitAccessPath(field);
field = path.length === 1 ? path[0] : field;
return accessor((opt && opt.get || getter$1)(path), [field], name || field);
}
const id = field$1('id');
const identity = accessor(_ => _, [], 'identity');
const zero$1 = accessor(() => 0, [], 'zero');
const one$1 = accessor(() => 1, [], 'one');
const truthy = accessor(() => true, [], 'true');
const falsy = accessor(() => false, [], 'false');
/** Utilities common to vega-interpreter and vega-expression for evaluating expresions */
/** JSON authors are not allowed to set these properties, as these are built-in to the JS Object Prototype and should not be overridden. */
const DisallowedObjectProperties = new Set([...Object.getOwnPropertyNames(Object.prototype).filter(name => typeof Object.prototype[name] === 'function'), '__proto__']);
function log$1$1(method, level, input) {
const args = [level].concat([].slice.call(input));
console[method].apply(console, args); // eslint-disable-line no-console
}
const None$2 = 0;
const Error$1 = 1;
const Warn = 2;
const Info = 3;
const Debug = 4;
function logger(_, method, handler = log$1$1) {
let level = _ || None$2;
return {
level(_) {
if (arguments.length) {
level = +_;
return this;
} else {
return level;
}
},
error() {
if (level >= Error$1) handler(method || 'error', 'ERROR', arguments);
return this;
},
warn() {
if (level >= Warn) handler(method || 'warn', 'WARN', arguments);
return this;
},
info() {
if (level >= Info) handler(method || 'log', 'INFO', arguments);
return this;
},
debug() {
if (level >= Debug) handler(method || 'log', 'DEBUG', arguments);
return this;
}
};
}
var isArray = Array.isArray;
function isObject(_) {
return _ === Object(_);
}
const isLegalKey = key => key !== '__proto__';
function mergeConfig(...configs) {
return configs.reduce((out, source) => {
for (const key in source) {
if (key === 'signals') {
// for signals, we merge the signals arrays
// source signals take precedence over
// existing signals with the same name
out.signals = mergeNamed(out.signals, source.signals);
} else {
// otherwise, merge objects subject to recursion constraints
// for legend block, recurse for the layout entry only
// for style block, recurse for all properties
// otherwise, no recursion: objects overwrite, no merging
const r = key === 'legend' ? {
layout: 1
} : key === 'style' ? true : null;
writeConfig(out, key, source[key], r);
}
}
return out;
}, {});
}
function writeConfig(output, key, value, recurse) {
if (!isLegalKey(key)) return;
let k, o;
if (isObject(value) && !isArray(value)) {
o = isObject(output[key]) ? output[key] : output[key] = {};
for (k in value) {
if (recurse && (recurse === true || recurse[k])) {
writeConfig(o, k, value[k]);
} else if (isLegalKey(k)) {
o[k] = value[k];
}
}
} else {
output[key] = value;
}
}
function mergeNamed(a, b) {
if (a == null) return b;
const map = {},
out = [];
function add(_) {
if (!map[_.name]) {
map[_.name] = 1;
out.push(_);
}
}
b.forEach(add);
a.forEach(add);
return out;
}
function peek$1(array) {
return array[array.length - 1];
}
function toNumber(_) {
return _ == null || _ === '' ? null : +_;
}
const exp$1 = sign => x => sign * Math.exp(x);
const log$2 = sign => x => Math.log(sign * x);
const symlog = c => x => Math.sign(x) * Math.log1p(Math.abs(x / c));
const symexp = c => x => Math.sign(x) * Math.expm1(Math.abs(x)) * c;
const pow$1 = exponent => x => x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent);
function pan(domain, delta, lift, ground) {
const d0 = lift(domain[0]),
d1 = lift(peek$1(domain)),
dd = (d1 - d0) * delta;
return [ground(d0 - dd), ground(d1 - dd)];
}
function panLinear(domain, delta) {
return pan(domain, delta, toNumber, identity);
}
function panLog(domain, delta) {
var sign = Math.sign(domain[0]);
return pan(domain, delta, log$2(sign), exp$1(sign));
}
function panPow(domain, delta, exponent) {
return pan(domain, delta, pow$1(exponent), pow$1(1 / exponent));
}
function panSymlog(domain, delta, constant) {
return pan(domain, delta, symlog(constant), symexp(constant));
}
function zoom(domain, anchor, scale, lift, ground) {
const d0 = lift(domain[0]),
d1 = lift(peek$1(domain)),
da = anchor != null ? lift(anchor) : (d0 + d1) / 2;
return [ground(da + (d0 - da) * scale), ground(da + (d1 - da) * scale)];
}
function zoomLinear(domain, anchor, scale) {
return zoom(domain, anchor, scale, toNumber, identity);
}
function zoomLog(domain, anchor, scale) {
const sign = Math.sign(domain[0]);
return zoom(domain, anchor, scale, log$2(sign), exp$1(sign));
}
function zoomPow(domain, anchor, scale, exponent) {
return zoom(domain, anchor, scale, pow$1(exponent), pow$1(1 / exponent));
}
function zoomSymlog(domain, anchor, scale, constant) {
return zoom(domain, anchor, scale, symlog(constant), symexp(constant));
}
function quarter(date) {
return 1 + ~~(new Date(date).getMonth() / 3);
}
function utcquarter(date) {
return 1 + ~~(new Date(date).getUTCMonth() / 3);
}
function array$2(_) {
return _ != null ? isArray(_) ? _ : [_] : [];
}
/**
* Span-preserving range clamp. If the span of the input range is less
* than (max - min) and an endpoint exceeds either the min or max value,
* the range is translated such that the span is preserved and one
* endpoint touches the boundary of the min/max range.
* If the span exceeds (max - min), the range [min, max] is returned.
*/
function clampRange(range, min, max) {
let lo = range[0],
hi = range[1],
span;
if (hi < lo) {
span = hi;
hi = lo;
lo = span;
}
span = hi - lo;
return span >= max - min ? [min, max] : [lo = Math.min(Math.max(lo, min), max - span), lo + span];
}
function isFunction(_) {
return typeof _ === 'function';
}
const DESCENDING = 'descending';
function compare$1(fields, orders, opt) {
opt = opt || {};
orders = array$2(orders) || [];
const ord = [],
get = [],
fmap = {},
gen = opt.comparator || comparator;
array$2(fields).forEach((f, i) => {
if (f == null) return;
ord.push(orders[i] === DESCENDING ? -1 : 1);
get.push(f = isFunction(f) ? f : field$1(f, null, opt));
(accessorFields(f) || []).forEach(_ => fmap[_] = 1);
});
return get.length === 0 ? null : accessor(gen(get, ord), Object.keys(fmap));
}
const ascending$1 = (u, v) => (u < v || u == null) && v != null ? -1 : (u > v || v == null) && u != null ? 1 : (v = v instanceof Date ? +v : v, u = u instanceof Date ? +u : u) !== u && v === v ? -1 : v !== v && u === u ? 1 : 0;
const comparator = (fields, orders) => fields.length === 1 ? compare1(fields[0], orders[0]) : compareN(fields, orders, fields.length);
const compare1 = (field, order) => function (a, b) {
return ascending$1(field(a), field(b)) * order;
};
const compareN = (fields, orders, n) => {
orders.push(0); // pad zero for convenient lookup
return function (a, b) {
let f,
c = 0,
i = -1;
while (c === 0 && ++i < n) {
f = fields[i];
c = ascending$1(f(a), f(b));
}
return c * orders[i];
};
};
function constant$1(_) {
return isFunction(_) ? _ : () => _;
}
function debounce(delay, handler) {
let tid;
return e => {
if (tid) clearTimeout(tid);
tid = setTimeout(() => (handler(e), tid = null), delay);
};
}
function extend(_) {
for (let x, k, i = 1, len = arguments.length; i < len; ++i) {
x = arguments[i];
for (k in x) {
_[k] = x[k];
}
}
return _;
}
/**
* Return an array with minimum and maximum values, in the
* form [min, max]. Ignores null, undefined, and NaN values.
*/
function extent(array, f) {
let i = 0,
n,
v,
min,
max;
if (array && (n = array.length)) {
if (f == null) {
// find first valid value
for (v = array[i]; i < n && (v == null || v !== v); v = array[++i]);
min = max = v;
// visit all other values
for (; i < n; ++i) {
v = array[i];
// skip null/undefined; NaN will fail all comparisons
if (v != null) {
if (v < min) min = v;
if (v > max) max = v;
}
}
} else {
// find first valid value
for (v = f(array[i]); i < n && (v == null || v !== v); v = f(array[++i]));
min = max = v;
// visit all other values
for (; i < n; ++i) {
v = f(array[i]);
// skip null/undefined; NaN will fail all comparisons
if (v != null) {
if (v < min) min = v;
if (v > max) max = v;
}
}
}
}
return [min, max];
}
function extentIndex(array, f) {
const n = array.length;
let i = -1,
a,
b,
c,
u,
v;
if (f == null) {
while (++i < n) {
b = array[i];
if (b != null && b >= b) {
a = c = b;
break;
}
}
if (i === n) return [-1, -1];
u = v = i;
while (++i < n) {
b = array[i];
if (b != null) {
if (a > b) {
a = b;
u = i;
}
if (c < b) {
c = b;
v = i;
}
}
}
} else {
while (++i < n) {
b = f(array[i], i, array);
if (b != null && b >= b) {
a = c = b;
break;
}
}
if (i === n) return [-1, -1];
u = v = i;
while (++i < n) {
b = f(array[i], i, array);
if (b != null) {
if (a > b) {
a = b;
u = i;
}
if (c < b) {
c = b;
v = i;
}
}
}
}
return [u, v];
}
function has$1(object, property) {
return Object.hasOwn(object, property);
}
const NULL = {};
function fastmap(input) {
let obj = {},
test;
function has$1$1(key) {
return has$1(obj, key) && obj[key] !== NULL;
}
const map = {
size: 0,
empty: 0,
object: obj,
has: has$1$1,
get(key) {
return has$1$1(key) ? obj[key] : undefined;
},
set(key, value) {
if (!has$1$1(key)) {
++map.size;
if (obj[key] === NULL) --map.empty;
}
obj[key] = value;
return this;
},
delete(key) {
if (has$1$1(key)) {
--map.size;
++map.empty;
obj[key] = NULL;
}
return this;
},
clear() {
map.size = map.empty = 0;
map.object = obj = {};
},
test(_) {
if (arguments.length) {
test = _;
return map;
} else {
return test;
}
},
clean() {
const next = {};
let size = 0;
for (const key in obj) {
const value = obj[key];
if (value !== NULL && (!test || !test(value))) {
next[key] = value;
++size;
}
}
map.size = size;
map.empty = 0;
map.object = obj = next;
}
};
if (input) Object.keys(input).forEach(key => {
map.set(key, input[key]);
});
return map;
}
function flush(range, value, threshold, left, right, center) {
if (!threshold && threshold !== 0) return center;
const t = +threshold;
let a = range[0],
b = peek$1(range),
l;
// swap endpoints if range is reversed
if (b < a) {
l = a;
a = b;
b = l;
}
// compare value to endpoints
l = Math.abs(value - a);
const r = Math.abs(b - value);
// adjust if value is within threshold distance of endpoint
return l < r && l <= t ? left : r <= t ? right : center;
}
function inherits(child, parent, members) {
const proto = child.prototype = Object.create(parent.prototype);
Object.defineProperty(proto, 'constructor', {
value: child,
writable: true,
enumerable: true,
configurable: true
});
return extend(proto, members);
}
/**
* Predicate that returns true if the value lies within the span
* of the given range. The left and right flags control the use
* of inclusive (true) or exclusive (false) comparisons.
*/
function inrange(value, range, left, right) {
let r0 = range[0],
r1 = range[range.length - 1],
t;
if (r0 > r1) {
t = r0;
r0 = r1;
r1 = t;
}
left = left === undefined || left;
right = right === undefined || right;
return (left ? r0 <= value : r0 < value) && (right ? value <= r1 : value < r1);
}
function isBoolean$1(_) {
return typeof _ === 'boolean';
}
function isDate$1(_) {
return Object.prototype.toString.call(_) === '[object Date]';
}
function isIterable(_) {
return _ && isFunction(_[Symbol.iterator]);
}
function isNumber$1(_) {
return typeof _ === 'number';
}
function isRegExp(_) {
return Object.prototype.toString.call(_) === '[object RegExp]';
}
function isString(_) {
return typeof _ === 'string';
}
function key(fields, flat, opt) {
if (fields) {
fields = flat ? array$2(fields).map(f => f.replace(/\\(.)/g, '$1')) : array$2(fields);
}
const len = fields && fields.length,
gen = opt && opt.get || getter$1,
map = f => gen(flat ? [f] : splitAccessPath(f));
let fn;
if (!len) {
fn = function () {
return '';
};
} else if (len === 1) {
const get = map(fields[0]);
fn = function (_) {
return '' + get(_);
};
} else {
const get = fields.map(map);
fn = function (_) {
let s = '' + get[0](_),
i = 0;
while (++i < len) s += '|' + get[i](_);
return s;
};
}
return accessor(fn, fields, 'key');
}
function lerp(array, frac) {
const lo = array[0],
hi = peek$1(array),
f = +frac;
return !f ? lo : f === 1 ? hi : lo + f * (hi - lo);
}
const DEFAULT_MAX_SIZE = 10000;
// adapted from https://github.com/dominictarr/hashlru/ (MIT License)
function lruCache(maxsize) {
maxsize = +maxsize || DEFAULT_MAX_SIZE;
let curr, prev, size;
const clear = () => {
curr = {};
prev = {};
size = 0;
};
const update = (key, value) => {
if (++size > maxsize) {
prev = curr;
curr = {};
size = 1;
}
return curr[key] = value;
};
clear();
return {
clear,
has: key => has$1(curr, key) || has$1(prev, key),
get: key => has$1(curr, key) ? curr[key] : has$1(prev, key) ? update(key, prev[key]) : undefined,
set: (key, value) => has$1(curr, key) ? curr[key] = value : update(key, value)
};
}
function merge$2(compare, array0, array1, output) {
const n0 = array0.length,
n1 = array1.length;
if (!n1) return array0;
if (!n0) return array1;
const merged = output || new array0.constructor(n0 + n1);
let i0 = 0,
i1 = 0,
i = 0;
for (; i0 < n0 && i1 < n1; ++i) {
merged[i] = compare(array0[i0], array1[i1]) > 0 ? array1[i1++] : array0[i0++];
}
for (; i0 < n0; ++i0, ++i) {
merged[i] = array0[i0];
}
for (; i1 < n1; ++i1, ++i) {
merged[i] = array1[i1];
}
return merged;
}
function repeat(str, reps) {
let s = '';
while (--reps >= 0) s += str;
return s;
}
function pad(str, length, padchar, align) {
const c = padchar || ' ',
s = str + '',
n = length - s.length;
return n <= 0 ? s : align === 'left' ? repeat(c, n) + s : align === 'center' ? repeat(c, ~~(n / 2)) + s + repeat(c, Math.ceil(n / 2)) : s + repeat(c, n);
}
/**
* Return the numerical span of an array: the difference between
* the last and first values.
*/
function span(array) {
return array && peek$1(array) - array[0] || 0;
}
function $(x) {
return isArray(x) ? `[${x.map(v => v === null ? 'null' : $(v))}]` : isObject(x) || isString(x) ?
// Output valid JSON and JS source strings.
// See https://github.com/judofyr/timeless/blob/master/posts/json-isnt-a-javascript-subset.md
JSON.stringify(x).replaceAll('\u2028', '\\u2028').replaceAll('\u2029', '\\u2029') : x;
}
function toBoolean(_) {
return _ == null || _ === '' ? null : !_ || _ === 'false' || _ === '0' ? false : !!_;
}
const defaultParser = _ => isNumber$1(_) ? _ : isDate$1(_) ? _ : Date.parse(_);
function toDate(_, parser) {
parser = parser || defaultParser;
return _ == null || _ === '' ? null : parser(_);
}
function toString(_) {
return _ == null || _ === '' ? null : _ + '';
}
function toSet(_) {
const s = {},
n = _.length;
for (let i = 0; i < n; ++i) s[_[i]] = true;
return s;
}
function truncate$1(str, length, align, ellipsis) {
const e = ellipsis != null ? ellipsis : '\u2026',
s = str + '',
n = s.length,
l = Math.max(0, length - e.length);
return n <= length ? s : align === 'left' ? e + s.slice(n - l) : align === 'center' ? s.slice(0, Math.ceil(l / 2)) + e + s.slice(n - ~~(l / 2)) : s.slice(0, l) + e;
}
function visitArray(array, filter, visitor) {
if (array) {
if (filter) {
const n = array.length;
for (let i = 0; i < n; ++i) {
const t = filter(array[i]);
if (t) visitor(t, i, array);
}
} else {
array.forEach(visitor);
}
}
}
const YEAR = 'year';
const QUARTER = 'quarter';
const MONTH = 'month';
const WEEK = 'week';
const DATE = 'date';
const DAY = 'day';
const DAYOFYEAR = 'dayofyear';
const HOURS = 'hours';
const MINUTES = 'minutes';
const SECONDS = 'seconds';
const MILLISECONDS = 'milliseconds';
const TIME_UNITS = [YEAR, QUARTER, MONTH, WEEK, DATE, DAY, DAYOFYEAR, HOURS, MINUTES, SECONDS, MILLISECONDS];
const UNITS = TIME_UNITS.reduce((o, u, i) => (o[u] = 1 + i, o), {});
function timeUnits(units) {
const u = array$2(units).slice(),
m = {};
// check validity
if (!u.length) error('Missing time unit.');
u.forEach(unit => {
if (has$1(UNITS, unit)) {
m[unit] = 1;
} else {
error(`Invalid time unit: ${unit}.`);
}
});
const numTypes = (m[WEEK] || m[DAY] ? 1 : 0) + (m[QUARTER] || m[MONTH] || m[DATE] ? 1 : 0) + (m[DAYOFYEAR] ? 1 : 0);
if (numTypes > 1) {
error(`Incompatible time units: ${units}`);
}
// ensure proper sort order
u.sort((a, b) => UNITS[a] - UNITS[b]);
return u;
}
const defaultSpecifiers = {
[YEAR]: '%Y ',
[QUARTER]: 'Q%q ',
[MONTH]: '%b ',
[DATE]: '%d ',
[WEEK]: 'W%U ',
[DAY]: '%a ',
[DAYOFYEAR]: '%j ',
[HOURS]: '%H:00',
[MINUTES]: '00:%M',
[SECONDS]: ':%S',
[MILLISECONDS]: '.%L',
[`${YEAR}-${MONTH}`]: '%Y-%m ',
[`${YEAR}-${MONTH}-${DATE}`]: '%Y-%m-%d ',
[`${HOURS}-${MINUTES}`]: '%H:%M'
};
function timeUnitSpecifier(units, specifiers) {
const s = extend({}, defaultSpecifiers, specifiers),
u = timeUnits(units),
n = u.length;
let fmt = '',
start = 0,
end,
key;
for (start = 0; start < n;) {
for (end = u.length; end > start; --end) {
key = u.slice(start, end).join('-');
if (s[key] != null) {
fmt += s[key];
start = end;
break;
}
}
}
return fmt.trim();
}
const t0 = new Date();
function localYear(y) {
t0.setFullYear(y);
t0.setMonth(0);
t0.setDate(1);
t0.setHours(0, 0, 0, 0);
return t0;
}
function dayofyear(d) {
return localDayOfYear(new Date(d));
}
function week(d) {
return localWeekNum(new Date(d));
}
function localDayOfYear(d) {
return d3Time.timeDay.count(localYear(d.getFullYear()) - 1, d);
}
function localWeekNum(d) {
return d3Time.timeWeek.count(localYear(d.getFullYear()) - 1, d);
}
function localFirst(y) {
return localYear(y).getDay();
}
function localDate(y, m, d, H, M, S, L) {
if (0 <= y && y < 100) {
const date = new Date(-1, m, d, H, M, S, L);
date.setFullYear(y);
return date;
}
return new Date(y, m, d, H, M, S, L);
}
function utcdayofyear(d) {
return utcDayOfYear(new Date(d));
}
function utcweek(d) {
return utcWeekNum(new Date(d));
}
function utcDayOfYear(d) {
const y = Date.UTC(d.getUTCFullYear(), 0, 1);
return d3Time.utcDay.count(y - 1, d);
}
function utcWeekNum(d) {
const y = Date.UTC(d.getUTCFullYear(), 0, 1);
return d3Time.utcWeek.count(y - 1, d);
}
function utcFirst(y) {
t0.setTime(Date.UTC(y, 0, 1));
return t0.getUTCDay();
}
function utcDate(y, m, d, H, M, S, L) {
if (0 <= y && y < 100) {
const date = new Date(Date.UTC(-1, m, d, H, M, S, L));
date.setUTCFullYear(d.y);
return date;
}
return new Date(Date.UTC(y, m, d, H, M, S, L));
}
function floor(units, step, get, inv, newDate) {
const s = step || 1,
b = peek$1(units),
_ = (unit, p, key) => {
key = key || unit;
return getUnit(get[key], inv[key], unit === b && s, p);
};
const t = new Date(),
u = toSet(units),
y = u[YEAR] ? _(YEAR) : constant$1(2012),
m = u[MONTH] ? _(MONTH) : u[QUARTER] ? _(QUARTER) : zero$1,
d = u[WEEK] && u[DAY] ? _(DAY, 1, WEEK + DAY) : u[WEEK] ? _(WEEK, 1) : u[DAY] ? _(DAY, 1) : u[DATE] ? _(DATE, 1) : u[DAYOFYEAR] ? _(DAYOFYEAR, 1) : one$1,
H = u[HOURS] ? _(HOURS) : zero$1,
M = u[MINUTES] ? _(MINUTES) : zero$1,
S = u[SECONDS] ? _(SECONDS) : zero$1,
L = u[MILLISECONDS] ? _(MILLISECONDS) : zero$1;
return function (v) {
t.setTime(+v);
const year = y(t);
return newDate(year, m(t), d(t, year), H(t), M(t), S(t), L(t));
};
}
function getUnit(f, inv, step, phase) {
const u = step <= 1 ? f : phase ? (d, y) => phase + step * Math.floor((f(d, y) - phase) / step) : (d, y) => step * Math.floor(f(d, y) / step);
return inv ? (d, y) => inv(u(d, y), y) : u;
}
// returns the day of the year based on week number, day of week,
// and the day of the week for the first day of the year
function weekday(week, day, firstDay) {
return day + week * 7 - (firstDay + 6) % 7;
}
// -- LOCAL TIME --
const localGet = {
[YEAR]: d => d.getFullYear(),
[QUARTER]: d => Math.floor(d.getMonth() / 3),
[MONTH]: d => d.getMonth(),
[DATE]: d => d.getDate(),
[HOURS]: d => d.getHours(),
[MINUTES]: d => d.getMinutes(),
[SECONDS]: d => d.getSeconds(),
[MILLISECONDS]: d => d.getMilliseconds(),
[DAYOFYEAR]: d => localDayOfYear(d),
[WEEK]: d => localWeekNum(d),
[WEEK + DAY]: (d, y) => weekday(localWeekNum(d), d.getDay(), localFirst(y)),
[DAY]: (d, y) => weekday(1, d.getDay(), localFirst(y))
};
const localInv = {
[QUARTER]: q => 3 * q,
[WEEK]: (w, y) => weekday(w, 0, localFirst(y))
};
function timeFloor(units, step) {
return floor(units, step || 1, localGet, localInv, localDate);
}
// -- UTC TIME --
const utcGet = {
[YEAR]: d => d.getUTCFullYear(),
[QUARTER]: d => Math.floor(d.getUTCMonth() / 3),
[MONTH]: d => d.getUTCMonth(),
[DATE]: d => d.getUTCDate(),
[HOURS]: d => d.getUTCHours(),
[MINUTES]: d => d.getUTCMinutes(),
[SECONDS]: d => d.getUTCSeconds(),
[MILLISECONDS]: d => d.getUTCMilliseconds(),
[DAYOFYEAR]: d => utcDayOfYear(d),
[WEEK]: d => utcWeekNum(d),
[DAY]: (d, y) => weekday(1, d.getUTCDay(), utcFirst(y)),
[WEEK + DAY]: (d, y) => weekday(utcWeekNum(d), d.getUTCDay(), utcFirst(y))
};
const utcInv = {
[QUARTER]: q => 3 * q,
[WEEK]: (w, y) => weekday(w, 0, utcFirst(y))
};
function utcFloor(units, step) {
return floor(units, step || 1, utcGet, utcInv, utcDate);
}
const timeIntervals = {
[YEAR]: d3Time.timeYear,
[QUARTER]: d3Time.timeMonth.every(3),
[MONTH]: d3Time.timeMonth,
[WEEK]: d3Time.timeWeek,
[DATE]: d3Time.timeDay,
[DAY]: d3Time.timeDay,
[DAYOFYEAR]: d3Time.timeDay,
[HOURS]: d3Time.timeHour,
[MINUTES]: d3Time.timeMinute,
[SECONDS]: d3Time.timeSecond,
[MILLISECONDS]: d3Time.timeMillisecond
};
const utcIntervals = {
[YEAR]: d3Time.utcYear,
[QUARTER]: d3Time.utcMonth.every(3),
[MONTH]: d3Time.utcMonth,
[WEEK]: d3Time.utcWeek,
[DATE]: d3Time.utcDay,
[DAY]: d3Time.utcDay,
[DAYOFYEAR]: d3Time.utcDay,
[HOURS]: d3Time.utcHour,
[MINUTES]: d3Time.utcMinute,
[SECONDS]: d3Time.utcSecond,
[MILLISECONDS]: d3Time.utcMillisecond
};
function timeInterval(unit) {
return timeIntervals[unit];
}
function utcInterval(unit) {
return utcIntervals[unit];
}
function offset$3(ival, date, step) {
return ival ? ival.offset(date, step) : undefined;
}
function timeOffset(unit, date, step) {
return offset$3(timeInterval(unit), date, step);
}
function utcOffset(unit, date, step) {
return offset$3(utcInterval(unit), date, step);
}
function sequence$1(ival, start, stop, step) {
return ival ? ival.range(start, stop, step) : undefined;
}
function timeSequence(unit, start, stop, step) {
return sequence$1(timeInterval(unit), start, stop, step);
}
function utcSequence(unit, start, stop, step) {
return sequence$1(utcInterval(unit), start, stop, step);
}
const durationSecond = 1000,
durationMinute = durationSecond * 60,
durationHour = durationMinute * 60,
durationDay = durationHour * 24,
durationWeek = durationDay * 7,
durationMonth = durationDay * 30,
durationYear = durationDay * 365;
const Milli = [YEAR, MONTH, DATE, HOURS, MINUTES, SECONDS, MILLISECONDS],
Seconds = Milli.slice(0, -1),
Minutes = Seconds.slice(0, -1),
Hours = Minutes.slice(0, -1),
Day = Hours.slice(0, -1),
Week = [YEAR, WEEK],
Month = [YEAR, MONTH],
Year = [YEAR];
const intervals = [[Seconds, 1, durationSecond], [Seconds, 5, 5 * durationSecond], [Seconds, 15, 15 * durationSecond], [Seconds, 30, 30 * durationSecond], [Minutes, 1, durationMinute], [Minutes, 5, 5 * durationMinute], [Minutes, 15, 15 * durationMinute], [Minutes, 30, 30 * durationMinute], [Hours, 1, durationHour], [Hours, 3, 3 * durationHour], [Hours, 6, 6 * durationHour], [Hours, 12, 12 * durationHour], [Day, 1, durationDay], [Week, 1, durationWeek], [Month, 1, durationMonth], [Month, 3, 3 * durationMonth], [Year, 1, durationYear]];
function bin$1(opt) {
const ext = opt.extent,
max = opt.maxbins || 40,
target = Math.abs(span(ext)) / max;
let i = d3Array.bisector(i => i[2]).right(intervals, target),
units,
step;
if (i === intervals.length) {
units = Year, step = d3Array.tickStep(ext[0] / durationYear, ext[1] / durationYear, max);
} else if (i) {
i = intervals[target / intervals[i - 1][2] < intervals[i][2] / target ? i - 1 : i];
units = i[0];
step = i[1];
} else {
units = Milli;
step = Math.max(d3Array.tickStep(ext[0], ext[1], max), 1);
}
return {
units,
step
};
}
function memoize(method) {
const cache = {};
return spec => cache[spec] || (cache[spec] = method(spec));
}
function trimZeroes(numberFormat, decimalChar) {
return x => {
const str = numberFormat(x),
dec = str.indexOf(decimalChar);
if (dec < 0) return str;
let idx = rightmostDigit(str, dec);
const end = idx < str.length ? str.slice(idx) : '';
while (--idx > dec) if (str[idx] !== '0') {
++idx;
break;
}
return str.slice(0, idx) + end;
};
}
function rightmostDigit(str, dec) {
let i = str.lastIndexOf('e'),
c;
if (i > 0) return i;
for (i = str.length; --i > dec;) {
c = str.charCodeAt(i);
if (c >= 48 && c <= 57) return i + 1; // is digit
}
}
function numberLocale(locale) {
const format = memoize(locale.format),
formatPrefix = locale.formatPrefix;
return {
format,
formatPrefix,
formatFloat(spec) {
const s = d3Format.formatSpecifier(spec || ',');
if (s.precision == null) {
s.precision = 12;
switch (s.type) {
case '%':
s.precision -= 2;
break;
case 'e':
s.precision -= 1;
break;
}
return trimZeroes(format(s),
// number format
format('.1f')(1)[1] // decimal point character
);
} else {
return format(s);
}
},
formatSpan(start, stop, count, specifier) {
specifier = d3Format.formatSpecifier(specifier == null ? ',f' : specifier);
const step = d3Array.tickStep(start, stop, count),
value = Math.max(Math.abs(start), Math.abs(stop));
let precision;
if (specifier.precision == null) {
switch (specifier.type) {
case 's':
{
if (!isNaN(precision = d3Format.precisionPrefix(step, value))) {
specifier.precision = precision;
}
return formatPrefix(specifier, value);
}
case '':
case 'e':
case 'g':
case 'p':
case 'r':
{
if (!isNaN(precision = d3Format.precisionRound(step, value))) {
specifier.precision = precision - (specifier.type === 'e');
}
break;
}
case 'f':
case '%':
{
if (!isNaN(precision = d3Format.precisionFixed(step))) {
specifier.precision = precision - (specifier.type === '%') * 2;
}
break;
}
}
}
return format(specifier);
}
};
}
let defaultNumberLocale;
resetNumberFormatDefaultLocale();
function resetNumberFormatDefaultLocale() {
return defaultNumberLocale = numberLocale({
format: d3Format.format,
formatPrefix: d3Format.formatPrefix
});
}
function numberFormatLocale(definition) {
return numberLocale(d3Format.formatLocale(definition));
}
function numberFormatDefaultLocale(definition) {
return arguments.length ? defaultNumberLocale = numberFormatLocale(definition) : defaultNumberLocale;
}
function timeMultiFormat(format, interval, spec) {
spec = spec || {};
if (!isObject(spec)) {
error(`Invalid time multi-format specifier: ${spec}`);
}
const second = interval(SECONDS),
minute = interval(MINUTES),
hour = interval(HOURS),
day = interval(DATE),
week = interval(WEEK),
month = interval(MONTH),
quarter = interval(QUARTER),
year = interval(YEAR),
L = format(spec[MILLISECONDS] || '.%L'),
S = format(spec[SECONDS] || ':%S'),
M = format(spec[MINUTES] || '%I:%M'),
H = format(spec[HOURS] || '%I %p'),
d = format(spec[DATE] || spec[DAY] || '%a %d'),
w = format(spec[WEEK] || '%b %d'),
m = format(spec[MONTH] || '%B'),
q = format(spec[QUARTER] || '%B'),
y = format(spec[YEAR] || '%Y');
return date => (second(date) < date ? L : minute(date) < date ? S : hour(date) < date ? M : day(date) < date ? H : month(date) < date ? week(date) < date ? d : w : year(date) < date ? quarter(date) < date ? m : q : y)(date);
}
function timeLocale(locale) {
const timeFormat = memoize(locale.format),
utcFormat = memoize(locale.utcFormat);
return {
timeFormat: spec => isString(spec) ? timeFormat(spec) : timeMultiFormat(timeFormat, timeInterval, spec),
utcFormat: spec => isString(spec) ? utcFormat(spec) : timeMultiFormat(utcFormat, utcInterval, spec),
timeParse: memoize(locale.parse),
utcParse: memoize(locale.utcParse)
};
}
let defaultTimeLocale;
resetTimeFormatDefaultLocale();
function resetTimeFormatDefaultLocale() {
return defaultTimeLocale = timeLocale({
format: d3TimeFormat.timeFormat,
parse: d3TimeFormat.timeParse,
utcFormat: d3TimeFormat.utcFormat,
utcParse: d3TimeFormat.utcParse
});
}
function timeFormatLocale(definition) {
return timeLocale(d3TimeFormat.timeFormatLocale(definition));
}
function timeFormatDefaultLocale(definition) {
return arguments.length ? defaultTimeLocale = timeFormatLocale(definition) : defaultTimeLocale;
}
const createLocale = (number, time) => extend({}, number, time);
function locale(numberSpec, timeSpec) {
const number = numberSpec ? numberFormatLocale(numberSpec) : numberFormatDefaultLocale();
const time = timeSpec ? timeFormatLocale(timeSpec) : timeFormatDefaultLocale();
return createLocale(number, time);
}
function defaultLocale(numberSpec, timeSpec) {
const args = arguments.length;
if (args && args !== 2) {
error('defaultLocale expects either zero or two arguments.');
}
return args ? createLocale(numberFormatDefaultLocale(numberSpec), timeFormatDefaultLocale(timeSpec)) : createLocale(numberFormatDefaultLocale(), timeFormatDefaultLocale());
}
function resetDefaultLocale() {
resetNumberFormatDefaultLocale();
resetTimeFormatDefaultLocale();
return defaultLocale();
}
// Matches absolute URLs with optional protocol
// https://... file://... //...
const protocol_re = /^(data:|([A-Za-z]+:)?\/\/)/;
// Matches allowed URIs. From https://github.com/cure53/DOMPurify/blob/master/src/regexp.js with added file://
const allowed_re = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|file|data):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape
const whitespace_re = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; // eslint-disable-line no-control-regex
// Special treatment in node.js for the file: protocol
const fileProtocol = 'file://';
/**
* Factory for a loader constructor that provides methods for requesting
* files from either the network or disk, and for sanitizing request URIs.
* @param {object} fs - The file system interface for file loading.
* If null or undefined, local file loading will be disabled.
* @return {function} A loader constructor with the following signature:
* param {object} [options] - Optional default loading options to use.
* return {object} - A new loader instance.
*/
function loaderFactory(fs) {
return options => ({
options: options || {},
sanitize: sanitize,
load: load$1,
fileAccess: false,
file: fileLoader(),
http: httpLoader
});
}
/**
* Load an external resource, typically either from the web or from the local
* filesystem. This function uses {@link sanitize} to first sanitize the uri,
* then calls either {@link http} (for web requests) or {@link file} (for
* filesystem loading).
* @param {string} uri - The resource indicator (e.g., URL or filename).
* @param {object} [options] - Optional loading options. These options will
* override any existing default options.
* @return {Promise} - A promise that resolves to the loaded content.
*/
async function load$1(uri, options) {
const opt = await this.sanitize(uri, options),
url = opt.href;
return opt.localFile ? this.file(url) : this.http(url, options?.http);
}
/**
* URI sanitizer function.
* @param {string} uri - The uri (url or filename) to check.
* @param {object} options - An options hash.
* @return {Promise} - A promise that resolves to an object containing
* sanitized uri data, or rejects it the input uri is deemed invalid.
* The properties of the resolved object are assumed to be
* valid attributes for an HTML 'a' tag. The sanitized uri *must* be
* provided by the 'href' property of the returned object.
*/
async function sanitize(uri, options) {
options = extend({}, this.options, options);
const fileAccess = this.fileAccess,
result = {
href: null
};
let isFile, loadFile, base;
const isAllowed = allowed_re.test(uri.replace(whitespace_re, ''));
if (uri == null || typeof uri !== 'string' || !isAllowed) {
error('Sanitize failure, invalid URI: ' + $(uri));
}
const hasProtocol = protocol_re.test(uri);
// if relative url (no protocol/host), prepend baseURL
if ((base = options.baseURL) && !hasProtocol) {
// Ensure that there is a slash between the baseURL (e.g. hostname) and url
if (!uri.startsWith('/') && !base.endsWith('/')) {
uri = '/' + uri;
}
uri = base + uri;
}
// should we load from file system?
loadFile = (isFile = uri.startsWith(fileProtocol)) || options.mode === 'file' || options.mode !== 'http' && !hasProtocol && fileAccess;
if (isFile) {
// strip file protocol
uri = uri.slice(fileProtocol.length);
} else if (uri.startsWith('//')) {
if (options.defaultProtocol === 'file') {
// if is file, strip protocol and set loadFile flag
uri = uri.slice(2);
loadFile = true;
} else {
// if relative protocol (starts with '//'), prepend default protocol
uri = (options.defaultProtocol || 'http') + ':' + uri;
}
}
// set non-enumerable mode flag to indicate local file load
Object.defineProperty(result, 'localFile', {
value: !!loadFile
});
// set uri
result.href = uri;
// set default result target, if specified
if (options.target) {
result.target = options.target + '';
}
// set default result rel, if specified (#1542)
if (options.rel) {
result.rel = options.rel + '';
}
// provide control over cross-origin image handling (#2238)
// https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
if (options.context === 'image' && options.crossOrigin) {
result.crossOrigin = options.crossOrigin + '';
}
// return
return result;
}
/**
* File system loader factory.
* @param {object} fs - The file system interface.
* @return {function} - A file loader with the following signature:
* param {string} filename - The file system path to load.
* param {string} filename - The file system path to load.
* return {Promise} A promise that resolves to the file contents.
*/
function fileLoader(fs) {
return fileReject;
}
/**
* Default file system loader that simply rejects.
*/
async function fileReject() {
error('No file system access.');
}
/**
* An http loader.
* @param {string} url - The url to request.
* @param {Partial<RequestInit>} options - An options hash.
* @return {Promise} - A promise that resolves to the file contents.
*/
async function httpLoader(url, options) {
const opt = extend({}, this.options.http, options),
type = options && options.response,
response = await fetch(url, opt);
return !response.ok ? error(response.status + '' + response.statusText) : isFunction(response[type]) ? response[type]() : response.text();
}
const isValid = _ => _ != null && _ === _;
const isBoolean = _ => _ === 'true' || _ === 'false' || _ === true || _ === false;
const isDate = _ => !Number.isNaN(Date.parse(_));
const isNumber = _ => !Number.isNaN(+_) && !(_ instanceof Date);
const isInteger = _ => isNumber(_) && Number.isInteger(+_);
const typeParsers = {
boolean: toBoolean,
integer: toNumber,
number: toNumber,
date: toDate,
string: toString,
unknown: identity
};
const typeTests = [isBoolean, isInteger, isNumber, isDate];
const typeList = ['boolean', 'integer', 'number', 'date'];
function inferType(values, field) {
if (!values || !values.length) return 'unknown';
const n = values.length,
m = typeTests.length,
a = typeTests.map((_, i) => i + 1);
for (let i = 0, t = 0, j, value; i < n; ++i) {
value = field ? values[i][field] : values[i];
for (j = 0; j < m; ++j) {
if (a[j] && isValid(value) && !typeTests[j](value)) {
a[j] = 0;
++t;
if (t === typeTests.length) return 'string';
}
}
}
return typeList[a.reduce((u, v) => u === 0 ? v : u, 0) - 1];
}
function inferTypes(data, fields) {
return fields.reduce((types, field) => {
types[field] = inferType(data, field);
return types;
}, {});
}
function delimitedFormat(delimiter) {
const parse = function (data, format) {
const delim = {
delimiter: delimiter
};
return dsv(data, format ? extend(format, delim) : delim);
};
parse.responseType = 'text';
return parse;
}
function dsv(data, format) {
if (format.header) {
data = format.header.map($).join(format.delimiter) + '\n' + data;
}
return d3Dsv.dsvFormat(format.delimiter).parse(data + '');
}
dsv.responseType = 'text';
function isBuffer(_) {
return typeof Buffer === 'function' && isFunction(Buffer.isBuffer) ? Buffer.isBuffer(_) : false;
}
function json(data, format) {
const prop = format && format.property ? field$1(format.property) : identity;
return isObject(data) && !isBuffer(data) ? parseJSON(prop(data), format) : prop(JSON.parse(data));
}
json.responseType = 'json';
function parseJSON(data, format) {
if (!isArray(data) && isIterable(data)) {
data = [...data];
}
return format && format.copy ? JSON.parse(JSON.stringify(data)) : data;
}
const filters = {
interior: (a, b) => a !== b,
exterior: (a, b) => a === b
};
function topojson(data, format) {
let method, object, property, filter;
data = json(data, format);
if (format && format.feature) {
method = topojsonClient.feature;
property = format.feature;
} else if (format && format.mesh) {
method = topojsonClient.mesh;
property = format.mesh;
filter = filters[format.filter];
} else {
error('Missing TopoJSON feature or mesh parameter.');
}
object = (object = data.objects[property]) ? method(data, object, filter) : error('Invalid TopoJSON object: ' + property);
return object && object.features || [object];
}
topojson.responseType = 'json';
const format$2 = {
dsv: dsv,
csv: delimitedFormat(','),
tsv: delimitedFormat('\t'),
json: json,
topojson: topojson
};
function formats$1(name, reader) {
if (arguments.length > 1) {
format$2[name] = reader;
return this;
} else {
return has$1(format$2, name) ? format$2[name] : null;
}
}
function responseType(type) {
const f = formats$1(type);
return f && f.responseType || 'text';
}
function read(data, schema, timeParser, utcParser) {
schema = schema || {};
const reader = formats$1(schema.type || 'json');
if (!reader) error('Unknown data format type: ' + schema.type);
data = reader(data, schema);
if (schema.parse) parse$6(data, schema.parse, timeParser, utcParser);
if (has$1(data, 'columns')) delete data.columns;
return data;
}
function parse$6(data, types, timeParser, utcParser) {
if (!data.length) return; // early exit for empty data
const locale = timeFormatDefaultLocale();
timeParser = timeParser || locale.timeParse;
utcParser = utcParser || locale.utcParse;
let fields = data.columns || Object.keys(data[0]),
datum,
field,
i,
j,
n,
m;
if (types === 'auto') types = inferTypes(data, fields);
fields = Object.keys(types);
const parsers = fields.map(field => {
const type = types[field];
let parts, pattern;
if (type && (type.startsWith('date:') || type.startsWith('utc:'))) {
parts = type.split(/:(.+)?/, 2); // split on first :
pattern = parts[1];
if (pattern[0] === '\'' && pattern[pattern.length - 1] === '\'' || pattern[0] === '"' && pattern[pattern.length - 1] === '"') {
pattern = pattern.slice(1, -1);
}
const parse = parts[0] === 'utc' ? utcParser : timeParser;
return parse(pattern);
}
if (!typeParsers[type]) {
throw Error('Illegal format pattern: ' + field + ':' + type);
}
return typeParsers[type];
});
for (i = 0, n = data.length, m = fields.length; i < n; ++i) {
datum = data[i];
for (j = 0; j < m; ++j) {
field = fields[j];
datum[field] = parsers[j](datum[field]);
}
}
}
const loader = loaderFactory();
function UniqueList(idFunc) {
const $ = idFunc || identity,
list = [],
ids = {};
list.add = _ => {
const id = $(_);
if (!ids[id]) {
ids[id] = 1;
list.push(_);
}
return list;
};
list.remove = _ => {
const id = $(_);
if (ids[id]) {
ids[id] = 0;
const idx = list.indexOf(_);
if (idx >= 0) list.splice(idx, 1);
}
return list;
};
return list;
}
/**
* Invoke and await a potentially async callback function. If
* an error occurs, trap it and route to Dataflow.error.
* @param {Dataflow} df - The dataflow instance
* @param {function} callback - A callback function to invoke
* and then await. The dataflow will be passed as the single
* argument to the function.
*/
async function asyncCallback(df, callback) {
try {
await callback(df);
} catch (err) {
df.error(err);
}
}
const TUPLE_ID_KEY = Symbol('veg