UNPKG

vega

Version:

The Vega visualization grammar.

1,561 lines (1,517 loc) 925 kB
(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