StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics
https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
45599 lines
1.2 MiB
45599 lines
1.2 MiB
"use strict"; |
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } |
|
|
|
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } |
|
|
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } |
|
|
|
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } |
|
|
|
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } |
|
|
|
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } |
|
|
|
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } |
|
|
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } |
|
|
|
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } |
|
|
|
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } |
|
|
|
function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } |
|
|
|
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } |
|
|
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } |
|
|
|
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } |
|
|
|
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } |
|
|
|
(function (global, factory) { |
|
(typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory(global.vega = {})); |
|
})(void 0, function (exports) { |
|
'use strict'; |
|
|
|
var _localGet, _localInv, _utcGet, _utcInv, _timeIntervals, _utcIntervals, _defaultSpecifiers, _symbols$, _formats$; |
|
|
|
var _marked = |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(numbers), |
|
_marked2 = |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(numbers$1), |
|
_marked3 = |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(flatIterable); |
|
|
|
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 error(message) { |
|
throw Error(message); |
|
} |
|
|
|
function splitAccessPath(p) { |
|
var path = [], |
|
q = null, |
|
b = 0, |
|
n = p.length, |
|
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); |
|
s += p.substring(++j, ++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; |
|
} |
|
|
|
var isArray = Array.isArray; |
|
|
|
function isObject(_) { |
|
return _ === Object(_); |
|
} |
|
|
|
function isString(_) { |
|
return typeof _ === 'string'; |
|
} |
|
|
|
function $(x) { |
|
return isArray(x) ? '[' + x.map($) + ']' : isObject(x) || isString(x) ? // Output valid JSON and JS source strings. |
|
// See http://timelessrepo.com/json-isnt-a-javascript-subset |
|
JSON.stringify(x).replace("\u2028", "\\u2028").replace("\u2029", "\\u2029") : x; |
|
} |
|
|
|
function field(field, name) { |
|
var path = splitAccessPath(field), |
|
code = 'return _[' + path.map($).join('][') + '];'; |
|
return accessor(Function('_', code), [field = path.length === 1 ? path[0] : field], name || field); |
|
} |
|
|
|
var empty = []; |
|
var id = field('id'); |
|
var identity = accessor(function (_) { |
|
return _; |
|
}, empty, 'identity'); |
|
var zero = accessor(function () { |
|
return 0; |
|
}, empty, 'zero'); |
|
var one = accessor(function () { |
|
return 1; |
|
}, empty, 'one'); |
|
var truthy = accessor(function () { |
|
return true; |
|
}, empty, 'true'); |
|
var falsy = accessor(function () { |
|
return false; |
|
}, empty, 'false'); |
|
|
|
function log(method, level, input) { |
|
var args = [level].concat([].slice.call(input)); |
|
console[method].apply(console, args); // eslint-disable-line no-console |
|
} |
|
|
|
var None = 0; |
|
var Error$1 = 1; |
|
var Warn = 2; |
|
var Info = 3; |
|
var Debug = 4; |
|
|
|
function logger(_, method) { |
|
var _level = _ || None; |
|
|
|
return { |
|
level: function level(_) { |
|
if (arguments.length) { |
|
_level = +_; |
|
return this; |
|
} else { |
|
return _level; |
|
} |
|
}, |
|
error: function error() { |
|
if (_level >= Error$1) log(method || 'error', 'ERROR', arguments); |
|
return this; |
|
}, |
|
warn: function warn() { |
|
if (_level >= Warn) log(method || 'warn', 'WARN', arguments); |
|
return this; |
|
}, |
|
info: function info() { |
|
if (_level >= Info) log(method || 'log', 'INFO', arguments); |
|
return this; |
|
}, |
|
debug: function debug() { |
|
if (_level >= Debug) log(method || 'log', 'DEBUG', arguments); |
|
return this; |
|
} |
|
}; |
|
} |
|
|
|
function mergeConfig() { |
|
for (var _len = arguments.length, configs = new Array(_len), _key = 0; _key < _len; _key++) { |
|
configs[_key] = arguments[_key]; |
|
} |
|
|
|
return configs.reduce(function (out, source) { |
|
for (var 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 |
|
var r = key === 'legend' ? { |
|
'layout': 1 |
|
} : key === 'style' ? true : null; |
|
writeConfig(out, key, source[key], r); |
|
} |
|
} |
|
|
|
return out; |
|
}, {}); |
|
} |
|
|
|
function writeConfig(output, key, value, recurse) { |
|
var 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 { |
|
o[k] = value[k]; |
|
} |
|
} |
|
} else { |
|
output[key] = value; |
|
} |
|
} |
|
|
|
function mergeNamed(a, b) { |
|
if (a == null) return b; |
|
var map = {}, |
|
out = []; |
|
|
|
function add(_) { |
|
if (!map[_.name]) { |
|
map[_.name] = 1; |
|
out.push(_); |
|
} |
|
} |
|
|
|
b.forEach(add); |
|
a.forEach(add); |
|
return out; |
|
} |
|
|
|
function peek(array) { |
|
return array[array.length - 1]; |
|
} |
|
|
|
function toNumber(_) { |
|
return _ == null || _ === '' ? null : +_; |
|
} |
|
|
|
function exp(sign) { |
|
return function (x) { |
|
return sign * Math.exp(x); |
|
}; |
|
} |
|
|
|
function log$1(sign) { |
|
return function (x) { |
|
return Math.log(sign * x); |
|
}; |
|
} |
|
|
|
function symlog(c) { |
|
return function (x) { |
|
return Math.sign(x) * Math.log1p(Math.abs(x / c)); |
|
}; |
|
} |
|
|
|
function symexp(c) { |
|
return function (x) { |
|
return Math.sign(x) * Math.expm1(Math.abs(x)) * c; |
|
}; |
|
} |
|
|
|
function pow(exponent) { |
|
return function (x) { |
|
return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent); |
|
}; |
|
} |
|
|
|
function pan(domain, delta, lift, ground) { |
|
var d0 = lift(domain[0]), |
|
d1 = lift(peek(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$1(sign), exp(sign)); |
|
} |
|
|
|
function panPow(domain, delta, exponent) { |
|
return pan(domain, delta, pow(exponent), pow(1 / exponent)); |
|
} |
|
|
|
function panSymlog(domain, delta, constant) { |
|
return pan(domain, delta, symlog(constant), symexp(constant)); |
|
} |
|
|
|
function zoom(domain, anchor, scale, lift, ground) { |
|
var d0 = lift(domain[0]), |
|
d1 = lift(peek(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) { |
|
var sign = Math.sign(domain[0]); |
|
return zoom(domain, anchor, scale, log$1(sign), exp(sign)); |
|
} |
|
|
|
function zoomPow(domain, anchor, scale, exponent) { |
|
return zoom(domain, anchor, scale, pow(exponent), pow(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(_) { |
|
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) { |
|
var 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'; |
|
} |
|
|
|
function compare(fields, orders) { |
|
var idx = [], |
|
cmp = (fields = array(fields)).map(function (f, i) { |
|
if (f == null) { |
|
return null; |
|
} else { |
|
idx.push(i); |
|
return isFunction(f) ? f : splitAccessPath(f).map($).join(']['); |
|
} |
|
}), |
|
n = idx.length - 1, |
|
ord = array(orders), |
|
code = 'var u,v;return ', |
|
i, |
|
j, |
|
f, |
|
u, |
|
v, |
|
d, |
|
t, |
|
lt, |
|
gt; |
|
if (n < 0) return null; |
|
|
|
for (j = 0; j <= n; ++j) { |
|
i = idx[j]; |
|
f = cmp[i]; |
|
|
|
if (isFunction(f)) { |
|
d = 'f' + i; |
|
u = '(u=this.' + d + '(a))'; |
|
v = '(v=this.' + d + '(b))'; |
|
(t = t || {})[d] = f; |
|
} else { |
|
u = '(u=a[' + f + '])'; |
|
v = '(v=b[' + f + '])'; |
|
} |
|
|
|
d = '((v=v instanceof Date?+v:v),(u=u instanceof Date?+u:u))'; |
|
|
|
if (ord[i] !== 'descending') { |
|
gt = 1; |
|
lt = -1; |
|
} else { |
|
gt = -1; |
|
lt = 1; |
|
} |
|
|
|
code += '(' + u + '<' + v + '||u==null)&&v!=null?' + lt + ':(u>v||v==null)&&u!=null?' + gt + ':' + d + '!==u&&v===v?' + lt + ':v!==v&&u===u?' + gt + (i < n ? ':' : ':0'); |
|
} |
|
|
|
f = Function('a', 'b', code + ';'); |
|
if (t) f = f.bind(t); |
|
fields = fields.reduce(function (map, field) { |
|
if (isFunction(field)) { |
|
(accessorFields(field) || []).forEach(function (_) { |
|
map[_] = 1; |
|
}); |
|
} else if (field != null) { |
|
map[field + ''] = 1; |
|
} |
|
|
|
return map; |
|
}, {}); |
|
return accessor(f, Object.keys(fields)); |
|
} |
|
|
|
function constant(_) { |
|
return isFunction(_) ? _ : function () { |
|
return _; |
|
}; |
|
} |
|
|
|
function debounce(delay, handler) { |
|
var tid, evt; |
|
|
|
function callback() { |
|
handler(evt); |
|
tid = evt = null; |
|
} |
|
|
|
return function (e) { |
|
evt = e; |
|
if (tid) clearTimeout(tid); |
|
tid = setTimeout(callback, delay); |
|
}; |
|
} |
|
|
|
function extend(_) { |
|
for (var 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) { |
|
var 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) { |
|
var i = -1, |
|
n = array.length, |
|
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]; |
|
} |
|
|
|
var hop = Object.prototype.hasOwnProperty; |
|
|
|
function hasOwnProperty(object, property) { |
|
return hop.call(object, property); |
|
} |
|
|
|
var NULL = {}; |
|
|
|
function fastmap(input) { |
|
var obj = {}, |
|
map, |
|
_test; |
|
|
|
function has(key) { |
|
return hasOwnProperty(obj, key) && obj[key] !== NULL; |
|
} |
|
|
|
map = { |
|
size: 0, |
|
empty: 0, |
|
object: obj, |
|
has: has, |
|
get: function get(key) { |
|
return has(key) ? obj[key] : undefined; |
|
}, |
|
set: function set(key, value) { |
|
if (!has(key)) { |
|
++map.size; |
|
if (obj[key] === NULL) --map.empty; |
|
} |
|
|
|
obj[key] = value; |
|
return this; |
|
}, |
|
delete: function _delete(key) { |
|
if (has(key)) { |
|
--map.size; |
|
++map.empty; |
|
obj[key] = NULL; |
|
} |
|
|
|
return this; |
|
}, |
|
clear: function clear() { |
|
map.size = map.empty = 0; |
|
map.object = obj = {}; |
|
}, |
|
test: function test(_) { |
|
if (arguments.length) { |
|
_test = _; |
|
return map; |
|
} else { |
|
return _test; |
|
} |
|
}, |
|
clean: function clean() { |
|
var next = {}, |
|
size = 0, |
|
key, |
|
value; |
|
|
|
for (key in obj) { |
|
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(function (key) { |
|
map.set(key, input[key]); |
|
}); |
|
return map; |
|
} |
|
|
|
function flush(range, value, threshold, left, right, center) { |
|
if (!threshold && threshold !== 0) return center; |
|
var a = range[0], |
|
b = peek(range), |
|
t = +threshold, |
|
l, |
|
r; // swap endpoints if range is reversed |
|
|
|
if (b < a) { |
|
l = a; |
|
a = b; |
|
b = l; |
|
} // compare value to endpoints |
|
|
|
|
|
l = Math.abs(value - a); |
|
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) { |
|
var proto = child.prototype = Object.create(parent.prototype); |
|
proto.constructor = child; |
|
return proto; |
|
} |
|
/** |
|
* 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) { |
|
var 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(_) { |
|
return typeof _ === 'boolean'; |
|
} |
|
|
|
function isDate(_) { |
|
return Object.prototype.toString.call(_) === '[object Date]'; |
|
} |
|
|
|
function isNumber(_) { |
|
return typeof _ === 'number'; |
|
} |
|
|
|
function isRegExp(_) { |
|
return Object.prototype.toString.call(_) === '[object RegExp]'; |
|
} |
|
|
|
function key(fields, flat) { |
|
if (fields) { |
|
fields = flat ? array(fields).map(function (f) { |
|
return f.replace(/\\(.)/g, '$1'); |
|
}) : array(fields); |
|
} |
|
|
|
var fn = !(fields && fields.length) ? function () { |
|
return ''; |
|
} : Function('_', 'return \'\'+' + fields.map(function (f) { |
|
return '_[' + (flat ? $(f) : splitAccessPath(f).map($).join('][')) + ']'; |
|
}).join('+\'|\'+') + ';'); |
|
return accessor(fn, fields, 'key'); |
|
} |
|
|
|
function lerp(array, frac) { |
|
var lo = array[0], |
|
hi = peek(array), |
|
f = +frac; |
|
return !f ? lo : f === 1 ? hi : lo + f * (hi - lo); |
|
} |
|
|
|
function merge(compare, array0, array1, output) { |
|
var n0 = array0.length, |
|
n1 = array1.length; |
|
if (!n1) return array0; |
|
if (!n0) return array1; |
|
var merged = output || new array0.constructor(n0 + n1), |
|
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) { |
|
var s = ''; |
|
|
|
while (--reps >= 0) { |
|
s += str; |
|
} |
|
|
|
return s; |
|
} |
|
|
|
function pad(str, length, padchar, align) { |
|
var 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(array) - array[0] || 0; |
|
} |
|
|
|
function toBoolean(_) { |
|
return _ == null || _ === '' ? null : !_ || _ === 'false' || _ === '0' ? false : !!_; |
|
} |
|
|
|
function defaultParser(_) { |
|
return isNumber(_) ? _ : isDate(_) ? _ : Date.parse(_); |
|
} |
|
|
|
function toDate(_, parser) { |
|
parser = parser || defaultParser; |
|
return _ == null || _ === '' ? null : parser(_); |
|
} |
|
|
|
function toString(_) { |
|
return _ == null || _ === '' ? null : _ + ''; |
|
} |
|
|
|
function toSet(_) { |
|
for (var s = {}, i = 0, n = _.length; i < n; ++i) { |
|
s[_[i]] = true; |
|
} |
|
|
|
return s; |
|
} |
|
|
|
function truncate(str, length, align, ellipsis) { |
|
var 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) { |
|
var i = 0, |
|
n = array.length, |
|
t; |
|
|
|
if (filter) { |
|
for (; i < n; ++i) { |
|
if (t = filter(array[i])) visitor(t, i, array); |
|
} |
|
} else { |
|
array.forEach(visitor); |
|
} |
|
} |
|
} |
|
|
|
function UniqueList(idFunc) { |
|
var $ = idFunc || identity, |
|
list = [], |
|
ids = {}; |
|
|
|
list.add = function (_) { |
|
var id = $(_); |
|
|
|
if (!ids[id]) { |
|
ids[id] = 1; |
|
list.push(_); |
|
} |
|
|
|
return list; |
|
}; |
|
|
|
list.remove = function (_) { |
|
var id = $(_), |
|
idx; |
|
|
|
if (ids[id]) { |
|
ids[id] = 0; |
|
|
|
if ((idx = list.indexOf(_)) >= 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. |
|
*/ |
|
|
|
|
|
function asyncCallback(_x, _x2) { |
|
return _asyncCallback.apply(this, arguments); |
|
} |
|
|
|
function _asyncCallback() { |
|
_asyncCallback = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee3(df, callback) { |
|
return regeneratorRuntime.wrap(function _callee3$(_context9) { |
|
while (1) { |
|
switch (_context9.prev = _context9.next) { |
|
case 0: |
|
_context9.prev = 0; |
|
_context9.next = 3; |
|
return callback(df); |
|
|
|
case 3: |
|
_context9.next = 8; |
|
break; |
|
|
|
case 5: |
|
_context9.prev = 5; |
|
_context9.t0 = _context9["catch"](0); |
|
df.error(_context9.t0); |
|
|
|
case 8: |
|
case "end": |
|
return _context9.stop(); |
|
} |
|
} |
|
}, _callee3, null, [[0, 5]]); |
|
})); |
|
return _asyncCallback.apply(this, arguments); |
|
} |
|
|
|
var TUPLE_ID_KEY = Symbol('vega_id'), |
|
TUPLE_ID = 1; |
|
/** |
|
* Checks if an input value is a registered tuple. |
|
* @param {*} t - The value to check. |
|
* @return {boolean} True if the input is a tuple, false otherwise. |
|
*/ |
|
|
|
function isTuple(t) { |
|
return !!(t && tupleid(t)); |
|
} |
|
/** |
|
* Returns the id of a tuple. |
|
* @param {object} t - The input tuple. |
|
* @return {*} the tuple id. |
|
*/ |
|
|
|
|
|
function tupleid(t) { |
|
return t[TUPLE_ID_KEY]; |
|
} |
|
/** |
|
* Sets the id of a tuple. |
|
* @param {object} t - The input tuple. |
|
* @param {*} id - The id value to set. |
|
* @return {object} the input tuple. |
|
*/ |
|
|
|
|
|
function setid(t, id) { |
|
t[TUPLE_ID_KEY] = id; |
|
return t; |
|
} |
|
/** |
|
* Ingest an object or value as a data tuple. |
|
* If the input value is an object, an id field will be added to it. For |
|
* efficiency, the input object is modified directly. A copy is not made. |
|
* If the input value is a literal, it will be wrapped in a new object |
|
* instance, with the value accessible as the 'data' property. |
|
* @param datum - The value to ingest. |
|
* @return {object} The ingested data tuple. |
|
*/ |
|
|
|
|
|
function ingest(datum) { |
|
var t = datum === Object(datum) ? datum : { |
|
data: datum |
|
}; |
|
return tupleid(t) ? t : setid(t, TUPLE_ID++); |
|
} |
|
/** |
|
* Given a source tuple, return a derived copy. |
|
* @param {object} t - The source tuple. |
|
* @return {object} The derived tuple. |
|
*/ |
|
|
|
|
|
function derive(t) { |
|
return rederive(t, ingest({})); |
|
} |
|
/** |
|
* Rederive a derived tuple by copying values from the source tuple. |
|
* @param {object} t - The source tuple. |
|
* @param {object} d - The derived tuple. |
|
* @return {object} The derived tuple. |
|
*/ |
|
|
|
|
|
function rederive(t, d) { |
|
for (var k in t) { |
|
d[k] = t[k]; |
|
} |
|
|
|
return d; |
|
} |
|
/** |
|
* Replace an existing tuple with a new tuple. |
|
* @param {object} t - The existing data tuple. |
|
* @param {object} d - The new tuple that replaces the old. |
|
* @return {object} The new tuple. |
|
*/ |
|
|
|
|
|
function replace(t, d) { |
|
return setid(d, tupleid(t)); |
|
} |
|
/** |
|
* Generate an augmented comparator function that provides stable |
|
* sorting by tuple id when the given comparator produces ties. |
|
* @param {function} cmp - The comparator to augment. |
|
* @param {function} [f] - Optional tuple accessor function. |
|
* @return {function} An augmented comparator function. |
|
*/ |
|
|
|
|
|
function stableCompare(cmp, f) { |
|
return !cmp ? null : f ? function (a, b) { |
|
return cmp(a, b) || tupleid(f(a)) - tupleid(f(b)); |
|
} : function (a, b) { |
|
return cmp(a, b) || tupleid(a) - tupleid(b); |
|
}; |
|
} |
|
|
|
function isChangeSet(v) { |
|
return v && v.constructor === changeset; |
|
} |
|
|
|
function changeset() { |
|
var add = [], |
|
// insert tuples |
|
rem = [], |
|
// remove tuples |
|
mod = [], |
|
// modify tuples |
|
remp = [], |
|
// remove by predicate |
|
modp = [], |
|
// modify by predicate |
|
_reflow = false; |
|
return { |
|
constructor: changeset, |
|
insert: function insert(t) { |
|
var d = array(t), |
|
i = 0, |
|
n = d.length; |
|
|
|
for (; i < n; ++i) { |
|
add.push(d[i]); |
|
} |
|
|
|
return this; |
|
}, |
|
remove: function remove(t) { |
|
var a = isFunction(t) ? remp : rem, |
|
d = array(t), |
|
i = 0, |
|
n = d.length; |
|
|
|
for (; i < n; ++i) { |
|
a.push(d[i]); |
|
} |
|
|
|
return this; |
|
}, |
|
modify: function modify(t, field, value) { |
|
var m = { |
|
field: field, |
|
value: constant(value) |
|
}; |
|
|
|
if (isFunction(t)) { |
|
m.filter = t; |
|
modp.push(m); |
|
} else { |
|
m.tuple = t; |
|
mod.push(m); |
|
} |
|
|
|
return this; |
|
}, |
|
encode: function encode(t, set) { |
|
if (isFunction(t)) modp.push({ |
|
filter: t, |
|
field: set |
|
});else mod.push({ |
|
tuple: t, |
|
field: set |
|
}); |
|
return this; |
|
}, |
|
reflow: function reflow() { |
|
_reflow = true; |
|
return this; |
|
}, |
|
pulse: function pulse(_pulse, tuples) { |
|
var cur = {}, |
|
out = {}, |
|
i, |
|
n, |
|
m, |
|
f, |
|
t, |
|
id; // build lookup table of current tuples |
|
|
|
for (i = 0, n = tuples.length; i < n; ++i) { |
|
cur[tupleid(tuples[i])] = 1; |
|
} // process individual tuples to remove |
|
|
|
|
|
for (i = 0, n = rem.length; i < n; ++i) { |
|
t = rem[i]; |
|
cur[tupleid(t)] = -1; |
|
} // process predicate-based removals |
|
|
|
|
|
for (i = 0, n = remp.length; i < n; ++i) { |
|
f = remp[i]; |
|
tuples.forEach(function (t) { |
|
if (f(t)) cur[tupleid(t)] = -1; |
|
}); |
|
} // process all add tuples |
|
|
|
|
|
for (i = 0, n = add.length; i < n; ++i) { |
|
t = add[i]; |
|
id = tupleid(t); |
|
|
|
if (cur[id]) { |
|
// tuple already resides in dataset |
|
// if flagged for both add and remove, cancel |
|
cur[id] = 1; |
|
} else { |
|
// tuple does not reside in dataset, add |
|
_pulse.add.push(ingest(add[i])); |
|
} |
|
} // populate pulse rem list |
|
|
|
|
|
for (i = 0, n = tuples.length; i < n; ++i) { |
|
t = tuples[i]; |
|
if (cur[tupleid(t)] < 0) _pulse.rem.push(t); |
|
} // modify helper method |
|
|
|
|
|
function modify(t, f, v) { |
|
if (v) { |
|
t[f] = v(t); |
|
} else { |
|
_pulse.encode = f; |
|
} |
|
|
|
if (!_reflow) out[tupleid(t)] = t; |
|
} // process individual tuples to modify |
|
|
|
|
|
for (i = 0, n = mod.length; i < n; ++i) { |
|
m = mod[i]; |
|
t = m.tuple; |
|
f = m.field; |
|
id = cur[tupleid(t)]; |
|
|
|
if (id > 0) { |
|
modify(t, f, m.value); |
|
|
|
_pulse.modifies(f); |
|
} |
|
} // process predicate-based modifications |
|
|
|
|
|
for (i = 0, n = modp.length; i < n; ++i) { |
|
m = modp[i]; |
|
f = m.filter; |
|
tuples.forEach(function (t) { |
|
if (f(t) && cur[tupleid(t)] > 0) { |
|
modify(t, m.field, m.value); |
|
} |
|
}); |
|
|
|
_pulse.modifies(m.field); |
|
} // upon reflow request, populate mod with all non-removed tuples |
|
// otherwise, populate mod with modified tuples only |
|
|
|
|
|
if (_reflow) { |
|
_pulse.mod = rem.length || remp.length ? tuples.filter(function (t) { |
|
return cur[tupleid(t)] > 0; |
|
}) : tuples.slice(); |
|
} else { |
|
for (id in out) { |
|
_pulse.mod.push(out[id]); |
|
} |
|
} |
|
|
|
return _pulse; |
|
} |
|
}; |
|
} |
|
|
|
var CACHE = '_:mod:_'; |
|
/** |
|
* Hash that tracks modifications to assigned values. |
|
* Callers *must* use the set method to update values. |
|
*/ |
|
|
|
function Parameters() { |
|
Object.defineProperty(this, CACHE, { |
|
writable: true, |
|
value: {} |
|
}); |
|
} |
|
|
|
var prototype = Parameters.prototype; |
|
/** |
|
* Set a parameter value. If the parameter value changes, the parameter |
|
* will be recorded as modified. |
|
* @param {string} name - The parameter name. |
|
* @param {number} index - The index into an array-value parameter. Ignored if |
|
* the argument is undefined, null or less than zero. |
|
* @param {*} value - The parameter value to set. |
|
* @param {boolean} [force=false] - If true, records the parameter as modified |
|
* even if the value is unchanged. |
|
* @return {Parameters} - This parameter object. |
|
*/ |
|
|
|
prototype.set = function (name, index, value, force) { |
|
var o = this, |
|
v = o[name], |
|
mod = o[CACHE]; |
|
|
|
if (index != null && index >= 0) { |
|
if (v[index] !== value || force) { |
|
v[index] = value; |
|
mod[index + ':' + name] = -1; |
|
mod[name] = -1; |
|
} |
|
} else if (v !== value || force) { |
|
o[name] = value; |
|
mod[name] = isArray(value) ? 1 + value.length : -1; |
|
} |
|
|
|
return o; |
|
}; |
|
/** |
|
* Tests if one or more parameters has been modified. If invoked with no |
|
* arguments, returns true if any parameter value has changed. If the first |
|
* argument is array, returns trues if any parameter name in the array has |
|
* changed. Otherwise, tests if the given name and optional array index has |
|
* changed. |
|
* @param {string} name - The parameter name to test. |
|
* @param {number} [index=undefined] - The parameter array index to test. |
|
* @return {boolean} - Returns true if a queried parameter was modified. |
|
*/ |
|
|
|
|
|
prototype.modified = function (name, index) { |
|
var mod = this[CACHE], |
|
k; |
|
|
|
if (!arguments.length) { |
|
for (k in mod) { |
|
if (mod[k]) return true; |
|
} |
|
|
|
return false; |
|
} else if (isArray(name)) { |
|
for (k = 0; k < name.length; ++k) { |
|
if (mod[name[k]]) return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
return index != null && index >= 0 ? index + 1 < mod[name] || !!mod[index + ':' + name] : !!mod[name]; |
|
}; |
|
/** |
|
* Clears the modification records. After calling this method, |
|
* all parameters are considered unmodified. |
|
*/ |
|
|
|
|
|
prototype.clear = function () { |
|
this[CACHE] = {}; |
|
return this; |
|
}; |
|
|
|
var OP_ID = 0; |
|
var PULSE = 'pulse'; |
|
var NO_PARAMS = new Parameters(); // Boolean Flags |
|
|
|
var SKIP = 1, |
|
MODIFIED = 2; |
|
/** |
|
* An Operator is a processing node in a dataflow graph. |
|
* Each operator stores a value and an optional value update function. |
|
* Operators can accept a hash of named parameters. Parameter values can |
|
* either be direct (JavaScript literals, arrays, objects) or indirect |
|
* (other operators whose values will be pulled dynamically). Operators |
|
* included as parameters will have this operator added as a dependency. |
|
* @constructor |
|
* @param {*} [init] - The initial value for this operator. |
|
* @param {function(object, Pulse)} [update] - An update function. Upon |
|
* evaluation of this operator, the update function will be invoked and the |
|
* return value will be used as the new value of this operator. |
|
* @param {object} [params] - The parameters for this operator. |
|
* @param {boolean} [react=true] - Flag indicating if this operator should |
|
* listen for changes to upstream operators included as parameters. |
|
* @see parameters |
|
*/ |
|
|
|
function Operator(init, update, params, react) { |
|
this.id = ++OP_ID; |
|
this.value = init; |
|
this.stamp = -1; |
|
this.rank = -1; |
|
this.qrank = -1; |
|
this.flags = 0; |
|
|
|
if (update) { |
|
this._update = update; |
|
} |
|
|
|
if (params) this.parameters(params, react); |
|
} |
|
|
|
var prototype$1 = Operator.prototype; |
|
/** |
|
* Returns a list of target operators dependent on this operator. |
|
* If this list does not exist, it is created and then returned. |
|
* @return {UniqueList} |
|
*/ |
|
|
|
prototype$1.targets = function () { |
|
return this._targets || (this._targets = UniqueList(id)); |
|
}; |
|
/** |
|
* Sets the value of this operator. |
|
* @param {*} value - the value to set. |
|
* @return {Number} Returns 1 if the operator value has changed |
|
* according to strict equality, returns 0 otherwise. |
|
*/ |
|
|
|
|
|
prototype$1.set = function (value) { |
|
if (this.value !== value) { |
|
this.value = value; |
|
return 1; |
|
} else { |
|
return 0; |
|
} |
|
}; |
|
|
|
function flag(bit) { |
|
return function (state) { |
|
var f = this.flags; |
|
if (arguments.length === 0) return !!(f & bit); |
|
this.flags = state ? f | bit : f & ~bit; |
|
return this; |
|
}; |
|
} |
|
/** |
|
* Indicates that operator evaluation should be skipped on the next pulse. |
|
* This operator will still propagate incoming pulses, but its update function |
|
* will not be invoked. The skip flag is reset after every pulse, so calling |
|
* this method will affect processing of the next pulse only. |
|
*/ |
|
|
|
|
|
prototype$1.skip = flag(SKIP); |
|
/** |
|
* Indicates that this operator's value has been modified on its most recent |
|
* pulse. Normally modification is checked via strict equality; however, in |
|
* some cases it is more efficient to update the internal state of an object. |
|
* In those cases, the modified flag can be used to trigger propagation. Once |
|
* set, the modification flag persists across pulses until unset. The flag can |
|
* be used with the last timestamp to test if a modification is recent. |
|
*/ |
|
|
|
prototype$1.modified = flag(MODIFIED); |
|
/** |
|
* Sets the parameters for this operator. The parameter values are analyzed for |
|
* operator instances. If found, this operator will be added as a dependency |
|
* of the parameterizing operator. Operator values are dynamically marshalled |
|
* from each operator parameter prior to evaluation. If a parameter value is |
|
* an array, the array will also be searched for Operator instances. However, |
|
* the search does not recurse into sub-arrays or object properties. |
|
* @param {object} params - A hash of operator parameters. |
|
* @param {boolean} [react=true] - A flag indicating if this operator should |
|
* automatically update (react) when parameter values change. In other words, |
|
* this flag determines if the operator registers itself as a listener on |
|
* any upstream operators included in the parameters. |
|
* @param {boolean} [initonly=false] - A flag indicating if this operator |
|
* should calculate an update only upon its initiatal evaluation, then |
|
* deregister dependencies and suppress all future update invocations. |
|
* @return {Operator[]} - An array of upstream dependencies. |
|
*/ |
|
|
|
prototype$1.parameters = function (params, react, initonly) { |
|
react = react !== false; |
|
var self = this, |
|
argval = self._argval = self._argval || new Parameters(), |
|
argops = self._argops = self._argops || [], |
|
deps = [], |
|
name, |
|
value, |
|
n, |
|
i; |
|
|
|
function add(name, index, value) { |
|
if (value instanceof Operator) { |
|
if (value !== self) { |
|
if (react) value.targets().add(self); |
|
deps.push(value); |
|
} |
|
|
|
argops.push({ |
|
op: value, |
|
name: name, |
|
index: index |
|
}); |
|
} else { |
|
argval.set(name, index, value); |
|
} |
|
} |
|
|
|
for (name in params) { |
|
value = params[name]; |
|
|
|
if (name === PULSE) { |
|
array(value).forEach(function (op) { |
|
if (!(op instanceof Operator)) { |
|
error('Pulse parameters must be operator instances.'); |
|
} else if (op !== self) { |
|
op.targets().add(self); |
|
deps.push(op); |
|
} |
|
}); |
|
self.source = value; |
|
} else if (isArray(value)) { |
|
argval.set(name, -1, Array(n = value.length)); |
|
|
|
for (i = 0; i < n; ++i) { |
|
add(name, i, value[i]); |
|
} |
|
} else { |
|
add(name, -1, value); |
|
} |
|
} |
|
|
|
this.marshall().clear(); // initialize values |
|
|
|
if (initonly) argops.initonly = true; |
|
return deps; |
|
}; |
|
/** |
|
* Internal method for marshalling parameter values. |
|
* Visits each operator dependency to pull the latest value. |
|
* @return {Parameters} A Parameters object to pass to the update function. |
|
*/ |
|
|
|
|
|
prototype$1.marshall = function (stamp) { |
|
var argval = this._argval || NO_PARAMS, |
|
argops = this._argops, |
|
item, |
|
i, |
|
n, |
|
op, |
|
mod; |
|
|
|
if (argops) { |
|
for (i = 0, n = argops.length; i < n; ++i) { |
|
item = argops[i]; |
|
op = item.op; |
|
mod = op.modified() && op.stamp === stamp; |
|
argval.set(item.name, item.index, op.value, mod); |
|
} |
|
|
|
if (argops.initonly) { |
|
for (i = 0; i < n; ++i) { |
|
item = argops[i]; |
|
item.op.targets().remove(this); |
|
} |
|
|
|
this._argops = null; |
|
this._update = null; |
|
} |
|
} |
|
|
|
return argval; |
|
}; |
|
/** |
|
* Delegate method to perform operator processing. |
|
* Subclasses can override this method to perform custom processing. |
|
* By default, it marshalls parameters and calls the update function |
|
* if that function is defined. If the update function does not |
|
* change the operator value then StopPropagation is returned. |
|
* If no update function is defined, this method does nothing. |
|
* @param {Pulse} pulse - the current dataflow pulse. |
|
* @return The output pulse or StopPropagation. A falsy return value |
|
* (including undefined) will let the input pulse pass through. |
|
*/ |
|
|
|
|
|
prototype$1.evaluate = function (pulse) { |
|
var update = this._update; |
|
|
|
if (update) { |
|
var params = this.marshall(pulse.stamp), |
|
v = update.call(this, params, pulse); |
|
params.clear(); |
|
|
|
if (v !== this.value) { |
|
this.value = v; |
|
} else if (!this.modified()) { |
|
return pulse.StopPropagation; |
|
} |
|
} |
|
}; |
|
/** |
|
* Run this operator for the current pulse. If this operator has already |
|
* been run at (or after) the pulse timestamp, returns StopPropagation. |
|
* Internally, this method calls {@link evaluate} to perform processing. |
|
* If {@link evaluate} returns a falsy value, the input pulse is returned. |
|
* This method should NOT be overridden, instead overrride {@link evaluate}. |
|
* @param {Pulse} pulse - the current dataflow pulse. |
|
* @return the output pulse for this operator (or StopPropagation) |
|
*/ |
|
|
|
|
|
prototype$1.run = function (pulse) { |
|
if (pulse.stamp < this.stamp) return pulse.StopPropagation; |
|
var rv; |
|
|
|
if (this.skip()) { |
|
this.skip(false); |
|
rv = 0; |
|
} else { |
|
rv = this.evaluate(pulse); |
|
} |
|
|
|
return this.pulse = rv || pulse; |
|
}; |
|
/** |
|
* Add an operator to the dataflow graph. This function accepts a |
|
* variety of input argument types. The basic signature supports an |
|
* initial value, update function and parameters. If the first parameter |
|
* is an Operator instance, it will be added directly. If it is a |
|
* constructor for an Operator subclass, a new instance will be instantiated. |
|
* Otherwise, if the first parameter is a function instance, it will be used |
|
* as the update function and a null initial value is assumed. |
|
* @param {*} init - One of: the operator to add, the initial value of |
|
* the operator, an operator class to instantiate, or an update function. |
|
* @param {function} [update] - The operator update function. |
|
* @param {object} [params] - The operator parameters. |
|
* @param {boolean} [react=true] - Flag indicating if this operator should |
|
* listen for changes to upstream operators included as parameters. |
|
* @return {Operator} - The added operator. |
|
*/ |
|
|
|
|
|
function add(init, update, params, react) { |
|
var shift = 1, |
|
op; |
|
|
|
if (init instanceof Operator) { |
|
op = init; |
|
} else if (init && init.prototype instanceof Operator) { |
|
op = new init(); |
|
} else if (isFunction(init)) { |
|
op = new Operator(null, init); |
|
} else { |
|
shift = 0; |
|
op = new Operator(init, update); |
|
} |
|
|
|
this.rank(op); |
|
|
|
if (shift) { |
|
react = params; |
|
params = update; |
|
} |
|
|
|
if (params) this.connect(op, op.parameters(params, react)); |
|
this.touch(op); |
|
return op; |
|
} |
|
/** |
|
* Connect a target operator as a dependent of source operators. |
|
* If necessary, this method will rerank the target operator and its |
|
* dependents to ensure propagation proceeds in a topologically sorted order. |
|
* @param {Operator} target - The target operator. |
|
* @param {Array<Operator>} - The source operators that should propagate |
|
* to the target operator. |
|
*/ |
|
|
|
|
|
function connect(target, sources) { |
|
var targetRank = target.rank, |
|
i, |
|
n; |
|
|
|
for (i = 0, n = sources.length; i < n; ++i) { |
|
if (targetRank < sources[i].rank) { |
|
this.rerank(target); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
var STREAM_ID = 0; |
|
/** |
|
* Models an event stream. |
|
* @constructor |
|
* @param {function(Object, number): boolean} [filter] - Filter predicate. |
|
* Events pass through when truthy, events are suppressed when falsy. |
|
* @param {function(Object): *} [apply] - Applied to input events to produce |
|
* new event values. |
|
* @param {function(Object)} [receive] - Event callback function to invoke |
|
* upon receipt of a new event. Use to override standard event processing. |
|
*/ |
|
|
|
function EventStream(filter, apply, receive) { |
|
this.id = ++STREAM_ID; |
|
this.value = null; |
|
if (receive) this.receive = receive; |
|
if (filter) this._filter = filter; |
|
if (apply) this._apply = apply; |
|
} |
|
/** |
|
* Creates a new event stream instance with the provided |
|
* (optional) filter, apply and receive functions. |
|
* @param {function(Object, number): boolean} [filter] - Filter predicate. |
|
* Events pass through when truthy, events are suppressed when falsy. |
|
* @param {function(Object): *} [apply] - Applied to input events to produce |
|
* new event values. |
|
* @see EventStream |
|
*/ |
|
|
|
|
|
function stream(filter, apply, receive) { |
|
return new EventStream(filter, apply, receive); |
|
} |
|
|
|
var prototype$2 = EventStream.prototype; |
|
prototype$2._filter = truthy; |
|
prototype$2._apply = identity; |
|
|
|
prototype$2.targets = function () { |
|
return this._targets || (this._targets = UniqueList(id)); |
|
}; |
|
|
|
prototype$2.consume = function (_) { |
|
if (!arguments.length) return !!this._consume; |
|
this._consume = !!_; |
|
return this; |
|
}; |
|
|
|
prototype$2.receive = function (evt) { |
|
if (this._filter(evt)) { |
|
var val = this.value = this._apply(evt), |
|
trg = this._targets, |
|
n = trg ? trg.length : 0, |
|
i = 0; |
|
|
|
for (; i < n; ++i) { |
|
trg[i].receive(val); |
|
} |
|
|
|
if (this._consume) { |
|
evt.preventDefault(); |
|
evt.stopPropagation(); |
|
} |
|
} |
|
}; |
|
|
|
prototype$2.filter = function (filter) { |
|
var s = stream(filter); |
|
this.targets().add(s); |
|
return s; |
|
}; |
|
|
|
prototype$2.apply = function (apply) { |
|
var s = stream(null, apply); |
|
this.targets().add(s); |
|
return s; |
|
}; |
|
|
|
prototype$2.merge = function () { |
|
var s = stream(); |
|
this.targets().add(s); |
|
|
|
for (var i = 0, n = arguments.length; i < n; ++i) { |
|
arguments[i].targets().add(s); |
|
} |
|
|
|
return s; |
|
}; |
|
|
|
prototype$2.throttle = function (pause) { |
|
var t = -1; |
|
return this.filter(function () { |
|
var now = Date.now(); |
|
|
|
if (now - t > pause) { |
|
t = now; |
|
return 1; |
|
} else { |
|
return 0; |
|
} |
|
}); |
|
}; |
|
|
|
prototype$2.debounce = function (delay) { |
|
var s = stream(); |
|
this.targets().add(stream(null, null, debounce(delay, function (e) { |
|
var df = e.dataflow; |
|
s.receive(e); |
|
if (df && df.run) df.run(); |
|
}))); |
|
return s; |
|
}; |
|
|
|
prototype$2.between = function (a, b) { |
|
var active = false; |
|
a.targets().add(stream(null, null, function () { |
|
active = true; |
|
})); |
|
b.targets().add(stream(null, null, function () { |
|
active = false; |
|
})); |
|
return this.filter(function () { |
|
return active; |
|
}); |
|
}; |
|
/** |
|
* Create a new event stream from an event source. |
|
* @param {object} source - The event source to monitor. The input must |
|
* support the addEventListener method. |
|
* @param {string} type - The event type. |
|
* @param {function(object): boolean} [filter] - Event filter function. |
|
* @param {function(object): *} [apply] - Event application function. |
|
* If provided, this function will be invoked and the result will be |
|
* used as the downstream event value. |
|
* @return {EventStream} |
|
*/ |
|
|
|
|
|
function events(source, type, filter, apply) { |
|
var df = this, |
|
s = stream(filter, apply), |
|
send = function send(e) { |
|
e.dataflow = df; |
|
|
|
try { |
|
s.receive(e); |
|
} catch (error) { |
|
df.error(error); |
|
} finally { |
|
df.run(); |
|
} |
|
}, |
|
sources; |
|
|
|
if (typeof source === 'string' && typeof document !== 'undefined') { |
|
sources = document.querySelectorAll(source); |
|
} else { |
|
sources = array(source); |
|
} |
|
|
|
for (var i = 0, n = sources.length; i < n; ++i) { |
|
sources[i].addEventListener(type, send); |
|
} |
|
|
|
return s; |
|
} // Matches absolute URLs with optional protocol |
|
// https://... file://... //... |
|
|
|
|
|
var protocol_re = /^([A-Za-z]+:)?\/\//; // Matches allowed URIs. From https://github.com/cure53/DOMPurify/blob/master/src/regexp.js with added file:// |
|
|
|
var allowed_re = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|file):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape |
|
|
|
var 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 |
|
|
|
var 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 {function} fetch - The Fetch API for HTTP network requests. |
|
* If null or undefined, HTTP loading will be disabled. |
|
* @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(fetch, fs) { |
|
return function (options) { |
|
return { |
|
options: options || {}, |
|
sanitize: sanitize, |
|
load: load, |
|
fileAccess: !!fs, |
|
file: fileLoader(fs), |
|
http: httpLoader(fetch) |
|
}; |
|
}; |
|
} |
|
/** |
|
* 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. |
|
*/ |
|
|
|
|
|
function load(_x3, _x4) { |
|
return _load.apply(this, arguments); |
|
} |
|
/** |
|
* URI sanitizer function. |
|
* @param {string} uri - The uri (url or filename) to sanity 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. |
|
*/ |
|
|
|
|
|
function _load() { |
|
_load = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee4(uri, options) { |
|
var opt, url; |
|
return regeneratorRuntime.wrap(function _callee4$(_context10) { |
|
while (1) { |
|
switch (_context10.prev = _context10.next) { |
|
case 0: |
|
_context10.next = 2; |
|
return this.sanitize(uri, options); |
|
|
|
case 2: |
|
opt = _context10.sent; |
|
url = opt.href; |
|
return _context10.abrupt("return", opt.localFile ? this.file(url) : this.http(url, options)); |
|
|
|
case 5: |
|
case "end": |
|
return _context10.stop(); |
|
} |
|
} |
|
}, _callee4, this); |
|
})); |
|
return _load.apply(this, arguments); |
|
} |
|
|
|
function sanitize(_x5, _x6) { |
|
return _sanitize.apply(this, arguments); |
|
} |
|
/** |
|
* 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 _sanitize() { |
|
_sanitize = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee5(uri, options) { |
|
var fileAccess, result, isFile, loadFile, base, isAllowed, hasProtocol; |
|
return regeneratorRuntime.wrap(function _callee5$(_context11) { |
|
while (1) { |
|
switch (_context11.prev = _context11.next) { |
|
case 0: |
|
options = extend({}, this.options, options); |
|
fileAccess = this.fileAccess, result = { |
|
href: null |
|
}; |
|
isAllowed = allowed_re.test(uri.replace(whitespace_re, '')); |
|
|
|
if (uri == null || typeof uri !== 'string' || !isAllowed) { |
|
error('Sanitize failure, invalid URI: ' + $(uri)); |
|
} |
|
|
|
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[base.length - 1] !== '/') { |
|
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 + ''; |
|
} // return |
|
|
|
|
|
return _context11.abrupt("return", result); |
|
|
|
case 13: |
|
case "end": |
|
return _context11.stop(); |
|
} |
|
} |
|
}, _callee5, this); |
|
})); |
|
return _sanitize.apply(this, arguments); |
|
} |
|
|
|
function fileLoader(fs) { |
|
return fs ? function (filename) { |
|
return new Promise(function (accept, reject) { |
|
fs.readFile(filename, function (error, data) { |
|
if (error) reject(error);else accept(data); |
|
}); |
|
}); |
|
} : fileReject; |
|
} |
|
/** |
|
* Default file system loader that simply rejects. |
|
*/ |
|
|
|
|
|
function fileReject() { |
|
return _fileReject.apply(this, arguments); |
|
} |
|
/** |
|
* HTTP request handler factory. |
|
* @param {function} fetch - The Fetch API method. |
|
* @return {function} - An http loader with the following signature: |
|
* param {string} url - The url to request. |
|
* param {object} options - An options hash. |
|
* return {Promise} - A promise that resolves to the file contents. |
|
*/ |
|
|
|
|
|
function _fileReject() { |
|
_fileReject = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee6() { |
|
return regeneratorRuntime.wrap(function _callee6$(_context12) { |
|
while (1) { |
|
switch (_context12.prev = _context12.next) { |
|
case 0: |
|
error('No file system access.'); |
|
|
|
case 1: |
|
case "end": |
|
return _context12.stop(); |
|
} |
|
} |
|
}, _callee6); |
|
})); |
|
return _fileReject.apply(this, arguments); |
|
} |
|
|
|
function httpLoader(fetch) { |
|
return fetch ? |
|
/*#__PURE__*/ |
|
function () { |
|
var _ref = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee(url, options) { |
|
var opt, type, response; |
|
return regeneratorRuntime.wrap(function _callee$(_context) { |
|
while (1) { |
|
switch (_context.prev = _context.next) { |
|
case 0: |
|
opt = extend({}, this.options.http, options); |
|
type = options && options.response; |
|
_context.next = 4; |
|
return fetch(url, opt); |
|
|
|
case 4: |
|
response = _context.sent; |
|
return _context.abrupt("return", !response.ok ? error(response.status + '' + response.statusText) : isFunction(response[type]) ? response[type]() : response.text()); |
|
|
|
case 6: |
|
case "end": |
|
return _context.stop(); |
|
} |
|
} |
|
}, _callee, this); |
|
})); |
|
|
|
return function (_x7, _x8) { |
|
return _ref.apply(this, arguments); |
|
}; |
|
}() : httpReject; |
|
} |
|
/** |
|
* Default http request handler that simply rejects. |
|
*/ |
|
|
|
|
|
function httpReject() { |
|
return _httpReject.apply(this, arguments); |
|
} |
|
|
|
function _httpReject() { |
|
_httpReject = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee7() { |
|
return regeneratorRuntime.wrap(function _callee7$(_context13) { |
|
while (1) { |
|
switch (_context13.prev = _context13.next) { |
|
case 0: |
|
error('No HTTP fetch method available.'); |
|
|
|
case 1: |
|
case "end": |
|
return _context13.stop(); |
|
} |
|
} |
|
}, _callee7); |
|
})); |
|
return _httpReject.apply(this, arguments); |
|
} |
|
|
|
var typeParsers = { |
|
boolean: toBoolean, |
|
integer: toNumber, |
|
number: toNumber, |
|
date: toDate, |
|
string: toString, |
|
unknown: identity |
|
}; |
|
var typeTests = [isBoolean$1, isInteger, isNumber$1, isDate$1]; |
|
var typeList = ['boolean', 'integer', 'number', 'date']; |
|
|
|
function inferType(values, field) { |
|
if (!values || !values.length) return 'unknown'; |
|
var value, |
|
i, |
|
j, |
|
t = 0, |
|
n = values.length, |
|
m = typeTests.length, |
|
a = typeTests.map(function (_, i) { |
|
return i + 1; |
|
}); |
|
|
|
for (i = 0, n = values.length; 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'; |
|
} |
|
} |
|
} |
|
|
|
t = a.reduce(function (u, v) { |
|
return u === 0 ? v : u; |
|
}, 0) - 1; |
|
return typeList[t]; |
|
} |
|
|
|
function inferTypes(data, fields) { |
|
return fields.reduce(function (types, field) { |
|
types[field] = inferType(data, field); |
|
return types; |
|
}, {}); |
|
} // -- Type Checks ---- |
|
|
|
|
|
function isValid(_) { |
|
return _ != null && _ === _; |
|
} |
|
|
|
function isBoolean$1(_) { |
|
return _ === 'true' || _ === 'false' || _ === true || _ === false; |
|
} |
|
|
|
function isDate$1(_) { |
|
return !Number.isNaN(Date.parse(_)); |
|
} |
|
|
|
function isNumber$1(_) { |
|
return !Number.isNaN(+_) && !(_ instanceof Date); |
|
} |
|
|
|
function isInteger(_) { |
|
return isNumber$1(_) && Number.isInteger(+_); |
|
} |
|
|
|
var EOL = {}, |
|
EOF = {}, |
|
QUOTE = 34, |
|
NEWLINE = 10, |
|
RETURN = 13; |
|
|
|
function objectConverter(columns) { |
|
return new Function("d", "return {" + columns.map(function (name, i) { |
|
return JSON.stringify(name) + ": d[" + i + "] || \"\""; |
|
}).join(",") + "}"); |
|
} |
|
|
|
function customConverter(columns, f) { |
|
var object = objectConverter(columns); |
|
return function (row, i) { |
|
return f(object(row), i, columns); |
|
}; |
|
} // Compute unique columns in order of discovery. |
|
|
|
|
|
function inferColumns(rows) { |
|
var columnSet = Object.create(null), |
|
columns = []; |
|
rows.forEach(function (row) { |
|
for (var column in row) { |
|
if (!(column in columnSet)) { |
|
columns.push(columnSet[column] = column); |
|
} |
|
} |
|
}); |
|
return columns; |
|
} |
|
|
|
function pad$1(value, width) { |
|
var s = value + "", |
|
length = s.length; |
|
return length < width ? new Array(width - length + 1).join(0) + s : s; |
|
} |
|
|
|
function formatYear(year) { |
|
return year < 0 ? "-" + pad$1(-year, 6) : year > 9999 ? "+" + pad$1(year, 6) : pad$1(year, 4); |
|
} |
|
|
|
function formatDate(date) { |
|
var hours = date.getUTCHours(), |
|
minutes = date.getUTCMinutes(), |
|
seconds = date.getUTCSeconds(), |
|
milliseconds = date.getUTCMilliseconds(); |
|
return isNaN(date) ? "Invalid Date" : formatYear(date.getUTCFullYear()) + "-" + pad$1(date.getUTCMonth() + 1, 2) + "-" + pad$1(date.getUTCDate(), 2) + (milliseconds ? "T" + pad$1(hours, 2) + ":" + pad$1(minutes, 2) + ":" + pad$1(seconds, 2) + "." + pad$1(milliseconds, 3) + "Z" : seconds ? "T" + pad$1(hours, 2) + ":" + pad$1(minutes, 2) + ":" + pad$1(seconds, 2) + "Z" : minutes || hours ? "T" + pad$1(hours, 2) + ":" + pad$1(minutes, 2) + "Z" : ""); |
|
} |
|
|
|
function dsvFormat(delimiter) { |
|
var reFormat = new RegExp("[\"" + delimiter + "\n\r]"), |
|
DELIMITER = delimiter.charCodeAt(0); |
|
|
|
function parse(text, f) { |
|
var convert, |
|
columns, |
|
rows = parseRows(text, function (row, i) { |
|
if (convert) return convert(row, i - 1); |
|
columns = row, convert = f ? customConverter(row, f) : objectConverter(row); |
|
}); |
|
rows.columns = columns || []; |
|
return rows; |
|
} |
|
|
|
function parseRows(text, f) { |
|
var rows = [], |
|
// output rows |
|
N = text.length, |
|
I = 0, |
|
// current character index |
|
n = 0, |
|
// current line number |
|
t, |
|
// current token |
|
eof = N <= 0, |
|
// current token followed by EOF? |
|
eol = false; // current token followed by EOL? |
|
// Strip the trailing newline. |
|
|
|
if (text.charCodeAt(N - 1) === NEWLINE) --N; |
|
if (text.charCodeAt(N - 1) === RETURN) --N; |
|
|
|
function token() { |
|
if (eof) return EOF; |
|
if (eol) return eol = false, EOL; // Unescape quotes. |
|
|
|
var i, |
|
j = I, |
|
c; |
|
|
|
if (text.charCodeAt(j) === QUOTE) { |
|
while (I++ < N && text.charCodeAt(I) !== QUOTE || text.charCodeAt(++I) === QUOTE) { |
|
; |
|
} |
|
|
|
if ((i = I) >= N) eof = true;else if ((c = text.charCodeAt(I++)) === NEWLINE) eol = true;else if (c === RETURN) { |
|
eol = true; |
|
if (text.charCodeAt(I) === NEWLINE) ++I; |
|
} |
|
return text.slice(j + 1, i - 1).replace(/""/g, "\""); |
|
} // Find next delimiter or newline. |
|
|
|
|
|
while (I < N) { |
|
if ((c = text.charCodeAt(i = I++)) === NEWLINE) eol = true;else if (c === RETURN) { |
|
eol = true; |
|
if (text.charCodeAt(I) === NEWLINE) ++I; |
|
} else if (c !== DELIMITER) continue; |
|
return text.slice(j, i); |
|
} // Return last token before EOF. |
|
|
|
|
|
return eof = true, text.slice(j, N); |
|
} |
|
|
|
while ((t = token()) !== EOF) { |
|
var row = []; |
|
|
|
while (t !== EOL && t !== EOF) { |
|
row.push(t), t = token(); |
|
} |
|
|
|
if (f && (row = f(row, n++)) == null) continue; |
|
rows.push(row); |
|
} |
|
|
|
return rows; |
|
} |
|
|
|
function preformatBody(rows, columns) { |
|
return rows.map(function (row) { |
|
return columns.map(function (column) { |
|
return formatValue(row[column]); |
|
}).join(delimiter); |
|
}); |
|
} |
|
|
|
function format(rows, columns) { |
|
if (columns == null) columns = inferColumns(rows); |
|
return [columns.map(formatValue).join(delimiter)].concat(preformatBody(rows, columns)).join("\n"); |
|
} |
|
|
|
function formatBody(rows, columns) { |
|
if (columns == null) columns = inferColumns(rows); |
|
return preformatBody(rows, columns).join("\n"); |
|
} |
|
|
|
function formatRows(rows) { |
|
return rows.map(formatRow).join("\n"); |
|
} |
|
|
|
function formatRow(row) { |
|
return row.map(formatValue).join(delimiter); |
|
} |
|
|
|
function formatValue(value) { |
|
return value == null ? "" : value instanceof Date ? formatDate(value) : reFormat.test(value += "") ? "\"" + value.replace(/"/g, "\"\"") + "\"" : value; |
|
} |
|
|
|
return { |
|
parse: parse, |
|
parseRows: parseRows, |
|
format: format, |
|
formatBody: formatBody, |
|
formatRows: formatRows, |
|
formatRow: formatRow, |
|
formatValue: formatValue |
|
}; |
|
} |
|
|
|
function delimitedFormat(delimiter) { |
|
var parse = function parse(data, format) { |
|
var 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 dsvFormat(format.delimiter).parse(data + ''); |
|
} |
|
|
|
dsv.responseType = 'text'; |
|
|
|
function isBuffer(_) { |
|
return typeof Buffer === 'function' && isFunction(Buffer.isBuffer) ? Buffer.isBuffer(_) : false; |
|
} |
|
|
|
function json(data, format) { |
|
var prop = format && format.property ? field(format.property) : identity; |
|
return isObject(data) && !isBuffer(data) ? parseJSON(prop(data)) : prop(JSON.parse(data)); |
|
} |
|
|
|
json.responseType = 'json'; |
|
|
|
function parseJSON(data, format) { |
|
return format && format.copy ? JSON.parse(JSON.stringify(data)) : data; |
|
} |
|
|
|
function identity$1(x) { |
|
return x; |
|
} |
|
|
|
function transform(transform) { |
|
if (transform == null) return identity$1; |
|
var x0, |
|
y0, |
|
kx = transform.scale[0], |
|
ky = transform.scale[1], |
|
dx = transform.translate[0], |
|
dy = transform.translate[1]; |
|
return function (input, i) { |
|
if (!i) x0 = y0 = 0; |
|
var j = 2, |
|
n = input.length, |
|
output = new Array(n); |
|
output[0] = (x0 += input[0]) * kx + dx; |
|
output[1] = (y0 += input[1]) * ky + dy; |
|
|
|
while (j < n) { |
|
output[j] = input[j], ++j; |
|
} |
|
|
|
return output; |
|
}; |
|
} |
|
|
|
function reverse(array, n) { |
|
var t, |
|
j = array.length, |
|
i = j - n; |
|
|
|
while (i < --j) { |
|
t = array[i], array[i++] = array[j], array[j] = t; |
|
} |
|
} |
|
|
|
function feature(topology, o) { |
|
if (typeof o === "string") o = topology.objects[o]; |
|
return o.type === "GeometryCollection" ? { |
|
type: "FeatureCollection", |
|
features: o.geometries.map(function (o) { |
|
return feature$1(topology, o); |
|
}) |
|
} : feature$1(topology, o); |
|
} |
|
|
|
function feature$1(topology, o) { |
|
var id = o.id, |
|
bbox = o.bbox, |
|
properties = o.properties == null ? {} : o.properties, |
|
geometry = object(topology, o); |
|
return id == null && bbox == null ? { |
|
type: "Feature", |
|
properties: properties, |
|
geometry: geometry |
|
} : bbox == null ? { |
|
type: "Feature", |
|
id: id, |
|
properties: properties, |
|
geometry: geometry |
|
} : { |
|
type: "Feature", |
|
id: id, |
|
bbox: bbox, |
|
properties: properties, |
|
geometry: geometry |
|
}; |
|
} |
|
|
|
function object(topology, o) { |
|
var transformPoint = transform(topology.transform), |
|
arcs = topology.arcs; |
|
|
|
function arc(i, points) { |
|
if (points.length) points.pop(); |
|
|
|
for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length; k < n; ++k) { |
|
points.push(transformPoint(a[k], k)); |
|
} |
|
|
|
if (i < 0) reverse(points, n); |
|
} |
|
|
|
function point(p) { |
|
return transformPoint(p); |
|
} |
|
|
|
function line(arcs) { |
|
var points = []; |
|
|
|
for (var i = 0, n = arcs.length; i < n; ++i) { |
|
arc(arcs[i], points); |
|
} |
|
|
|
if (points.length < 2) points.push(points[0]); // This should never happen per the specification. |
|
|
|
return points; |
|
} |
|
|
|
function ring(arcs) { |
|
var points = line(arcs); |
|
|
|
while (points.length < 4) { |
|
points.push(points[0]); |
|
} // This may happen if an arc has only two points. |
|
|
|
|
|
return points; |
|
} |
|
|
|
function polygon(arcs) { |
|
return arcs.map(ring); |
|
} |
|
|
|
function geometry(o) { |
|
var type = o.type, |
|
coordinates; |
|
|
|
switch (type) { |
|
case "GeometryCollection": |
|
return { |
|
type: type, |
|
geometries: o.geometries.map(geometry) |
|
}; |
|
|
|
case "Point": |
|
coordinates = point(o.coordinates); |
|
break; |
|
|
|
case "MultiPoint": |
|
coordinates = o.coordinates.map(point); |
|
break; |
|
|
|
case "LineString": |
|
coordinates = line(o.arcs); |
|
break; |
|
|
|
case "MultiLineString": |
|
coordinates = o.arcs.map(line); |
|
break; |
|
|
|
case "Polygon": |
|
coordinates = polygon(o.arcs); |
|
break; |
|
|
|
case "MultiPolygon": |
|
coordinates = o.arcs.map(polygon); |
|
break; |
|
|
|
default: |
|
return null; |
|
} |
|
|
|
return { |
|
type: type, |
|
coordinates: coordinates |
|
}; |
|
} |
|
|
|
return geometry(o); |
|
} |
|
|
|
function stitch(topology, arcs) { |
|
var stitchedArcs = {}, |
|
fragmentByStart = {}, |
|
fragmentByEnd = {}, |
|
fragments = [], |
|
emptyIndex = -1; // Stitch empty arcs first, since they may be subsumed by other arcs. |
|
|
|
arcs.forEach(function (i, j) { |
|
var arc = topology.arcs[i < 0 ? ~i : i], |
|
t; |
|
|
|
if (arc.length < 3 && !arc[1][0] && !arc[1][1]) { |
|
t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t; |
|
} |
|
}); |
|
arcs.forEach(function (i) { |
|
var e = ends(i), |
|
start = e[0], |
|
end = e[1], |
|
f, |
|
g; |
|
|
|
if (f = fragmentByEnd[start]) { |
|
delete fragmentByEnd[f.end]; |
|
f.push(i); |
|
f.end = end; |
|
|
|
if (g = fragmentByStart[end]) { |
|
delete fragmentByStart[g.start]; |
|
var fg = g === f ? f : f.concat(g); |
|
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg; |
|
} else { |
|
fragmentByStart[f.start] = fragmentByEnd[f.end] = f; |
|
} |
|
} else if (f = fragmentByStart[end]) { |
|
delete fragmentByStart[f.start]; |
|
f.unshift(i); |
|
f.start = start; |
|
|
|
if (g = fragmentByEnd[start]) { |
|
delete fragmentByEnd[g.end]; |
|
var gf = g === f ? f : g.concat(f); |
|
fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf; |
|
} else { |
|
fragmentByStart[f.start] = fragmentByEnd[f.end] = f; |
|
} |
|
} else { |
|
f = [i]; |
|
fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f; |
|
} |
|
}); |
|
|
|
function ends(i) { |
|
var arc = topology.arcs[i < 0 ? ~i : i], |
|
p0 = arc[0], |
|
p1; |
|
if (topology.transform) p1 = [0, 0], arc.forEach(function (dp) { |
|
p1[0] += dp[0], p1[1] += dp[1]; |
|
});else p1 = arc[arc.length - 1]; |
|
return i < 0 ? [p1, p0] : [p0, p1]; |
|
} |
|
|
|
function flush(fragmentByEnd, fragmentByStart) { |
|
for (var k in fragmentByEnd) { |
|
var f = fragmentByEnd[k]; |
|
delete fragmentByStart[f.start]; |
|
delete f.start; |
|
delete f.end; |
|
f.forEach(function (i) { |
|
stitchedArcs[i < 0 ? ~i : i] = 1; |
|
}); |
|
fragments.push(f); |
|
} |
|
} |
|
|
|
flush(fragmentByEnd, fragmentByStart); |
|
flush(fragmentByStart, fragmentByEnd); |
|
arcs.forEach(function (i) { |
|
if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]); |
|
}); |
|
return fragments; |
|
} |
|
|
|
function mesh(topology) { |
|
return object(topology, meshArcs.apply(this, arguments)); |
|
} |
|
|
|
function meshArcs(topology, object, filter) { |
|
var arcs, i, n; |
|
if (arguments.length > 1) arcs = extractArcs(topology, object, filter);else for (i = 0, arcs = new Array(n = topology.arcs.length); i < n; ++i) { |
|
arcs[i] = i; |
|
} |
|
return { |
|
type: "MultiLineString", |
|
arcs: stitch(topology, arcs) |
|
}; |
|
} |
|
|
|
function extractArcs(topology, object, filter) { |
|
var arcs = [], |
|
geomsByArc = [], |
|
geom; |
|
|
|
function extract0(i) { |
|
var j = i < 0 ? ~i : i; |
|
(geomsByArc[j] || (geomsByArc[j] = [])).push({ |
|
i: i, |
|
g: geom |
|
}); |
|
} |
|
|
|
function extract1(arcs) { |
|
arcs.forEach(extract0); |
|
} |
|
|
|
function extract2(arcs) { |
|
arcs.forEach(extract1); |
|
} |
|
|
|
function extract3(arcs) { |
|
arcs.forEach(extract2); |
|
} |
|
|
|
function geometry(o) { |
|
switch (geom = o, o.type) { |
|
case "GeometryCollection": |
|
o.geometries.forEach(geometry); |
|
break; |
|
|
|
case "LineString": |
|
extract1(o.arcs); |
|
break; |
|
|
|
case "MultiLineString": |
|
case "Polygon": |
|
extract2(o.arcs); |
|
break; |
|
|
|
case "MultiPolygon": |
|
extract3(o.arcs); |
|
break; |
|
} |
|
} |
|
|
|
geometry(object); |
|
geomsByArc.forEach(filter == null ? function (geoms) { |
|
arcs.push(geoms[0].i); |
|
} : function (geoms) { |
|
if (filter(geoms[0].g, geoms[geoms.length - 1].g)) arcs.push(geoms[0].i); |
|
}); |
|
return arcs; |
|
} |
|
|
|
var filters = { |
|
interior: function interior(a, b) { |
|
return a !== b; |
|
}, |
|
exterior: function exterior(a, b) { |
|
return a === b; |
|
} |
|
}; |
|
|
|
function topojson(data, format) { |
|
var method, object, property, filter; |
|
data = json(data, format); |
|
|
|
if (format && format.feature) { |
|
method = feature; |
|
property = format.feature; |
|
} else if (format && format.mesh) { |
|
method = 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'; |
|
var format = { |
|
dsv: dsv, |
|
csv: delimitedFormat(','), |
|
tsv: delimitedFormat('\t'), |
|
json: json, |
|
topojson: topojson |
|
}; |
|
|
|
function formats(name, reader) { |
|
if (arguments.length > 1) { |
|
format[name] = reader; |
|
return this; |
|
} else { |
|
return hasOwnProperty(format, name) ? format[name] : null; |
|
} |
|
} |
|
|
|
function responseType(type) { |
|
var f = formats(type); |
|
return f && f.responseType || 'text'; |
|
} |
|
|
|
var t0 = new Date(), |
|
t1 = new Date(); |
|
|
|
function newInterval(floori, offseti, count, field) { |
|
function interval(date) { |
|
return floori(date = arguments.length === 0 ? new Date() : new Date(+date)), date; |
|
} |
|
|
|
interval.floor = function (date) { |
|
return floori(date = new Date(+date)), date; |
|
}; |
|
|
|
interval.ceil = function (date) { |
|
return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date; |
|
}; |
|
|
|
interval.round = function (date) { |
|
var d0 = interval(date), |
|
d1 = interval.ceil(date); |
|
return date - d0 < d1 - date ? d0 : d1; |
|
}; |
|
|
|
interval.offset = function (date, step) { |
|
return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date; |
|
}; |
|
|
|
interval.range = function (start, stop, step) { |
|
var range = [], |
|
previous; |
|
start = interval.ceil(start); |
|
step = step == null ? 1 : Math.floor(step); |
|
if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date |
|
|
|
do { |
|
range.push(previous = new Date(+start)), offseti(start, step), floori(start); |
|
} while (previous < start && start < stop); |
|
|
|
return range; |
|
}; |
|
|
|
interval.filter = function (test) { |
|
return newInterval(function (date) { |
|
if (date >= date) while (floori(date), !test(date)) { |
|
date.setTime(date - 1); |
|
} |
|
}, function (date, step) { |
|
if (date >= date) { |
|
if (step < 0) while (++step <= 0) { |
|
while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty |
|
|
|
} else while (--step >= 0) { |
|
while (offseti(date, +1), !test(date)) {} // eslint-disable-line no-empty |
|
|
|
} |
|
} |
|
}); |
|
}; |
|
|
|
if (count) { |
|
interval.count = function (start, end) { |
|
t0.setTime(+start), t1.setTime(+end); |
|
floori(t0), floori(t1); |
|
return Math.floor(count(t0, t1)); |
|
}; |
|
|
|
interval.every = function (step) { |
|
step = Math.floor(step); |
|
return !isFinite(step) || !(step > 0) ? null : !(step > 1) ? interval : interval.filter(field ? function (d) { |
|
return field(d) % step === 0; |
|
} : function (d) { |
|
return interval.count(0, d) % step === 0; |
|
}); |
|
}; |
|
} |
|
|
|
return interval; |
|
} |
|
|
|
var millisecond = newInterval(function () {// noop |
|
}, function (date, step) { |
|
date.setTime(+date + step); |
|
}, function (start, end) { |
|
return end - start; |
|
}); // An optimized implementation for this simple case. |
|
|
|
millisecond.every = function (k) { |
|
k = Math.floor(k); |
|
if (!isFinite(k) || !(k > 0)) return null; |
|
if (!(k > 1)) return millisecond; |
|
return newInterval(function (date) { |
|
date.setTime(Math.floor(date / k) * k); |
|
}, function (date, step) { |
|
date.setTime(+date + step * k); |
|
}, function (start, end) { |
|
return (end - start) / k; |
|
}); |
|
}; |
|
|
|
var durationSecond = 1e3; |
|
var durationMinute = 6e4; |
|
var durationHour = 36e5; |
|
var durationDay = 864e5; |
|
var durationWeek = 6048e5; |
|
var second = newInterval(function (date) { |
|
date.setTime(date - date.getMilliseconds()); |
|
}, function (date, step) { |
|
date.setTime(+date + step * durationSecond); |
|
}, function (start, end) { |
|
return (end - start) / durationSecond; |
|
}, function (date) { |
|
return date.getUTCSeconds(); |
|
}); |
|
var minute = newInterval(function (date) { |
|
date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond); |
|
}, function (date, step) { |
|
date.setTime(+date + step * durationMinute); |
|
}, function (start, end) { |
|
return (end - start) / durationMinute; |
|
}, function (date) { |
|
return date.getMinutes(); |
|
}); |
|
var hour = newInterval(function (date) { |
|
date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond - date.getMinutes() * durationMinute); |
|
}, function (date, step) { |
|
date.setTime(+date + step * durationHour); |
|
}, function (start, end) { |
|
return (end - start) / durationHour; |
|
}, function (date) { |
|
return date.getHours(); |
|
}); |
|
var day = newInterval(function (date) { |
|
date.setHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setDate(date.getDate() + step); |
|
}, function (start, end) { |
|
return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay; |
|
}, function (date) { |
|
return date.getDate() - 1; |
|
}); |
|
|
|
function weekday(i) { |
|
return newInterval(function (date) { |
|
date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7); |
|
date.setHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setDate(date.getDate() + step * 7); |
|
}, function (start, end) { |
|
return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek; |
|
}); |
|
} |
|
|
|
var sunday = weekday(0); |
|
var monday = weekday(1); |
|
var tuesday = weekday(2); |
|
var wednesday = weekday(3); |
|
var thursday = weekday(4); |
|
var friday = weekday(5); |
|
var saturday = weekday(6); |
|
var month = newInterval(function (date) { |
|
date.setDate(1); |
|
date.setHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setMonth(date.getMonth() + step); |
|
}, function (start, end) { |
|
return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12; |
|
}, function (date) { |
|
return date.getMonth(); |
|
}); |
|
var year = newInterval(function (date) { |
|
date.setMonth(0, 1); |
|
date.setHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setFullYear(date.getFullYear() + step); |
|
}, function (start, end) { |
|
return end.getFullYear() - start.getFullYear(); |
|
}, function (date) { |
|
return date.getFullYear(); |
|
}); // An optimized implementation for this simple case. |
|
|
|
year.every = function (k) { |
|
return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : newInterval(function (date) { |
|
date.setFullYear(Math.floor(date.getFullYear() / k) * k); |
|
date.setMonth(0, 1); |
|
date.setHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setFullYear(date.getFullYear() + step * k); |
|
}); |
|
}; |
|
|
|
var utcMinute = newInterval(function (date) { |
|
date.setUTCSeconds(0, 0); |
|
}, function (date, step) { |
|
date.setTime(+date + step * durationMinute); |
|
}, function (start, end) { |
|
return (end - start) / durationMinute; |
|
}, function (date) { |
|
return date.getUTCMinutes(); |
|
}); |
|
var utcHour = newInterval(function (date) { |
|
date.setUTCMinutes(0, 0, 0); |
|
}, function (date, step) { |
|
date.setTime(+date + step * durationHour); |
|
}, function (start, end) { |
|
return (end - start) / durationHour; |
|
}, function (date) { |
|
return date.getUTCHours(); |
|
}); |
|
var utcDay = newInterval(function (date) { |
|
date.setUTCHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setUTCDate(date.getUTCDate() + step); |
|
}, function (start, end) { |
|
return (end - start) / durationDay; |
|
}, function (date) { |
|
return date.getUTCDate() - 1; |
|
}); |
|
|
|
function utcWeekday(i) { |
|
return newInterval(function (date) { |
|
date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7); |
|
date.setUTCHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setUTCDate(date.getUTCDate() + step * 7); |
|
}, function (start, end) { |
|
return (end - start) / durationWeek; |
|
}); |
|
} |
|
|
|
var utcSunday = utcWeekday(0); |
|
var utcMonday = utcWeekday(1); |
|
var utcTuesday = utcWeekday(2); |
|
var utcWednesday = utcWeekday(3); |
|
var utcThursday = utcWeekday(4); |
|
var utcFriday = utcWeekday(5); |
|
var utcSaturday = utcWeekday(6); |
|
var utcMonth = newInterval(function (date) { |
|
date.setUTCDate(1); |
|
date.setUTCHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setUTCMonth(date.getUTCMonth() + step); |
|
}, function (start, end) { |
|
return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12; |
|
}, function (date) { |
|
return date.getUTCMonth(); |
|
}); |
|
var utcYear = newInterval(function (date) { |
|
date.setUTCMonth(0, 1); |
|
date.setUTCHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setUTCFullYear(date.getUTCFullYear() + step); |
|
}, function (start, end) { |
|
return end.getUTCFullYear() - start.getUTCFullYear(); |
|
}, function (date) { |
|
return date.getUTCFullYear(); |
|
}); // An optimized implementation for this simple case. |
|
|
|
utcYear.every = function (k) { |
|
return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : newInterval(function (date) { |
|
date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k); |
|
date.setUTCMonth(0, 1); |
|
date.setUTCHours(0, 0, 0, 0); |
|
}, function (date, step) { |
|
date.setUTCFullYear(date.getUTCFullYear() + step * k); |
|
}); |
|
}; |
|
|
|
function localDate(d) { |
|
if (0 <= d.y && d.y < 100) { |
|
var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L); |
|
date.setFullYear(d.y); |
|
return date; |
|
} |
|
|
|
return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L); |
|
} |
|
|
|
function utcDate(d) { |
|
if (0 <= d.y && d.y < 100) { |
|
var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L)); |
|
date.setUTCFullYear(d.y); |
|
return date; |
|
} |
|
|
|
return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L)); |
|
} |
|
|
|
function newDate(y, m, d) { |
|
return { |
|
y: y, |
|
m: m, |
|
d: d, |
|
H: 0, |
|
M: 0, |
|
S: 0, |
|
L: 0 |
|
}; |
|
} |
|
|
|
function formatLocale(locale) { |
|
var locale_dateTime = locale.dateTime, |
|
locale_date = locale.date, |
|
locale_time = locale.time, |
|
locale_periods = locale.periods, |
|
locale_weekdays = locale.days, |
|
locale_shortWeekdays = locale.shortDays, |
|
locale_months = locale.months, |
|
locale_shortMonths = locale.shortMonths; |
|
var periodRe = formatRe(locale_periods), |
|
periodLookup = formatLookup(locale_periods), |
|
weekdayRe = formatRe(locale_weekdays), |
|
weekdayLookup = formatLookup(locale_weekdays), |
|
shortWeekdayRe = formatRe(locale_shortWeekdays), |
|
shortWeekdayLookup = formatLookup(locale_shortWeekdays), |
|
monthRe = formatRe(locale_months), |
|
monthLookup = formatLookup(locale_months), |
|
shortMonthRe = formatRe(locale_shortMonths), |
|
shortMonthLookup = formatLookup(locale_shortMonths); |
|
var formats = { |
|
"a": formatShortWeekday, |
|
"A": formatWeekday, |
|
"b": formatShortMonth, |
|
"B": formatMonth, |
|
"c": null, |
|
"d": formatDayOfMonth, |
|
"e": formatDayOfMonth, |
|
"f": formatMicroseconds, |
|
"H": formatHour24, |
|
"I": formatHour12, |
|
"j": formatDayOfYear, |
|
"L": formatMilliseconds, |
|
"m": formatMonthNumber, |
|
"M": formatMinutes, |
|
"p": formatPeriod, |
|
"q": formatQuarter, |
|
"Q": formatUnixTimestamp, |
|
"s": formatUnixTimestampSeconds, |
|
"S": formatSeconds, |
|
"u": formatWeekdayNumberMonday, |
|
"U": formatWeekNumberSunday, |
|
"V": formatWeekNumberISO, |
|
"w": formatWeekdayNumberSunday, |
|
"W": formatWeekNumberMonday, |
|
"x": null, |
|
"X": null, |
|
"y": formatYear$1, |
|
"Y": formatFullYear, |
|
"Z": formatZone, |
|
"%": formatLiteralPercent |
|
}; |
|
var utcFormats = { |
|
"a": formatUTCShortWeekday, |
|
"A": formatUTCWeekday, |
|
"b": formatUTCShortMonth, |
|
"B": formatUTCMonth, |
|
"c": null, |
|
"d": formatUTCDayOfMonth, |
|
"e": formatUTCDayOfMonth, |
|
"f": formatUTCMicroseconds, |
|
"H": formatUTCHour24, |
|
"I": formatUTCHour12, |
|
"j": formatUTCDayOfYear, |
|
"L": formatUTCMilliseconds, |
|
"m": formatUTCMonthNumber, |
|
"M": formatUTCMinutes, |
|
"p": formatUTCPeriod, |
|
"q": formatUTCQuarter, |
|
"Q": formatUnixTimestamp, |
|
"s": formatUnixTimestampSeconds, |
|
"S": formatUTCSeconds, |
|
"u": formatUTCWeekdayNumberMonday, |
|
"U": formatUTCWeekNumberSunday, |
|
"V": formatUTCWeekNumberISO, |
|
"w": formatUTCWeekdayNumberSunday, |
|
"W": formatUTCWeekNumberMonday, |
|
"x": null, |
|
"X": null, |
|
"y": formatUTCYear, |
|
"Y": formatUTCFullYear, |
|
"Z": formatUTCZone, |
|
"%": formatLiteralPercent |
|
}; |
|
var parses = { |
|
"a": parseShortWeekday, |
|
"A": parseWeekday, |
|
"b": parseShortMonth, |
|
"B": parseMonth, |
|
"c": parseLocaleDateTime, |
|
"d": parseDayOfMonth, |
|
"e": parseDayOfMonth, |
|
"f": parseMicroseconds, |
|
"H": parseHour24, |
|
"I": parseHour24, |
|
"j": parseDayOfYear, |
|
"L": parseMilliseconds, |
|
"m": parseMonthNumber, |
|
"M": parseMinutes, |
|
"p": parsePeriod, |
|
"q": parseQuarter, |
|
"Q": parseUnixTimestamp, |
|
"s": parseUnixTimestampSeconds, |
|
"S": parseSeconds, |
|
"u": parseWeekdayNumberMonday, |
|
"U": parseWeekNumberSunday, |
|
"V": parseWeekNumberISO, |
|
"w": parseWeekdayNumberSunday, |
|
"W": parseWeekNumberMonday, |
|
"x": parseLocaleDate, |
|
"X": parseLocaleTime, |
|
"y": parseYear, |
|
"Y": parseFullYear, |
|
"Z": parseZone, |
|
"%": parseLiteralPercent |
|
}; // These recursive directive definitions must be deferred. |
|
|
|
formats.x = newFormat(locale_date, formats); |
|
formats.X = newFormat(locale_time, formats); |
|
formats.c = newFormat(locale_dateTime, formats); |
|
utcFormats.x = newFormat(locale_date, utcFormats); |
|
utcFormats.X = newFormat(locale_time, utcFormats); |
|
utcFormats.c = newFormat(locale_dateTime, utcFormats); |
|
|
|
function newFormat(specifier, formats) { |
|
return function (date) { |
|
var string = [], |
|
i = -1, |
|
j = 0, |
|
n = specifier.length, |
|
c, |
|
pad, |
|
format; |
|
if (!(date instanceof Date)) date = new Date(+date); |
|
|
|
while (++i < n) { |
|
if (specifier.charCodeAt(i) === 37) { |
|
string.push(specifier.slice(j, i)); |
|
if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i);else pad = c === "e" ? " " : "0"; |
|
if (format = formats[c]) c = format(date, pad); |
|
string.push(c); |
|
j = i + 1; |
|
} |
|
} |
|
|
|
string.push(specifier.slice(j, i)); |
|
return string.join(""); |
|
}; |
|
} |
|
|
|
function newParse(specifier, Z) { |
|
return function (string) { |
|
var d = newDate(1900, undefined, 1), |
|
i = parseSpecifier(d, specifier, string += "", 0), |
|
week, |
|
day$1; |
|
if (i != string.length) return null; // If a UNIX timestamp is specified, return it. |
|
|
|
if ("Q" in d) return new Date(d.Q); |
|
if ("s" in d) return new Date(d.s * 1000 + ("L" in d ? d.L : 0)); // If this is utcParse, never use the local timezone. |
|
|
|
if (Z && !("Z" in d)) d.Z = 0; // The am-pm flag is 0 for AM, and 1 for PM. |
|
|
|
if ("p" in d) d.H = d.H % 12 + d.p * 12; // If the month was not specified, inherit from the quarter. |
|
|
|
if (d.m === undefined) d.m = "q" in d ? d.q : 0; // Convert day-of-week and week-of-year to day-of-year. |
|
|
|
if ("V" in d) { |
|
if (d.V < 1 || d.V > 53) return null; |
|
if (!("w" in d)) d.w = 1; |
|
|
|
if ("Z" in d) { |
|
week = utcDate(newDate(d.y, 0, 1)), day$1 = week.getUTCDay(); |
|
week = day$1 > 4 || day$1 === 0 ? utcMonday.ceil(week) : utcMonday(week); |
|
week = utcDay.offset(week, (d.V - 1) * 7); |
|
d.y = week.getUTCFullYear(); |
|
d.m = week.getUTCMonth(); |
|
d.d = week.getUTCDate() + (d.w + 6) % 7; |
|
} else { |
|
week = localDate(newDate(d.y, 0, 1)), day$1 = week.getDay(); |
|
week = day$1 > 4 || day$1 === 0 ? monday.ceil(week) : monday(week); |
|
week = day.offset(week, (d.V - 1) * 7); |
|
d.y = week.getFullYear(); |
|
d.m = week.getMonth(); |
|
d.d = week.getDate() + (d.w + 6) % 7; |
|
} |
|
} else if ("W" in d || "U" in d) { |
|
if (!("w" in d)) d.w = "u" in d ? d.u % 7 : "W" in d ? 1 : 0; |
|
day$1 = "Z" in d ? utcDate(newDate(d.y, 0, 1)).getUTCDay() : localDate(newDate(d.y, 0, 1)).getDay(); |
|
d.m = 0; |
|
d.d = "W" in d ? (d.w + 6) % 7 + d.W * 7 - (day$1 + 5) % 7 : d.w + d.U * 7 - (day$1 + 6) % 7; |
|
} // If a time zone is specified, all fields are interpreted as UTC and then |
|
// offset according to the specified time zone. |
|
|
|
|
|
if ("Z" in d) { |
|
d.H += d.Z / 100 | 0; |
|
d.M += d.Z % 100; |
|
return utcDate(d); |
|
} // Otherwise, all fields are in local time. |
|
|
|
|
|
return localDate(d); |
|
}; |
|
} |
|
|
|
function parseSpecifier(d, specifier, string, j) { |
|
var i = 0, |
|
n = specifier.length, |
|
m = string.length, |
|
c, |
|
parse; |
|
|
|
while (i < n) { |
|
if (j >= m) return -1; |
|
c = specifier.charCodeAt(i++); |
|
|
|
if (c === 37) { |
|
c = specifier.charAt(i++); |
|
parse = parses[c in pads ? specifier.charAt(i++) : c]; |
|
if (!parse || (j = parse(d, string, j)) < 0) return -1; |
|
} else if (c != string.charCodeAt(j++)) { |
|
return -1; |
|
} |
|
} |
|
|
|
return j; |
|
} |
|
|
|
function parsePeriod(d, string, i) { |
|
var n = periodRe.exec(string.slice(i)); |
|
return n ? (d.p = periodLookup[n[0].toLowerCase()], i + n[0].length) : -1; |
|
} |
|
|
|
function parseShortWeekday(d, string, i) { |
|
var n = shortWeekdayRe.exec(string.slice(i)); |
|
return n ? (d.w = shortWeekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1; |
|
} |
|
|
|
function parseWeekday(d, string, i) { |
|
var n = weekdayRe.exec(string.slice(i)); |
|
return n ? (d.w = weekdayLookup[n[0].toLowerCase()], i + n[0].length) : -1; |
|
} |
|
|
|
function parseShortMonth(d, string, i) { |
|
var n = shortMonthRe.exec(string.slice(i)); |
|
return n ? (d.m = shortMonthLookup[n[0].toLowerCase()], i + n[0].length) : -1; |
|
} |
|
|
|
function parseMonth(d, string, i) { |
|
var n = monthRe.exec(string.slice(i)); |
|
return n ? (d.m = monthLookup[n[0].toLowerCase()], i + n[0].length) : -1; |
|
} |
|
|
|
function parseLocaleDateTime(d, string, i) { |
|
return parseSpecifier(d, locale_dateTime, string, i); |
|
} |
|
|
|
function parseLocaleDate(d, string, i) { |
|
return parseSpecifier(d, locale_date, string, i); |
|
} |
|
|
|
function parseLocaleTime(d, string, i) { |
|
return parseSpecifier(d, locale_time, string, i); |
|
} |
|
|
|
function formatShortWeekday(d) { |
|
return locale_shortWeekdays[d.getDay()]; |
|
} |
|
|
|
function formatWeekday(d) { |
|
return locale_weekdays[d.getDay()]; |
|
} |
|
|
|
function formatShortMonth(d) { |
|
return locale_shortMonths[d.getMonth()]; |
|
} |
|
|
|
function formatMonth(d) { |
|
return locale_months[d.getMonth()]; |
|
} |
|
|
|
function formatPeriod(d) { |
|
return locale_periods[+(d.getHours() >= 12)]; |
|
} |
|
|
|
function formatQuarter(d) { |
|
return 1 + ~~(d.getMonth() / 3); |
|
} |
|
|
|
function formatUTCShortWeekday(d) { |
|
return locale_shortWeekdays[d.getUTCDay()]; |
|
} |
|
|
|
function formatUTCWeekday(d) { |
|
return locale_weekdays[d.getUTCDay()]; |
|
} |
|
|
|
function formatUTCShortMonth(d) { |
|
return locale_shortMonths[d.getUTCMonth()]; |
|
} |
|
|
|
function formatUTCMonth(d) { |
|
return locale_months[d.getUTCMonth()]; |
|
} |
|
|
|
function formatUTCPeriod(d) { |
|
return locale_periods[+(d.getUTCHours() >= 12)]; |
|
} |
|
|
|
function formatUTCQuarter(d) { |
|
return 1 + ~~(d.getUTCMonth() / 3); |
|
} |
|
|
|
return { |
|
format: function format(specifier) { |
|
var f = newFormat(specifier += "", formats); |
|
|
|
f.toString = function () { |
|
return specifier; |
|
}; |
|
|
|
return f; |
|
}, |
|
parse: function parse(specifier) { |
|
var p = newParse(specifier += "", false); |
|
|
|
p.toString = function () { |
|
return specifier; |
|
}; |
|
|
|
return p; |
|
}, |
|
utcFormat: function utcFormat(specifier) { |
|
var f = newFormat(specifier += "", utcFormats); |
|
|
|
f.toString = function () { |
|
return specifier; |
|
}; |
|
|
|
return f; |
|
}, |
|
utcParse: function utcParse(specifier) { |
|
var p = newParse(specifier += "", true); |
|
|
|
p.toString = function () { |
|
return specifier; |
|
}; |
|
|
|
return p; |
|
} |
|
}; |
|
} |
|
|
|
var pads = { |
|
"-": "", |
|
"_": " ", |
|
"0": "0" |
|
}, |
|
numberRe = /^\s*\d+/, |
|
// note: ignores next directive |
|
percentRe = /^%/, |
|
requoteRe = /[\\^$*+?|[\]().{}]/g; |
|
|
|
function pad$2(value, fill, width) { |
|
var sign = value < 0 ? "-" : "", |
|
string = (sign ? -value : value) + "", |
|
length = string.length; |
|
return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); |
|
} |
|
|
|
function requote(s) { |
|
return s.replace(requoteRe, "\\$&"); |
|
} |
|
|
|
function formatRe(names) { |
|
return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i"); |
|
} |
|
|
|
function formatLookup(names) { |
|
var map = {}, |
|
i = -1, |
|
n = names.length; |
|
|
|
while (++i < n) { |
|
map[names[i].toLowerCase()] = i; |
|
} |
|
|
|
return map; |
|
} |
|
|
|
function parseWeekdayNumberSunday(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 1)); |
|
return n ? (d.w = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseWeekdayNumberMonday(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 1)); |
|
return n ? (d.u = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseWeekNumberSunday(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 2)); |
|
return n ? (d.U = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseWeekNumberISO(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 2)); |
|
return n ? (d.V = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseWeekNumberMonday(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 2)); |
|
return n ? (d.W = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseFullYear(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 4)); |
|
return n ? (d.y = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseYear(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 2)); |
|
return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1; |
|
} |
|
|
|
function parseZone(d, string, i) { |
|
var n = /^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(string.slice(i, i + 6)); |
|
return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || "00")), i + n[0].length) : -1; |
|
} |
|
|
|
function parseQuarter(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 1)); |
|
return n ? (d.q = n[0] * 3 - 3, i + n[0].length) : -1; |
|
} |
|
|
|
function parseMonthNumber(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 2)); |
|
return n ? (d.m = n[0] - 1, i + n[0].length) : -1; |
|
} |
|
|
|
function parseDayOfMonth(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 2)); |
|
return n ? (d.d = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseDayOfYear(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 3)); |
|
return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseHour24(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 2)); |
|
return n ? (d.H = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseMinutes(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 2)); |
|
return n ? (d.M = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseSeconds(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 2)); |
|
return n ? (d.S = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseMilliseconds(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 3)); |
|
return n ? (d.L = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseMicroseconds(d, string, i) { |
|
var n = numberRe.exec(string.slice(i, i + 6)); |
|
return n ? (d.L = Math.floor(n[0] / 1000), i + n[0].length) : -1; |
|
} |
|
|
|
function parseLiteralPercent(d, string, i) { |
|
var n = percentRe.exec(string.slice(i, i + 1)); |
|
return n ? i + n[0].length : -1; |
|
} |
|
|
|
function parseUnixTimestamp(d, string, i) { |
|
var n = numberRe.exec(string.slice(i)); |
|
return n ? (d.Q = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function parseUnixTimestampSeconds(d, string, i) { |
|
var n = numberRe.exec(string.slice(i)); |
|
return n ? (d.s = +n[0], i + n[0].length) : -1; |
|
} |
|
|
|
function formatDayOfMonth(d, p) { |
|
return pad$2(d.getDate(), p, 2); |
|
} |
|
|
|
function formatHour24(d, p) { |
|
return pad$2(d.getHours(), p, 2); |
|
} |
|
|
|
function formatHour12(d, p) { |
|
return pad$2(d.getHours() % 12 || 12, p, 2); |
|
} |
|
|
|
function formatDayOfYear(d, p) { |
|
return pad$2(1 + day.count(year(d), d), p, 3); |
|
} |
|
|
|
function formatMilliseconds(d, p) { |
|
return pad$2(d.getMilliseconds(), p, 3); |
|
} |
|
|
|
function formatMicroseconds(d, p) { |
|
return formatMilliseconds(d, p) + "000"; |
|
} |
|
|
|
function formatMonthNumber(d, p) { |
|
return pad$2(d.getMonth() + 1, p, 2); |
|
} |
|
|
|
function formatMinutes(d, p) { |
|
return pad$2(d.getMinutes(), p, 2); |
|
} |
|
|
|
function formatSeconds(d, p) { |
|
return pad$2(d.getSeconds(), p, 2); |
|
} |
|
|
|
function formatWeekdayNumberMonday(d) { |
|
var day = d.getDay(); |
|
return day === 0 ? 7 : day; |
|
} |
|
|
|
function formatWeekNumberSunday(d, p) { |
|
return pad$2(sunday.count(year(d) - 1, d), p, 2); |
|
} |
|
|
|
function formatWeekNumberISO(d, p) { |
|
var day = d.getDay(); |
|
d = day >= 4 || day === 0 ? thursday(d) : thursday.ceil(d); |
|
return pad$2(thursday.count(year(d), d) + (year(d).getDay() === 4), p, 2); |
|
} |
|
|
|
function formatWeekdayNumberSunday(d) { |
|
return d.getDay(); |
|
} |
|
|
|
function formatWeekNumberMonday(d, p) { |
|
return pad$2(monday.count(year(d) - 1, d), p, 2); |
|
} |
|
|
|
function formatYear$1(d, p) { |
|
return pad$2(d.getFullYear() % 100, p, 2); |
|
} |
|
|
|
function formatFullYear(d, p) { |
|
return pad$2(d.getFullYear() % 10000, p, 4); |
|
} |
|
|
|
function formatZone(d) { |
|
var z = d.getTimezoneOffset(); |
|
return (z > 0 ? "-" : (z *= -1, "+")) + pad$2(z / 60 | 0, "0", 2) + pad$2(z % 60, "0", 2); |
|
} |
|
|
|
function formatUTCDayOfMonth(d, p) { |
|
return pad$2(d.getUTCDate(), p, 2); |
|
} |
|
|
|
function formatUTCHour24(d, p) { |
|
return pad$2(d.getUTCHours(), p, 2); |
|
} |
|
|
|
function formatUTCHour12(d, p) { |
|
return pad$2(d.getUTCHours() % 12 || 12, p, 2); |
|
} |
|
|
|
function formatUTCDayOfYear(d, p) { |
|
return pad$2(1 + utcDay.count(utcYear(d), d), p, 3); |
|
} |
|
|
|
function formatUTCMilliseconds(d, p) { |
|
return pad$2(d.getUTCMilliseconds(), p, 3); |
|
} |
|
|
|
function formatUTCMicroseconds(d, p) { |
|
return formatUTCMilliseconds(d, p) + "000"; |
|
} |
|
|
|
function formatUTCMonthNumber(d, p) { |
|
return pad$2(d.getUTCMonth() + 1, p, 2); |
|
} |
|
|
|
function formatUTCMinutes(d, p) { |
|
return pad$2(d.getUTCMinutes(), p, 2); |
|
} |
|
|
|
function formatUTCSeconds(d, p) { |
|
return pad$2(d.getUTCSeconds(), p, 2); |
|
} |
|
|
|
function formatUTCWeekdayNumberMonday(d) { |
|
var dow = d.getUTCDay(); |
|
return dow === 0 ? 7 : dow; |
|
} |
|
|
|
function formatUTCWeekNumberSunday(d, p) { |
|
return pad$2(utcSunday.count(utcYear(d) - 1, d), p, 2); |
|
} |
|
|
|
function formatUTCWeekNumberISO(d, p) { |
|
var day = d.getUTCDay(); |
|
d = day >= 4 || day === 0 ? utcThursday(d) : utcThursday.ceil(d); |
|
return pad$2(utcThursday.count(utcYear(d), d) + (utcYear(d).getUTCDay() === 4), p, 2); |
|
} |
|
|
|
function formatUTCWeekdayNumberSunday(d) { |
|
return d.getUTCDay(); |
|
} |
|
|
|
function formatUTCWeekNumberMonday(d, p) { |
|
return pad$2(utcMonday.count(utcYear(d) - 1, d), p, 2); |
|
} |
|
|
|
function formatUTCYear(d, p) { |
|
return pad$2(d.getUTCFullYear() % 100, p, 2); |
|
} |
|
|
|
function formatUTCFullYear(d, p) { |
|
return pad$2(d.getUTCFullYear() % 10000, p, 4); |
|
} |
|
|
|
function formatUTCZone() { |
|
return "+0000"; |
|
} |
|
|
|
function formatLiteralPercent() { |
|
return "%"; |
|
} |
|
|
|
function formatUnixTimestamp(d) { |
|
return +d; |
|
} |
|
|
|
function formatUnixTimestampSeconds(d) { |
|
return Math.floor(+d / 1000); |
|
} |
|
|
|
var locale; |
|
var timeFormat; |
|
var timeParse; |
|
var utcFormat; |
|
var utcParse; |
|
defaultLocale({ |
|
dateTime: "%x, %X", |
|
date: "%-m/%-d/%Y", |
|
time: "%-I:%M:%S %p", |
|
periods: ["AM", "PM"], |
|
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], |
|
shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], |
|
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], |
|
shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
|
}); |
|
|
|
function defaultLocale(definition) { |
|
locale = formatLocale(definition); |
|
timeFormat = locale.format; |
|
timeParse = locale.parse; |
|
utcFormat = locale.utcFormat; |
|
utcParse = locale.utcParse; |
|
return locale; |
|
} |
|
|
|
function read(data, schema, dateParse) { |
|
schema = schema || {}; |
|
var reader = formats(schema.type || 'json'); |
|
if (!reader) error('Unknown data format type: ' + schema.type); |
|
data = reader(data, schema); |
|
if (schema.parse) parse(data, schema.parse, dateParse); |
|
if (hasOwnProperty(data, 'columns')) delete data.columns; |
|
return data; |
|
} |
|
|
|
function parse(data, types, dateParse) { |
|
if (!data.length) return; // early exit for empty data |
|
|
|
dateParse = dateParse || timeParse; |
|
var fields = data.columns || Object.keys(data[0]), |
|
parsers, |
|
datum, |
|
field, |
|
i, |
|
j, |
|
n, |
|
m; |
|
if (types === 'auto') types = inferTypes(data, fields); |
|
fields = Object.keys(types); |
|
parsers = fields.map(function (field) { |
|
var type = types[field], |
|
parts, |
|
pattern; |
|
|
|
if (type && (type.indexOf('date:') === 0 || type.indexOf('utc:') === 0)) { |
|
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); |
|
} |
|
|
|
return parts[0] === 'utc' ? utcParse(pattern) : dateParse(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]); |
|
} |
|
} |
|
} |
|
|
|
var loader = loaderFactory(typeof fetch !== 'undefined' && fetch, // use built-in fetch API |
|
null // no file system access |
|
); |
|
var parse$1 = read; |
|
/** |
|
* Ingests new data into the dataflow. First parses the data using the |
|
* vega-loader read method, then pulses a changeset to the target operator. |
|
* @param {Operator} target - The Operator to target with ingested data, |
|
* typically a Collect transform instance. |
|
* @param {*} data - The input data, prior to parsing. For JSON this may |
|
* be a string or an object. For CSV, TSV, etc should be a string. |
|
* @param {object} format - The data format description for parsing |
|
* loaded data. This object is passed to the vega-loader read method. |
|
* @returns {Dataflow} |
|
*/ |
|
|
|
function ingest$1(target, data, format) { |
|
return this.pulse(target, this.changeset().insert(parse$1(data, format))); |
|
} |
|
/** |
|
* Request data from an external source, parse it, and return a Promise. |
|
* @param {string} url - The URL from which to load the data. This string |
|
* is passed to the vega-loader load method. |
|
* @param {object} [format] - The data format description for parsing |
|
* loaded data. This object is passed to the vega-loader read method. |
|
* @return {Promise} A Promise that resolves upon completion of the request. |
|
* The resolved object contains the following properties: |
|
* - data: an array of parsed data (or null upon error) |
|
* - status: a code for success (0), load fail (-1), or parse fail (-2) |
|
*/ |
|
|
|
|
|
function request(_x9, _x10) { |
|
return _request.apply(this, arguments); |
|
} |
|
|
|
function _request() { |
|
_request = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee8(url, format) { |
|
var df, status, data; |
|
return regeneratorRuntime.wrap(function _callee8$(_context14) { |
|
while (1) { |
|
switch (_context14.prev = _context14.next) { |
|
case 0: |
|
df = this; |
|
status = 0; |
|
_context14.prev = 2; |
|
_context14.next = 5; |
|
return df.loader().load(url, { |
|
context: 'dataflow', |
|
response: responseType(format && format.type) |
|
}); |
|
|
|
case 5: |
|
data = _context14.sent; |
|
|
|
try { |
|
data = parse$1(data, format); |
|
} catch (err) { |
|
status = -2; |
|
df.warn('Data ingestion failed', url, err); |
|
} |
|
|
|
_context14.next = 13; |
|
break; |
|
|
|
case 9: |
|
_context14.prev = 9; |
|
_context14.t0 = _context14["catch"](2); |
|
status = -1; |
|
df.warn('Loading failed', url, _context14.t0); |
|
|
|
case 13: |
|
return _context14.abrupt("return", { |
|
data: data, |
|
status: status |
|
}); |
|
|
|
case 14: |
|
case "end": |
|
return _context14.stop(); |
|
} |
|
} |
|
}, _callee8, this, [[2, 9]]); |
|
})); |
|
return _request.apply(this, arguments); |
|
} |
|
|
|
function preload(_x11, _x12, _x13) { |
|
return _preload.apply(this, arguments); |
|
} |
|
|
|
function _preload() { |
|
_preload = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee9(target, url, format) { |
|
var df, pending, res; |
|
return regeneratorRuntime.wrap(function _callee9$(_context15) { |
|
while (1) { |
|
switch (_context15.prev = _context15.next) { |
|
case 0: |
|
df = this, pending = df._pending || loadPending(df); |
|
pending.requests += 1; |
|
_context15.next = 4; |
|
return df.request(url, format); |
|
|
|
case 4: |
|
res = _context15.sent; |
|
df.pulse(target, df.changeset().remove(truthy).insert(res.data || [])); |
|
pending.done(); |
|
return _context15.abrupt("return", res); |
|
|
|
case 8: |
|
case "end": |
|
return _context15.stop(); |
|
} |
|
} |
|
}, _callee9, this); |
|
})); |
|
return _preload.apply(this, arguments); |
|
} |
|
|
|
function loadPending(df) { |
|
var pending = new Promise(function (a) { |
|
accept = a; |
|
}), |
|
accept; |
|
pending.requests = 0; |
|
|
|
pending.done = function () { |
|
if (--pending.requests === 0) { |
|
df._pending = null; |
|
accept(df); |
|
} |
|
}; |
|
|
|
return df._pending = pending; |
|
} |
|
|
|
var SKIP$1 = { |
|
skip: true |
|
}; |
|
/** |
|
* Perform operator updates in response to events. Applies an |
|
* update function to compute a new operator value. If the update function |
|
* returns a {@link ChangeSet}, the operator will be pulsed with those tuple |
|
* changes. Otherwise, the operator value will be updated to the return value. |
|
* @param {EventStream|Operator} source - The event source to react to. |
|
* This argument can be either an EventStream or an Operator. |
|
* @param {Operator|function(object):Operator} target - The operator to update. |
|
* This argument can either be an Operator instance or (if the source |
|
* argument is an EventStream), a function that accepts an event object as |
|
* input and returns an Operator to target. |
|
* @param {function(Parameters,Event): *} [update] - Optional update function |
|
* to compute the new operator value, or a literal value to set. Update |
|
* functions expect to receive a parameter object and event as arguments. |
|
* This function can either return a new operator value or (if the source |
|
* argument is an EventStream) a {@link ChangeSet} instance to pulse |
|
* the target operator with tuple changes. |
|
* @param {object} [params] - The update function parameters. |
|
* @param {object} [options] - Additional options hash. If not overridden, |
|
* updated operators will be skipped by default. |
|
* @param {boolean} [options.skip] - If true, the operator will |
|
* be skipped: it will not be evaluated, but its dependents will be. |
|
* @param {boolean} [options.force] - If true, the operator will |
|
* be re-evaluated even if its value has not changed. |
|
* @return {Dataflow} |
|
*/ |
|
|
|
function on(source, target, update, params, options) { |
|
var fn = source instanceof Operator ? onOperator : onStream; |
|
fn(this, source, target, update, params, options); |
|
return this; |
|
} |
|
|
|
function onStream(df, stream, target, update, params, options) { |
|
var opt = extend({}, options, SKIP$1), |
|
func, |
|
op; |
|
if (!isFunction(target)) target = constant(target); |
|
|
|
if (update === undefined) { |
|
func = function func(e) { |
|
return df.touch(target(e)); |
|
}; |
|
} else if (isFunction(update)) { |
|
op = new Operator(null, update, params, false); |
|
|
|
func = function func(e) { |
|
op.evaluate(e); |
|
var t = target(e), |
|
v = op.value; |
|
isChangeSet(v) ? df.pulse(t, v, options) : df.update(t, v, opt); |
|
}; |
|
} else { |
|
func = function func(e) { |
|
return df.update(target(e), update, opt); |
|
}; |
|
} |
|
|
|
stream.apply(func); |
|
} |
|
|
|
function onOperator(df, source, target, update, params, options) { |
|
if (update === undefined) { |
|
source.targets().add(target); |
|
} else { |
|
var opt = options || {}, |
|
op = new Operator(null, updater(target, update), params, false); |
|
op.modified(opt.force); |
|
op.rank = source.rank; // immediately follow source |
|
|
|
source.targets().add(op); // add dependency |
|
|
|
if (target) { |
|
op.skip(true); // skip first invocation |
|
|
|
op.value = target.value; // initialize value |
|
|
|
op.targets().add(target); // chain dependencies |
|
|
|
df.connect(target, [op]); // rerank as needed, #1672 |
|
} |
|
} |
|
} |
|
|
|
function updater(target, update) { |
|
update = isFunction(update) ? update : constant(update); |
|
return target ? function (_, pulse) { |
|
var value = update(_, pulse); |
|
|
|
if (!target.skip()) { |
|
target.skip(value !== this.value).value = value; |
|
} |
|
|
|
return value; |
|
} : update; |
|
} |
|
/** |
|
* Assigns a rank to an operator. Ranks are assigned in increasing order |
|
* by incrementing an internal rank counter. |
|
* @param {Operator} op - The operator to assign a rank. |
|
*/ |
|
|
|
|
|
function rank(op) { |
|
op.rank = ++this._rank; |
|
} |
|
/** |
|
* Re-ranks an operator and all downstream target dependencies. This |
|
* is necessary when upstream dependencies of higher rank are added to |
|
* a target operator. |
|
* @param {Operator} op - The operator to re-rank. |
|
*/ |
|
|
|
|
|
function rerank(op) { |
|
var queue = [op], |
|
cur, |
|
list, |
|
i; |
|
|
|
while (queue.length) { |
|
this.rank(cur = queue.pop()); |
|
|
|
if (list = cur._targets) { |
|
for (i = list.length; --i >= 0;) { |
|
queue.push(cur = list[i]); |
|
if (cur === op) error('Cycle detected in dataflow graph.'); |
|
} |
|
} |
|
} |
|
} |
|
/** |
|
* Sentinel value indicating pulse propagation should stop. |
|
*/ |
|
|
|
|
|
var StopPropagation = {}; // Pulse visit type flags |
|
|
|
var ADD = 1 << 0, |
|
REM = 1 << 1, |
|
MOD = 1 << 2, |
|
ADD_REM = ADD | REM, |
|
ADD_MOD = ADD | MOD, |
|
ALL = ADD | REM | MOD, |
|
REFLOW = 1 << 3, |
|
SOURCE = 1 << 4, |
|
NO_SOURCE = 1 << 5, |
|
NO_FIELDS = 1 << 6; |
|
/** |
|
* A Pulse enables inter-operator communication during a run of the |
|
* dataflow graph. In addition to the current timestamp, a pulse may also |
|
* contain a change-set of added, removed or modified data tuples, as well as |
|
* a pointer to a full backing data source. Tuple change sets may not |
|
* be fully materialized; for example, to prevent needless array creation |
|
* a change set may include larger arrays and corresponding filter functions. |
|
* The pulse provides a {@link visit} method to enable proper and efficient |
|
* iteration over requested data tuples. |
|
* |
|
* In addition, each pulse can track modification flags for data tuple fields. |
|
* Responsible transform operators should call the {@link modifies} method to |
|
* indicate changes to data fields. The {@link modified} method enables |
|
* querying of this modification state. |
|
* |
|
* @constructor |
|
* @param {Dataflow} dataflow - The backing dataflow instance. |
|
* @param {number} stamp - The current propagation timestamp. |
|
* @param {string} [encode] - An optional encoding set name, which is then |
|
* accessible as Pulse.encode. Operators can respond to (or ignore) this |
|
* setting as appropriate. This parameter can be used in conjunction with |
|
* the Encode transform in the vega-encode module. |
|
*/ |
|
|
|
function Pulse(dataflow, stamp, encode) { |
|
this.dataflow = dataflow; |
|
this.stamp = stamp == null ? -1 : stamp; |
|
this.add = []; |
|
this.rem = []; |
|
this.mod = []; |
|
this.fields = null; |
|
this.encode = encode || null; |
|
} |
|
|
|
var prototype$3 = Pulse.prototype; |
|
/** |
|
* Sentinel value indicating pulse propagation should stop. |
|
*/ |
|
|
|
prototype$3.StopPropagation = StopPropagation; |
|
/** |
|
* Boolean flag indicating ADD (added) tuples. |
|
*/ |
|
|
|
prototype$3.ADD = ADD; |
|
/** |
|
* Boolean flag indicating REM (removed) tuples. |
|
*/ |
|
|
|
prototype$3.REM = REM; |
|
/** |
|
* Boolean flag indicating MOD (modified) tuples. |
|
*/ |
|
|
|
prototype$3.MOD = MOD; |
|
/** |
|
* Boolean flag indicating ADD (added) and REM (removed) tuples. |
|
*/ |
|
|
|
prototype$3.ADD_REM = ADD_REM; |
|
/** |
|
* Boolean flag indicating ADD (added) and MOD (modified) tuples. |
|
*/ |
|
|
|
prototype$3.ADD_MOD = ADD_MOD; |
|
/** |
|
* Boolean flag indicating ADD, REM and MOD tuples. |
|
*/ |
|
|
|
prototype$3.ALL = ALL; |
|
/** |
|
* Boolean flag indicating all tuples in a data source |
|
* except for the ADD, REM and MOD tuples. |
|
*/ |
|
|
|
prototype$3.REFLOW = REFLOW; |
|
/** |
|
* Boolean flag indicating a 'pass-through' to a |
|
* backing data source, ignoring ADD, REM and MOD tuples. |
|
*/ |
|
|
|
prototype$3.SOURCE = SOURCE; |
|
/** |
|
* Boolean flag indicating that source data should be |
|
* suppressed when creating a forked pulse. |
|
*/ |
|
|
|
prototype$3.NO_SOURCE = NO_SOURCE; |
|
/** |
|
* Boolean flag indicating that field modifications should be |
|
* suppressed when creating a forked pulse. |
|
*/ |
|
|
|
prototype$3.NO_FIELDS = NO_FIELDS; |
|
/** |
|
* Creates a new pulse based on the values of this pulse. |
|
* The dataflow, time stamp and field modification values are copied over. |
|
* By default, new empty ADD, REM and MOD arrays are created. |
|
* @param {number} flags - Integer of boolean flags indicating which (if any) |
|
* tuple arrays should be copied to the new pulse. The supported flag values |
|
* are ADD, REM and MOD. Array references are copied directly: new array |
|
* instances are not created. |
|
* @return {Pulse} - The forked pulse instance. |
|
* @see init |
|
*/ |
|
|
|
prototype$3.fork = function (flags) { |
|
return new Pulse(this.dataflow).init(this, flags); |
|
}; |
|
/** |
|
* Creates a copy of this pulse with new materialized array |
|
* instances for the ADD, REM, MOD, and SOURCE arrays. |
|
* The dataflow, time stamp and field modification values are copied over. |
|
* @return {Pulse} - The cloned pulse instance. |
|
* @see init |
|
*/ |
|
|
|
|
|
prototype$3.clone = function () { |
|
var p = this.fork(ALL); |
|
p.add = p.add.slice(); |
|
p.rem = p.rem.slice(); |
|
p.mod = p.mod.slice(); |
|
if (p.source) p.source = p.source.slice(); |
|
return p.materialize(ALL | SOURCE); |
|
}; |
|
/** |
|
* Returns a pulse that adds all tuples from a backing source. This is |
|
* useful for cases where operators are added to a dataflow after an |
|
* upstream data pipeline has already been processed, ensuring that |
|
* new operators can observe all tuples within a stream. |
|
* @return {Pulse} - A pulse instance with all source tuples included |
|
* in the add array. If the current pulse already has all source |
|
* tuples in its add array, it is returned directly. If the current |
|
* pulse does not have a backing source, it is returned directly. |
|
*/ |
|
|
|
|
|
prototype$3.addAll = function () { |
|
var p = this; |
|
|
|
if (!this.source || this.source.length === this.add.length) { |
|
return p; |
|
} else { |
|
p = new Pulse(this.dataflow).init(this); |
|
p.add = p.source; |
|
return p; |
|
} |
|
}; |
|
/** |
|
* Initialize this pulse based on the values of another pulse. This method |
|
* is used internally by {@link fork} to initialize a new forked tuple. |
|
* The dataflow, time stamp and field modification values are copied over. |
|
* By default, new empty ADD, REM and MOD arrays are created. |
|
* @param {Pulse} src - The source pulse to copy from. |
|
* @param {number} flags - Integer of boolean flags indicating which (if any) |
|
* tuple arrays should be copied to the new pulse. The supported flag values |
|
* are ADD, REM and MOD. Array references are copied directly: new array |
|
* instances are not created. By default, source data arrays are copied |
|
* to the new pulse. Use the NO_SOURCE flag to enforce a null source. |
|
* @return {Pulse} - Returns this Pulse instance. |
|
*/ |
|
|
|
|
|
prototype$3.init = function (src, flags) { |
|
var p = this; |
|
p.stamp = src.stamp; |
|
p.encode = src.encode; |
|
|
|
if (src.fields && !(flags & NO_FIELDS)) { |
|
p.fields = src.fields; |
|
} |
|
|
|
if (flags & ADD) { |
|
p.addF = src.addF; |
|
p.add = src.add; |
|
} else { |
|
p.addF = null; |
|
p.add = []; |
|
} |
|
|
|
if (flags & REM) { |
|
p.remF = src.remF; |
|
p.rem = src.rem; |
|
} else { |
|
p.remF = null; |
|
p.rem = []; |
|
} |
|
|
|
if (flags & MOD) { |
|
p.modF = src.modF; |
|
p.mod = src.mod; |
|
} else { |
|
p.modF = null; |
|
p.mod = []; |
|
} |
|
|
|
if (flags & NO_SOURCE) { |
|
p.srcF = null; |
|
p.source = null; |
|
} else { |
|
p.srcF = src.srcF; |
|
p.source = src.source; |
|
} |
|
|
|
return p; |
|
}; |
|
/** |
|
* Schedules a function to run after pulse propagation completes. |
|
* @param {function} func - The function to run. |
|
*/ |
|
|
|
|
|
prototype$3.runAfter = function (func) { |
|
this.dataflow.runAfter(func); |
|
}; |
|
/** |
|
* Indicates if tuples have been added, removed or modified. |
|
* @param {number} [flags] - The tuple types (ADD, REM or MOD) to query. |
|
* Defaults to ALL, returning true if any tuple type has changed. |
|
* @return {boolean} - Returns true if one or more queried tuple types have |
|
* changed, false otherwise. |
|
*/ |
|
|
|
|
|
prototype$3.changed = function (flags) { |
|
var f = flags || ALL; |
|
return f & ADD && this.add.length || f & REM && this.rem.length || f & MOD && this.mod.length; |
|
}; |
|
/** |
|
* Forces a "reflow" of tuple values, such that all tuples in the backing |
|
* source are added to the MOD set, unless already present in the ADD set. |
|
* @param {boolean} [fork=false] - If true, returns a forked copy of this |
|
* pulse, and invokes reflow on that derived pulse. |
|
* @return {Pulse} - The reflowed pulse instance. |
|
*/ |
|
|
|
|
|
prototype$3.reflow = function (fork) { |
|
if (fork) return this.fork(ALL).reflow(); |
|
var len = this.add.length, |
|
src = this.source && this.source.length; |
|
|
|
if (src && src !== len) { |
|
this.mod = this.source; |
|
if (len) this.filter(MOD, filter(this, ADD)); |
|
} |
|
|
|
return this; |
|
}; |
|
/** |
|
* Marks one or more data field names as modified to assist dependency |
|
* tracking and incremental processing by transform operators. |
|
* @param {string|Array<string>} _ - The field(s) to mark as modified. |
|
* @return {Pulse} - This pulse instance. |
|
*/ |
|
|
|
|
|
prototype$3.modifies = function (_) { |
|
var hash = this.fields || (this.fields = {}); |
|
|
|
if (isArray(_)) { |
|
_.forEach(function (f) { |
|
return hash[f] = true; |
|
}); |
|
} else { |
|
hash[_] = true; |
|
} |
|
|
|
return this; |
|
}; |
|
/** |
|
* Checks if one or more data fields have been modified during this pulse |
|
* propagation timestamp. |
|
* @param {string|Array<string>} _ - The field(s) to check for modified. |
|
* @param {boolean} nomod - If true, will check the modified flag even if |
|
* no mod tuples exist. If false (default), mod tuples must be present. |
|
* @return {boolean} - Returns true if any of the provided fields has been |
|
* marked as modified, false otherwise. |
|
*/ |
|
|
|
|
|
prototype$3.modified = function (_, nomod) { |
|
var fields = this.fields; |
|
return !((nomod || this.mod.length) && fields) ? false : !arguments.length ? !!fields : isArray(_) ? _.some(function (f) { |
|
return fields[f]; |
|
}) : fields[_]; |
|
}; |
|
/** |
|
* Adds a filter function to one more tuple sets. Filters are applied to |
|
* backing tuple arrays, to determine the actual set of tuples considered |
|
* added, removed or modified. They can be used to delay materialization of |
|
* a tuple set in order to avoid expensive array copies. In addition, the |
|
* filter functions can serve as value transformers: unlike standard predicate |
|
* function (which return boolean values), Pulse filters should return the |
|
* actual tuple value to process. If a tuple set is already filtered, the |
|
* new filter function will be appended into a conjuntive ('and') query. |
|
* @param {number} flags - Flags indicating the tuple set(s) to filter. |
|
* @param {function(*):object} filter - Filter function that will be applied |
|
* to the tuple set array, and should return a data tuple if the value |
|
* should be included in the tuple set, and falsy (or null) otherwise. |
|
* @return {Pulse} - Returns this pulse instance. |
|
*/ |
|
|
|
|
|
prototype$3.filter = function (flags, filter) { |
|
var p = this; |
|
if (flags & ADD) p.addF = addFilter(p.addF, filter); |
|
if (flags & REM) p.remF = addFilter(p.remF, filter); |
|
if (flags & MOD) p.modF = addFilter(p.modF, filter); |
|
if (flags & SOURCE) p.srcF = addFilter(p.srcF, filter); |
|
return p; |
|
}; |
|
|
|
function addFilter(a, b) { |
|
return a ? function (t, i) { |
|
return a(t, i) && b(t, i); |
|
} : b; |
|
} |
|
/** |
|
* Materialize one or more tuple sets in this pulse. If the tuple set(s) have |
|
* a registered filter function, it will be applied and the tuple set(s) will |
|
* be replaced with materialized tuple arrays. |
|
* @param {number} flags - Flags indicating the tuple set(s) to materialize. |
|
* @return {Pulse} - Returns this pulse instance. |
|
*/ |
|
|
|
|
|
prototype$3.materialize = function (flags) { |
|
flags = flags || ALL; |
|
var p = this; |
|
|
|
if (flags & ADD && p.addF) { |
|
p.add = materialize(p.add, p.addF); |
|
p.addF = null; |
|
} |
|
|
|
if (flags & REM && p.remF) { |
|
p.rem = materialize(p.rem, p.remF); |
|
p.remF = null; |
|
} |
|
|
|
if (flags & MOD && p.modF) { |
|
p.mod = materialize(p.mod, p.modF); |
|
p.modF = null; |
|
} |
|
|
|
if (flags & SOURCE && p.srcF) { |
|
p.source = p.source.filter(p.srcF); |
|
p.srcF = null; |
|
} |
|
|
|
return p; |
|
}; |
|
|
|
function materialize(data, filter) { |
|
var out = []; |
|
visitArray(data, filter, function (_) { |
|
out.push(_); |
|
}); |
|
return out; |
|
} |
|
|
|
function filter(pulse, flags) { |
|
var map = {}; |
|
pulse.visit(flags, function (t) { |
|
map[tupleid(t)] = 1; |
|
}); |
|
return function (t) { |
|
return map[tupleid(t)] ? null : t; |
|
}; |
|
} |
|
/** |
|
* Visit one or more tuple sets in this pulse. |
|
* @param {number} flags - Flags indicating the tuple set(s) to visit. |
|
* Legal values are ADD, REM, MOD and SOURCE (if a backing data source |
|
* has been set). |
|
* @param {function(object):*} - Visitor function invoked per-tuple. |
|
* @return {Pulse} - Returns this pulse instance. |
|
*/ |
|
|
|
|
|
prototype$3.visit = function (flags, visitor) { |
|
var p = this, |
|
v = visitor, |
|
src, |
|
sum; |
|
|
|
if (flags & SOURCE) { |
|
visitArray(p.source, p.srcF, v); |
|
return p; |
|
} |
|
|
|
if (flags & ADD) visitArray(p.add, p.addF, v); |
|
if (flags & REM) visitArray(p.rem, p.remF, v); |
|
if (flags & MOD) visitArray(p.mod, p.modF, v); |
|
|
|
if (flags & REFLOW && (src = p.source)) { |
|
sum = p.add.length + p.mod.length; |
|
if (sum === src.length) ;else if (sum) { |
|
visitArray(src, filter(p, ADD_MOD), v); |
|
} else { |
|
// if no add/rem/mod tuples, visit source |
|
visitArray(src, p.srcF, v); |
|
} |
|
} |
|
|
|
return p; |
|
}; |
|
/** |
|
* Represents a set of multiple pulses. Used as input for operators |
|
* that accept multiple pulses at a time. Contained pulses are |
|
* accessible via the public "pulses" array property. This pulse doe |
|
* not carry added, removed or modified tuples directly. However, |
|
* the visit method can be used to traverse all such tuples contained |
|
* in sub-pulses with a timestamp matching this parent multi-pulse. |
|
* @constructor |
|
* @param {Dataflow} dataflow - The backing dataflow instance. |
|
* @param {number} stamp - The timestamp. |
|
* @param {Array<Pulse>} pulses - The sub-pulses for this multi-pulse. |
|
*/ |
|
|
|
|
|
function MultiPulse(dataflow, stamp, pulses, encode) { |
|
var p = this, |
|
c = 0, |
|
pulse, |
|
hash, |
|
i, |
|
n, |
|
f; |
|
this.dataflow = dataflow; |
|
this.stamp = stamp; |
|
this.fields = null; |
|
this.encode = encode || null; |
|
this.pulses = pulses; |
|
|
|
for (i = 0, n = pulses.length; i < n; ++i) { |
|
pulse = pulses[i]; |
|
if (pulse.stamp !== stamp) continue; |
|
|
|
if (pulse.fields) { |
|
hash = p.fields || (p.fields = {}); |
|
|
|
for (f in pulse.fields) { |
|
hash[f] = 1; |
|
} |
|
} |
|
|
|
if (pulse.changed(p.ADD)) c |= p.ADD; |
|
if (pulse.changed(p.REM)) c |= p.REM; |
|
if (pulse.changed(p.MOD)) c |= p.MOD; |
|
} |
|
|
|
this.changes = c; |
|
} |
|
|
|
var prototype$4 = inherits(MultiPulse, Pulse); |
|
/** |
|
* Creates a new pulse based on the values of this pulse. |
|
* The dataflow, time stamp and field modification values are copied over. |
|
* @return {Pulse} |
|
*/ |
|
|
|
prototype$4.fork = function (flags) { |
|
var p = new Pulse(this.dataflow).init(this, flags & this.NO_FIELDS); |
|
|
|
if (flags !== undefined) { |
|
if (flags & p.ADD) { |
|
this.visit(p.ADD, function (t) { |
|
return p.add.push(t); |
|
}); |
|
} |
|
|
|
if (flags & p.REM) { |
|
this.visit(p.REM, function (t) { |
|
return p.rem.push(t); |
|
}); |
|
} |
|
|
|
if (flags & p.MOD) { |
|
this.visit(p.MOD, function (t) { |
|
return p.mod.push(t); |
|
}); |
|
} |
|
} |
|
|
|
return p; |
|
}; |
|
|
|
prototype$4.changed = function (flags) { |
|
return this.changes & flags; |
|
}; |
|
|
|
prototype$4.modified = function (_) { |
|
var p = this, |
|
fields = p.fields; |
|
return !(fields && p.changes & p.MOD) ? 0 : isArray(_) ? _.some(function (f) { |
|
return fields[f]; |
|
}) : fields[_]; |
|
}; |
|
|
|
prototype$4.filter = function () { |
|
error('MultiPulse does not support filtering.'); |
|
}; |
|
|
|
prototype$4.materialize = function () { |
|
error('MultiPulse does not support materialization.'); |
|
}; |
|
|
|
prototype$4.visit = function (flags, visitor) { |
|
var p = this, |
|
pulses = p.pulses, |
|
n = pulses.length, |
|
i = 0; |
|
|
|
if (flags & p.SOURCE) { |
|
for (; i < n; ++i) { |
|
pulses[i].visit(flags, visitor); |
|
} |
|
} else { |
|
for (; i < n; ++i) { |
|
if (pulses[i].stamp === p.stamp) { |
|
pulses[i].visit(flags, visitor); |
|
} |
|
} |
|
} |
|
|
|
return p; |
|
}; |
|
/* eslint-disable require-atomic-updates */ |
|
|
|
/** |
|
* Evaluates the dataflow and returns a Promise that resolves when pulse |
|
* propagation completes. This method will increment the current timestamp |
|
* and process all updated, pulsed and touched operators. When invoked for |
|
* the first time, all registered operators will be processed. This method |
|
* should not be invoked by third-party clients, use {@link runAsync} or |
|
* {@link run} instead. |
|
* @param {string} [encode] - The name of an encoding set to invoke during |
|
* propagation. This value is added to generated Pulse instances; |
|
* operators can then respond to (or ignore) this setting as appropriate. |
|
* This parameter can be used in conjunction with the Encode transform in |
|
* the vega-encode package. |
|
* @param {function} [prerun] - An optional callback function to invoke |
|
* immediately before dataflow evaluation commences. |
|
* @param {function} [postrun] - An optional callback function to invoke |
|
* after dataflow evaluation completes. The callback will be invoked |
|
* after those registered via {@link runAfter}. |
|
* @return {Promise} - A promise that resolves to this dataflow after |
|
* evaluation completes. |
|
*/ |
|
|
|
|
|
function evaluate(_x14, _x15, _x16) { |
|
return _evaluate.apply(this, arguments); |
|
} |
|
/** |
|
* Queues dataflow evaluation to run once any other queued evaluations have |
|
* completed and returns a Promise that resolves when the queued pulse |
|
* propagation completes. If provided, a callback function will be invoked |
|
* immediately before evaluation commences. This method will ensure a |
|
* separate evaluation is invoked for each time it is called. |
|
* @param {string} [encode] - The name of an encoding set to invoke during |
|
* propagation. This value is added to generated Pulse instances; |
|
* operators can then respond to (or ignore) this setting as appropriate. |
|
* This parameter can be used in conjunction with the Encode transform in |
|
* the vega-encode package. |
|
* @param {function} [prerun] - An optional callback function to invoke |
|
* immediately before dataflow evaluation commences. |
|
* @param {function} [postrun] - An optional callback function to invoke |
|
* after dataflow evaluation completes. The callback will be invoked |
|
* after those registered via {@link runAfter}. |
|
* @return {Promise} - A promise that resolves to this dataflow after |
|
* evaluation completes. |
|
*/ |
|
|
|
|
|
function _evaluate() { |
|
_evaluate = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee10(encode, prerun, postrun) { |
|
var df, level, async, stamp, count, op, next, dt, error, pr, i; |
|
return regeneratorRuntime.wrap(function _callee10$(_context16) { |
|
while (1) { |
|
switch (_context16.prev = _context16.next) { |
|
case 0: |
|
df = this, level = df.logLevel(), async = []; // if the pulse value is set, this is a re-entrant call |
|
|
|
if (!df._pulse) { |
|
_context16.next = 3; |
|
break; |
|
} |
|
|
|
return _context16.abrupt("return", reentrant(df)); |
|
|
|
case 3: |
|
if (!df._pending) { |
|
_context16.next = 6; |
|
break; |
|
} |
|
|
|
_context16.next = 6; |
|
return df._pending; |
|
|
|
case 6: |
|
if (!prerun) { |
|
_context16.next = 9; |
|
break; |
|
} |
|
|
|
_context16.next = 9; |
|
return asyncCallback(df, prerun); |
|
|
|
case 9: |
|
if (df._touched.length) { |
|
_context16.next = 12; |
|
break; |
|
} |
|
|
|
df.info('Dataflow invoked, but nothing to do.'); |
|
return _context16.abrupt("return", df); |
|
|
|
case 12: |
|
// increment timestamp clock |
|
stamp = ++df._clock, count = 0; // set the current pulse |
|
|
|
df._pulse = new Pulse(df, stamp, encode); |
|
|
|
if (level >= Info) { |
|
dt = Date.now(); |
|
df.debug('-- START PROPAGATION (' + stamp + ') -----'); |
|
} // initialize priority queue, reset touched operators |
|
|
|
|
|
df._touched.forEach(function (op) { |
|
return df._enqueue(op, true); |
|
}); |
|
|
|
df._touched = UniqueList(id); |
|
_context16.prev = 17; |
|
|
|
case 18: |
|
if (!(df._heap.size() > 0)) { |
|
_context16.next = 36; |
|
break; |
|
} |
|
|
|
// dequeue operator with highest priority |
|
op = df._heap.pop(); // re-queue if rank changed |
|
|
|
if (!(op.rank !== op.qrank)) { |
|
_context16.next = 23; |
|
break; |
|
} |
|
|
|
df._enqueue(op, true); |
|
|
|
return _context16.abrupt("continue", 18); |
|
|
|
case 23: |
|
// otherwise, evaluate the operator |
|
next = op.run(df._getPulse(op, encode)); |
|
|
|
if (!next.then) { |
|
_context16.next = 30; |
|
break; |
|
} |
|
|
|
_context16.next = 27; |
|
return next; |
|
|
|
case 27: |
|
next = _context16.sent; |
|
_context16.next = 31; |
|
break; |
|
|
|
case 30: |
|
if (next.async) { |
|
// queue parallel asynchronous execution |
|
async.push(next.async); |
|
next = StopPropagation; |
|
} |
|
|
|
case 31: |
|
if (level >= Debug) { |
|
df.debug(op.id, next === StopPropagation ? 'STOP' : next, op); |
|
} // propagate evaluation, enqueue dependent operators |
|
|
|
|
|
if (next !== StopPropagation) { |
|
if (op._targets) op._targets.forEach(function (op) { |
|
return df._enqueue(op); |
|
}); |
|
} // increment visit counter |
|
|
|
|
|
++count; |
|
_context16.next = 18; |
|
break; |
|
|
|
case 36: |
|
_context16.next = 42; |
|
break; |
|
|
|
case 38: |
|
_context16.prev = 38; |
|
_context16.t0 = _context16["catch"](17); |
|
|
|
df._heap.clear(); |
|
|
|
error = _context16.t0; |
|
|
|
case 42: |
|
// reset pulse map |
|
df._input = {}; |
|
df._pulse = null; |
|
|
|
if (level >= Info) { |
|
dt = Date.now() - dt; |
|
df.info('> Pulse ' + stamp + ': ' + count + ' operators; ' + dt + 'ms'); |
|
} |
|
|
|
if (error) { |
|
df._postrun = []; |
|
df.error(error); |
|
} // invoke callbacks queued via runAfter |
|
|
|
|
|
if (!df._postrun.length) { |
|
_context16.next = 56; |
|
break; |
|
} |
|
|
|
pr = df._postrun.sort(function (a, b) { |
|
return b.priority - a.priority; |
|
}); |
|
df._postrun = []; |
|
i = 0; |
|
|
|
case 50: |
|
if (!(i < pr.length)) { |
|
_context16.next = 56; |
|
break; |
|
} |
|
|
|
_context16.next = 53; |
|
return asyncCallback(df, pr[i].callback); |
|
|
|
case 53: |
|
++i; |
|
_context16.next = 50; |
|
break; |
|
|
|
case 56: |
|
if (!postrun) { |
|
_context16.next = 59; |
|
break; |
|
} |
|
|
|
_context16.next = 59; |
|
return asyncCallback(df, postrun); |
|
|
|
case 59: |
|
// handle non-blocking asynchronous callbacks |
|
if (async.length) { |
|
Promise.all(async).then(function (cb) { |
|
return df.runAsync(null, function () { |
|
cb.forEach(function (f) { |
|
try { |
|
f(df); |
|
} catch (err) { |
|
df.error(err); |
|
} |
|
}); |
|
}); |
|
}); |
|
} |
|
|
|
return _context16.abrupt("return", df); |
|
|
|
case 61: |
|
case "end": |
|
return _context16.stop(); |
|
} |
|
} |
|
}, _callee10, this, [[17, 38]]); |
|
})); |
|
return _evaluate.apply(this, arguments); |
|
} |
|
|
|
function runAsync(_x17, _x18, _x19) { |
|
return _runAsync.apply(this, arguments); |
|
} |
|
/** |
|
* Requests dataflow evaluation and the immediately returns this dataflow |
|
* instance. If there are pending data loading or other asynchronous |
|
* operations, the dataflow will evaluate asynchronously after this method |
|
* has been invoked. To track when dataflow evaluation completes, use the |
|
* {@link runAsync} method instead. This method will raise an error if |
|
* invoked while the dataflow is already in the midst of evaluation. |
|
* @param {string} [encode] - The name of an encoding set to invoke during |
|
* propagation. This value is added to generated Pulse instances; |
|
* operators can then respond to (or ignore) this setting as appropriate. |
|
* This parameter can be used in conjunction with the Encode transform in |
|
* the vega-encode module. |
|
* @param {function} [prerun] - An optional callback function to invoke |
|
* immediately before dataflow evaluation commences. |
|
* @param {function} [postrun] - An optional callback function to invoke |
|
* after dataflow evaluation completes. The callback will be invoked |
|
* after those registered via {@link runAfter}. |
|
* @return {Dataflow} - This dataflow instance. |
|
*/ |
|
|
|
|
|
function _runAsync() { |
|
_runAsync = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee11(encode, prerun, postrun) { |
|
var _this3 = this; |
|
|
|
var clear; |
|
return regeneratorRuntime.wrap(function _callee11$(_context17) { |
|
while (1) { |
|
switch (_context17.prev = _context17.next) { |
|
case 0: |
|
if (!this._running) { |
|
_context17.next = 5; |
|
break; |
|
} |
|
|
|
_context17.next = 3; |
|
return this._running; |
|
|
|
case 3: |
|
_context17.next = 0; |
|
break; |
|
|
|
case 5: |
|
// run dataflow, manage running promise |
|
clear = function clear() { |
|
return _this3._running = null; |
|
}; |
|
|
|
(this._running = this.evaluate(encode, prerun, postrun)).then(clear, clear); |
|
return _context17.abrupt("return", this._running); |
|
|
|
case 8: |
|
case "end": |
|
return _context17.stop(); |
|
} |
|
} |
|
}, _callee11, this); |
|
})); |
|
return _runAsync.apply(this, arguments); |
|
} |
|
|
|
function run(encode, prerun, postrun) { |
|
return this._pulse ? reentrant(this) : (this.evaluate(encode, prerun, postrun), this); |
|
} |
|
/** |
|
* Schedules a callback function to be invoked after the current pulse |
|
* propagation completes. If no propagation is currently occurring, |
|
* the function is invoked immediately. Callbacks scheduled via runAfter |
|
* are invoked immediately upon completion of the current cycle, before |
|
* any request queued via runAsync. This method is primarily intended for |
|
* internal use. Third-party callers using runAfter to schedule a callback |
|
* that invokes {@link run} or {@link runAsync} should not use this method, |
|
* but instead use {@link runAsync} with prerun or postrun arguments. |
|
* @param {function(Dataflow)} callback - The callback function to run. |
|
* The callback will be invoked with this Dataflow instance as its |
|
* sole argument. |
|
* @param {boolean} enqueue - A boolean flag indicating that the |
|
* callback should be queued up to run after the next propagation |
|
* cycle, suppressing immediate invocation when propagation is not |
|
* currently occurring. |
|
* @param {number} [priority] - A priority value used to sort registered |
|
* callbacks to determine execution order. This argument is intended |
|
* for internal Vega use only. |
|
*/ |
|
|
|
|
|
function runAfter(callback, enqueue, priority) { |
|
if (this._pulse || enqueue) { |
|
// pulse propagation is currently running, queue to run after |
|
this._postrun.push({ |
|
priority: priority || 0, |
|
callback: callback |
|
}); |
|
} else { |
|
// pulse propagation already complete, invoke immediately |
|
try { |
|
callback(this); |
|
} catch (err) { |
|
this.error(err); |
|
} |
|
} |
|
} |
|
/** |
|
* Raise an error for re-entrant dataflow evaluation. |
|
*/ |
|
|
|
|
|
function reentrant(df) { |
|
df.error('Dataflow already running. Use runAsync() to chain invocations.'); |
|
return df; |
|
} |
|
/** |
|
* Enqueue an operator into the priority queue for evaluation. The operator |
|
* will be enqueued if it has no registered pulse for the current cycle, or if |
|
* the force argument is true. Upon enqueue, this method also sets the |
|
* operator's qrank to the current rank value. |
|
* @param {Operator} op - The operator to enqueue. |
|
* @param {boolean} [force] - A flag indicating if the operator should be |
|
* forceably added to the queue, even if it has already been previously |
|
* enqueued during the current pulse propagation. This is useful when the |
|
* dataflow graph is dynamically modified and the operator rank changes. |
|
*/ |
|
|
|
|
|
function enqueue(op, force) { |
|
var q = op.stamp < this._clock; |
|
if (q) op.stamp = this._clock; |
|
|
|
if (q || force) { |
|
op.qrank = op.rank; |
|
|
|
this._heap.push(op); |
|
} |
|
} |
|
/** |
|
* Provide a correct pulse for evaluating an operator. If the operator has an |
|
* explicit source operator, we will try to pull the pulse(s) from it. |
|
* If there is an array of source operators, we build a multi-pulse. |
|
* Otherwise, we return a current pulse with correct source data. |
|
* If the pulse is the pulse map has an explicit target set, we use that. |
|
* Else if the pulse on the upstream source operator is current, we use that. |
|
* Else we use the pulse from the pulse map, but copy the source tuple array. |
|
* @param {Operator} op - The operator for which to get an input pulse. |
|
* @param {string} [encode] - An (optional) encoding set name with which to |
|
* annotate the returned pulse. See {@link run} for more information. |
|
*/ |
|
|
|
|
|
function getPulse(op, encode) { |
|
var s = op.source, |
|
stamp = this._clock; |
|
return s && isArray(s) ? new MultiPulse(this, stamp, s.map(function (_) { |
|
return _.pulse; |
|
}), encode) : this._input[op.id] || singlePulse(this._pulse, s && s.pulse); |
|
} |
|
|
|
function singlePulse(p, s) { |
|
if (s && s.stamp === p.stamp) { |
|
return s; |
|
} |
|
|
|
p = p.fork(); |
|
|
|
if (s && s !== StopPropagation) { |
|
p.source = s.source; |
|
} |
|
|
|
return p; |
|
} |
|
|
|
var NO_OPT = { |
|
skip: false, |
|
force: false |
|
}; |
|
/** |
|
* Touches an operator, scheduling it to be evaluated. If invoked outside of |
|
* a pulse propagation, the operator will be evaluated the next time this |
|
* dataflow is run. If invoked in the midst of pulse propagation, the operator |
|
* will be queued for evaluation if and only if the operator has not yet been |
|
* evaluated on the current propagation timestamp. |
|
* @param {Operator} op - The operator to touch. |
|
* @param {object} [options] - Additional options hash. |
|
* @param {boolean} [options.skip] - If true, the operator will |
|
* be skipped: it will not be evaluated, but its dependents will be. |
|
* @return {Dataflow} |
|
*/ |
|
|
|
function touch(op, options) { |
|
var opt = options || NO_OPT; |
|
|
|
if (this._pulse) { |
|
// if in midst of propagation, add to priority queue |
|
this._enqueue(op); |
|
} else { |
|
// otherwise, queue for next propagation |
|
this._touched.add(op); |
|
} |
|
|
|
if (opt.skip) op.skip(true); |
|
return this; |
|
} |
|
/** |
|
* Updates the value of the given operator. |
|
* @param {Operator} op - The operator to update. |
|
* @param {*} value - The value to set. |
|
* @param {object} [options] - Additional options hash. |
|
* @param {boolean} [options.force] - If true, the operator will |
|
* be re-evaluated even if its value has not changed. |
|
* @param {boolean} [options.skip] - If true, the operator will |
|
* be skipped: it will not be evaluated, but its dependents will be. |
|
* @return {Dataflow} |
|
*/ |
|
|
|
|
|
function update(op, value, options) { |
|
var opt = options || NO_OPT; |
|
|
|
if (op.set(value) || opt.force) { |
|
this.touch(op, opt); |
|
} |
|
|
|
return this; |
|
} |
|
/** |
|
* Pulses an operator with a changeset of tuples. If invoked outside of |
|
* a pulse propagation, the pulse will be applied the next time this |
|
* dataflow is run. If invoked in the midst of pulse propagation, the pulse |
|
* will be added to the set of active pulses and will be applied if and |
|
* only if the target operator has not yet been evaluated on the current |
|
* propagation timestamp. |
|
* @param {Operator} op - The operator to pulse. |
|
* @param {ChangeSet} value - The tuple changeset to apply. |
|
* @param {object} [options] - Additional options hash. |
|
* @param {boolean} [options.skip] - If true, the operator will |
|
* be skipped: it will not be evaluated, but its dependents will be. |
|
* @return {Dataflow} |
|
*/ |
|
|
|
|
|
function pulse(op, changeset, options) { |
|
this.touch(op, options || NO_OPT); |
|
var p = new Pulse(this, this._clock + (this._pulse ? 0 : 1)), |
|
t = op.pulse && op.pulse.source || []; |
|
p.target = op; |
|
this._input[op.id] = changeset.pulse(p, t); |
|
return this; |
|
} |
|
|
|
function Heap(cmp) { |
|
var nodes = []; |
|
return { |
|
clear: function clear() { |
|
return nodes = []; |
|
}, |
|
size: function size() { |
|
return nodes.length; |
|
}, |
|
peek: function peek() { |
|
return nodes[0]; |
|
}, |
|
push: function push(x) { |
|
nodes.push(x); |
|
return siftdown(nodes, 0, nodes.length - 1, cmp); |
|
}, |
|
pop: function pop() { |
|
var last = nodes.pop(), |
|
item; |
|
|
|
if (nodes.length) { |
|
item = nodes[0]; |
|
nodes[0] = last; |
|
siftup(nodes, 0, cmp); |
|
} else { |
|
item = last; |
|
} |
|
|
|
return item; |
|
} |
|
}; |
|
} |
|
|
|
function siftdown(array, start, idx, cmp) { |
|
var item, parent, pidx; |
|
item = array[idx]; |
|
|
|
while (idx > start) { |
|
pidx = idx - 1 >> 1; |
|
parent = array[pidx]; |
|
|
|
if (cmp(item, parent) < 0) { |
|
array[idx] = parent; |
|
idx = pidx; |
|
continue; |
|
} |
|
|
|
break; |
|
} |
|
|
|
return array[idx] = item; |
|
} |
|
|
|
function siftup(array, idx, cmp) { |
|
var start = idx, |
|
end = array.length, |
|
item = array[idx], |
|
cidx = (idx << 1) + 1, |
|
ridx; |
|
|
|
while (cidx < end) { |
|
ridx = cidx + 1; |
|
|
|
if (ridx < end && cmp(array[cidx], array[ridx]) >= 0) { |
|
cidx = ridx; |
|
} |
|
|
|
array[idx] = array[cidx]; |
|
idx = cidx; |
|
cidx = (idx << 1) + 1; |
|
} |
|
|
|
array[idx] = item; |
|
return siftdown(array, start, idx, cmp); |
|
} |
|
/** |
|
* A dataflow graph for reactive processing of data streams. |
|
* @constructor |
|
*/ |
|
|
|
|
|
function Dataflow() { |
|
this.logger(logger()); |
|
this.logLevel(Error$1); |
|
this._clock = 0; |
|
this._rank = 0; |
|
|
|
try { |
|
this._loader = loader(); |
|
} catch (e) {// do nothing if loader module is unavailable |
|
} |
|
|
|
this._touched = UniqueList(id); |
|
this._input = {}; |
|
this._pulse = null; |
|
this._heap = Heap(function (a, b) { |
|
return a.qrank - b.qrank; |
|
}); |
|
this._postrun = []; |
|
} |
|
|
|
var prototype$5 = Dataflow.prototype; |
|
/** |
|
* The current timestamp of this dataflow. This value reflects the |
|
* timestamp of the previous dataflow run. The dataflow is initialized |
|
* with a stamp value of 0. The initial run of the dataflow will have |
|
* a timestap of 1, and so on. This value will match the |
|
* {@link Pulse.stamp} property. |
|
* @return {number} - The current timestamp value. |
|
*/ |
|
|
|
prototype$5.stamp = function () { |
|
return this._clock; |
|
}; |
|
/** |
|
* Gets or sets the loader instance to use for data file loading. A |
|
* loader object must provide a "load" method for loading files and a |
|
* "sanitize" method for checking URL/filename validity. Both methods |
|
* should accept a URI and options hash as arguments, and return a Promise |
|
* that resolves to the loaded file contents (load) or a hash containing |
|
* sanitized URI data with the sanitized url assigned to the "href" property |
|
* (sanitize). |
|
* @param {object} _ - The loader instance to use. |
|
* @return {object|Dataflow} - If no arguments are provided, returns |
|
* the current loader instance. Otherwise returns this Dataflow instance. |
|
*/ |
|
|
|
|
|
prototype$5.loader = function (_) { |
|
if (arguments.length) { |
|
this._loader = _; |
|
return this; |
|
} else { |
|
return this._loader; |
|
} |
|
}; |
|
/** |
|
* Empty entry threshold for garbage cleaning. Map data structures will |
|
* perform cleaning once the number of empty entries exceeds this value. |
|
*/ |
|
|
|
|
|
prototype$5.cleanThreshold = 1e4; // OPERATOR REGISTRATION |
|
|
|
prototype$5.add = add; |
|
prototype$5.connect = connect; |
|
prototype$5.rank = rank; |
|
prototype$5.rerank = rerank; // OPERATOR UPDATES |
|
|
|
prototype$5.pulse = pulse; |
|
prototype$5.touch = touch; |
|
prototype$5.update = update; |
|
prototype$5.changeset = changeset; // DATA LOADING |
|
|
|
prototype$5.ingest = ingest$1; |
|
prototype$5.parse = parse$1; |
|
prototype$5.preload = preload; |
|
prototype$5.request = request; // EVENT HANDLING |
|
|
|
prototype$5.events = events; |
|
prototype$5.on = on; // PULSE PROPAGATION |
|
|
|
prototype$5.evaluate = evaluate; |
|
prototype$5.run = run; |
|
prototype$5.runAsync = runAsync; |
|
prototype$5.runAfter = runAfter; |
|
prototype$5._enqueue = enqueue; |
|
prototype$5._getPulse = getPulse; // LOGGING AND ERROR HANDLING |
|
|
|
function logMethod(method) { |
|
return function () { |
|
return this._log[method].apply(this, arguments); |
|
}; |
|
} |
|
/** |
|
* Get or set the logger instance used to log messages. If no arguments are |
|
* provided, returns the current logger instance. Otherwise, sets the logger |
|
* and return this Dataflow instance. Provided loggers must support the full |
|
* API of logger objects generated by the vega-util logger method. Note that |
|
* by default the log level of the new logger will be used; use the logLevel |
|
* method to adjust the log level as needed. |
|
*/ |
|
|
|
|
|
prototype$5.logger = function (logger) { |
|
if (arguments.length) { |
|
this._log = logger; |
|
return this; |
|
} else { |
|
return this._log; |
|
} |
|
}; |
|
/** |
|
* Logs an error message. By default, logged messages are written to console |
|
* output. The message will only be logged if the current log level is high |
|
* enough to permit error messages. |
|
*/ |
|
|
|
|
|
prototype$5.error = logMethod('error'); |
|
/** |
|
* Logs a warning message. By default, logged messages are written to console |
|
* output. The message will only be logged if the current log level is high |
|
* enough to permit warning messages. |
|
*/ |
|
|
|
prototype$5.warn = logMethod('warn'); |
|
/** |
|
* Logs a information message. By default, logged messages are written to |
|
* console output. The message will only be logged if the current log level is |
|
* high enough to permit information messages. |
|
*/ |
|
|
|
prototype$5.info = logMethod('info'); |
|
/** |
|
* Logs a debug message. By default, logged messages are written to console |
|
* output. The message will only be logged if the current log level is high |
|
* enough to permit debug messages. |
|
*/ |
|
|
|
prototype$5.debug = logMethod('debug'); |
|
/** |
|
* Get or set the current log level. If an argument is provided, it |
|
* will be used as the new log level. |
|
* @param {number} [level] - Should be one of None, Warn, Info |
|
* @return {number} - The current log level. |
|
*/ |
|
|
|
prototype$5.logLevel = logMethod('level'); |
|
/** |
|
* Abstract class for operators that process data tuples. |
|
* Subclasses must provide a {@link transform} method for operator processing. |
|
* @constructor |
|
* @param {*} [init] - The initial value for this operator. |
|
* @param {object} [params] - The parameters for this operator. |
|
* @param {Operator} [source] - The operator from which to receive pulses. |
|
*/ |
|
|
|
function Transform(init, params) { |
|
Operator.call(this, init, null, params); |
|
} |
|
|
|
var prototype$6 = inherits(Transform, Operator); |
|
/** |
|
* Overrides {@link Operator.evaluate} for transform operators. |
|
* Internally, this method calls {@link evaluate} to perform processing. |
|
* If {@link evaluate} returns a falsy value, the input pulse is returned. |
|
* This method should NOT be overridden, instead overrride {@link evaluate}. |
|
* @param {Pulse} pulse - the current dataflow pulse. |
|
* @return the output pulse for this operator (or StopPropagation) |
|
*/ |
|
|
|
prototype$6.run = function (pulse) { |
|
var _this = this; |
|
|
|
if (pulse.stamp < this.stamp) return pulse.StopPropagation; |
|
var rv; |
|
|
|
if (this.skip()) { |
|
this.skip(false); |
|
} else { |
|
rv = this.evaluate(pulse); |
|
} |
|
|
|
rv = rv || pulse; |
|
|
|
if (rv.then) { |
|
rv = rv.then(function (_) { |
|
return _this.pulse = _; |
|
}); |
|
} else if (rv !== pulse.StopPropagation) { |
|
this.pulse = rv; |
|
} |
|
|
|
return rv; |
|
}; |
|
/** |
|
* Overrides {@link Operator.evaluate} for transform operators. |
|
* Marshalls parameter values and then invokes {@link transform}. |
|
* @param {Pulse} pulse - the current dataflow pulse. |
|
* @return {Pulse} The output pulse (or StopPropagation). A falsy return |
|
value (including undefined) will let the input pulse pass through. |
|
*/ |
|
|
|
|
|
prototype$6.evaluate = function (pulse) { |
|
var params = this.marshall(pulse.stamp), |
|
out = this.transform(params, pulse); |
|
params.clear(); |
|
return out; |
|
}; |
|
/** |
|
* Process incoming pulses. |
|
* Subclasses should override this method to implement transforms. |
|
* @param {Parameters} _ - The operator parameter values. |
|
* @param {Pulse} pulse - The current dataflow pulse. |
|
* @return {Pulse} The output pulse (or StopPropagation). A falsy return |
|
* value (including undefined) will let the input pulse pass through. |
|
*/ |
|
|
|
|
|
prototype$6.transform = function () {}; |
|
|
|
var transforms = {}; |
|
|
|
function definition(type) { |
|
var t = transform$1(type); |
|
return t && t.Definition || null; |
|
} |
|
|
|
function transform$1(type) { |
|
type = type && type.toLowerCase(); |
|
return hasOwnProperty(transforms, type) ? transforms[type] : null; |
|
} |
|
|
|
function multikey(f) { |
|
return function (x) { |
|
var n = f.length, |
|
i = 1, |
|
k = String(f[0](x)); |
|
|
|
for (; i < n; ++i) { |
|
k += '|' + f[i](x); |
|
} |
|
|
|
return k; |
|
}; |
|
} |
|
|
|
function groupkey(fields) { |
|
return !fields || !fields.length ? function () { |
|
return ''; |
|
} : fields.length === 1 ? fields[0] : multikey(fields); |
|
} |
|
|
|
function measureName(op, field, as) { |
|
return as || op + (!field ? '' : '_' + field); |
|
} |
|
|
|
var AggregateOps = { |
|
'values': measure({ |
|
name: 'values', |
|
init: 'cell.store = true;', |
|
set: 'cell.data.values()', |
|
idx: -1 |
|
}), |
|
'count': measure({ |
|
name: 'count', |
|
set: 'cell.num' |
|
}), |
|
'__count__': measure({ |
|
name: 'count', |
|
set: 'this.missing + this.valid' |
|
}), |
|
'missing': measure({ |
|
name: 'missing', |
|
set: 'this.missing' |
|
}), |
|
'valid': measure({ |
|
name: 'valid', |
|
set: 'this.valid' |
|
}), |
|
'sum': measure({ |
|
name: 'sum', |
|
init: 'this.sum = 0;', |
|
add: 'this.sum += +v;', |
|
rem: 'this.sum -= v;', |
|
set: 'this.sum' |
|
}), |
|
'mean': measure({ |
|
name: 'mean', |
|
init: 'this.mean = 0;', |
|
add: 'var d = v - this.mean; this.mean += d / this.valid;', |
|
rem: 'var d = v - this.mean; this.mean -= this.valid ? d / this.valid : this.mean;', |
|
set: 'this.valid ? this.mean : undefined' |
|
}), |
|
'average': measure({ |
|
name: 'average', |
|
set: 'this.valid ? this.mean : undefined', |
|
req: ['mean'], |
|
idx: 1 |
|
}), |
|
'variance': measure({ |
|
name: 'variance', |
|
init: 'this.dev = 0;', |
|
add: 'this.dev += d * (v - this.mean);', |
|
rem: 'this.dev -= d * (v - this.mean);', |
|
set: 'this.valid > 1 ? this.dev / (this.valid-1) : undefined', |
|
req: ['mean'], |
|
idx: 1 |
|
}), |
|
'variancep': measure({ |
|
name: 'variancep', |
|
set: 'this.valid > 1 ? this.dev / this.valid : undefined', |
|
req: ['variance'], |
|
idx: 2 |
|
}), |
|
'stdev': measure({ |
|
name: 'stdev', |
|
set: 'this.valid > 1 ? Math.sqrt(this.dev / (this.valid-1)) : undefined', |
|
req: ['variance'], |
|
idx: 2 |
|
}), |
|
'stdevp': measure({ |
|
name: 'stdevp', |
|
set: 'this.valid > 1 ? Math.sqrt(this.dev / this.valid) : undefined', |
|
req: ['variance'], |
|
idx: 2 |
|
}), |
|
'stderr': measure({ |
|
name: 'stderr', |
|
set: 'this.valid > 1 ? Math.sqrt(this.dev / (this.valid * (this.valid-1))) : undefined', |
|
req: ['variance'], |
|
idx: 2 |
|
}), |
|
'distinct': measure({ |
|
name: 'distinct', |
|
set: 'cell.data.distinct(this.get)', |
|
req: ['values'], |
|
idx: 3 |
|
}), |
|
'ci0': measure({ |
|
name: 'ci0', |
|
set: 'cell.data.ci0(this.get)', |
|
req: ['values'], |
|
idx: 3 |
|
}), |
|
'ci1': measure({ |
|
name: 'ci1', |
|
set: 'cell.data.ci1(this.get)', |
|
req: ['values'], |
|
idx: 3 |
|
}), |
|
'median': measure({ |
|
name: 'median', |
|
set: 'cell.data.q2(this.get)', |
|
req: ['values'], |
|
idx: 3 |
|
}), |
|
'q1': measure({ |
|
name: 'q1', |
|
set: 'cell.data.q1(this.get)', |
|
req: ['values'], |
|
idx: 3 |
|
}), |
|
'q3': measure({ |
|
name: 'q3', |
|
set: 'cell.data.q3(this.get)', |
|
req: ['values'], |
|
idx: 3 |
|
}), |
|
'argmin': measure({ |
|
name: 'argmin', |
|
init: 'this.argmin = undefined;', |
|
add: 'if (v < this.min) this.argmin = t;', |
|
rem: 'if (v <= this.min) this.argmin = undefined;', |
|
set: 'this.argmin || cell.data.argmin(this.get)', |
|
req: ['min'], |
|
str: ['values'], |
|
idx: 3 |
|
}), |
|
'argmax': measure({ |
|
name: 'argmax', |
|
init: 'this.argmax = undefined;', |
|
add: 'if (v > this.max) this.argmax = t;', |
|
rem: 'if (v >= this.max) this.argmax = undefined;', |
|
set: 'this.argmax || cell.data.argmax(this.get)', |
|
req: ['max'], |
|
str: ['values'], |
|
idx: 3 |
|
}), |
|
'min': measure({ |
|
name: 'min', |
|
init: 'this.min = undefined;', |
|
add: 'if (v < this.min || this.min === undefined) this.min = v;', |
|
rem: 'if (v <= this.min) this.min = NaN;', |
|
set: 'this.min = (Number.isNaN(this.min) ? cell.data.min(this.get) : this.min)', |
|
str: ['values'], |
|
idx: 4 |
|
}), |
|
'max': measure({ |
|
name: 'max', |
|
init: 'this.max = undefined;', |
|
add: 'if (v > this.max || this.max === undefined) this.max = v;', |
|
rem: 'if (v >= this.max) this.max = NaN;', |
|
set: 'this.max = (Number.isNaN(this.max) ? cell.data.max(this.get) : this.max)', |
|
str: ['values'], |
|
idx: 4 |
|
}) |
|
}; |
|
var ValidAggregateOps = Object.keys(AggregateOps); |
|
|
|
function createMeasure(op, name) { |
|
return AggregateOps[op](name); |
|
} |
|
|
|
function measure(base) { |
|
return function (out) { |
|
var m = extend({ |
|
init: '', |
|
add: '', |
|
rem: '', |
|
idx: 0 |
|
}, base); |
|
m.out = out || base.name; |
|
return m; |
|
}; |
|
} |
|
|
|
function compareIndex(a, b) { |
|
return a.idx - b.idx; |
|
} |
|
|
|
function resolve(agg, stream) { |
|
function collect(m, a) { |
|
function helper(r) { |
|
if (!m[r]) collect(m, m[r] = AggregateOps[r]()); |
|
} |
|
|
|
if (a.req) a.req.forEach(helper); |
|
if (stream && a.str) a.str.forEach(helper); |
|
return m; |
|
} |
|
|
|
var map = agg.reduce(collect, agg.reduce(function (m, a) { |
|
m[a.name] = a; |
|
return m; |
|
}, {})); |
|
var values = [], |
|
key; |
|
|
|
for (key in map) { |
|
values.push(map[key]); |
|
} |
|
|
|
return values.sort(compareIndex); |
|
} |
|
|
|
function compileMeasures(agg, field) { |
|
var get = field || identity, |
|
all = resolve(agg, true), |
|
// assume streaming removes may occur |
|
init = 'var cell = this.cell; this.valid = 0; this.missing = 0;', |
|
ctr = 'this.cell = cell; this.init();', |
|
add = 'if(v==null){++this.missing; return;} if(v!==v) return; ++this.valid;', |
|
rem = 'if(v==null){--this.missing; return;} if(v!==v) return; --this.valid;', |
|
set = 'var cell = this.cell;'; |
|
all.forEach(function (a) { |
|
init += a.init; |
|
add += a.add; |
|
rem += a.rem; |
|
}); |
|
agg.slice().sort(compareIndex).forEach(function (a) { |
|
set += 't[' + $(a.out) + ']=' + a.set + ';'; |
|
}); |
|
set += 'return t;'; |
|
ctr = Function('cell', ctr); |
|
ctr.prototype.init = Function(init); |
|
ctr.prototype.add = Function('v', 't', add); |
|
ctr.prototype.rem = Function('v', 't', rem); |
|
ctr.prototype.set = Function('t', set); |
|
ctr.prototype.get = get; |
|
ctr.fields = agg.map(function (_) { |
|
return _.out; |
|
}); |
|
return ctr; |
|
} |
|
|
|
function numbers(values, valueof) { |
|
var _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, _value, _index, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, _value2; |
|
|
|
return regeneratorRuntime.wrap(function numbers$(_context2) { |
|
while (1) { |
|
switch (_context2.prev = _context2.next) { |
|
case 0: |
|
if (!(valueof === undefined)) { |
|
_context2.next = 30; |
|
break; |
|
} |
|
|
|
_iteratorNormalCompletion = true; |
|
_didIteratorError = false; |
|
_iteratorError = undefined; |
|
_context2.prev = 4; |
|
_iterator = values[Symbol.iterator](); |
|
|
|
case 6: |
|
if (_iteratorNormalCompletion = (_step = _iterator.next()).done) { |
|
_context2.next = 14; |
|
break; |
|
} |
|
|
|
_value = _step.value; |
|
|
|
if (!(_value != null && (_value = +_value) >= _value)) { |
|
_context2.next = 11; |
|
break; |
|
} |
|
|
|
_context2.next = 11; |
|
return _value; |
|
|
|
case 11: |
|
_iteratorNormalCompletion = true; |
|
_context2.next = 6; |
|
break; |
|
|
|
case 14: |
|
_context2.next = 20; |
|
break; |
|
|
|
case 16: |
|
_context2.prev = 16; |
|
_context2.t0 = _context2["catch"](4); |
|
_didIteratorError = true; |
|
_iteratorError = _context2.t0; |
|
|
|
case 20: |
|
_context2.prev = 20; |
|
_context2.prev = 21; |
|
|
|
if (!_iteratorNormalCompletion && _iterator.return != null) { |
|
_iterator.return(); |
|
} |
|
|
|
case 23: |
|
_context2.prev = 23; |
|
|
|
if (!_didIteratorError) { |
|
_context2.next = 26; |
|
break; |
|
} |
|
|
|
throw _iteratorError; |
|
|
|
case 26: |
|
return _context2.finish(23); |
|
|
|
case 27: |
|
return _context2.finish(20); |
|
|
|
case 28: |
|
_context2.next = 58; |
|
break; |
|
|
|
case 30: |
|
_index = -1; |
|
_iteratorNormalCompletion2 = true; |
|
_didIteratorError2 = false; |
|
_iteratorError2 = undefined; |
|
_context2.prev = 34; |
|
_iterator2 = values[Symbol.iterator](); |
|
|
|
case 36: |
|
if (_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done) { |
|
_context2.next = 44; |
|
break; |
|
} |
|
|
|
_value2 = _step2.value; |
|
|
|
if (!((_value2 = valueof(_value2, ++_index, values)) != null && (_value2 = +_value2) >= _value2)) { |
|
_context2.next = 41; |
|
break; |
|
} |
|
|
|
_context2.next = 41; |
|
return _value2; |
|
|
|
case 41: |
|
_iteratorNormalCompletion2 = true; |
|
_context2.next = 36; |
|
break; |
|
|
|
case 44: |
|
_context2.next = 50; |
|
break; |
|
|
|
case 46: |
|
_context2.prev = 46; |
|
_context2.t1 = _context2["catch"](34); |
|
_didIteratorError2 = true; |
|
_iteratorError2 = _context2.t1; |
|
|
|
case 50: |
|
_context2.prev = 50; |
|
_context2.prev = 51; |
|
|
|
if (!_iteratorNormalCompletion2 && _iterator2.return != null) { |
|
_iterator2.return(); |
|
} |
|
|
|
case 53: |
|
_context2.prev = 53; |
|
|
|
if (!_didIteratorError2) { |
|
_context2.next = 56; |
|
break; |
|
} |
|
|
|
throw _iteratorError2; |
|
|
|
case 56: |
|
return _context2.finish(53); |
|
|
|
case 57: |
|
return _context2.finish(50); |
|
|
|
case 58: |
|
case "end": |
|
return _context2.stop(); |
|
} |
|
} |
|
}, _marked, null, [[4, 16, 20, 28], [21,, 23, 27], [34, 46, 50, 58], [51,, 53, 57]]); |
|
} |
|
|
|
function ascending(a, b) { |
|
return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; |
|
} |
|
|
|
function bisector(compare) { |
|
if (compare.length === 1) compare = ascendingComparator(compare); |
|
return { |
|
left: function left(a, x, lo, hi) { |
|
if (lo == null) lo = 0; |
|
if (hi == null) hi = a.length; |
|
|
|
while (lo < hi) { |
|
var mid = lo + hi >>> 1; |
|
if (compare(a[mid], x) < 0) lo = mid + 1;else hi = mid; |
|
} |
|
|
|
return lo; |
|
}, |
|
right: function right(a, x, lo, hi) { |
|
if (lo == null) lo = 0; |
|
if (hi == null) hi = a.length; |
|
|
|
while (lo < hi) { |
|
var mid = lo + hi >>> 1; |
|
if (compare(a[mid], x) > 0) hi = mid;else lo = mid + 1; |
|
} |
|
|
|
return lo; |
|
} |
|
}; |
|
} |
|
|
|
function ascendingComparator(f) { |
|
return function (d, x) { |
|
return ascending(f(d), x); |
|
}; |
|
} |
|
|
|
var ascendingBisect = bisector(ascending); |
|
var bisectRight = ascendingBisect.right; |
|
var bisectLeft = ascendingBisect.left; |
|
|
|
function variance(values, valueof) { |
|
var count = 0; |
|
var delta; |
|
var mean = 0; |
|
var sum = 0; |
|
|
|
if (valueof === undefined) { |
|
var _iteratorNormalCompletion3 = true; |
|
var _didIteratorError3 = false; |
|
var _iteratorError3 = undefined; |
|
|
|
try { |
|
for (var _iterator3 = values[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { |
|
var _value3 = _step3.value; |
|
|
|
if (_value3 != null && (_value3 = +_value3) >= _value3) { |
|
delta = _value3 - mean; |
|
mean += delta / ++count; |
|
sum += delta * (_value3 - mean); |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError3 = true; |
|
_iteratorError3 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion3 && _iterator3.return != null) { |
|
_iterator3.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError3) { |
|
throw _iteratorError3; |
|
} |
|
} |
|
} |
|
} else { |
|
var _index2 = -1; |
|
|
|
var _iteratorNormalCompletion4 = true; |
|
var _didIteratorError4 = false; |
|
var _iteratorError4 = undefined; |
|
|
|
try { |
|
for (var _iterator4 = values[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { |
|
var _value4 = _step4.value; |
|
|
|
if ((_value4 = valueof(_value4, ++_index2, values)) != null && (_value4 = +_value4) >= _value4) { |
|
delta = _value4 - mean; |
|
mean += delta / ++count; |
|
sum += delta * (_value4 - mean); |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError4 = true; |
|
_iteratorError4 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion4 && _iterator4.return != null) { |
|
_iterator4.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError4) { |
|
throw _iteratorError4; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (count > 1) return sum / (count - 1); |
|
} |
|
|
|
function deviation(values, valueof) { |
|
var v = variance(values, valueof); |
|
return v ? Math.sqrt(v) : v; |
|
} |
|
|
|
function sequence(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 e10 = Math.sqrt(50), |
|
e5 = Math.sqrt(10), |
|
e2 = Math.sqrt(2); |
|
|
|
function ticks(start, stop, count) { |
|
var reverse, |
|
i = -1, |
|
n, |
|
ticks, |
|
step; |
|
stop = +stop, start = +start, count = +count; |
|
if (start === stop && count > 0) return [start]; |
|
if (reverse = stop < start) n = start, start = stop, stop = n; |
|
if ((step = tickIncrement(start, stop, count)) === 0 || !isFinite(step)) return []; |
|
|
|
if (step > 0) { |
|
start = Math.ceil(start / step); |
|
stop = Math.floor(stop / step); |
|
ticks = new Array(n = Math.ceil(stop - start + 1)); |
|
|
|
while (++i < n) { |
|
ticks[i] = (start + i) * step; |
|
} |
|
} else { |
|
start = Math.floor(start * step); |
|
stop = Math.ceil(stop * step); |
|
ticks = new Array(n = Math.ceil(start - stop + 1)); |
|
|
|
while (++i < n) { |
|
ticks[i] = (start - i) / step; |
|
} |
|
} |
|
|
|
if (reverse) ticks.reverse(); |
|
return ticks; |
|
} |
|
|
|
function tickIncrement(start, stop, count) { |
|
var step = (stop - start) / Math.max(0, count), |
|
power = Math.floor(Math.log(step) / Math.LN10), |
|
error = step / Math.pow(10, power); |
|
return power >= 0 ? (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1) * Math.pow(10, power) : -Math.pow(10, -power) / (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1); |
|
} |
|
|
|
function tickStep(start, stop, count) { |
|
var step0 = Math.abs(stop - start) / Math.max(0, count), |
|
step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)), |
|
error = step0 / step1; |
|
if (error >= e10) step1 *= 10;else if (error >= e5) step1 *= 5;else if (error >= e2) step1 *= 2; |
|
return stop < start ? -step1 : step1; |
|
} |
|
|
|
function max(values, valueof) { |
|
var max; |
|
|
|
if (valueof === undefined) { |
|
var _iteratorNormalCompletion5 = true; |
|
var _didIteratorError5 = false; |
|
var _iteratorError5 = undefined; |
|
|
|
try { |
|
for (var _iterator5 = values[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { |
|
var _value5 = _step5.value; |
|
|
|
if (_value5 != null && (max < _value5 || max === undefined && _value5 >= _value5)) { |
|
max = _value5; |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError5 = true; |
|
_iteratorError5 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion5 && _iterator5.return != null) { |
|
_iterator5.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError5) { |
|
throw _iteratorError5; |
|
} |
|
} |
|
} |
|
} else { |
|
var _index3 = -1; |
|
|
|
var _iteratorNormalCompletion6 = true; |
|
var _didIteratorError6 = false; |
|
var _iteratorError6 = undefined; |
|
|
|
try { |
|
for (var _iterator6 = values[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { |
|
var _value6 = _step6.value; |
|
|
|
if ((_value6 = valueof(_value6, ++_index3, values)) != null && (max < _value6 || max === undefined && _value6 >= _value6)) { |
|
max = _value6; |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError6 = true; |
|
_iteratorError6 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion6 && _iterator6.return != null) { |
|
_iterator6.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError6) { |
|
throw _iteratorError6; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return max; |
|
} |
|
|
|
function min(values, valueof) { |
|
var min; |
|
|
|
if (valueof === undefined) { |
|
var _iteratorNormalCompletion7 = true; |
|
var _didIteratorError7 = false; |
|
var _iteratorError7 = undefined; |
|
|
|
try { |
|
for (var _iterator7 = values[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { |
|
var _value7 = _step7.value; |
|
|
|
if (_value7 != null && (min > _value7 || min === undefined && _value7 >= _value7)) { |
|
min = _value7; |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError7 = true; |
|
_iteratorError7 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion7 && _iterator7.return != null) { |
|
_iterator7.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError7) { |
|
throw _iteratorError7; |
|
} |
|
} |
|
} |
|
} else { |
|
var _index4 = -1; |
|
|
|
var _iteratorNormalCompletion8 = true; |
|
var _didIteratorError8 = false; |
|
var _iteratorError8 = undefined; |
|
|
|
try { |
|
for (var _iterator8 = values[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { |
|
var _value8 = _step8.value; |
|
|
|
if ((_value8 = valueof(_value8, ++_index4, values)) != null && (min > _value8 || min === undefined && _value8 >= _value8)) { |
|
min = _value8; |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError8 = true; |
|
_iteratorError8 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion8 && _iterator8.return != null) { |
|
_iterator8.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError8) { |
|
throw _iteratorError8; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return min; |
|
} // Based on https://github.com/mourner/quickselect |
|
// ISC license, Copyright 2018 Vladimir Agafonkin. |
|
|
|
|
|
function quickselect(array, k) { |
|
var left = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; |
|
var right = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : array.length - 1; |
|
var compare = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : ascending; |
|
|
|
while (right > left) { |
|
if (right - left > 600) { |
|
var n = right - left + 1; |
|
var m = k - left + 1; |
|
var z = Math.log(n); |
|
var s = 0.5 * Math.exp(2 * z / 3); |
|
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); |
|
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); |
|
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); |
|
quickselect(array, k, newLeft, newRight, compare); |
|
} |
|
|
|
var t = array[k]; |
|
var i = left; |
|
var j = right; |
|
swap(array, left, k); |
|
if (compare(array[right], t) > 0) swap(array, left, right); |
|
|
|
while (i < j) { |
|
swap(array, i, j), ++i, --j; |
|
|
|
while (compare(array[i], t) < 0) { |
|
++i; |
|
} |
|
|
|
while (compare(array[j], t) > 0) { |
|
--j; |
|
} |
|
} |
|
|
|
if (compare(array[left], t) === 0) swap(array, left, j);else ++j, swap(array, j, right); |
|
if (j <= k) left = j + 1; |
|
if (k <= j) right = j - 1; |
|
} |
|
|
|
return array; |
|
} |
|
|
|
function swap(array, i, j) { |
|
var t = array[i]; |
|
array[i] = array[j]; |
|
array[j] = t; |
|
} |
|
|
|
function number(x) { |
|
return x === null ? NaN : +x; |
|
} |
|
|
|
function numbers$1(values, valueof) { |
|
var _iteratorNormalCompletion9, _didIteratorError9, _iteratorError9, _iterator9, _step9, _value9, _index5, _iteratorNormalCompletion10, _didIteratorError10, _iteratorError10, _iterator10, _step10, _value10; |
|
|
|
return regeneratorRuntime.wrap(function numbers$1$(_context3) { |
|
while (1) { |
|
switch (_context3.prev = _context3.next) { |
|
case 0: |
|
if (!(valueof === undefined)) { |
|
_context3.next = 30; |
|
break; |
|
} |
|
|
|
_iteratorNormalCompletion9 = true; |
|
_didIteratorError9 = false; |
|
_iteratorError9 = undefined; |
|
_context3.prev = 4; |
|
_iterator9 = values[Symbol.iterator](); |
|
|
|
case 6: |
|
if (_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done) { |
|
_context3.next = 14; |
|
break; |
|
} |
|
|
|
_value9 = _step9.value; |
|
|
|
if (!(_value9 != null && (_value9 = +_value9) >= _value9)) { |
|
_context3.next = 11; |
|
break; |
|
} |
|
|
|
_context3.next = 11; |
|
return _value9; |
|
|
|
case 11: |
|
_iteratorNormalCompletion9 = true; |
|
_context3.next = 6; |
|
break; |
|
|
|
case 14: |
|
_context3.next = 20; |
|
break; |
|
|
|
case 16: |
|
_context3.prev = 16; |
|
_context3.t0 = _context3["catch"](4); |
|
_didIteratorError9 = true; |
|
_iteratorError9 = _context3.t0; |
|
|
|
case 20: |
|
_context3.prev = 20; |
|
_context3.prev = 21; |
|
|
|
if (!_iteratorNormalCompletion9 && _iterator9.return != null) { |
|
_iterator9.return(); |
|
} |
|
|
|
case 23: |
|
_context3.prev = 23; |
|
|
|
if (!_didIteratorError9) { |
|
_context3.next = 26; |
|
break; |
|
} |
|
|
|
throw _iteratorError9; |
|
|
|
case 26: |
|
return _context3.finish(23); |
|
|
|
case 27: |
|
return _context3.finish(20); |
|
|
|
case 28: |
|
_context3.next = 58; |
|
break; |
|
|
|
case 30: |
|
_index5 = -1; |
|
_iteratorNormalCompletion10 = true; |
|
_didIteratorError10 = false; |
|
_iteratorError10 = undefined; |
|
_context3.prev = 34; |
|
_iterator10 = values[Symbol.iterator](); |
|
|
|
case 36: |
|
if (_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done) { |
|
_context3.next = 44; |
|
break; |
|
} |
|
|
|
_value10 = _step10.value; |
|
|
|
if (!((_value10 = valueof(_value10, ++_index5, values)) != null && (_value10 = +_value10) >= _value10)) { |
|
_context3.next = 41; |
|
break; |
|
} |
|
|
|
_context3.next = 41; |
|
return _value10; |
|
|
|
case 41: |
|
_iteratorNormalCompletion10 = true; |
|
_context3.next = 36; |
|
break; |
|
|
|
case 44: |
|
_context3.next = 50; |
|
break; |
|
|
|
case 46: |
|
_context3.prev = 46; |
|
_context3.t1 = _context3["catch"](34); |
|
_didIteratorError10 = true; |
|
_iteratorError10 = _context3.t1; |
|
|
|
case 50: |
|
_context3.prev = 50; |
|
_context3.prev = 51; |
|
|
|
if (!_iteratorNormalCompletion10 && _iterator10.return != null) { |
|
_iterator10.return(); |
|
} |
|
|
|
case 53: |
|
_context3.prev = 53; |
|
|
|
if (!_didIteratorError10) { |
|
_context3.next = 56; |
|
break; |
|
} |
|
|
|
throw _iteratorError10; |
|
|
|
case 56: |
|
return _context3.finish(53); |
|
|
|
case 57: |
|
return _context3.finish(50); |
|
|
|
case 58: |
|
case "end": |
|
return _context3.stop(); |
|
} |
|
} |
|
}, _marked2, null, [[4, 16, 20, 28], [21,, 23, 27], [34, 46, 50, 58], [51,, 53, 57]]); |
|
} |
|
|
|
function quantile(values, p, valueof) { |
|
values = Float64Array.from(numbers$1(values, valueof)); |
|
if (!(n = values.length)) return; |
|
if ((p = +p) <= 0 || n < 2) return min(values); |
|
if (p >= 1) return max(values); |
|
var n, |
|
i = (n - 1) * p, |
|
i0 = Math.floor(i), |
|
value0 = max(quickselect(values, i0).subarray(0, i0 + 1)), |
|
value1 = min(values.subarray(i0 + 1)); |
|
return value0 + (value1 - value0) * (i - i0); |
|
} |
|
|
|
function quantileSorted(values, p) { |
|
var valueof = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : number; |
|
if (!(n = values.length)) return; |
|
if ((p = +p) <= 0 || n < 2) return +valueof(values[0], 0, values); |
|
if (p >= 1) return +valueof(values[n - 1], n - 1, values); |
|
var n, |
|
i = (n - 1) * p, |
|
i0 = Math.floor(i), |
|
value0 = +valueof(values[i0], i0, values), |
|
value1 = +valueof(values[i0 + 1], i0 + 1, values); |
|
return value0 + (value1 - value0) * (i - i0); |
|
} |
|
|
|
function mean(values, valueof) { |
|
var count = 0; |
|
var sum = 0; |
|
|
|
if (valueof === undefined) { |
|
var _iteratorNormalCompletion11 = true; |
|
var _didIteratorError11 = false; |
|
var _iteratorError11 = undefined; |
|
|
|
try { |
|
for (var _iterator11 = values[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { |
|
var _value11 = _step11.value; |
|
|
|
if (_value11 != null && (_value11 = +_value11) >= _value11) { |
|
++count, sum += _value11; |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError11 = true; |
|
_iteratorError11 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion11 && _iterator11.return != null) { |
|
_iterator11.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError11) { |
|
throw _iteratorError11; |
|
} |
|
} |
|
} |
|
} else { |
|
var _index6 = -1; |
|
|
|
var _iteratorNormalCompletion12 = true; |
|
var _didIteratorError12 = false; |
|
var _iteratorError12 = undefined; |
|
|
|
try { |
|
for (var _iterator12 = values[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) { |
|
var _value12 = _step12.value; |
|
|
|
if ((_value12 = valueof(_value12, ++_index6, values)) != null && (_value12 = +_value12) >= _value12) { |
|
++count, sum += _value12; |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError12 = true; |
|
_iteratorError12 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion12 && _iterator12.return != null) { |
|
_iterator12.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError12) { |
|
throw _iteratorError12; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (count) return sum / count; |
|
} |
|
|
|
function median(values, valueof) { |
|
return quantile(values, 0.5, valueof); |
|
} |
|
|
|
function permute(source, keys) { |
|
return Array.from(keys, function (key) { |
|
return source[key]; |
|
}); |
|
} |
|
|
|
function sum(values, valueof) { |
|
var sum = 0; |
|
|
|
if (valueof === undefined) { |
|
var _iteratorNormalCompletion13 = true; |
|
var _didIteratorError13 = false; |
|
var _iteratorError13 = undefined; |
|
|
|
try { |
|
for (var _iterator13 = values[Symbol.iterator](), _step13; !(_iteratorNormalCompletion13 = (_step13 = _iterator13.next()).done); _iteratorNormalCompletion13 = true) { |
|
var _value13 = _step13.value; |
|
|
|
if (_value13 = +_value13) { |
|
sum += _value13; |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError13 = true; |
|
_iteratorError13 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion13 && _iterator13.return != null) { |
|
_iterator13.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError13) { |
|
throw _iteratorError13; |
|
} |
|
} |
|
} |
|
} else { |
|
var _index7 = -1; |
|
|
|
var _iteratorNormalCompletion14 = true; |
|
var _didIteratorError14 = false; |
|
var _iteratorError14 = undefined; |
|
|
|
try { |
|
for (var _iterator14 = values[Symbol.iterator](), _step14; !(_iteratorNormalCompletion14 = (_step14 = _iterator14.next()).done); _iteratorNormalCompletion14 = true) { |
|
var _value14 = _step14.value; |
|
|
|
if (_value14 = +valueof(_value14, ++_index7, values)) { |
|
sum += _value14; |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError14 = true; |
|
_iteratorError14 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion14 && _iterator14.return != null) { |
|
_iterator14.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError14) { |
|
throw _iteratorError14; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return sum; |
|
} |
|
|
|
function quantiles(array, p, f) { |
|
var values = Float64Array.from(numbers(array, f)); // don't depend on return value from typed array sort call |
|
// protects against undefined sort results in Safari (vega/vega-lite#4964) |
|
|
|
values.sort(ascending); |
|
return p.map(function (_) { |
|
return quantileSorted(values, _); |
|
}); |
|
} |
|
|
|
function quartiles(array, f) { |
|
return quantiles(array, [0.25, 0.50, 0.75], f); |
|
} // Scott, D. W. (1992) Multivariate Density Estimation: |
|
// Theory, Practice, and Visualization. Wiley. |
|
|
|
|
|
function bandwidthNRD(array, f) { |
|
var n = array.length, |
|
v = deviation(array, f), |
|
q = quartiles(array, f), |
|
h = (q[2] - q[0]) / 1.34; |
|
v = Math.min(v, h) || v || Math.abs(q[0]) || 1; |
|
return 1.06 * v * Math.pow(n, -0.2); |
|
} |
|
|
|
function bin(_) { |
|
// determine range |
|
var maxb = _.maxbins || 20, |
|
base = _.base || 10, |
|
logb = Math.log(base), |
|
div = _.divide || [5, 2], |
|
min = _.extent[0], |
|
max = _.extent[1], |
|
span = _.span || max - min || Math.abs(min) || 1, |
|
step, |
|
level, |
|
minstep, |
|
precision, |
|
v, |
|
i, |
|
n, |
|
eps; |
|
|
|
if (_.step) { |
|
// if step size is explicitly given, use that |
|
step = _.step; |
|
} else if (_.steps) { |
|
// if provided, limit choice to acceptable step sizes |
|
v = span / maxb; |
|
|
|
for (i = 0, n = _.steps.length; i < n && _.steps[i] < v; ++i) { |
|
; |
|
} |
|
|
|
step = _.steps[Math.max(0, i - 1)]; |
|
} else { |
|
// else use span to determine step size |
|
level = Math.ceil(Math.log(maxb) / logb); |
|
minstep = _.minstep || 0; |
|
step = Math.max(minstep, Math.pow(base, Math.round(Math.log(span) / logb) - level)); // increase step size if too many bins |
|
|
|
while (Math.ceil(span / step) > maxb) { |
|
step *= base; |
|
} // decrease step size if allowed |
|
|
|
|
|
for (i = 0, n = div.length; i < n; ++i) { |
|
v = step / div[i]; |
|
if (v >= minstep && span / v <= maxb) step = v; |
|
} |
|
} // update precision, min and max |
|
|
|
|
|
v = Math.log(step); |
|
precision = v >= 0 ? 0 : ~~(-v / logb) + 1; |
|
eps = Math.pow(base, -precision - 1); |
|
|
|
if (_.nice || _.nice === undefined) { |
|
v = Math.floor(min / step + eps) * step; |
|
min = min < v ? v - step : v; |
|
max = Math.ceil(max / step) * step; |
|
} |
|
|
|
return { |
|
start: min, |
|
stop: max === min ? min + step : max, |
|
step: step |
|
}; |
|
} |
|
|
|
exports.random = Math.random; |
|
|
|
function setRandom(r) { |
|
exports.random = r; |
|
} |
|
|
|
function bootstrapCI(array, samples, alpha, f) { |
|
if (!array.length) return [undefined, undefined]; |
|
var values = Float64Array.from(numbers(array, f)), |
|
n = values.length, |
|
m = samples, |
|
a, |
|
i, |
|
j, |
|
mu; |
|
|
|
for (j = 0, mu = Array(m); j < m; ++j) { |
|
for (a = 0, i = 0; i < n; ++i) { |
|
a += values[~~(exports.random() * n)]; |
|
} |
|
|
|
mu[j] = a / n; |
|
} |
|
|
|
mu.sort(ascending); |
|
return [quantile(mu, alpha / 2), quantile(mu, 1 - alpha / 2)]; |
|
} // Dot density binning for dot plot construction. |
|
// Based on Leland Wilkinson, Dot Plots, The American Statistician, 1999. |
|
// https://www.cs.uic.edu/~wilkinson/Publications/dotplots.pdf |
|
|
|
|
|
function dotbin(array, step, smooth, f) { |
|
f = f || function (_) { |
|
return _; |
|
}; |
|
|
|
var i = 0, |
|
j = 1, |
|
n = array.length, |
|
v = new Float64Array(n), |
|
a = f(array[0]), |
|
b = a, |
|
w = a + step, |
|
x; |
|
|
|
for (; j < n; ++j) { |
|
x = f(array[j]); |
|
|
|
if (x >= w) { |
|
b = (a + b) / 2; |
|
|
|
for (; i < j; ++i) { |
|
v[i] = b; |
|
} |
|
|
|
w = x + step; |
|
a = x; |
|
} |
|
|
|
b = x; |
|
} |
|
|
|
b = (a + b) / 2; |
|
|
|
for (; i < j; ++i) { |
|
v[i] = b; |
|
} |
|
|
|
return smooth ? smoothing(v, step + step / 4) : v; |
|
} // perform smoothing to reduce variance |
|
// swap points between "adjacent" stacks |
|
// Wilkinson defines adjacent as within step/4 units |
|
|
|
|
|
function smoothing(v, thresh) { |
|
var n = v.length, |
|
a = 0, |
|
b = 1, |
|
c, |
|
d; // get left stack |
|
|
|
while (v[a] === v[b]) { |
|
++b; |
|
} |
|
|
|
while (b < n) { |
|
// get right stack |
|
c = b + 1; |
|
|
|
while (v[b] === v[c]) { |
|
++c; |
|
} // are stacks adjacent? |
|
// if so, compare sizes and swap as needed |
|
|
|
|
|
if (v[b] - v[b - 1] < thresh) { |
|
d = b + (a + c - b - b >> 1); |
|
|
|
while (d < b) { |
|
v[d++] = v[b]; |
|
} |
|
|
|
while (d > b) { |
|
v[d--] = v[a]; |
|
} |
|
} // update left stack indices |
|
|
|
|
|
a = b; |
|
b = c; |
|
} |
|
|
|
return v; |
|
} |
|
|
|
function lcg(seed) { |
|
// Random numbers using a Linear Congruential Generator with seed value |
|
// Uses glibc values from https://en.wikipedia.org/wiki/Linear_congruential_generator |
|
return function () { |
|
seed = (1103515245 * seed + 12345) % 2147483647; |
|
return seed / 2147483647; |
|
}; |
|
} |
|
|
|
function integer(min, max) { |
|
if (max == null) { |
|
max = min; |
|
min = 0; |
|
} |
|
|
|
var dist = {}, |
|
a, |
|
b, |
|
d; |
|
|
|
dist.min = function (_) { |
|
if (arguments.length) { |
|
a = _ || 0; |
|
d = b - a; |
|
return dist; |
|
} else { |
|
return a; |
|
} |
|
}; |
|
|
|
dist.max = function (_) { |
|
if (arguments.length) { |
|
b = _ || 0; |
|
d = b - a; |
|
return dist; |
|
} else { |
|
return b; |
|
} |
|
}; |
|
|
|
dist.sample = function () { |
|
return a + Math.floor(d * exports.random()); |
|
}; |
|
|
|
dist.pdf = function (x) { |
|
return x === Math.floor(x) && x >= a && x < b ? 1 / d : 0; |
|
}; |
|
|
|
dist.cdf = function (x) { |
|
var v = Math.floor(x); |
|
return v < a ? 0 : v >= b ? 1 : (v - a + 1) / d; |
|
}; |
|
|
|
dist.icdf = function (p) { |
|
return p >= 0 && p <= 1 ? a - 1 + Math.floor(p * d) : NaN; |
|
}; |
|
|
|
return dist.min(min).max(max); |
|
} |
|
|
|
var SQRT2PI = Math.sqrt(2 * Math.PI); |
|
var SQRT2 = Math.SQRT2; |
|
var nextSample = NaN; |
|
|
|
function sampleNormal(mean, stdev) { |
|
mean = mean || 0; |
|
stdev = stdev == null ? 1 : stdev; |
|
var x = 0, |
|
y = 0, |
|
rds, |
|
c; |
|
|
|
if (nextSample === nextSample) { |
|
x = nextSample; |
|
nextSample = NaN; |
|
} else { |
|
do { |
|
x = exports.random() * 2 - 1; |
|
y = exports.random() * 2 - 1; |
|
rds = x * x + y * y; |
|
} while (rds === 0 || rds > 1); |
|
|
|
c = Math.sqrt(-2 * Math.log(rds) / rds); // Box-Muller transform |
|
|
|
x *= c; |
|
nextSample = y * c; |
|
} |
|
|
|
return mean + x * stdev; |
|
} |
|
|
|
function densityNormal(value, mean, stdev) { |
|
stdev = stdev == null ? 1 : stdev; |
|
var z = (value - (mean || 0)) / stdev; |
|
return Math.exp(-0.5 * z * z) / (stdev * SQRT2PI); |
|
} // Approximation from West (2009) |
|
// Better Approximations to Cumulative Normal Functions |
|
|
|
|
|
function cumulativeNormal(value, mean, stdev) { |
|
mean = mean || 0; |
|
stdev = stdev == null ? 1 : stdev; |
|
var cd, |
|
z = (value - mean) / stdev, |
|
Z = Math.abs(z); |
|
|
|
if (Z > 37) { |
|
cd = 0; |
|
} else { |
|
var _sum, |
|
_exp = Math.exp(-Z * Z / 2); |
|
|
|
if (Z < 7.07106781186547) { |
|
_sum = 3.52624965998911e-02 * Z + 0.700383064443688; |
|
_sum = _sum * Z + 6.37396220353165; |
|
_sum = _sum * Z + 33.912866078383; |
|
_sum = _sum * Z + 112.079291497871; |
|
_sum = _sum * Z + 221.213596169931; |
|
_sum = _sum * Z + 220.206867912376; |
|
cd = _exp * _sum; |
|
_sum = 8.83883476483184e-02 * Z + 1.75566716318264; |
|
_sum = _sum * Z + 16.064177579207; |
|
_sum = _sum * Z + 86.7807322029461; |
|
_sum = _sum * Z + 296.564248779674; |
|
_sum = _sum * Z + 637.333633378831; |
|
_sum = _sum * Z + 793.826512519948; |
|
_sum = _sum * Z + 440.413735824752; |
|
cd = cd / _sum; |
|
} else { |
|
_sum = Z + 0.65; |
|
_sum = Z + 4 / _sum; |
|
_sum = Z + 3 / _sum; |
|
_sum = Z + 2 / _sum; |
|
_sum = Z + 1 / _sum; |
|
cd = _exp / _sum / 2.506628274631; |
|
} |
|
} |
|
|
|
return z > 0 ? 1 - cd : cd; |
|
} // Approximation of Probit function using inverse error function. |
|
|
|
|
|
function quantileNormal(p, mean, stdev) { |
|
if (p < 0 || p > 1) return NaN; |
|
return (mean || 0) + (stdev == null ? 1 : stdev) * SQRT2 * erfinv(2 * p - 1); |
|
} // Approximate inverse error function. Implementation from "Approximating |
|
// the erfinv function" by Mike Giles, GPU Computing Gems, volume 2, 2010. |
|
// Ported from Apache Commons Math, http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
|
|
function erfinv(x) { |
|
// beware that the logarithm argument must be |
|
// commputed as (1.0 - x) * (1.0 + x), |
|
// it must NOT be simplified as 1.0 - x * x as this |
|
// would induce rounding errors near the boundaries +/-1 |
|
var w = -Math.log((1 - x) * (1 + x)), |
|
p; |
|
|
|
if (w < 6.25) { |
|
w -= 3.125; |
|
p = -3.6444120640178196996e-21; |
|
p = -1.685059138182016589e-19 + p * w; |
|
p = 1.2858480715256400167e-18 + p * w; |
|
p = 1.115787767802518096e-17 + p * w; |
|
p = -1.333171662854620906e-16 + p * w; |
|
p = 2.0972767875968561637e-17 + p * w; |
|
p = 6.6376381343583238325e-15 + p * w; |
|
p = -4.0545662729752068639e-14 + p * w; |
|
p = -8.1519341976054721522e-14 + p * w; |
|
p = 2.6335093153082322977e-12 + p * w; |
|
p = -1.2975133253453532498e-11 + p * w; |
|
p = -5.4154120542946279317e-11 + p * w; |
|
p = 1.051212273321532285e-09 + p * w; |
|
p = -4.1126339803469836976e-09 + p * w; |
|
p = -2.9070369957882005086e-08 + p * w; |
|
p = 4.2347877827932403518e-07 + p * w; |
|
p = -1.3654692000834678645e-06 + p * w; |
|
p = -1.3882523362786468719e-05 + p * w; |
|
p = 0.0001867342080340571352 + p * w; |
|
p = -0.00074070253416626697512 + p * w; |
|
p = -0.0060336708714301490533 + p * w; |
|
p = 0.24015818242558961693 + p * w; |
|
p = 1.6536545626831027356 + p * w; |
|
} else if (w < 16.0) { |
|
w = Math.sqrt(w) - 3.25; |
|
p = 2.2137376921775787049e-09; |
|
p = 9.0756561938885390979e-08 + p * w; |
|
p = -2.7517406297064545428e-07 + p * w; |
|
p = 1.8239629214389227755e-08 + p * w; |
|
p = 1.5027403968909827627e-06 + p * w; |
|
p = -4.013867526981545969e-06 + p * w; |
|
p = 2.9234449089955446044e-06 + p * w; |
|
p = 1.2475304481671778723e-05 + p * w; |
|
p = -4.7318229009055733981e-05 + p * w; |
|
p = 6.8284851459573175448e-05 + p * w; |
|
p = 2.4031110387097893999e-05 + p * w; |
|
p = -0.0003550375203628474796 + p * w; |
|
p = 0.00095328937973738049703 + p * w; |
|
p = -0.0016882755560235047313 + p * w; |
|
p = 0.0024914420961078508066 + p * w; |
|
p = -0.0037512085075692412107 + p * w; |
|
p = 0.005370914553590063617 + p * w; |
|
p = 1.0052589676941592334 + p * w; |
|
p = 3.0838856104922207635 + p * w; |
|
} else if (Number.isFinite(w)) { |
|
w = Math.sqrt(w) - 5.0; |
|
p = -2.7109920616438573243e-11; |
|
p = -2.5556418169965252055e-10 + p * w; |
|
p = 1.5076572693500548083e-09 + p * w; |
|
p = -3.7894654401267369937e-09 + p * w; |
|
p = 7.6157012080783393804e-09 + p * w; |
|
p = -1.4960026627149240478e-08 + p * w; |
|
p = 2.9147953450901080826e-08 + p * w; |
|
p = -6.7711997758452339498e-08 + p * w; |
|
p = 2.2900482228026654717e-07 + p * w; |
|
p = -9.9298272942317002539e-07 + p * w; |
|
p = 4.5260625972231537039e-06 + p * w; |
|
p = -1.9681778105531670567e-05 + p * w; |
|
p = 7.5995277030017761139e-05 + p * w; |
|
p = -0.00021503011930044477347 + p * w; |
|
p = -0.00013871931833623122026 + p * w; |
|
p = 1.0103004648645343977 + p * w; |
|
p = 4.8499064014085844221 + p * w; |
|
} else { |
|
p = Infinity; |
|
} |
|
|
|
return p * x; |
|
} |
|
|
|
function randomNormal(mean, stdev) { |
|
var mu, |
|
sigma, |
|
dist = { |
|
mean: function mean(_) { |
|
if (arguments.length) { |
|
mu = _ || 0; |
|
return dist; |
|
} else { |
|
return mu; |
|
} |
|
}, |
|
stdev: function stdev(_) { |
|
if (arguments.length) { |
|
sigma = _ == null ? 1 : _; |
|
return dist; |
|
} else { |
|
return sigma; |
|
} |
|
}, |
|
sample: function sample() { |
|
return sampleNormal(mu, sigma); |
|
}, |
|
pdf: function pdf(value) { |
|
return densityNormal(value, mu, sigma); |
|
}, |
|
cdf: function cdf(value) { |
|
return cumulativeNormal(value, mu, sigma); |
|
}, |
|
icdf: function icdf(p) { |
|
return quantileNormal(p, mu, sigma); |
|
} |
|
}; |
|
return dist.mean(mean).stdev(stdev); |
|
} // TODO: support for additional kernels? |
|
|
|
|
|
function randomKDE(support, bandwidth) { |
|
var kernel = randomNormal(), |
|
dist = {}, |
|
n = 0; |
|
|
|
dist.data = function (_) { |
|
if (arguments.length) { |
|
support = _; |
|
n = _ ? _.length : 0; |
|
return dist.bandwidth(bandwidth); |
|
} else { |
|
return support; |
|
} |
|
}; |
|
|
|
dist.bandwidth = function (_) { |
|
if (!arguments.length) return bandwidth; |
|
bandwidth = _; |
|
if (!bandwidth && support) bandwidth = bandwidthNRD(support); |
|
return dist; |
|
}; |
|
|
|
dist.sample = function () { |
|
return support[~~(exports.random() * n)] + bandwidth * kernel.sample(); |
|
}; |
|
|
|
dist.pdf = function (x) { |
|
for (var y = 0, i = 0; i < n; ++i) { |
|
y += kernel.pdf((x - support[i]) / bandwidth); |
|
} |
|
|
|
return y / bandwidth / n; |
|
}; |
|
|
|
dist.cdf = function (x) { |
|
for (var y = 0, i = 0; i < n; ++i) { |
|
y += kernel.cdf((x - support[i]) / bandwidth); |
|
} |
|
|
|
return y / n; |
|
}; |
|
|
|
dist.icdf = function () { |
|
throw Error('KDE icdf not supported.'); |
|
}; |
|
|
|
return dist.data(support); |
|
} |
|
|
|
function sampleLogNormal(mean, stdev) { |
|
mean = mean || 0; |
|
stdev = stdev == null ? 1 : stdev; |
|
return Math.exp(mean + sampleNormal() * stdev); |
|
} |
|
|
|
function densityLogNormal(value, mean, stdev) { |
|
if (value <= 0) return 0; |
|
mean = mean || 0; |
|
stdev = stdev == null ? 1 : stdev; |
|
var z = (Math.log(value) - mean) / stdev; |
|
return Math.exp(-0.5 * z * z) / (stdev * SQRT2PI * value); |
|
} |
|
|
|
function cumulativeLogNormal(value, mean, stdev) { |
|
return cumulativeNormal(Math.log(value), mean, stdev); |
|
} |
|
|
|
function quantileLogNormal(p, mean, stdev) { |
|
return Math.exp(quantileNormal(p, mean, stdev)); |
|
} |
|
|
|
function randomLogNormal(mean, stdev) { |
|
var mu, |
|
sigma, |
|
dist = { |
|
mean: function mean(_) { |
|
if (arguments.length) { |
|
mu = _ || 0; |
|
return dist; |
|
} else { |
|
return mu; |
|
} |
|
}, |
|
stdev: function stdev(_) { |
|
if (arguments.length) { |
|
sigma = _ == null ? 1 : _; |
|
return dist; |
|
} else { |
|
return sigma; |
|
} |
|
}, |
|
sample: function sample() { |
|
return sampleLogNormal(mu, sigma); |
|
}, |
|
pdf: function pdf(value) { |
|
return densityLogNormal(value, mu, sigma); |
|
}, |
|
cdf: function cdf(value) { |
|
return cumulativeLogNormal(value, mu, sigma); |
|
}, |
|
icdf: function icdf(p) { |
|
return quantileLogNormal(p, mu, sigma); |
|
} |
|
}; |
|
return dist.mean(mean).stdev(stdev); |
|
} |
|
|
|
function randomMixture(dists, weights) { |
|
var dist = {}, |
|
m = 0, |
|
w; |
|
|
|
function normalize(x) { |
|
var w = [], |
|
sum = 0, |
|
i; |
|
|
|
for (i = 0; i < m; ++i) { |
|
sum += w[i] = x[i] == null ? 1 : +x[i]; |
|
} |
|
|
|
for (i = 0; i < m; ++i) { |
|
w[i] /= sum; |
|
} |
|
|
|
return w; |
|
} |
|
|
|
dist.weights = function (_) { |
|
if (arguments.length) { |
|
w = normalize(weights = _ || []); |
|
return dist; |
|
} |
|
|
|
return weights; |
|
}; |
|
|
|
dist.distributions = function (_) { |
|
if (arguments.length) { |
|
if (_) { |
|
m = _.length; |
|
dists = _; |
|
} else { |
|
m = 0; |
|
dists = []; |
|
} |
|
|
|
return dist.weights(weights); |
|
} |
|
|
|
return dists; |
|
}; |
|
|
|
dist.sample = function () { |
|
var r = exports.random(), |
|
d = dists[m - 1], |
|
v = w[0], |
|
i = 0; // first select distribution |
|
|
|
for (; i < m - 1; v += w[++i]) { |
|
if (r < v) { |
|
d = dists[i]; |
|
break; |
|
} |
|
} // then sample from it |
|
|
|
|
|
return d.sample(); |
|
}; |
|
|
|
dist.pdf = function (x) { |
|
for (var p = 0, i = 0; i < m; ++i) { |
|
p += w[i] * dists[i].pdf(x); |
|
} |
|
|
|
return p; |
|
}; |
|
|
|
dist.cdf = function (x) { |
|
for (var p = 0, i = 0; i < m; ++i) { |
|
p += w[i] * dists[i].cdf(x); |
|
} |
|
|
|
return p; |
|
}; |
|
|
|
dist.icdf = function () { |
|
throw Error('Mixture icdf not supported.'); |
|
}; |
|
|
|
return dist.distributions(dists).weights(weights); |
|
} |
|
|
|
function sampleUniform(min, max) { |
|
if (max == null) { |
|
max = min == null ? 1 : min; |
|
min = 0; |
|
} |
|
|
|
return min + (max - min) * exports.random(); |
|
} |
|
|
|
function densityUniform(value, min, max) { |
|
if (max == null) { |
|
max = min == null ? 1 : min; |
|
min = 0; |
|
} |
|
|
|
return value >= min && value <= max ? 1 / (max - min) : 0; |
|
} |
|
|
|
function cumulativeUniform(value, min, max) { |
|
if (max == null) { |
|
max = min == null ? 1 : min; |
|
min = 0; |
|
} |
|
|
|
return value < min ? 0 : value > max ? 1 : (value - min) / (max - min); |
|
} |
|
|
|
function quantileUniform(p, min, max) { |
|
if (max == null) { |
|
max = min == null ? 1 : min; |
|
min = 0; |
|
} |
|
|
|
return p >= 0 && p <= 1 ? min + p * (max - min) : NaN; |
|
} |
|
|
|
function randomUniform(min, max) { |
|
var a, |
|
b, |
|
dist = { |
|
min: function min(_) { |
|
if (arguments.length) { |
|
a = _ || 0; |
|
return dist; |
|
} else { |
|
return a; |
|
} |
|
}, |
|
max: function max(_) { |
|
if (arguments.length) { |
|
b = _ == null ? 1 : _; |
|
return dist; |
|
} else { |
|
return b; |
|
} |
|
}, |
|
sample: function sample() { |
|
return sampleUniform(a, b); |
|
}, |
|
pdf: function pdf(value) { |
|
return densityUniform(value, a, b); |
|
}, |
|
cdf: function cdf(value) { |
|
return cumulativeUniform(value, a, b); |
|
}, |
|
icdf: function icdf(p) { |
|
return quantileUniform(p, a, b); |
|
} |
|
}; |
|
|
|
if (max == null) { |
|
max = min == null ? 1 : min; |
|
min = 0; |
|
} |
|
|
|
return dist.min(min).max(max); |
|
} // Ordinary Least Squares |
|
|
|
|
|
function ols(uX, uY, uXY, uX2) { |
|
var delta = uX2 - uX * uX, |
|
slope = Math.abs(delta) < 1e-24 ? 0 : (uXY - uX * uY) / delta, |
|
intercept = uY - slope * uX; |
|
return [intercept, slope]; |
|
} |
|
|
|
function points(data, x, y, sort) { |
|
data = data.filter(function (d) { |
|
var u = x(d), |
|
v = y(d); |
|
return u != null && (u = +u) >= u && v != null && (v = +v) >= v; |
|
}); |
|
|
|
if (sort) { |
|
data.sort(function (a, b) { |
|
return x(a) - x(b); |
|
}); |
|
} |
|
|
|
var n = data.length, |
|
X = new Float64Array(n), |
|
Y = new Float64Array(n); // extract values, calculate means |
|
|
|
var i = 0, |
|
ux = 0, |
|
uy = 0, |
|
xv, |
|
yv, |
|
d; |
|
var _iteratorNormalCompletion15 = true; |
|
var _didIteratorError15 = false; |
|
var _iteratorError15 = undefined; |
|
|
|
try { |
|
for (var _iterator15 = data[Symbol.iterator](), _step15; !(_iteratorNormalCompletion15 = (_step15 = _iterator15.next()).done); _iteratorNormalCompletion15 = true) { |
|
d = _step15.value; |
|
X[i] = xv = +x(d); |
|
Y[i] = yv = +y(d); |
|
++i; |
|
ux += (xv - ux) / i; |
|
uy += (yv - uy) / i; |
|
} // mean center the data |
|
|
|
} catch (err) { |
|
_didIteratorError15 = true; |
|
_iteratorError15 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion15 && _iterator15.return != null) { |
|
_iterator15.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError15) { |
|
throw _iteratorError15; |
|
} |
|
} |
|
} |
|
|
|
for (i = 0; i < n; ++i) { |
|
X[i] -= ux; |
|
Y[i] -= uy; |
|
} |
|
|
|
return [X, Y, ux, uy]; |
|
} |
|
|
|
function visitPoints(data, x, y, callback) { |
|
var i = -1, |
|
u, |
|
v; |
|
var _iteratorNormalCompletion16 = true; |
|
var _didIteratorError16 = false; |
|
var _iteratorError16 = undefined; |
|
|
|
try { |
|
for (var _iterator16 = data[Symbol.iterator](), _step16; !(_iteratorNormalCompletion16 = (_step16 = _iterator16.next()).done); _iteratorNormalCompletion16 = true) { |
|
var d = _step16.value; |
|
u = x(d); |
|
v = y(d); |
|
|
|
if (u != null && (u = +u) >= u && v != null && (v = +v) >= v) { |
|
callback(u, v, ++i); |
|
} |
|
} |
|
} catch (err) { |
|
_didIteratorError16 = true; |
|
_iteratorError16 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion16 && _iterator16.return != null) { |
|
_iterator16.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError16) { |
|
throw _iteratorError16; |
|
} |
|
} |
|
} |
|
} // Adapted from d3-regression by Harry Stevens |
|
// License: https://github.com/HarryStevens/d3-regression/blob/master/LICENSE |
|
|
|
|
|
function rSquared(data, x, y, uY, predict) { |
|
var SSE = 0, |
|
SST = 0; |
|
visitPoints(data, x, y, function (dx, dy) { |
|
var sse = dy - predict(dx), |
|
sst = dy - uY; |
|
SSE += sse * sse; |
|
SST += sst * sst; |
|
}); |
|
return 1 - SSE / SST; |
|
} // Adapted from d3-regression by Harry Stevens |
|
// License: https://github.com/HarryStevens/d3-regression/blob/master/LICENSE |
|
|
|
|
|
function regressionLinear(data, x, y) { |
|
var X = 0, |
|
Y = 0, |
|
XY = 0, |
|
X2 = 0, |
|
n = 0; |
|
visitPoints(data, x, y, function (dx, dy) { |
|
++n; |
|
X += (dx - X) / n; |
|
Y += (dy - Y) / n; |
|
XY += (dx * dy - XY) / n; |
|
X2 += (dx * dx - X2) / n; |
|
}); |
|
|
|
var coef = ols(X, Y, XY, X2), |
|
predict = function predict(x) { |
|
return coef[0] + coef[1] * x; |
|
}; |
|
|
|
return { |
|
coef: coef, |
|
predict: predict, |
|
rSquared: rSquared(data, x, y, Y, predict) |
|
}; |
|
} // Adapted from d3-regression by Harry Stevens |
|
// License: https://github.com/HarryStevens/d3-regression/blob/master/LICENSE |
|
|
|
|
|
function regressionLog(data, x, y) { |
|
var X = 0, |
|
Y = 0, |
|
XY = 0, |
|
X2 = 0, |
|
n = 0; |
|
visitPoints(data, x, y, function (dx, dy) { |
|
++n; |
|
dx = Math.log(dx); |
|
X += (dx - X) / n; |
|
Y += (dy - Y) / n; |
|
XY += (dx * dy - XY) / n; |
|
X2 += (dx * dx - X2) / n; |
|
}); |
|
|
|
var coef = ols(X, Y, XY, X2), |
|
predict = function predict(x) { |
|
return coef[0] + coef[1] * Math.log(x); |
|
}; |
|
|
|
return { |
|
coef: coef, |
|
predict: predict, |
|
rSquared: rSquared(data, x, y, Y, predict) |
|
}; |
|
} |
|
|
|
function regressionExp(data, x, y) { |
|
var Y = 0, |
|
YL = 0, |
|
XY = 0, |
|
XYL = 0, |
|
X2Y = 0, |
|
n = 0; |
|
visitPoints(data, x, y, function (dx, dy) { |
|
var ly = Math.log(dy), |
|
xy = dx * dy; |
|
++n; |
|
Y += (dy - Y) / n; |
|
XY += (xy - XY) / n; |
|
X2Y += (dx * xy - X2Y) / n; |
|
YL += (dy * ly - YL) / n; |
|
XYL += (xy * ly - XYL) / n; |
|
}); |
|
|
|
var coef = ols(XY / Y, YL / Y, XYL / Y, X2Y / Y), |
|
predict = function predict(x) { |
|
return coef[0] * Math.exp(coef[1] * x); |
|
}; |
|
|
|
coef[0] = Math.exp(coef[0]); |
|
return { |
|
coef: coef, |
|
predict: predict, |
|
rSquared: rSquared(data, x, y, Y, predict) |
|
}; |
|
} // Adapted from d3-regression by Harry Stevens |
|
// License: https://github.com/HarryStevens/d3-regression/blob/master/LICENSE |
|
|
|
|
|
function regressionPow(data, x, y) { |
|
var X = 0, |
|
Y = 0, |
|
XY = 0, |
|
X2 = 0, |
|
YS = 0, |
|
n = 0; |
|
visitPoints(data, x, y, function (dx, dy) { |
|
var lx = Math.log(dx), |
|
ly = Math.log(dy); |
|
++n; |
|
X += (lx - X) / n; |
|
Y += (ly - Y) / n; |
|
XY += (lx * ly - XY) / n; |
|
X2 += (lx * lx - X2) / n; |
|
YS += (dy - YS) / n; |
|
}); |
|
|
|
var coef = ols(X, Y, XY, X2), |
|
predict = function predict(x) { |
|
return coef[0] * Math.pow(x, coef[1]); |
|
}; |
|
|
|
coef[0] = Math.exp(coef[0]); |
|
return { |
|
coef: coef, |
|
predict: predict, |
|
rSquared: rSquared(data, x, y, YS, predict) |
|
}; |
|
} |
|
|
|
function regressionQuad(data, x, y) { |
|
var _points = points(data, x, y), |
|
_points2 = _slicedToArray(_points, 4), |
|
xv = _points2[0], |
|
yv = _points2[1], |
|
ux = _points2[2], |
|
uy = _points2[3], |
|
n = xv.length; |
|
|
|
var X2 = 0, |
|
X3 = 0, |
|
X4 = 0, |
|
XY = 0, |
|
X2Y = 0, |
|
i, |
|
dx, |
|
dy, |
|
x2; |
|
|
|
for (i = 0; i < n;) { |
|
dx = xv[i]; |
|
dy = yv[i++]; |
|
x2 = dx * dx; |
|
X2 += (x2 - X2) / i; |
|
X3 += (x2 * dx - X3) / i; |
|
X4 += (x2 * x2 - X4) / i; |
|
XY += (dx * dy - XY) / i; |
|
X2Y += (x2 * dy - X2Y) / i; |
|
} |
|
|
|
var X2X2 = X4 - X2 * X2, |
|
d = X2 * X2X2 - X3 * X3, |
|
a = (X2Y * X2 - XY * X3) / d, |
|
b = (XY * X2X2 - X2Y * X3) / d, |
|
c = -a * X2, |
|
predict = function predict(x) { |
|
x = x - ux; |
|
return a * x * x + b * x + c + uy; |
|
}; // transform coefficients back from mean-centered space |
|
|
|
|
|
return { |
|
coef: [c - b * ux + a * ux * ux + uy, b - 2 * a * ux, a], |
|
predict: predict, |
|
rSquared: rSquared(data, x, y, uy, predict) |
|
}; |
|
} // Adapted from d3-regression by Harry Stevens |
|
// License: https://github.com/HarryStevens/d3-regression/blob/master/LICENSE |
|
// ... which was adapted from regression-js by Tom Alexander |
|
// Source: https://github.com/Tom-Alexander/regression-js/blob/master/src/regression.js#L246 |
|
// License: https://github.com/Tom-Alexander/regression-js/blob/master/LICENSE |
|
|
|
|
|
function regressionPoly(data, x, y, order) { |
|
// use more efficient methods for lower orders |
|
if (order === 1) return regressionLinear(data, x, y); |
|
if (order === 2) return regressionQuad(data, x, y); |
|
|
|
var _points3 = points(data, x, y), |
|
_points4 = _slicedToArray(_points3, 4), |
|
xv = _points4[0], |
|
yv = _points4[1], |
|
ux = _points4[2], |
|
uy = _points4[3], |
|
n = xv.length, |
|
lhs = [], |
|
rhs = [], |
|
k = order + 1; |
|
|
|
var i, j, l, v, c; |
|
|
|
for (i = 0; i < k; ++i) { |
|
for (l = 0, v = 0; l < n; ++l) { |
|
v += Math.pow(xv[l], i) * yv[l]; |
|
} |
|
|
|
lhs.push(v); |
|
c = new Float64Array(k); |
|
|
|
for (j = 0; j < k; ++j) { |
|
for (l = 0, v = 0; l < n; ++l) { |
|
v += Math.pow(xv[l], i + j); |
|
} |
|
|
|
c[j] = v; |
|
} |
|
|
|
rhs.push(c); |
|
} |
|
|
|
rhs.push(lhs); |
|
|
|
var coef = gaussianElimination(rhs), |
|
predict = function predict(x) { |
|
x -= ux; |
|
var y = uy + coef[0] + coef[1] * x + coef[2] * x * x; |
|
|
|
for (i = 3; i < k; ++i) { |
|
y += coef[i] * Math.pow(x, i); |
|
} |
|
|
|
return y; |
|
}; |
|
|
|
return { |
|
coef: uncenter(k, coef, -ux, uy), |
|
predict: predict, |
|
rSquared: rSquared(data, x, y, uy, predict) |
|
}; |
|
} |
|
|
|
function uncenter(k, a, x, y) { |
|
var z = Array(k); |
|
var i, j, v, c; // initialize to zero |
|
|
|
for (i = 0; i < k; ++i) { |
|
z[i] = 0; |
|
} // polynomial expansion |
|
|
|
|
|
for (i = k - 1; i >= 0; --i) { |
|
v = a[i]; |
|
c = 1; |
|
z[i] += v; |
|
|
|
for (j = 1; j <= i; ++j) { |
|
c *= (i + 1 - j) / j; // binomial coefficent |
|
|
|
z[i - j] += v * Math.pow(x, j) * c; |
|
} |
|
} // bias term |
|
|
|
|
|
z[0] += y; |
|
return z; |
|
} // Given an array for a two-dimensional matrix and the polynomial order, |
|
// solve A * x = b using Gaussian elimination. |
|
|
|
|
|
function gaussianElimination(matrix) { |
|
var n = matrix.length - 1, |
|
coef = []; |
|
var i, j, k, r, t; |
|
|
|
for (i = 0; i < n; ++i) { |
|
r = i; // max row |
|
|
|
for (j = i + 1; j < n; ++j) { |
|
if (Math.abs(matrix[i][j]) > Math.abs(matrix[i][r])) { |
|
r = j; |
|
} |
|
} |
|
|
|
for (k = i; k < n + 1; ++k) { |
|
t = matrix[k][i]; |
|
matrix[k][i] = matrix[k][r]; |
|
matrix[k][r] = t; |
|
} |
|
|
|
for (j = i + 1; j < n; ++j) { |
|
for (k = n; k >= i; k--) { |
|
matrix[k][j] -= matrix[k][i] * matrix[i][j] / matrix[i][i]; |
|
} |
|
} |
|
} |
|
|
|
for (j = n - 1; j >= 0; --j) { |
|
t = 0; |
|
|
|
for (k = j + 1; k < n; ++k) { |
|
t += matrix[k][j] * coef[k]; |
|
} |
|
|
|
coef[j] = (matrix[n][j] - t) / matrix[j][j]; |
|
} |
|
|
|
return coef; |
|
} |
|
|
|
var maxiters = 2, |
|
epsilon = 1e-12; // Adapted from science.js by Jason Davies |
|
// Source: https://github.com/jasondavies/science.js/blob/master/src/stats/loess.js |
|
// License: https://github.com/jasondavies/science.js/blob/master/LICENSE |
|
|
|
function regressionLoess(data, x, y, bandwidth) { |
|
var _points5 = points(data, x, y, true), |
|
_points6 = _slicedToArray(_points5, 4), |
|
xv = _points6[0], |
|
yv = _points6[1], |
|
ux = _points6[2], |
|
uy = _points6[3], |
|
n = xv.length, |
|
bw = Math.max(2, ~~(bandwidth * n)), |
|
yhat = new Float64Array(n), |
|
residuals = new Float64Array(n), |
|
robustWeights = new Float64Array(n).fill(1); |
|
|
|
for (var iter = -1; ++iter <= maxiters;) { |
|
var _interval = [0, bw - 1]; |
|
|
|
for (var i = 0; i < n; ++i) { |
|
var dx = xv[i], |
|
i0 = _interval[0], |
|
i1 = _interval[1], |
|
edge = dx - xv[i0] > xv[i1] - dx ? i0 : i1; |
|
var W = 0, |
|
_X = 0, |
|
_Y = 0, |
|
XY = 0, |
|
_X2 = 0, |
|
denom = 1 / Math.abs(xv[edge] - dx || 1); // avoid singularity! |
|
|
|
for (var k = i0; k <= i1; ++k) { |
|
var xk = xv[k], |
|
yk = yv[k], |
|
_w = tricube(Math.abs(dx - xk) * denom) * robustWeights[k], |
|
xkw = xk * _w; |
|
|
|
W += _w; |
|
_X += xkw; |
|
_Y += yk * _w; |
|
XY += yk * xkw; |
|
_X2 += xk * xkw; |
|
} // linear regression fit |
|
|
|
|
|
var _ols = ols(_X / W, _Y / W, XY / W, _X2 / W), |
|
_ols2 = _slicedToArray(_ols, 2), |
|
a = _ols2[0], |
|
_b = _ols2[1]; |
|
|
|
yhat[i] = a + _b * dx; |
|
residuals[i] = Math.abs(yv[i] - yhat[i]); |
|
updateInterval(xv, i + 1, _interval); |
|
} |
|
|
|
if (iter === maxiters) { |
|
break; |
|
} |
|
|
|
var medianResidual = median(residuals); |
|
if (Math.abs(medianResidual) < epsilon) break; |
|
|
|
for (var _i2 = 0, arg, _w2; _i2 < n; ++_i2) { |
|
arg = residuals[_i2] / (6 * medianResidual); // default to epsilon (rather than zero) for large deviations |
|
// keeping weights tiny but non-zero prevents singularites |
|
|
|
robustWeights[_i2] = arg >= 1 ? epsilon : (_w2 = 1 - arg * arg) * _w2; |
|
} |
|
} |
|
|
|
return output(xv, yhat, ux, uy); |
|
} // weighting kernel for local regression |
|
|
|
|
|
function tricube(x) { |
|
return (x = 1 - x * x * x) * x * x; |
|
} // advance sliding window interval of nearest neighbors |
|
|
|
|
|
function updateInterval(xv, i, interval) { |
|
var val = xv[i], |
|
left = interval[0], |
|
right = interval[1] + 1; |
|
if (right >= xv.length) return; // step right if distance to new right edge is <= distance to old left edge |
|
// step when distance is equal to ensure movement over duplicate x values |
|
|
|
while (i > left && xv[right] - val <= val - xv[left]) { |
|
interval[0] = ++left; |
|
interval[1] = right; |
|
++right; |
|
} |
|
} // generate smoothed output points |
|
// average points with repeated x values |
|
|
|
|
|
function output(xv, yhat, ux, uy) { |
|
var n = xv.length, |
|
out = []; |
|
var i = 0, |
|
cnt = 0, |
|
prev = [], |
|
v; |
|
|
|
for (; i < n; ++i) { |
|
v = xv[i] + ux; |
|
|
|
if (prev[0] === v) { |
|
// average output values via online update |
|
prev[1] += (yhat[i] - prev[1]) / ++cnt; |
|
} else { |
|
// add new output point |
|
cnt = 0; |
|
prev[1] += uy; |
|
prev = [v, yhat[i]]; |
|
out.push(prev); |
|
} |
|
} |
|
|
|
prev[1] += uy; |
|
return out; |
|
} // subdivide up to accuracy of 0.1 degrees |
|
|
|
|
|
var MIN_RADIANS = 0.1 * Math.PI / 180; // Adaptively sample an interpolated function over a domain extent |
|
|
|
function sampleCurve(f, extent, minSteps, maxSteps) { |
|
minSteps = minSteps || 25; |
|
maxSteps = Math.max(minSteps, maxSteps || 200); |
|
|
|
var point = function point(x) { |
|
return [x, f(x)]; |
|
}, |
|
minX = extent[0], |
|
maxX = extent[1], |
|
span = maxX - minX, |
|
stop = span / maxSteps, |
|
prev = [point(minX)], |
|
next = []; |
|
|
|
if (minSteps === maxSteps) { |
|
// no adaptation, sample uniform grid directly and return |
|
for (var i = 1; i < maxSteps; ++i) { |
|
prev.push(point(minX + i / minSteps * span)); |
|
} |
|
|
|
prev.push(point(maxX)); |
|
return prev; |
|
} else { |
|
// sample minimum points on uniform grid |
|
// then move on to perform adaptive refinement |
|
next.push(point(maxX)); |
|
|
|
for (var _i3 = minSteps; --_i3 > 0;) { |
|
next.push(point(minX + _i3 / minSteps * span)); |
|
} |
|
} |
|
|
|
var p0 = prev[0], |
|
p1 = next[next.length - 1]; |
|
|
|
while (p1) { |
|
// midpoint for potential curve subdivision |
|
var pm = point((p0[0] + p1[0]) / 2); |
|
|
|
if (pm[0] - p0[0] >= stop && angleDelta(p0, pm, p1) > MIN_RADIANS) { |
|
// maximum resolution has not yet been met, and |
|
// subdivision midpoint sufficiently different from endpoint |
|
// save subdivision, push midpoint onto the visitation stack |
|
next.push(pm); |
|
} else { |
|
// subdivision midpoint sufficiently similar to endpoint |
|
// skip subdivision, store endpoint, move to next point on the stack |
|
p0 = p1; |
|
prev.push(p1); |
|
next.pop(); |
|
} |
|
|
|
p1 = next[next.length - 1]; |
|
} |
|
|
|
return prev; |
|
} |
|
|
|
function angleDelta(p, q, r) { |
|
var a0 = Math.atan2(r[1] - p[1], r[0] - p[0]), |
|
a1 = Math.atan2(q[1] - p[1], q[0] - p[0]); |
|
return Math.abs(a0 - a1); |
|
} |
|
|
|
function TupleStore(key) { |
|
this._key = key ? field(key) : tupleid; |
|
this.reset(); |
|
} |
|
|
|
var prototype$7 = TupleStore.prototype; |
|
|
|
prototype$7.reset = function () { |
|
this._add = []; |
|
this._rem = []; |
|
this._ext = null; |
|
this._get = null; |
|
this._q = null; |
|
}; |
|
|
|
prototype$7.add = function (v) { |
|
this._add.push(v); |
|
}; |
|
|
|
prototype$7.rem = function (v) { |
|
this._rem.push(v); |
|
}; |
|
|
|
prototype$7.values = function () { |
|
this._get = null; |
|
if (this._rem.length === 0) return this._add; |
|
var a = this._add, |
|
r = this._rem, |
|
k = this._key, |
|
n = a.length, |
|
m = r.length, |
|
x = Array(n - m), |
|
map = {}, |
|
i, |
|
j, |
|
v; // use unique key field to clear removed values |
|
|
|
for (i = 0; i < m; ++i) { |
|
map[k(r[i])] = 1; |
|
} |
|
|
|
for (i = 0, j = 0; i < n; ++i) { |
|
if (map[k(v = a[i])]) { |
|
map[k(v)] = 0; |
|
} else { |
|
x[j++] = v; |
|
} |
|
} |
|
|
|
this._rem = []; |
|
return this._add = x; |
|
}; // memoizing statistics methods |
|
|
|
|
|
prototype$7.distinct = function (get) { |
|
var v = this.values(), |
|
n = v.length, |
|
map = {}, |
|
count = 0, |
|
s; |
|
|
|
while (--n >= 0) { |
|
s = get(v[n]) + ''; |
|
|
|
if (!hasOwnProperty(map, s)) { |
|
map[s] = 1; |
|
++count; |
|
} |
|
} |
|
|
|
return count; |
|
}; |
|
|
|
prototype$7.extent = function (get) { |
|
if (this._get !== get || !this._ext) { |
|
var v = this.values(), |
|
i = extentIndex(v, get); |
|
this._ext = [v[i[0]], v[i[1]]]; |
|
this._get = get; |
|
} |
|
|
|
return this._ext; |
|
}; |
|
|
|
prototype$7.argmin = function (get) { |
|
return this.extent(get)[0] || {}; |
|
}; |
|
|
|
prototype$7.argmax = function (get) { |
|
return this.extent(get)[1] || {}; |
|
}; |
|
|
|
prototype$7.min = function (get) { |
|
var m = this.extent(get)[0]; |
|
return m != null ? get(m) : undefined; |
|
}; |
|
|
|
prototype$7.max = function (get) { |
|
var m = this.extent(get)[1]; |
|
return m != null ? get(m) : undefined; |
|
}; |
|
|
|
prototype$7.quartile = function (get) { |
|
if (this._get !== get || !this._q) { |
|
this._q = quartiles(this.values(), get); |
|
this._get = get; |
|
} |
|
|
|
return this._q; |
|
}; |
|
|
|
prototype$7.q1 = function (get) { |
|
return this.quartile(get)[0]; |
|
}; |
|
|
|
prototype$7.q2 = function (get) { |
|
return this.quartile(get)[1]; |
|
}; |
|
|
|
prototype$7.q3 = function (get) { |
|
return this.quartile(get)[2]; |
|
}; |
|
|
|
prototype$7.ci = function (get) { |
|
if (this._get !== get || !this._ci) { |
|
this._ci = bootstrapCI(this.values(), 1000, 0.05, get); |
|
this._get = get; |
|
} |
|
|
|
return this._ci; |
|
}; |
|
|
|
prototype$7.ci0 = function (get) { |
|
return this.ci(get)[0]; |
|
}; |
|
|
|
prototype$7.ci1 = function (get) { |
|
return this.ci(get)[1]; |
|
}; |
|
/** |
|
* Group-by aggregation operator. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby. |
|
* @param {Array<function(object): *>} [params.fields] - An array of accessors to aggregate. |
|
* @param {Array<string>} [params.ops] - An array of strings indicating aggregation operations. |
|
* @param {Array<string>} [params.as] - An array of output field names for aggregated values. |
|
* @param {boolean} [params.cross=false] - A flag indicating that the full |
|
* cross-product of groupby values should be generated, including empty cells. |
|
* If true, the drop parameter is ignored and empty cells are retained. |
|
* @param {boolean} [params.drop=true] - A flag indicating if empty cells should be removed. |
|
*/ |
|
|
|
|
|
function Aggregate(params) { |
|
Transform.call(this, null, params); |
|
this._adds = []; // array of added output tuples |
|
|
|
this._mods = []; // array of modified output tuples |
|
|
|
this._alen = 0; // number of active added tuples |
|
|
|
this._mlen = 0; // number of active modified tuples |
|
|
|
this._drop = true; // should empty aggregation cells be removed |
|
|
|
this._cross = false; // produce full cross-product of group-by values |
|
|
|
this._dims = []; // group-by dimension accessors |
|
|
|
this._dnames = []; // group-by dimension names |
|
|
|
this._measures = []; // collection of aggregation monoids |
|
|
|
this._countOnly = false; // flag indicating only count aggregation |
|
|
|
this._counts = null; // collection of count fields |
|
|
|
this._prev = null; // previous aggregation cells |
|
|
|
this._inputs = null; // array of dependent input tuple field names |
|
|
|
this._outputs = null; // array of output tuple field names |
|
} |
|
|
|
Aggregate.Definition = { |
|
"type": "Aggregate", |
|
"metadata": { |
|
"generates": true, |
|
"changes": true |
|
}, |
|
"params": [{ |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "ops", |
|
"type": "enum", |
|
"array": true, |
|
"values": ValidAggregateOps |
|
}, { |
|
"name": "fields", |
|
"type": "field", |
|
"null": true, |
|
"array": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"null": true, |
|
"array": true |
|
}, { |
|
"name": "drop", |
|
"type": "boolean", |
|
"default": true |
|
}, { |
|
"name": "cross", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "key", |
|
"type": "field" |
|
}] |
|
}; |
|
var prototype$8 = inherits(Aggregate, Transform); |
|
|
|
prototype$8.transform = function (_, pulse) { |
|
var aggr = this, |
|
out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS), |
|
mod = _.modified(); |
|
|
|
aggr.stamp = out.stamp; |
|
|
|
if (aggr.value && (mod || pulse.modified(aggr._inputs, true))) { |
|
aggr._prev = aggr.value; |
|
aggr.value = mod ? aggr.init(_) : {}; |
|
pulse.visit(pulse.SOURCE, function (t) { |
|
return aggr.add(t); |
|
}); |
|
} else { |
|
aggr.value = aggr.value || aggr.init(_); |
|
pulse.visit(pulse.REM, function (t) { |
|
return aggr.rem(t); |
|
}); |
|
pulse.visit(pulse.ADD, function (t) { |
|
return aggr.add(t); |
|
}); |
|
} // Indicate output fields and return aggregate tuples. |
|
|
|
|
|
out.modifies(aggr._outputs); // Should empty cells be dropped? |
|
|
|
aggr._drop = _.drop !== false; // If domain cross-product requested, generate empty cells as needed |
|
// and ensure that empty cells are not dropped |
|
|
|
if (_.cross && aggr._dims.length > 1) { |
|
aggr._drop = false; |
|
aggr.cross(); |
|
} |
|
|
|
return aggr.changes(out); |
|
}; |
|
|
|
prototype$8.cross = function () { |
|
var aggr = this, |
|
curr = aggr.value, |
|
dims = aggr._dnames, |
|
vals = dims.map(function () { |
|
return {}; |
|
}), |
|
n = dims.length; // collect all group-by domain values |
|
|
|
function collect(cells) { |
|
var key, i, t, v; |
|
|
|
for (key in cells) { |
|
t = cells[key].tuple; |
|
|
|
for (i = 0; i < n; ++i) { |
|
vals[i][v = t[dims[i]]] = v; |
|
} |
|
} |
|
} |
|
|
|
collect(aggr._prev); |
|
collect(curr); // iterate over key cross-product, create cells as needed |
|
|
|
function generate(base, tuple, index) { |
|
var name = dims[index], |
|
v = vals[index++], |
|
k, |
|
key; |
|
|
|
for (k in v) { |
|
tuple[name] = v[k]; |
|
key = base ? base + '|' + k : k; |
|
if (index < n) generate(key, tuple, index);else if (!curr[key]) aggr.cell(key, tuple); |
|
} |
|
} |
|
|
|
generate('', {}, 0); |
|
}; |
|
|
|
prototype$8.init = function (_) { |
|
// initialize input and output fields |
|
var inputs = this._inputs = [], |
|
outputs = this._outputs = [], |
|
inputMap = {}; |
|
|
|
function inputVisit(get) { |
|
var fields = array(accessorFields(get)), |
|
i = 0, |
|
n = fields.length, |
|
f; |
|
|
|
for (; i < n; ++i) { |
|
if (!inputMap[f = fields[i]]) { |
|
inputMap[f] = 1; |
|
inputs.push(f); |
|
} |
|
} |
|
} // initialize group-by dimensions |
|
|
|
|
|
this._dims = array(_.groupby); |
|
this._dnames = this._dims.map(function (d) { |
|
var dname = accessorName(d); |
|
inputVisit(d); |
|
outputs.push(dname); |
|
return dname; |
|
}); |
|
this.cellkey = _.key ? _.key : groupkey(this._dims); // initialize aggregate measures |
|
|
|
this._countOnly = true; |
|
this._counts = []; |
|
this._measures = []; |
|
var fields = _.fields || [null], |
|
ops = _.ops || ['count'], |
|
as = _.as || [], |
|
n = fields.length, |
|
map = {}, |
|
field, |
|
op, |
|
m, |
|
mname, |
|
outname, |
|
i; |
|
|
|
if (n !== ops.length) { |
|
error('Unmatched number of fields and aggregate ops.'); |
|
} |
|
|
|
for (i = 0; i < n; ++i) { |
|
field = fields[i]; |
|
op = ops[i]; |
|
|
|
if (field == null && op !== 'count') { |
|
error('Null aggregate field specified.'); |
|
} |
|
|
|
mname = accessorName(field); |
|
outname = measureName(op, mname, as[i]); |
|
outputs.push(outname); |
|
|
|
if (op === 'count') { |
|
this._counts.push(outname); |
|
|
|
continue; |
|
} |
|
|
|
m = map[mname]; |
|
|
|
if (!m) { |
|
inputVisit(field); |
|
m = map[mname] = []; |
|
m.field = field; |
|
|
|
this._measures.push(m); |
|
} |
|
|
|
if (op !== 'count') this._countOnly = false; |
|
m.push(createMeasure(op, outname)); |
|
} |
|
|
|
this._measures = this._measures.map(function (m) { |
|
return compileMeasures(m, m.field); |
|
}); |
|
return {}; // aggregation cells (this.value) |
|
}; // -- Cell Management ----- |
|
|
|
|
|
prototype$8.cellkey = groupkey(); |
|
|
|
prototype$8.cell = function (key, t) { |
|
var cell = this.value[key]; |
|
|
|
if (!cell) { |
|
cell = this.value[key] = this.newcell(key, t); |
|
this._adds[this._alen++] = cell; |
|
} else if (cell.num === 0 && this._drop && cell.stamp < this.stamp) { |
|
cell.stamp = this.stamp; |
|
this._adds[this._alen++] = cell; |
|
} else if (cell.stamp < this.stamp) { |
|
cell.stamp = this.stamp; |
|
this._mods[this._mlen++] = cell; |
|
} |
|
|
|
return cell; |
|
}; |
|
|
|
prototype$8.newcell = function (key, t) { |
|
var cell = { |
|
key: key, |
|
num: 0, |
|
agg: null, |
|
tuple: this.newtuple(t, this._prev && this._prev[key]), |
|
stamp: this.stamp, |
|
store: false |
|
}; |
|
|
|
if (!this._countOnly) { |
|
var measures = this._measures, |
|
n = measures.length, |
|
i; |
|
cell.agg = Array(n); |
|
|
|
for (i = 0; i < n; ++i) { |
|
cell.agg[i] = new measures[i](cell); |
|
} |
|
} |
|
|
|
if (cell.store) { |
|
cell.data = new TupleStore(); |
|
} |
|
|
|
return cell; |
|
}; |
|
|
|
prototype$8.newtuple = function (t, p) { |
|
var names = this._dnames, |
|
dims = this._dims, |
|
x = {}, |
|
i, |
|
n; |
|
|
|
for (i = 0, n = dims.length; i < n; ++i) { |
|
x[names[i]] = dims[i](t); |
|
} |
|
|
|
return p ? replace(p.tuple, x) : ingest(x); |
|
}; // -- Process Tuples ----- |
|
|
|
|
|
prototype$8.add = function (t) { |
|
var key = this.cellkey(t), |
|
cell = this.cell(key, t), |
|
agg, |
|
i, |
|
n; |
|
cell.num += 1; |
|
if (this._countOnly) return; |
|
if (cell.store) cell.data.add(t); |
|
agg = cell.agg; |
|
|
|
for (i = 0, n = agg.length; i < n; ++i) { |
|
agg[i].add(agg[i].get(t), t); |
|
} |
|
}; |
|
|
|
prototype$8.rem = function (t) { |
|
var key = this.cellkey(t), |
|
cell = this.cell(key, t), |
|
agg, |
|
i, |
|
n; |
|
cell.num -= 1; |
|
if (this._countOnly) return; |
|
if (cell.store) cell.data.rem(t); |
|
agg = cell.agg; |
|
|
|
for (i = 0, n = agg.length; i < n; ++i) { |
|
agg[i].rem(agg[i].get(t), t); |
|
} |
|
}; |
|
|
|
prototype$8.celltuple = function (cell) { |
|
var tuple = cell.tuple, |
|
counts = this._counts, |
|
agg, |
|
i, |
|
n; // consolidate stored values |
|
|
|
if (cell.store) { |
|
cell.data.values(); |
|
} // update tuple properties |
|
|
|
|
|
for (i = 0, n = counts.length; i < n; ++i) { |
|
tuple[counts[i]] = cell.num; |
|
} |
|
|
|
if (!this._countOnly) { |
|
agg = cell.agg; |
|
|
|
for (i = 0, n = agg.length; i < n; ++i) { |
|
agg[i].set(tuple); |
|
} |
|
} |
|
|
|
return tuple; |
|
}; |
|
|
|
prototype$8.changes = function (out) { |
|
var adds = this._adds, |
|
mods = this._mods, |
|
prev = this._prev, |
|
drop = this._drop, |
|
add = out.add, |
|
rem = out.rem, |
|
mod = out.mod, |
|
cell, |
|
key, |
|
i, |
|
n; |
|
if (prev) for (key in prev) { |
|
cell = prev[key]; |
|
if (!drop || cell.num) rem.push(cell.tuple); |
|
} |
|
|
|
for (i = 0, n = this._alen; i < n; ++i) { |
|
add.push(this.celltuple(adds[i])); |
|
adds[i] = null; // for garbage collection |
|
} |
|
|
|
for (i = 0, n = this._mlen; i < n; ++i) { |
|
cell = mods[i]; |
|
(cell.num === 0 && drop ? rem : mod).push(this.celltuple(cell)); |
|
mods[i] = null; // for garbage collection |
|
} |
|
|
|
this._alen = this._mlen = 0; // reset list of active cells |
|
|
|
this._prev = null; |
|
return out; |
|
}; // epsilon bias to offset floating point error (#1737) |
|
|
|
|
|
var EPSILON = 1e-14; |
|
/** |
|
* Generates a binning function for discretizing data. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. The |
|
* provided values should be valid options for the {@link bin} function. |
|
* @param {function(object): *} params.field - The data field to bin. |
|
*/ |
|
|
|
function Bin(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Bin.Definition = { |
|
"type": "Bin", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "interval", |
|
"type": "boolean", |
|
"default": true |
|
}, { |
|
"name": "anchor", |
|
"type": "number" |
|
}, { |
|
"name": "maxbins", |
|
"type": "number", |
|
"default": 20 |
|
}, { |
|
"name": "base", |
|
"type": "number", |
|
"default": 10 |
|
}, { |
|
"name": "divide", |
|
"type": "number", |
|
"array": true, |
|
"default": [5, 2] |
|
}, { |
|
"name": "extent", |
|
"type": "number", |
|
"array": true, |
|
"length": 2, |
|
"required": true |
|
}, { |
|
"name": "span", |
|
"type": "number" |
|
}, { |
|
"name": "step", |
|
"type": "number" |
|
}, { |
|
"name": "steps", |
|
"type": "number", |
|
"array": true |
|
}, { |
|
"name": "minstep", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "nice", |
|
"type": "boolean", |
|
"default": true |
|
}, { |
|
"name": "name", |
|
"type": "string" |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": 2, |
|
"default": ["bin0", "bin1"] |
|
}] |
|
}; |
|
var prototype$9 = inherits(Bin, Transform); |
|
|
|
prototype$9.transform = function (_, pulse) { |
|
var band = _.interval !== false, |
|
bins = this._bins(_), |
|
start = bins.start, |
|
step = bins.step, |
|
as = _.as || ['bin0', 'bin1'], |
|
b0 = as[0], |
|
b1 = as[1], |
|
flag; |
|
|
|
if (_.modified()) { |
|
pulse = pulse.reflow(true); |
|
flag = pulse.SOURCE; |
|
} else { |
|
flag = pulse.modified(accessorFields(_.field)) ? pulse.ADD_MOD : pulse.ADD; |
|
} |
|
|
|
pulse.visit(flag, band ? function (t) { |
|
var v = bins(t); // minimum bin value (inclusive) |
|
|
|
t[b0] = v; // maximum bin value (exclusive) |
|
// use convoluted math for better floating point agreement |
|
// see https://github.com/vega/vega/issues/830 |
|
// infinite values propagate through this formula! #2227 |
|
|
|
t[b1] = v == null ? null : start + step * (1 + (v - start) / step); |
|
} : function (t) { |
|
t[b0] = bins(t); |
|
}); |
|
return pulse.modifies(band ? as : b0); |
|
}; |
|
|
|
prototype$9._bins = function (_) { |
|
if (this.value && !_.modified()) { |
|
return this.value; |
|
} |
|
|
|
var field = _.field, |
|
bins = bin(_), |
|
step = bins.step, |
|
start = bins.start, |
|
stop = start + Math.ceil((bins.stop - start) / step) * step, |
|
a, |
|
d; |
|
|
|
if ((a = _.anchor) != null) { |
|
d = a - (start + step * Math.floor((a - start) / step)); |
|
start += d; |
|
stop += d; |
|
} |
|
|
|
var f = function f(t) { |
|
var v = field(t); |
|
return v == null ? null : v < start ? -Infinity : v > stop ? +Infinity : (v = Math.max(start, Math.min(+v, stop - step)), start + step * Math.floor(EPSILON + (v - start) / step)); |
|
}; |
|
|
|
f.start = start; |
|
f.stop = bins.stop; |
|
f.step = step; |
|
return this.value = accessor(f, accessorFields(field), _.name || 'bin_' + accessorName(field)); |
|
}; |
|
|
|
function SortedList(idFunc, source, input) { |
|
var $ = idFunc, |
|
_data = source || [], |
|
_add = input || [], |
|
rem = {}, |
|
cnt = 0; |
|
|
|
return { |
|
add: function add(t) { |
|
_add.push(t); |
|
}, |
|
remove: function remove(t) { |
|
rem[$(t)] = ++cnt; |
|
}, |
|
size: function size() { |
|
return _data.length; |
|
}, |
|
data: function data(compare, resort) { |
|
if (cnt) { |
|
_data = _data.filter(function (t) { |
|
return !rem[$(t)]; |
|
}); |
|
rem = {}; |
|
cnt = 0; |
|
} |
|
|
|
if (resort && compare) { |
|
_data.sort(compare); |
|
} |
|
|
|
if (_add.length) { |
|
_data = compare ? merge(compare, _data, _add.sort(compare)) : _data.concat(_add); |
|
_add = []; |
|
} |
|
|
|
return _data; |
|
} |
|
}; |
|
} |
|
/** |
|
* Collects all data tuples that pass through this operator. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(*,*): number} [params.sort] - An optional |
|
* comparator function for additionally sorting the collected tuples. |
|
*/ |
|
|
|
|
|
function Collect(params) { |
|
Transform.call(this, [], params); |
|
} |
|
|
|
Collect.Definition = { |
|
"type": "Collect", |
|
"metadata": { |
|
"source": true |
|
}, |
|
"params": [{ |
|
"name": "sort", |
|
"type": "compare" |
|
}] |
|
}; |
|
var prototype$a = inherits(Collect, Transform); |
|
|
|
prototype$a.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.ALL), |
|
list = SortedList(tupleid, this.value, out.materialize(out.ADD).add), |
|
sort = _.sort, |
|
mod = pulse.changed() || sort && (_.modified('sort') || pulse.modified(sort.fields)); |
|
out.visit(out.REM, list.remove); |
|
this.modified(mod); |
|
this.value = out.source = list.data(stableCompare(sort), mod); // propagate tree root if defined |
|
|
|
if (pulse.source && pulse.source.root) { |
|
this.value.root = pulse.source.root; |
|
} |
|
|
|
return out; |
|
}; |
|
/** |
|
* Generates a comparator function. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<string|function>} params.fields - The fields to compare. |
|
* @param {Array<string>} [params.orders] - The sort orders. |
|
* Each entry should be one of "ascending" (default) or "descending". |
|
*/ |
|
|
|
|
|
function Compare(params) { |
|
Operator.call(this, null, update$1, params); |
|
} |
|
|
|
inherits(Compare, Operator); |
|
|
|
function update$1(_) { |
|
return this.value && !_.modified() ? this.value : compare(_.fields, _.orders); |
|
} |
|
/** |
|
* Count regexp-defined pattern occurrences in a text field. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - An accessor for the text field. |
|
* @param {string} [params.pattern] - RegExp string defining the text pattern. |
|
* @param {string} [params.case] - One of 'lower', 'upper' or null (mixed) case. |
|
* @param {string} [params.stopwords] - RegExp string of words to ignore. |
|
*/ |
|
|
|
|
|
function CountPattern(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
CountPattern.Definition = { |
|
"type": "CountPattern", |
|
"metadata": { |
|
"generates": true, |
|
"changes": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "case", |
|
"type": "enum", |
|
"values": ["upper", "lower", "mixed"], |
|
"default": "mixed" |
|
}, { |
|
"name": "pattern", |
|
"type": "string", |
|
"default": "[\\w\"]+" |
|
}, { |
|
"name": "stopwords", |
|
"type": "string", |
|
"default": "" |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": 2, |
|
"default": ["text", "count"] |
|
}] |
|
}; |
|
|
|
function tokenize(text, tcase, match) { |
|
switch (tcase) { |
|
case 'upper': |
|
text = text.toUpperCase(); |
|
break; |
|
|
|
case 'lower': |
|
text = text.toLowerCase(); |
|
break; |
|
} |
|
|
|
return text.match(match); |
|
} |
|
|
|
var prototype$b = inherits(CountPattern, Transform); |
|
|
|
prototype$b.transform = function (_, pulse) { |
|
function process(update) { |
|
return function (tuple) { |
|
var tokens = tokenize(get(tuple), _.case, match) || [], |
|
t; |
|
|
|
for (var i = 0, n = tokens.length; i < n; ++i) { |
|
if (!stop.test(t = tokens[i])) update(t); |
|
} |
|
}; |
|
} |
|
|
|
var init = this._parameterCheck(_, pulse), |
|
counts = this._counts, |
|
match = this._match, |
|
stop = this._stop, |
|
get = _.field, |
|
as = _.as || ['text', 'count'], |
|
add = process(function (t) { |
|
counts[t] = 1 + (counts[t] || 0); |
|
}), |
|
rem = process(function (t) { |
|
counts[t] -= 1; |
|
}); |
|
|
|
if (init) { |
|
pulse.visit(pulse.SOURCE, add); |
|
} else { |
|
pulse.visit(pulse.ADD, add); |
|
pulse.visit(pulse.REM, rem); |
|
} |
|
|
|
return this._finish(pulse, as); // generate output tuples |
|
}; |
|
|
|
prototype$b._parameterCheck = function (_, pulse) { |
|
var init = false; |
|
|
|
if (_.modified('stopwords') || !this._stop) { |
|
this._stop = new RegExp('^' + (_.stopwords || '') + '$', 'i'); |
|
init = true; |
|
} |
|
|
|
if (_.modified('pattern') || !this._match) { |
|
this._match = new RegExp(_.pattern || '[\\w\']+', 'g'); |
|
init = true; |
|
} |
|
|
|
if (_.modified('field') || pulse.modified(_.field.fields)) { |
|
init = true; |
|
} |
|
|
|
if (init) this._counts = {}; |
|
return init; |
|
}; |
|
|
|
prototype$b._finish = function (pulse, as) { |
|
var counts = this._counts, |
|
tuples = this._tuples || (this._tuples = {}), |
|
text = as[0], |
|
count = as[1], |
|
out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS), |
|
w, |
|
t, |
|
c; |
|
|
|
for (w in counts) { |
|
t = tuples[w]; |
|
c = counts[w] || 0; |
|
|
|
if (!t && c) { |
|
tuples[w] = t = ingest({}); |
|
t[text] = w; |
|
t[count] = c; |
|
out.add.push(t); |
|
} else if (c === 0) { |
|
if (t) out.rem.push(t); |
|
counts[w] = null; |
|
tuples[w] = null; |
|
} else if (t[count] !== c) { |
|
t[count] = c; |
|
out.mod.push(t); |
|
} |
|
} |
|
|
|
return out.modifies(as); |
|
}; |
|
/** |
|
* Perform a cross-product of a tuple stream with itself. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object):boolean} [params.filter] - An optional filter |
|
* function for selectively including tuples in the cross product. |
|
* @param {Array<string>} [params.as] - The names of the output fields. |
|
*/ |
|
|
|
|
|
function Cross(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Cross.Definition = { |
|
"type": "Cross", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "filter", |
|
"type": "expr" |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": 2, |
|
"default": ["a", "b"] |
|
}] |
|
}; |
|
var prototype$c = inherits(Cross, Transform); |
|
|
|
prototype$c.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.NO_SOURCE), |
|
data = this.value, |
|
as = _.as || ['a', 'b'], |
|
a = as[0], |
|
b = as[1], |
|
reset = !data || pulse.changed(pulse.ADD_REM) || _.modified('as') || _.modified('filter'); |
|
|
|
if (reset) { |
|
if (data) out.rem = data; |
|
data = pulse.materialize(pulse.SOURCE).source; |
|
out.add = this.value = cross(data, a, b, _.filter || truthy); |
|
} else { |
|
out.mod = data; |
|
} |
|
|
|
out.source = this.value; |
|
return out.modifies(as); |
|
}; |
|
|
|
function cross(input, a, b, filter) { |
|
var data = [], |
|
t = {}, |
|
n = input.length, |
|
i = 0, |
|
j, |
|
left; |
|
|
|
for (; i < n; ++i) { |
|
t[a] = left = input[i]; |
|
|
|
for (j = 0; j < n; ++j) { |
|
t[b] = input[j]; |
|
|
|
if (filter(t)) { |
|
data.push(ingest(t)); |
|
t = {}; |
|
t[a] = left; |
|
} |
|
} |
|
} |
|
|
|
return data; |
|
} |
|
|
|
var Distributions = { |
|
kde: randomKDE, |
|
mixture: randomMixture, |
|
normal: randomNormal, |
|
lognormal: randomLogNormal, |
|
uniform: randomUniform |
|
}; |
|
var DISTRIBUTIONS = 'distributions', |
|
FUNCTION = 'function', |
|
FIELD = 'field'; |
|
/** |
|
* Parse a parameter object for a probability distribution. |
|
* @param {object} def - The distribution parameter object. |
|
* @param {function():Array<object>} - A method for requesting |
|
* source data. Used for distributions (such as KDE) that |
|
* require sample data points. This method will only be |
|
* invoked if the 'from' parameter for a target data source |
|
* is not provided. Typically this method returns backing |
|
* source data for a Pulse object. |
|
* @return {object} - The output distribution object. |
|
*/ |
|
|
|
function parse$2(def, data) { |
|
var func = def[FUNCTION]; |
|
|
|
if (!hasOwnProperty(Distributions, func)) { |
|
error('Unknown distribution function: ' + func); |
|
} |
|
|
|
var d = Distributions[func](); |
|
|
|
for (var name in def) { |
|
// if data field, extract values |
|
if (name === FIELD) { |
|
d.data((def.from || data()).map(def[name])); |
|
} // if distribution mixture, recurse to parse each definition |
|
else if (name === DISTRIBUTIONS) { |
|
d[name](def[name].map(function (_) { |
|
return parse$2(_, data); |
|
})); |
|
} // otherwise, simply set the parameter |
|
else if (_typeof(d[name]) === FUNCTION) { |
|
d[name](def[name]); |
|
} |
|
} |
|
|
|
return d; |
|
} |
|
/** |
|
* Grid sample points for a probability density. Given a distribution and |
|
* a sampling extent, will generate points suitable for plotting either |
|
* PDF (probability density function) or CDF (cumulative distribution |
|
* function) curves. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {object} params.distribution - The probability distribution. This |
|
* is an object parameter dependent on the distribution type. |
|
* @param {string} [params.method='pdf'] - The distribution method to sample. |
|
* One of 'pdf' or 'cdf'. |
|
* @param {Array<number>} [params.extent] - The [min, max] extent over which |
|
* to sample the distribution. This argument is required in most cases, but |
|
* can be omitted if the distribution (e.g., 'kde') supports a 'data' method |
|
* that returns numerical sample points from which the extent can be deduced. |
|
* @param {number} [params.minsteps=25] - The minimum number of curve samples |
|
* for plotting the density. |
|
* @param {number} [params.maxsteps=200] - The maximum number of curve samples |
|
* for plotting the density. |
|
* @param {number} [params.steps] - The exact number of curve samples for |
|
* plotting the density. If specified, overrides both minsteps and maxsteps |
|
* to set an exact number of uniform samples. Useful in conjunction with |
|
* a fixed extent to ensure consistent sample points for stacked densities. |
|
*/ |
|
|
|
|
|
function Density(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var distributions = [{ |
|
"key": { |
|
"function": "normal" |
|
}, |
|
"params": [{ |
|
"name": "mean", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "stdev", |
|
"type": "number", |
|
"default": 1 |
|
}] |
|
}, { |
|
"key": { |
|
"function": "lognormal" |
|
}, |
|
"params": [{ |
|
"name": "mean", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "stdev", |
|
"type": "number", |
|
"default": 1 |
|
}] |
|
}, { |
|
"key": { |
|
"function": "uniform" |
|
}, |
|
"params": [{ |
|
"name": "min", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "max", |
|
"type": "number", |
|
"default": 1 |
|
}] |
|
}, { |
|
"key": { |
|
"function": "kde" |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "from", |
|
"type": "data" |
|
}, { |
|
"name": "bandwidth", |
|
"type": "number", |
|
"default": 0 |
|
}] |
|
}]; |
|
var mixture = { |
|
"key": { |
|
"function": "mixture" |
|
}, |
|
"params": [{ |
|
"name": "distributions", |
|
"type": "param", |
|
"array": true, |
|
"params": distributions |
|
}, { |
|
"name": "weights", |
|
"type": "number", |
|
"array": true |
|
}] |
|
}; |
|
Density.Definition = { |
|
"type": "Density", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "extent", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "steps", |
|
"type": "number" |
|
}, { |
|
"name": "minsteps", |
|
"type": "number", |
|
"default": 25 |
|
}, { |
|
"name": "maxsteps", |
|
"type": "number", |
|
"default": 200 |
|
}, { |
|
"name": "method", |
|
"type": "string", |
|
"default": "pdf", |
|
"values": ["pdf", "cdf"] |
|
}, { |
|
"name": "distribution", |
|
"type": "param", |
|
"params": distributions.concat(mixture) |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"default": ["value", "density"] |
|
}] |
|
}; |
|
var prototype$d = inherits(Density, Transform); |
|
|
|
prototype$d.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS); |
|
|
|
if (!this.value || pulse.changed() || _.modified()) { |
|
var dist = parse$2(_.distribution, source(pulse)), |
|
minsteps = _.steps || _.minsteps || 25, |
|
maxsteps = _.steps || _.maxsteps || 200, |
|
method = _.method || 'pdf'; |
|
|
|
if (method !== 'pdf' && method !== 'cdf') { |
|
error('Invalid density method: ' + method); |
|
} |
|
|
|
if (!_.extent && !dist.data) { |
|
error('Missing density extent parameter.'); |
|
} |
|
|
|
method = dist[method]; |
|
var as = _.as || ['value', 'density'], |
|
domain = _.extent || extent(dist.data()), |
|
values = sampleCurve(method, domain, minsteps, maxsteps).map(function (v) { |
|
var tuple = {}; |
|
tuple[as[0]] = v[0]; |
|
tuple[as[1]] = v[1]; |
|
return ingest(tuple); |
|
}); |
|
if (this.value) out.rem = this.value; |
|
this.value = out.add = out.source = values; |
|
} |
|
|
|
return out; |
|
}; |
|
|
|
function source(pulse) { |
|
return function () { |
|
return pulse.materialize(pulse.SOURCE).source; |
|
}; |
|
} // use either provided alias or accessor field name |
|
|
|
|
|
function fieldNames(fields, as) { |
|
if (!fields) return null; |
|
return fields.map(function (f, i) { |
|
return as[i] || accessorName(f); |
|
}); |
|
} |
|
|
|
function partition(data, groupby, field) { |
|
var groups = [], |
|
get = function get(f) { |
|
return f(t); |
|
}, |
|
map, |
|
i, |
|
n, |
|
t, |
|
k, |
|
g; // partition data points into groups |
|
|
|
|
|
if (groupby == null) { |
|
groups.push(data.map(field)); |
|
} else { |
|
for (map = {}, i = 0, n = data.length; i < n; ++i) { |
|
t = data[i]; |
|
k = groupby.map(get); |
|
g = map[k]; |
|
|
|
if (!g) { |
|
map[k] = g = []; |
|
g.dims = k; |
|
groups.push(g); |
|
} |
|
|
|
g.push(field(t)); |
|
} |
|
} |
|
|
|
return groups; |
|
} |
|
|
|
var Output = 'bin'; |
|
/** |
|
* Dot density binning for dot plot construction. |
|
* Based on Leland Wilkinson, Dot Plots, The American Statistician, 1999. |
|
* https://www.cs.uic.edu/~wilkinson/Publications/dotplots.pdf |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The value field to bin. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby. |
|
* @param {number} [params.step] - The step size (bin width) within which dots should be |
|
* stacked. Defaults to 1/30 of the extent of the data *field*. |
|
* @param {boolean} [params.smooth=false] - A boolean flag indicating if dot density |
|
* stacks should be smoothed to reduce variance. |
|
*/ |
|
|
|
function DotBin(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
DotBin.Definition = { |
|
"type": "DotBin", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "step", |
|
"type": "number" |
|
}, { |
|
"name": "smooth", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"default": Output |
|
}] |
|
}; |
|
var prototype$e = inherits(DotBin, Transform); |
|
|
|
prototype$e.transform = function (_, pulse) { |
|
if (this.value && !(_.modified() || pulse.changed())) { |
|
return pulse; // early exit |
|
} |
|
|
|
var source = pulse.materialize(pulse.SOURCE).source, |
|
groups = partition(pulse.source, _.groupby, identity), |
|
smooth = _.smooth || false, |
|
field = _.field, |
|
step = _.step || autostep(source, field), |
|
sort = stableCompare(function (a, b) { |
|
return field(a) - field(b); |
|
}), |
|
as = _.as || Output, |
|
n = groups.length; // compute dotplot bins per group |
|
|
|
var min = Infinity, |
|
max = -Infinity, |
|
i = 0, |
|
j; |
|
|
|
for (; i < n; ++i) { |
|
var g = groups[i].sort(sort); |
|
j = -1; |
|
var _iteratorNormalCompletion17 = true; |
|
var _didIteratorError17 = false; |
|
var _iteratorError17 = undefined; |
|
|
|
try { |
|
for (var _iterator17 = dotbin(g, step, smooth, field)[Symbol.iterator](), _step17; !(_iteratorNormalCompletion17 = (_step17 = _iterator17.next()).done); _iteratorNormalCompletion17 = true) { |
|
var _v = _step17.value; |
|
if (_v < min) min = _v; |
|
if (_v > max) max = _v; |
|
g[++j][as] = _v; |
|
} |
|
} catch (err) { |
|
_didIteratorError17 = true; |
|
_iteratorError17 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion17 && _iterator17.return != null) { |
|
_iterator17.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError17) { |
|
throw _iteratorError17; |
|
} |
|
} |
|
} |
|
} |
|
|
|
this.value = { |
|
start: min, |
|
stop: max, |
|
step: step |
|
}; |
|
return pulse.reflow(true).modifies(as); |
|
}; |
|
|
|
function autostep(data, field) { |
|
return span(extent(data, field)) / 30; |
|
} |
|
/** |
|
* Wraps an expression function with access to external parameters. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function} params.expr - The expression function. The |
|
* function should accept both a datum and a parameter object. |
|
* This operator's value will be a new function that wraps the |
|
* expression function with access to this operator's parameters. |
|
*/ |
|
|
|
|
|
function Expression(params) { |
|
Operator.call(this, null, update$2, params); |
|
this.modified(true); |
|
} |
|
|
|
inherits(Expression, Operator); |
|
|
|
function update$2(_) { |
|
var expr = _.expr; |
|
return this.value && !_.modified('expr') ? this.value : accessor(function (datum) { |
|
return expr(datum, _); |
|
}, accessorFields(expr), accessorName(expr)); |
|
} |
|
/** |
|
* Computes extents (min/max) for a data field. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The field over which to compute extends. |
|
*/ |
|
|
|
|
|
function Extent(params) { |
|
Transform.call(this, [undefined, undefined], params); |
|
} |
|
|
|
Extent.Definition = { |
|
"type": "Extent", |
|
"metadata": {}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}] |
|
}; |
|
var prototype$f = inherits(Extent, Transform); |
|
|
|
prototype$f.transform = function (_, pulse) { |
|
var extent = this.value, |
|
field = _.field, |
|
min = extent[0], |
|
max = extent[1], |
|
mod; |
|
mod = pulse.changed() || pulse.modified(field.fields) || _.modified('field'); |
|
|
|
if (mod || min == null) { |
|
min = +Infinity; |
|
max = -Infinity; |
|
} |
|
|
|
pulse.visit(mod ? pulse.SOURCE : pulse.ADD, function (t) { |
|
var v = field(t); |
|
|
|
if (v != null) { |
|
// coerce to number |
|
v = +v; // NaNs will fail all comparisons! |
|
|
|
if (v < min) min = v; |
|
if (v > max) max = v; |
|
} |
|
}); |
|
|
|
if (!Number.isFinite(min) || !Number.isFinite(max)) { |
|
var name = accessorName(field); |
|
if (name) name = " for field \"".concat(name, "\""); |
|
pulse.dataflow.warn("Infinite extent".concat(name, ": [").concat(min, ", ").concat(max, "]")); |
|
min = max = undefined; |
|
} |
|
|
|
this.value = [min, max]; |
|
}; |
|
/** |
|
* Provides a bridge between a parent transform and a target subflow that |
|
* consumes only a subset of the tuples that pass through the parent. |
|
* @constructor |
|
* @param {Pulse} pulse - A pulse to use as the value of this operator. |
|
* @param {Transform} parent - The parent transform (typically a Facet instance). |
|
* @param {Transform} target - A transform that receives the subflow of tuples. |
|
*/ |
|
|
|
|
|
function Subflow(pulse, parent) { |
|
Operator.call(this, pulse); |
|
this.parent = parent; |
|
} |
|
|
|
var prototype$g = inherits(Subflow, Operator); |
|
|
|
prototype$g.connect = function (target) { |
|
this.targets().add(target); |
|
return target.source = this; |
|
}; |
|
/** |
|
* Add an 'add' tuple to the subflow pulse. |
|
* @param {Tuple} t - The tuple being added. |
|
*/ |
|
|
|
|
|
prototype$g.add = function (t) { |
|
this.value.add.push(t); |
|
}; |
|
/** |
|
* Add a 'rem' tuple to the subflow pulse. |
|
* @param {Tuple} t - The tuple being removed. |
|
*/ |
|
|
|
|
|
prototype$g.rem = function (t) { |
|
this.value.rem.push(t); |
|
}; |
|
/** |
|
* Add a 'mod' tuple to the subflow pulse. |
|
* @param {Tuple} t - The tuple being modified. |
|
*/ |
|
|
|
|
|
prototype$g.mod = function (t) { |
|
this.value.mod.push(t); |
|
}; |
|
/** |
|
* Re-initialize this operator's pulse value. |
|
* @param {Pulse} pulse - The pulse to copy from. |
|
* @see Pulse.init |
|
*/ |
|
|
|
|
|
prototype$g.init = function (pulse) { |
|
this.value.init(pulse, pulse.NO_SOURCE); |
|
}; |
|
/** |
|
* Evaluate this operator. This method overrides the |
|
* default behavior to simply return the contained pulse value. |
|
* @return {Pulse} |
|
*/ |
|
|
|
|
|
prototype$g.evaluate = function () { |
|
// assert: this.value.stamp === pulse.stamp |
|
return this.value; |
|
}; |
|
/** |
|
* Facets a dataflow into a set of subflows based on a key. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(Dataflow, string): Operator} params.subflow - A function |
|
* that generates a subflow of operators and returns its root operator. |
|
* @param {function(object): *} params.key - The key field to facet by. |
|
*/ |
|
|
|
|
|
function Facet(params) { |
|
Transform.call(this, {}, params); |
|
this._keys = fastmap(); // cache previously calculated key values |
|
// keep track of active subflows, use as targets array for listeners |
|
// this allows us to limit propagation to only updated subflows |
|
|
|
var a = this._targets = []; |
|
a.active = 0; |
|
|
|
a.forEach = function (f) { |
|
for (var i = 0, n = a.active; i < n; ++i) { |
|
f(a[i], i, a); |
|
} |
|
}; |
|
} |
|
|
|
var prototype$h = inherits(Facet, Transform); |
|
|
|
prototype$h.activate = function (flow) { |
|
this._targets[this._targets.active++] = flow; |
|
}; |
|
|
|
prototype$h.subflow = function (key, flow, pulse, parent) { |
|
var flows = this.value, |
|
sf = hasOwnProperty(flows, key) && flows[key], |
|
df, |
|
p; |
|
|
|
if (!sf) { |
|
p = parent || (p = this._group[key]) && p.tuple; |
|
df = pulse.dataflow; |
|
sf = df.add(new Subflow(pulse.fork(pulse.NO_SOURCE), this)).connect(flow(df, key, p)); |
|
flows[key] = sf; |
|
this.activate(sf); |
|
} else if (sf.value.stamp < pulse.stamp) { |
|
sf.init(pulse); |
|
this.activate(sf); |
|
} |
|
|
|
return sf; |
|
}; |
|
|
|
prototype$h.transform = function (_, pulse) { |
|
var df = pulse.dataflow, |
|
self = this, |
|
key = _.key, |
|
flow = _.subflow, |
|
cache = this._keys, |
|
rekey = _.modified('key'); |
|
|
|
function subflow(key) { |
|
return self.subflow(key, flow, pulse); |
|
} |
|
|
|
this._group = _.group || {}; |
|
this._targets.active = 0; // reset list of active subflows |
|
|
|
pulse.visit(pulse.REM, function (t) { |
|
var id = tupleid(t), |
|
k = cache.get(id); |
|
|
|
if (k !== undefined) { |
|
cache.delete(id); |
|
subflow(k).rem(t); |
|
} |
|
}); |
|
pulse.visit(pulse.ADD, function (t) { |
|
var k = key(t); |
|
cache.set(tupleid(t), k); |
|
subflow(k).add(t); |
|
}); |
|
|
|
if (rekey || pulse.modified(key.fields)) { |
|
pulse.visit(pulse.MOD, function (t) { |
|
var id = tupleid(t), |
|
k0 = cache.get(id), |
|
k1 = key(t); |
|
|
|
if (k0 === k1) { |
|
subflow(k1).mod(t); |
|
} else { |
|
cache.set(id, k1); |
|
subflow(k0).rem(t); |
|
subflow(k1).add(t); |
|
} |
|
}); |
|
} else if (pulse.changed(pulse.MOD)) { |
|
pulse.visit(pulse.MOD, function (t) { |
|
subflow(cache.get(tupleid(t))).mod(t); |
|
}); |
|
} |
|
|
|
if (rekey) { |
|
pulse.visit(pulse.REFLOW, function (t) { |
|
var id = tupleid(t), |
|
k0 = cache.get(id), |
|
k1 = key(t); |
|
|
|
if (k0 !== k1) { |
|
cache.set(id, k1); |
|
subflow(k0).rem(t); |
|
subflow(k1).add(t); |
|
} |
|
}); |
|
} |
|
|
|
if (cache.empty > df.cleanThreshold) df.runAfter(cache.clean); |
|
return pulse; |
|
}; |
|
/** |
|
* Generates one or more field accessor functions. |
|
* If the 'name' parameter is an array, an array of field accessors |
|
* will be created and the 'as' parameter will be ignored. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {string} params.name - The field name(s) to access. |
|
* @param {string} params.as - The accessor function name. |
|
*/ |
|
|
|
|
|
function Field(params) { |
|
Operator.call(this, null, update$3, params); |
|
} |
|
|
|
inherits(Field, Operator); |
|
|
|
function update$3(_) { |
|
return this.value && !_.modified() ? this.value : isArray(_.name) ? array(_.name).map(function (f) { |
|
return field(f); |
|
}) : field(_.name, _.as); |
|
} |
|
/** |
|
* Filters data tuples according to a predicate function. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.expr - The predicate expression function |
|
* that determines a tuple's filter status. Truthy values pass the filter. |
|
*/ |
|
|
|
|
|
function Filter(params) { |
|
Transform.call(this, fastmap(), params); |
|
} |
|
|
|
Filter.Definition = { |
|
"type": "Filter", |
|
"metadata": { |
|
"changes": true |
|
}, |
|
"params": [{ |
|
"name": "expr", |
|
"type": "expr", |
|
"required": true |
|
}] |
|
}; |
|
var prototype$i = inherits(Filter, Transform); |
|
|
|
prototype$i.transform = function (_, pulse) { |
|
var df = pulse.dataflow, |
|
cache = this.value, |
|
// cache ids of filtered tuples |
|
output = pulse.fork(), |
|
add = output.add, |
|
rem = output.rem, |
|
mod = output.mod, |
|
test = _.expr, |
|
isMod = true; |
|
pulse.visit(pulse.REM, function (t) { |
|
var id = tupleid(t); |
|
if (!cache.has(id)) rem.push(t);else cache.delete(id); |
|
}); |
|
pulse.visit(pulse.ADD, function (t) { |
|
if (test(t, _)) add.push(t);else cache.set(tupleid(t), 1); |
|
}); |
|
|
|
function revisit(t) { |
|
var id = tupleid(t), |
|
b = test(t, _), |
|
s = cache.get(id); |
|
|
|
if (b && s) { |
|
cache.delete(id); |
|
add.push(t); |
|
} else if (!b && !s) { |
|
cache.set(id, 1); |
|
rem.push(t); |
|
} else if (isMod && b && !s) { |
|
mod.push(t); |
|
} |
|
} |
|
|
|
pulse.visit(pulse.MOD, revisit); |
|
|
|
if (_.modified()) { |
|
isMod = false; |
|
pulse.visit(pulse.REFLOW, revisit); |
|
} |
|
|
|
if (cache.empty > df.cleanThreshold) df.runAfter(cache.clean); |
|
return output; |
|
}; |
|
/** |
|
* Flattens array-typed field values into new data objects. |
|
* If multiple fields are specified, they are treated as parallel arrays, |
|
* with output values included for each matching index (or null if missing). |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<function(object): *>} params.fields - An array of field |
|
* accessors for the tuple fields that should be flattened. |
|
* @param {string} [params.index] - Optional output field name for index |
|
* value. If unspecified, no index field is included in the output. |
|
* @param {Array<string>} [params.as] - Output field names for flattened |
|
* array fields. Any unspecified fields will use the field name provided |
|
* by the fields accessors. |
|
*/ |
|
|
|
|
|
function Flatten(params) { |
|
Transform.call(this, [], params); |
|
} |
|
|
|
Flatten.Definition = { |
|
"type": "Flatten", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "fields", |
|
"type": "field", |
|
"array": true, |
|
"required": true |
|
}, { |
|
"name": "index", |
|
"type": "string" |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true |
|
}] |
|
}; |
|
var prototype$j = inherits(Flatten, Transform); |
|
|
|
prototype$j.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.NO_SOURCE), |
|
fields = _.fields, |
|
as = fieldNames(fields, _.as || []), |
|
index = _.index || null, |
|
m = as.length; // remove any previous results |
|
|
|
out.rem = this.value; // generate flattened tuples |
|
|
|
pulse.visit(pulse.SOURCE, function (t) { |
|
var arrays = fields.map(function (f) { |
|
return f(t); |
|
}), |
|
maxlen = arrays.reduce(function (l, a) { |
|
return Math.max(l, a.length); |
|
}, 0), |
|
i = 0, |
|
j, |
|
d, |
|
v; |
|
|
|
for (; i < maxlen; ++i) { |
|
d = derive(t); |
|
|
|
for (j = 0; j < m; ++j) { |
|
d[as[j]] = (v = arrays[j][i]) == null ? null : v; |
|
} |
|
|
|
if (index) { |
|
d[index] = i; |
|
} |
|
|
|
out.add.push(d); |
|
} |
|
}); |
|
this.value = out.source = out.add; |
|
if (index) out.modifies(index); |
|
return out.modifies(as); |
|
}; |
|
/** |
|
* Folds one more tuple fields into multiple tuples in which the field |
|
* name and values are available under new 'key' and 'value' fields. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.fields - An array of field accessors |
|
* for the tuple fields that should be folded. |
|
* @param {Array<string>} [params.as] - Output field names for folded key |
|
* and value fields, defaults to ['key', 'value']. |
|
*/ |
|
|
|
|
|
function Fold(params) { |
|
Transform.call(this, [], params); |
|
} |
|
|
|
Fold.Definition = { |
|
"type": "Fold", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "fields", |
|
"type": "field", |
|
"array": true, |
|
"required": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": 2, |
|
"default": ["key", "value"] |
|
}] |
|
}; |
|
var prototype$k = inherits(Fold, Transform); |
|
|
|
prototype$k.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.NO_SOURCE), |
|
fields = _.fields, |
|
fnames = fields.map(accessorName), |
|
as = _.as || ['key', 'value'], |
|
k = as[0], |
|
v = as[1], |
|
n = fields.length; |
|
out.rem = this.value; |
|
pulse.visit(pulse.SOURCE, function (t) { |
|
for (var i = 0, d; i < n; ++i) { |
|
d = derive(t); |
|
d[k] = fnames[i]; |
|
d[v] = fields[i](t); |
|
out.add.push(d); |
|
} |
|
}); |
|
this.value = out.source = out.add; |
|
return out.modifies(as); |
|
}; |
|
/** |
|
* Invokes a function for each data tuple and saves the results as a new field. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.expr - The formula function to invoke for each tuple. |
|
* @param {string} params.as - The field name under which to save the result. |
|
* @param {boolean} [params.initonly=false] - If true, the formula is applied to |
|
* added tuples only, and does not update in response to modifications. |
|
*/ |
|
|
|
|
|
function Formula(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Formula.Definition = { |
|
"type": "Formula", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "expr", |
|
"type": "expr", |
|
"required": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"required": true |
|
}, { |
|
"name": "initonly", |
|
"type": "boolean" |
|
}] |
|
}; |
|
var prototype$l = inherits(Formula, Transform); |
|
|
|
prototype$l.transform = function (_, pulse) { |
|
var func = _.expr, |
|
as = _.as, |
|
mod = _.modified(), |
|
flag = _.initonly ? pulse.ADD : mod ? pulse.SOURCE : pulse.modified(func.fields) || pulse.modified(as) ? pulse.ADD_MOD : pulse.ADD; |
|
|
|
if (mod) { |
|
// parameters updated, need to reflow |
|
pulse = pulse.materialize().reflow(true); |
|
} |
|
|
|
if (!_.initonly) { |
|
pulse.modifies(as); |
|
} |
|
|
|
return pulse.visit(flag, function (t) { |
|
return t[as] = func(t, _); |
|
}); |
|
}; |
|
/** |
|
* Generates data tuples using a provided generator function. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(Parameters): object} params.generator - A tuple generator |
|
* function. This function is given the operator parameters as input. |
|
* Changes to any additional parameters will not trigger re-calculation |
|
* of previously generated tuples. Only future tuples are affected. |
|
* @param {number} params.size - The number of tuples to produce. |
|
*/ |
|
|
|
|
|
function Generate(params) { |
|
Transform.call(this, [], params); |
|
} |
|
|
|
var prototype$m = inherits(Generate, Transform); |
|
|
|
prototype$m.transform = function (_, pulse) { |
|
var data = this.value, |
|
out = pulse.fork(pulse.ALL), |
|
num = _.size - data.length, |
|
gen = _.generator, |
|
add, |
|
rem, |
|
t; |
|
|
|
if (num > 0) { |
|
// need more tuples, generate and add |
|
for (add = []; --num >= 0;) { |
|
add.push(t = ingest(gen(_))); |
|
data.push(t); |
|
} |
|
|
|
out.add = out.add.length ? out.materialize(out.ADD).add.concat(add) : add; |
|
} else { |
|
// need fewer tuples, remove |
|
rem = data.slice(0, -num); |
|
out.rem = out.rem.length ? out.materialize(out.REM).rem.concat(rem) : rem; |
|
data = data.slice(-num); |
|
} |
|
|
|
out.source = this.value = data; |
|
return out; |
|
}; |
|
|
|
var Methods = { |
|
value: 'value', |
|
median: median, |
|
mean: mean, |
|
min: min, |
|
max: max |
|
}; |
|
var Empty = []; |
|
/** |
|
* Impute missing values. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The value field to impute. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of |
|
* accessors to determine series within which to perform imputation. |
|
* @param {function(object): *} params.key - An accessor for a key value. |
|
* Each key value should be unique within a group. New tuples will be |
|
* imputed for any key values that are not found within a group. |
|
* @param {Array<*>} [params.keyvals] - Optional array of required key |
|
* values. New tuples will be imputed for any key values that are not |
|
* found within a group. In addition, these values will be automatically |
|
* augmented with the key values observed in the input data. |
|
* @param {string} [method='value'] - The imputation method to use. One of |
|
* 'value', 'mean', 'median', 'max', 'min'. |
|
* @param {*} [value=0] - The constant value to use for imputation |
|
* when using method 'value'. |
|
*/ |
|
|
|
function Impute(params) { |
|
Transform.call(this, [], params); |
|
} |
|
|
|
Impute.Definition = { |
|
"type": "Impute", |
|
"metadata": { |
|
"changes": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "key", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "keyvals", |
|
"array": true |
|
}, { |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "method", |
|
"type": "enum", |
|
"default": "value", |
|
"values": ["value", "mean", "median", "max", "min"] |
|
}, { |
|
"name": "value", |
|
"default": 0 |
|
}] |
|
}; |
|
var prototype$n = inherits(Impute, Transform); |
|
|
|
function getValue(_) { |
|
var m = _.method || Methods.value, |
|
v; |
|
|
|
if (Methods[m] == null) { |
|
error('Unrecognized imputation method: ' + m); |
|
} else if (m === Methods.value) { |
|
v = _.value !== undefined ? _.value : 0; |
|
return function () { |
|
return v; |
|
}; |
|
} else { |
|
return Methods[m]; |
|
} |
|
} |
|
|
|
function getField(_) { |
|
var f = _.field; |
|
return function (t) { |
|
return t ? f(t) : NaN; |
|
}; |
|
} |
|
|
|
prototype$n.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.ALL), |
|
impute = getValue(_), |
|
field = getField(_), |
|
fName = accessorName(_.field), |
|
kName = accessorName(_.key), |
|
gNames = (_.groupby || []).map(accessorName), |
|
groups = partition$1(pulse.source, _.groupby, _.key, _.keyvals), |
|
curr = [], |
|
prev = this.value, |
|
m = groups.domain.length, |
|
group, |
|
value, |
|
gVals, |
|
kVal, |
|
g, |
|
i, |
|
j, |
|
l, |
|
n, |
|
t; |
|
|
|
for (g = 0, l = groups.length; g < l; ++g) { |
|
group = groups[g]; |
|
gVals = group.values; |
|
value = NaN; // add tuples for missing values |
|
|
|
for (j = 0; j < m; ++j) { |
|
if (group[j] != null) continue; |
|
kVal = groups.domain[j]; |
|
t = { |
|
_impute: true |
|
}; |
|
|
|
for (i = 0, n = gVals.length; i < n; ++i) { |
|
t[gNames[i]] = gVals[i]; |
|
} |
|
|
|
t[kName] = kVal; |
|
t[fName] = Number.isNaN(value) ? value = impute(group, field) : value; |
|
curr.push(ingest(t)); |
|
} |
|
} // update pulse with imputed tuples |
|
|
|
|
|
if (curr.length) out.add = out.materialize(out.ADD).add.concat(curr); |
|
if (prev.length) out.rem = out.materialize(out.REM).rem.concat(prev); |
|
this.value = curr; |
|
return out; |
|
}; |
|
|
|
function partition$1(data, groupby, key, keyvals) { |
|
var get = function get(f) { |
|
return f(t); |
|
}, |
|
groups = [], |
|
domain = keyvals ? keyvals.slice() : [], |
|
kMap = {}, |
|
gMap = {}, |
|
gVals, |
|
gKey, |
|
group, |
|
i, |
|
j, |
|
k, |
|
n, |
|
t; |
|
|
|
domain.forEach(function (k, i) { |
|
kMap[k] = i + 1; |
|
}); |
|
|
|
for (i = 0, n = data.length; i < n; ++i) { |
|
t = data[i]; |
|
k = key(t); |
|
j = kMap[k] || (kMap[k] = domain.push(k)); |
|
gKey = (gVals = groupby ? groupby.map(get) : Empty) + ''; |
|
|
|
if (!(group = gMap[gKey])) { |
|
group = gMap[gKey] = []; |
|
groups.push(group); |
|
group.values = gVals; |
|
} |
|
|
|
group[j - 1] = t; |
|
} |
|
|
|
groups.domain = domain; |
|
return groups; |
|
} |
|
/** |
|
* Extend input tuples with aggregate values. |
|
* Calcuates aggregate values and joins them with the input stream. |
|
* @constructor |
|
*/ |
|
|
|
|
|
function JoinAggregate(params) { |
|
Aggregate.call(this, params); |
|
} |
|
|
|
JoinAggregate.Definition = { |
|
"type": "JoinAggregate", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "fields", |
|
"type": "field", |
|
"null": true, |
|
"array": true |
|
}, { |
|
"name": "ops", |
|
"type": "enum", |
|
"array": true, |
|
"values": ValidAggregateOps |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"null": true, |
|
"array": true |
|
}, { |
|
"name": "key", |
|
"type": "field" |
|
}] |
|
}; |
|
var prototype$o = inherits(JoinAggregate, Aggregate); |
|
|
|
prototype$o.transform = function (_, pulse) { |
|
var aggr = this, |
|
mod = _.modified(), |
|
cells; // process all input tuples to calculate aggregates |
|
|
|
|
|
if (aggr.value && (mod || pulse.modified(aggr._inputs, true))) { |
|
cells = aggr.value = mod ? aggr.init(_) : {}; |
|
pulse.visit(pulse.SOURCE, function (t) { |
|
aggr.add(t); |
|
}); |
|
} else { |
|
cells = aggr.value = aggr.value || this.init(_); |
|
pulse.visit(pulse.REM, function (t) { |
|
aggr.rem(t); |
|
}); |
|
pulse.visit(pulse.ADD, function (t) { |
|
aggr.add(t); |
|
}); |
|
} // update aggregation cells |
|
|
|
|
|
aggr.changes(); // write aggregate values to input tuples |
|
|
|
pulse.visit(pulse.SOURCE, function (t) { |
|
extend(t, cells[aggr.cellkey(t)].tuple); |
|
}); |
|
return pulse.reflow(mod).modifies(this._outputs); |
|
}; |
|
|
|
prototype$o.changes = function () { |
|
var adds = this._adds, |
|
mods = this._mods, |
|
i, |
|
n; |
|
|
|
for (i = 0, n = this._alen; i < n; ++i) { |
|
this.celltuple(adds[i]); |
|
adds[i] = null; // for garbage collection |
|
} |
|
|
|
for (i = 0, n = this._mlen; i < n; ++i) { |
|
this.celltuple(mods[i]); |
|
mods[i] = null; // for garbage collection |
|
} |
|
|
|
this._alen = this._mlen = 0; // reset list of active cells |
|
}; |
|
/** |
|
* Compute kernel density estimates (KDE) for one or more data groups. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors |
|
* to groupby. |
|
* @param {function(object): *} params.field - An accessor for the data field |
|
* to estimate. |
|
* @param {number} [params.bandwidth=0] - The KDE kernel bandwidth. |
|
* If zero or unspecified, the bandwidth is automatically determined. |
|
* @param {boolean} [params.counts=false] - A boolean flag indicating if the |
|
* output values should be probability estimates (false, default) or |
|
* smoothed counts (true). |
|
* @param {string} [params.cumulative=false] - A boolean flag indicating if a |
|
* density (false) or cumulative distribution (true) should be generated. |
|
* @param {Array<number>} [params.extent] - The domain extent over which to |
|
* plot the density. If unspecified, the [min, max] data extent is used. |
|
* @param {string} [params.resolve='independent'] - Indicates how parameters for |
|
* multiple densities should be resolved. If "independent" (the default), each |
|
* density may have its own domain extent and dynamic number of curve sample |
|
* steps. If "shared", the KDE transform will ensure that all densities are |
|
* defined over a shared domain and curve steps, enabling stacking. |
|
* @param {number} [params.minsteps=25] - The minimum number of curve samples |
|
* for plotting the density. |
|
* @param {number} [params.maxsteps=200] - The maximum number of curve samples |
|
* for plotting the density. |
|
* @param {number} [params.steps] - The exact number of curve samples for |
|
* plotting the density. If specified, overrides both minsteps and maxsteps |
|
* to set an exact number of uniform samples. Useful in conjunction with |
|
* a fixed extent to ensure consistent sample points for stacked densities. |
|
*/ |
|
|
|
|
|
function KDE(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
KDE.Definition = { |
|
"type": "KDE", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "cumulative", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "counts", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "bandwidth", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "extent", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "resolve", |
|
"type": "enum", |
|
"values": ["shared", "independent"], |
|
"default": "independent" |
|
}, { |
|
"name": "steps", |
|
"type": "number" |
|
}, { |
|
"name": "minsteps", |
|
"type": "number", |
|
"default": 25 |
|
}, { |
|
"name": "maxsteps", |
|
"type": "number", |
|
"default": 200 |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"default": ["value", "density"] |
|
}] |
|
}; |
|
var prototype$p = inherits(KDE, Transform); |
|
|
|
prototype$p.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS); |
|
|
|
if (!this.value || pulse.changed() || _.modified()) { |
|
var _source = pulse.materialize(pulse.SOURCE).source, |
|
groups = partition(_source, _.groupby, _.field), |
|
names = (_.groupby || []).map(accessorName), |
|
_bandwidth = _.bandwidth, |
|
_method = _.cumulative ? 'cdf' : 'pdf', |
|
as = _.as || ['value', 'density'], |
|
_values = []; |
|
|
|
var _domain = _.extent, |
|
minsteps = _.steps || _.minsteps || 25, |
|
maxsteps = _.steps || _.maxsteps || 200; |
|
|
|
if (_method !== 'pdf' && _method !== 'cdf') { |
|
error('Invalid density method: ' + _method); |
|
} |
|
|
|
if (_.resolve === 'shared') { |
|
if (!_domain) _domain = extent(_source, _.field); |
|
minsteps = maxsteps = _.steps || maxsteps; |
|
} |
|
|
|
groups.forEach(function (g) { |
|
var density = randomKDE(g, _bandwidth)[_method], |
|
scale = _.counts ? g.length : 1, |
|
local = _domain || extent(g); |
|
|
|
sampleCurve(density, local, minsteps, maxsteps).forEach(function (v) { |
|
var t = {}; |
|
|
|
for (var i = 0; i < names.length; ++i) { |
|
t[names[i]] = g.dims[i]; |
|
} |
|
|
|
t[as[0]] = v[0]; |
|
t[as[1]] = v[1] * scale; |
|
|
|
_values.push(ingest(t)); |
|
}); |
|
}); |
|
if (this.value) out.rem = this.value; |
|
this.value = out.add = out.source = _values; |
|
} |
|
|
|
return out; |
|
}; |
|
/** |
|
* Generates a key function. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<string>} params.fields - The field name(s) for the key function. |
|
* @param {boolean} params.flat - A boolean flag indicating if the field names |
|
* should be treated as flat property names, side-stepping nested field |
|
* lookups normally indicated by dot or bracket notation. |
|
*/ |
|
|
|
|
|
function Key(params) { |
|
Operator.call(this, null, update$4, params); |
|
} |
|
|
|
inherits(Key, Operator); |
|
|
|
function update$4(_) { |
|
return this.value && !_.modified() ? this.value : key(_.fields, _.flat); |
|
} |
|
/** |
|
* Load and parse data from an external source. Marshalls parameter |
|
* values and then invokes the Dataflow request method. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {string} params.url - The URL to load from. |
|
* @param {object} params.format - The data format options. |
|
*/ |
|
|
|
|
|
function Load(params) { |
|
Transform.call(this, [], params); |
|
this._pending = null; |
|
} |
|
|
|
var prototype$q = inherits(Load, Transform); |
|
|
|
prototype$q.transform = function (_, pulse) { |
|
var _this2 = this; |
|
|
|
var df = pulse.dataflow; |
|
|
|
if (this._pending) { |
|
// update state and return pulse |
|
return output$1(this, pulse, this._pending); |
|
} |
|
|
|
if (stop(_)) return pulse.StopPropagation; |
|
|
|
if (_.values) { |
|
// parse and ingest values, return output pulse |
|
return output$1(this, pulse, df.parse(_.values, _.format)); |
|
} else if (_.async) { |
|
// return promise for non-blocking async loading |
|
var p = df.request(_.url, _.format).then(function (res) { |
|
_this2._pending = array(res.data); |
|
return function (df) { |
|
return df.touch(_this2); |
|
}; |
|
}); |
|
return { |
|
async: p |
|
}; |
|
} else { |
|
// return promise for synchronous loading |
|
return df.request(_.url, _.format).then(function (res) { |
|
return output$1(_this2, pulse, array(res.data)); |
|
}); |
|
} |
|
}; |
|
|
|
function stop(_) { |
|
return _.modified('async') && !(_.modified('values') || _.modified('url') || _.modified('format')); |
|
} |
|
|
|
function output$1(op, pulse, data) { |
|
data.forEach(ingest); |
|
var out = pulse.fork(pulse.NO_FIELDS & pulse.NO_SOURCE); |
|
out.rem = op.value; |
|
op.value = out.source = out.add = data; |
|
op._pending = null; |
|
return out; |
|
} |
|
/** |
|
* Extend tuples by joining them with values from a lookup table. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Map} params.index - The lookup table map. |
|
* @param {Array<function(object): *} params.fields - The fields to lookup. |
|
* @param {Array<string>} params.as - Output field names for each lookup value. |
|
* @param {*} [params.default] - A default value to use if lookup fails. |
|
*/ |
|
|
|
|
|
function Lookup(params) { |
|
Transform.call(this, {}, params); |
|
} |
|
|
|
Lookup.Definition = { |
|
"type": "Lookup", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "index", |
|
"type": "index", |
|
"params": [{ |
|
"name": "from", |
|
"type": "data", |
|
"required": true |
|
}, { |
|
"name": "key", |
|
"type": "field", |
|
"required": true |
|
}] |
|
}, { |
|
"name": "values", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "fields", |
|
"type": "field", |
|
"array": true, |
|
"required": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true |
|
}, { |
|
"name": "default", |
|
"default": null |
|
}] |
|
}; |
|
var prototype$r = inherits(Lookup, Transform); |
|
|
|
prototype$r.transform = function (_, pulse) { |
|
var out = pulse, |
|
as = _.as, |
|
keys = _.fields, |
|
index = _.index, |
|
values = _.values, |
|
defaultValue = _.default == null ? null : _.default, |
|
reset = _.modified(), |
|
flag = reset ? pulse.SOURCE : pulse.ADD, |
|
n = keys.length, |
|
set, |
|
m, |
|
mods; |
|
|
|
if (values) { |
|
m = values.length; |
|
|
|
if (n > 1 && !as) { |
|
error('Multi-field lookup requires explicit "as" parameter.'); |
|
} |
|
|
|
if (as && as.length !== n * m) { |
|
error('The "as" parameter has too few output field names.'); |
|
} |
|
|
|
as = as || values.map(accessorName); |
|
|
|
set = function set(t) { |
|
for (var i = 0, k = 0, j, v; i < n; ++i) { |
|
v = index.get(keys[i](t)); |
|
if (v == null) for (j = 0; j < m; ++j, ++k) { |
|
t[as[k]] = defaultValue; |
|
} else for (j = 0; j < m; ++j, ++k) { |
|
t[as[k]] = values[j](v); |
|
} |
|
} |
|
}; |
|
} else { |
|
if (!as) { |
|
error('Missing output field names.'); |
|
} |
|
|
|
set = function set(t) { |
|
for (var i = 0, v; i < n; ++i) { |
|
v = index.get(keys[i](t)); |
|
t[as[i]] = v == null ? defaultValue : v; |
|
} |
|
}; |
|
} |
|
|
|
if (reset) { |
|
out = pulse.reflow(true); |
|
} else { |
|
mods = keys.some(function (k) { |
|
return pulse.modified(k.fields); |
|
}); |
|
flag |= mods ? pulse.MOD : 0; |
|
} |
|
|
|
pulse.visit(flag, set); |
|
return out.modifies(as); |
|
}; |
|
/** |
|
* Computes global min/max extents over a collection of extents. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<Array<number>>} params.extents - The input extents. |
|
*/ |
|
|
|
|
|
function MultiExtent(params) { |
|
Operator.call(this, null, update$5, params); |
|
} |
|
|
|
inherits(MultiExtent, Operator); |
|
|
|
function update$5(_) { |
|
if (this.value && !_.modified()) { |
|
return this.value; |
|
} |
|
|
|
var min = +Infinity, |
|
max = -Infinity, |
|
ext = _.extents, |
|
i, |
|
n, |
|
e; |
|
|
|
for (i = 0, n = ext.length; i < n; ++i) { |
|
e = ext[i]; |
|
if (e[0] < min) min = e[0]; |
|
if (e[1] > max) max = e[1]; |
|
} |
|
|
|
return [min, max]; |
|
} |
|
/** |
|
* Merge a collection of value arrays. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<Array<*>>} params.values - The input value arrrays. |
|
*/ |
|
|
|
|
|
function MultiValues(params) { |
|
Operator.call(this, null, update$6, params); |
|
} |
|
|
|
inherits(MultiValues, Operator); |
|
|
|
function update$6(_) { |
|
return this.value && !_.modified() ? this.value : _.values.reduce(function (data, _) { |
|
return data.concat(_); |
|
}, []); |
|
} |
|
/** |
|
* Operator whose value is simply its parameter hash. This operator is |
|
* useful for enabling reactive updates to values of nested objects. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
*/ |
|
|
|
|
|
function Params(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
inherits(Params, Transform); |
|
|
|
Params.prototype.transform = function (_, pulse) { |
|
this.modified(_.modified()); |
|
this.value = _; |
|
return pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS); // do not pass tuples |
|
}; |
|
/** |
|
* Aggregate and pivot selected field values to become new fields. |
|
* This operator is useful to construction cross-tabulations. |
|
* @constructor |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors |
|
* to groupby. These fields act just like groupby fields of an Aggregate transform. |
|
* @param {function(object): *} params.field - The field to pivot on. The unique |
|
* values of this field become new field names in the output stream. |
|
* @param {function(object): *} params.value - The field to populate pivoted fields. |
|
* The aggregate values of this field become the values of the new pivoted fields. |
|
* @param {string} [params.op] - The aggregation operation for the value field, |
|
* applied per cell in the output stream. The default is "sum". |
|
* @param {number} [params.limit] - An optional parameter indicating the maximum |
|
* number of pivoted fields to generate. The pivoted field names are sorted in |
|
* ascending order prior to enforcing the limit. |
|
*/ |
|
|
|
|
|
function Pivot(params) { |
|
Aggregate.call(this, params); |
|
} |
|
|
|
Pivot.Definition = { |
|
"type": "Pivot", |
|
"metadata": { |
|
"generates": true, |
|
"changes": true |
|
}, |
|
"params": [{ |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "value", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "op", |
|
"type": "enum", |
|
"values": ValidAggregateOps, |
|
"default": "sum" |
|
}, { |
|
"name": "limit", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "key", |
|
"type": "field" |
|
}] |
|
}; |
|
var prototype$s = inherits(Pivot, Aggregate); |
|
prototype$s._transform = prototype$s.transform; |
|
|
|
prototype$s.transform = function (_, pulse) { |
|
return this._transform(aggregateParams(_, pulse), pulse); |
|
}; // Shoehorn a pivot transform into an aggregate transform! |
|
// First collect all unique pivot field values. |
|
// Then generate aggregate fields for each output pivot field. |
|
|
|
|
|
function aggregateParams(_, pulse) { |
|
var key = _.field, |
|
value = _.value, |
|
op = (_.op === 'count' ? '__count__' : _.op) || 'sum', |
|
fields = accessorFields(key).concat(accessorFields(value)), |
|
keys = pivotKeys(key, _.limit || 0, pulse); // if data stream content changes, pivot fields may change |
|
// flag parameter modification to ensure re-initialization |
|
|
|
if (pulse.changed()) _.set('__pivot__', null, null, true); |
|
return { |
|
key: _.key, |
|
groupby: _.groupby, |
|
ops: keys.map(function () { |
|
return op; |
|
}), |
|
fields: keys.map(function (k) { |
|
return get(k, key, value, fields); |
|
}), |
|
as: keys.map(function (k) { |
|
return k + ''; |
|
}), |
|
modified: _.modified.bind(_) |
|
}; |
|
} // Generate aggregate field accessor. |
|
// Output NaN for non-existent values; aggregator will ignore! |
|
|
|
|
|
function get(k, key, value, fields) { |
|
return accessor(function (d) { |
|
return key(d) === k ? value(d) : NaN; |
|
}, fields, k + ''); |
|
} // Collect (and optionally limit) all unique pivot values. |
|
|
|
|
|
function pivotKeys(key, limit, pulse) { |
|
var map = {}, |
|
list = []; |
|
pulse.visit(pulse.SOURCE, function (t) { |
|
var k = key(t); |
|
|
|
if (!map[k]) { |
|
map[k] = 1; |
|
list.push(k); |
|
} |
|
}); // TODO? Move this comparator to vega-util? |
|
|
|
list.sort(function (u, v) { |
|
return (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; |
|
}); |
|
return limit ? list.slice(0, limit) : list; |
|
} |
|
/** |
|
* Partitions pre-faceted data into tuple subflows. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(Dataflow, string): Operator} params.subflow - A function |
|
* that generates a subflow of operators and returns its root operator. |
|
* @param {function(object): Array<object>} params.field - The field |
|
* accessor for an array of subflow tuple objects. |
|
*/ |
|
|
|
|
|
function PreFacet(params) { |
|
Facet.call(this, params); |
|
} |
|
|
|
var prototype$t = inherits(PreFacet, Facet); |
|
|
|
prototype$t.transform = function (_, pulse) { |
|
var self = this, |
|
flow = _.subflow, |
|
field = _.field; |
|
|
|
if (_.modified('field') || field && pulse.modified(accessorFields(field))) { |
|
error('PreFacet does not support field modification.'); |
|
} |
|
|
|
this._targets.active = 0; // reset list of active subflows |
|
|
|
pulse.visit(pulse.MOD, function (t) { |
|
var sf = self.subflow(tupleid(t), flow, pulse, t); |
|
field ? field(t).forEach(function (_) { |
|
sf.mod(_); |
|
}) : sf.mod(t); |
|
}); |
|
pulse.visit(pulse.ADD, function (t) { |
|
var sf = self.subflow(tupleid(t), flow, pulse, t); |
|
field ? field(t).forEach(function (_) { |
|
sf.add(ingest(_)); |
|
}) : sf.add(t); |
|
}); |
|
pulse.visit(pulse.REM, function (t) { |
|
var sf = self.subflow(tupleid(t), flow, pulse, t); |
|
field ? field(t).forEach(function (_) { |
|
sf.rem(_); |
|
}) : sf.rem(t); |
|
}); |
|
return pulse; |
|
}; |
|
/** |
|
* Performs a relational projection, copying selected fields from source |
|
* tuples to a new set of derived tuples. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<function(object): *} params.fields - The fields to project, |
|
* as an array of field accessors. If unspecified, all fields will be |
|
* copied with names unchanged. |
|
* @param {Array<string>} [params.as] - Output field names for each projected |
|
* field. Any unspecified fields will use the field name provided by |
|
* the field accessor. |
|
*/ |
|
|
|
|
|
function Project(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Project.Definition = { |
|
"type": "Project", |
|
"metadata": { |
|
"generates": true, |
|
"changes": true |
|
}, |
|
"params": [{ |
|
"name": "fields", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"null": true, |
|
"array": true |
|
}] |
|
}; |
|
var prototype$u = inherits(Project, Transform); |
|
|
|
prototype$u.transform = function (_, pulse) { |
|
var fields = _.fields, |
|
as = fieldNames(_.fields, _.as || []), |
|
derive = fields ? function (s, t) { |
|
return project(s, t, fields, as); |
|
} : rederive, |
|
out, |
|
lut; |
|
|
|
if (this.value) { |
|
lut = this.value; |
|
} else { |
|
pulse = pulse.addAll(); |
|
lut = this.value = {}; |
|
} |
|
|
|
out = pulse.fork(pulse.NO_SOURCE); |
|
pulse.visit(pulse.REM, function (t) { |
|
var id = tupleid(t); |
|
out.rem.push(lut[id]); |
|
lut[id] = null; |
|
}); |
|
pulse.visit(pulse.ADD, function (t) { |
|
var dt = derive(t, ingest({})); |
|
lut[tupleid(t)] = dt; |
|
out.add.push(dt); |
|
}); |
|
pulse.visit(pulse.MOD, function (t) { |
|
out.mod.push(derive(t, lut[tupleid(t)])); |
|
}); |
|
return out; |
|
}; |
|
|
|
function project(s, t, fields, as) { |
|
for (var i = 0, n = fields.length; i < n; ++i) { |
|
t[as[i]] = fields[i](s); |
|
} |
|
|
|
return t; |
|
} |
|
/** |
|
* Proxy the value of another operator as a pure signal value. |
|
* Ensures no tuples are propagated. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {*} params.value - The value to proxy, becomes the value of this operator. |
|
*/ |
|
|
|
|
|
function Proxy(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$v = inherits(Proxy, Transform); |
|
|
|
prototype$v.transform = function (_, pulse) { |
|
this.value = _.value; |
|
return _.modified('value') ? pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS) : pulse.StopPropagation; |
|
}; |
|
/** |
|
* Generates sample quantile values from an input data stream. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - An accessor for the data field |
|
* over which to calculate quantile values. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors |
|
* to groupby. |
|
* @param {Array<number>} [params.probs] - An array of probabilities in |
|
* the range (0, 1) for which to compute quantile values. If not specified, |
|
* the *step* parameter will be used. |
|
* @param {Array<number>} [params.step=0.01] - A probability step size for |
|
* sampling quantile values. All values from one-half the step size up to |
|
* 1 (exclusive) will be sampled. This parameter is only used if the |
|
* *quantiles* parameter is not provided. |
|
*/ |
|
|
|
|
|
function Quantile(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Quantile.Definition = { |
|
"type": "Quantile", |
|
"metadata": { |
|
"generates": true, |
|
"changes": true |
|
}, |
|
"params": [{ |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "probs", |
|
"type": "number", |
|
"array": true |
|
}, { |
|
"name": "step", |
|
"type": "number", |
|
"default": 0.01 |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"default": ["prob", "value"] |
|
}] |
|
}; |
|
var prototype$w = inherits(Quantile, Transform); |
|
var EPSILON$1 = 1e-14; |
|
|
|
prototype$w.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS), |
|
as = _.as || ['prob', 'value']; |
|
|
|
if (this.value && !_.modified() && !pulse.changed()) { |
|
out.source = this.value; |
|
return out; |
|
} |
|
|
|
var source = pulse.materialize(pulse.SOURCE).source, |
|
groups = partition(source, _.groupby, _.field), |
|
names = (_.groupby || []).map(accessorName), |
|
values = [], |
|
step = _.step || 0.01, |
|
p = _.probs || sequence(step / 2, 1 - EPSILON$1, step), |
|
n = p.length; |
|
groups.forEach(function (g) { |
|
var q = quantiles(g, p); |
|
|
|
for (var i = 0; i < n; ++i) { |
|
var t = {}; |
|
|
|
for (var _i4 = 0; _i4 < names.length; ++_i4) { |
|
t[names[_i4]] = g.dims[_i4]; |
|
} |
|
|
|
t[as[0]] = p[i]; |
|
t[as[1]] = q[i]; |
|
values.push(ingest(t)); |
|
} |
|
}); |
|
if (this.value) out.rem = this.value; |
|
this.value = out.add = out.source = values; |
|
return out; |
|
}; |
|
/** |
|
* Relays a data stream between data processing pipelines. |
|
* If the derive parameter is set, this transform will create derived |
|
* copies of observed tuples. This provides derived data streams in which |
|
* modifications to the tuples do not pollute an upstream data source. |
|
* @param {object} params - The parameters for this operator. |
|
* @param {number} [params.derive=false] - Boolean flag indicating if |
|
* the transform should make derived copies of incoming tuples. |
|
* @constructor |
|
*/ |
|
|
|
|
|
function Relay(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$x = inherits(Relay, Transform); |
|
|
|
prototype$x.transform = function (_, pulse) { |
|
var out, lut; |
|
|
|
if (this.value) { |
|
lut = this.value; |
|
} else { |
|
out = pulse = pulse.addAll(); |
|
lut = this.value = {}; |
|
} |
|
|
|
if (_.derive) { |
|
out = pulse.fork(pulse.NO_SOURCE); |
|
pulse.visit(pulse.REM, function (t) { |
|
var id = tupleid(t); |
|
out.rem.push(lut[id]); |
|
lut[id] = null; |
|
}); |
|
pulse.visit(pulse.ADD, function (t) { |
|
var dt = derive(t); |
|
lut[tupleid(t)] = dt; |
|
out.add.push(dt); |
|
}); |
|
pulse.visit(pulse.MOD, function (t) { |
|
var dt = lut[tupleid(t)], |
|
k; |
|
|
|
for (k in t) { |
|
dt[k] = t[k]; // down stream writes may overwrite re-derived tuples |
|
// conservatively mark all source fields as modified |
|
|
|
out.modifies(k); |
|
} |
|
|
|
out.mod.push(dt); |
|
}); |
|
} |
|
|
|
return out; |
|
}; |
|
/** |
|
* Samples tuples passing through this operator. |
|
* Uses reservoir sampling to maintain a representative sample. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {number} [params.size=1000] - The maximum number of samples. |
|
*/ |
|
|
|
|
|
function Sample(params) { |
|
Transform.call(this, [], params); |
|
this.count = 0; |
|
} |
|
|
|
Sample.Definition = { |
|
"type": "Sample", |
|
"metadata": {}, |
|
"params": [{ |
|
"name": "size", |
|
"type": "number", |
|
"default": 1000 |
|
}] |
|
}; |
|
var prototype$y = inherits(Sample, Transform); |
|
|
|
prototype$y.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.NO_SOURCE), |
|
mod = _.modified('size'), |
|
num = _.size, |
|
res = this.value, |
|
cnt = this.count, |
|
cap = 0, |
|
map = res.reduce(function (m, t) { |
|
m[tupleid(t)] = 1; |
|
return m; |
|
}, {}); // sample reservoir update function |
|
|
|
|
|
function update(t) { |
|
var p, idx; |
|
|
|
if (res.length < num) { |
|
res.push(t); |
|
} else { |
|
idx = ~~((cnt + 1) * exports.random()); |
|
|
|
if (idx < res.length && idx >= cap) { |
|
p = res[idx]; |
|
if (map[tupleid(p)]) out.rem.push(p); // eviction |
|
|
|
res[idx] = t; |
|
} |
|
} |
|
|
|
++cnt; |
|
} |
|
|
|
if (pulse.rem.length) { |
|
// find all tuples that should be removed, add to output |
|
pulse.visit(pulse.REM, function (t) { |
|
var id = tupleid(t); |
|
|
|
if (map[id]) { |
|
map[id] = -1; |
|
out.rem.push(t); |
|
} |
|
|
|
--cnt; |
|
}); // filter removed tuples out of the sample reservoir |
|
|
|
res = res.filter(function (t) { |
|
return map[tupleid(t)] !== -1; |
|
}); |
|
} |
|
|
|
if ((pulse.rem.length || mod) && res.length < num && pulse.source) { |
|
// replenish sample if backing data source is available |
|
cap = cnt = res.length; |
|
pulse.visit(pulse.SOURCE, function (t) { |
|
// update, but skip previously sampled tuples |
|
if (!map[tupleid(t)]) update(t); |
|
}); |
|
cap = -1; |
|
} |
|
|
|
if (mod && res.length > num) { |
|
for (var i = 0, n = res.length - num; i < n; ++i) { |
|
map[tupleid(res[i])] = -1; |
|
out.rem.push(res[i]); |
|
} |
|
|
|
res = res.slice(n); |
|
} |
|
|
|
if (pulse.mod.length) { |
|
// propagate modified tuples in the sample reservoir |
|
pulse.visit(pulse.MOD, function (t) { |
|
if (map[tupleid(t)]) out.mod.push(t); |
|
}); |
|
} |
|
|
|
if (pulse.add.length) { |
|
// update sample reservoir |
|
pulse.visit(pulse.ADD, update); |
|
} |
|
|
|
if (pulse.add.length || cap < 0) { |
|
// output newly added tuples |
|
out.add = res.filter(function (t) { |
|
return !map[tupleid(t)]; |
|
}); |
|
} |
|
|
|
this.count = cnt; |
|
this.value = out.source = res; |
|
return out; |
|
}; |
|
/** |
|
* Generates data tuples for a specified sequence range of numbers. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {number} params.start - The first number in the sequence. |
|
* @param {number} params.stop - The last number (exclusive) in the sequence. |
|
* @param {number} [params.step=1] - The step size between numbers in the sequence. |
|
*/ |
|
|
|
|
|
function Sequence(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Sequence.Definition = { |
|
"type": "Sequence", |
|
"metadata": { |
|
"generates": true, |
|
"changes": true |
|
}, |
|
"params": [{ |
|
"name": "start", |
|
"type": "number", |
|
"required": true |
|
}, { |
|
"name": "stop", |
|
"type": "number", |
|
"required": true |
|
}, { |
|
"name": "step", |
|
"type": "number", |
|
"default": 1 |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"default": "data" |
|
}] |
|
}; |
|
var prototype$z = inherits(Sequence, Transform); |
|
|
|
prototype$z.transform = function (_, pulse) { |
|
if (this.value && !_.modified()) return; |
|
var out = pulse.materialize().fork(pulse.MOD), |
|
as = _.as || 'data'; |
|
out.rem = this.value ? pulse.rem.concat(this.value) : pulse.rem; |
|
this.value = sequence(_.start, _.stop, _.step || 1).map(function (v) { |
|
var t = {}; |
|
t[as] = v; |
|
return ingest(t); |
|
}); |
|
out.add = pulse.add.concat(this.value); |
|
return out; |
|
}; |
|
/** |
|
* Propagates a new pulse without any tuples so long as the input |
|
* pulse contains some added, removed or modified tuples. |
|
* @param {object} params - The parameters for this operator. |
|
* @constructor |
|
*/ |
|
|
|
|
|
function Sieve(params) { |
|
Transform.call(this, null, params); |
|
this.modified(true); // always treat as modified |
|
} |
|
|
|
var prototype$A = inherits(Sieve, Transform); |
|
|
|
prototype$A.transform = function (_, pulse) { |
|
this.value = pulse.source; |
|
return pulse.changed() ? pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS) : pulse.StopPropagation; |
|
}; |
|
|
|
var YEAR = 'year'; |
|
var QUARTER = 'quarter'; |
|
var MONTH = 'month'; |
|
var WEEK = 'week'; |
|
var DATE = 'date'; |
|
var DAY = 'day'; |
|
var HOURS = 'hours'; |
|
var MINUTES = 'minutes'; |
|
var SECONDS = 'seconds'; |
|
var MILLISECONDS = 'milliseconds'; |
|
var UNITS = [YEAR, QUARTER, MONTH, WEEK, DATE, DAY, HOURS, MINUTES, SECONDS, MILLISECONDS].reduce(function (o, u, i) { |
|
return o[u] = 1 + i, o; |
|
}, {}); |
|
|
|
function timeUnits(units) { |
|
var u = array(units).slice(), |
|
m = {}; // check validity |
|
|
|
if (!u.length) error('Missing time unit.'); |
|
u.forEach(function (unit) { |
|
if (hasOwnProperty(UNITS, unit)) { |
|
m[unit] = 1; |
|
} else { |
|
error("Invalid time unit: ".concat(unit, ".")); |
|
} |
|
}); |
|
|
|
if ((m[WEEK] || m[DAY]) && (m[QUARTER] || m[MONTH] || m[DATE])) { |
|
error("Incompatible time units: ".concat(units)); |
|
} // ensure proper sort order |
|
|
|
|
|
u.sort(function (a, b) { |
|
return UNITS[a] - UNITS[b]; |
|
}); |
|
return u; |
|
} |
|
|
|
var t0$1 = new Date(); |
|
|
|
function floor(units, step, get, inv, newDate) { |
|
var s = step || 1, |
|
b = peek(units), |
|
_ = function _(unit, p, key) { |
|
key = key || unit; |
|
return getUnit(get[key], inv[key], unit === b && s, p); |
|
}; |
|
|
|
var t = new Date(), |
|
u = toSet(units), |
|
y = u[YEAR] ? _(YEAR) : constant(2012), |
|
m = u[MONTH] ? _(MONTH) : u[QUARTER] ? _(QUARTER) : zero, |
|
d = u[WEEK] && u[DAY] ? _(DAY, 1, WEEK + DAY) : u[WEEK] ? _(WEEK, 1) : u[DAY] ? _(DAY, 1) : u[DATE] ? _(DATE, 1) : one, |
|
H = u[HOURS] ? _(HOURS) : zero, |
|
M = u[MINUTES] ? _(MINUTES) : zero, |
|
S = u[SECONDS] ? _(SECONDS) : zero, |
|
L = u[MILLISECONDS] ? _(MILLISECONDS) : zero; |
|
return function (v) { |
|
t.setTime(+v); |
|
var 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) { |
|
var u = step <= 1 ? f : phase ? function (d, y) { |
|
return phase + step * Math.floor((f(d, y) - phase) / step); |
|
} : function (d, y) { |
|
return step * Math.floor(f(d, y) / step); |
|
}; |
|
return inv ? function (d, y) { |
|
return 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$1(week, day, firstDay) { |
|
return day + week * 7 - (firstDay + 6) % 7; |
|
} // -- LOCAL TIME -- |
|
|
|
|
|
var localGet = (_localGet = {}, _defineProperty(_localGet, YEAR, function (d) { |
|
return d.getFullYear(); |
|
}), _defineProperty(_localGet, QUARTER, function (d) { |
|
return Math.floor(d.getMonth() / 3); |
|
}), _defineProperty(_localGet, MONTH, function (d) { |
|
return d.getMonth(); |
|
}), _defineProperty(_localGet, DATE, function (d) { |
|
return d.getDate(); |
|
}), _defineProperty(_localGet, HOURS, function (d) { |
|
return d.getHours(); |
|
}), _defineProperty(_localGet, MINUTES, function (d) { |
|
return d.getMinutes(); |
|
}), _defineProperty(_localGet, SECONDS, function (d) { |
|
return d.getSeconds(); |
|
}), _defineProperty(_localGet, MILLISECONDS, function (d) { |
|
return d.getMilliseconds(); |
|
}), _defineProperty(_localGet, WEEK, function (d) { |
|
return localWeekNum(d); |
|
}), _defineProperty(_localGet, WEEK + DAY, function (d, y) { |
|
return weekday$1(localWeekNum(d), d.getDay(), localFirst(y)); |
|
}), _defineProperty(_localGet, DAY, function (d, y) { |
|
return weekday$1(1, d.getDay(), localFirst(y)); |
|
}), _localGet); |
|
var localInv = (_localInv = {}, _defineProperty(_localInv, QUARTER, function (q) { |
|
return 3 * q; |
|
}), _defineProperty(_localInv, WEEK, function (w, y) { |
|
return weekday$1(w, 0, localFirst(y)); |
|
}), _localInv); |
|
|
|
function localYear(y) { |
|
t0$1.setFullYear(y); |
|
t0$1.setMonth(0); |
|
t0$1.setDate(1); |
|
t0$1.setHours(0, 0, 0, 0); |
|
return t0$1; |
|
} |
|
|
|
function localWeekNum(d) { |
|
return sunday.count(localYear(d.getFullYear()) - 1, d); |
|
} |
|
|
|
function localFirst(y) { |
|
return localYear(y).getDay(); |
|
} |
|
|
|
function localDate$1(y, m, d, H, M, S, L) { |
|
if (0 <= y && y < 100) { |
|
var 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 timeFloor(units, step) { |
|
return floor(units, step || 1, localGet, localInv, localDate$1); |
|
} // -- UTC TIME -- |
|
|
|
|
|
var utcGet = (_utcGet = {}, _defineProperty(_utcGet, YEAR, function (d) { |
|
return d.getUTCFullYear(); |
|
}), _defineProperty(_utcGet, QUARTER, function (d) { |
|
return Math.floor(d.getUTCMonth() / 3); |
|
}), _defineProperty(_utcGet, MONTH, function (d) { |
|
return d.getUTCMonth(); |
|
}), _defineProperty(_utcGet, DATE, function (d) { |
|
return d.getUTCDate(); |
|
}), _defineProperty(_utcGet, HOURS, function (d) { |
|
return d.getUTCHours(); |
|
}), _defineProperty(_utcGet, MINUTES, function (d) { |
|
return d.getUTCMinutes(); |
|
}), _defineProperty(_utcGet, SECONDS, function (d) { |
|
return d.getUTCSeconds(); |
|
}), _defineProperty(_utcGet, MILLISECONDS, function (d) { |
|
return d.getUTCMilliseconds(); |
|
}), _defineProperty(_utcGet, WEEK, function (d) { |
|
return utcWeekNum(d); |
|
}), _defineProperty(_utcGet, DAY, function (d, y) { |
|
return weekday$1(1, d.getUTCDay(), utcFirst(y)); |
|
}), _defineProperty(_utcGet, WEEK + DAY, function (d, y) { |
|
return weekday$1(utcWeekNum(d), d.getUTCDay(), utcFirst(y)); |
|
}), _utcGet); |
|
var utcInv = (_utcInv = {}, _defineProperty(_utcInv, QUARTER, function (q) { |
|
return 3 * q; |
|
}), _defineProperty(_utcInv, WEEK, function (w, y) { |
|
return weekday$1(w, 0, utcFirst(y)); |
|
}), _utcInv); |
|
|
|
function utcWeekNum(d) { |
|
var y = Date.UTC(d.getUTCFullYear(), 0, 1); |
|
return utcSunday.count(y - 1, d); |
|
} |
|
|
|
function utcFirst(y) { |
|
t0$1.setTime(Date.UTC(y, 0, 1)); |
|
return t0$1.getUTCDay(); |
|
} |
|
|
|
function utcDate$1(y, m, d, H, M, S, L) { |
|
if (0 <= y && y < 100) { |
|
var 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 utcFloor(units, step) { |
|
return floor(units, step || 1, utcGet, utcInv, utcDate$1); |
|
} |
|
|
|
var timeIntervals = (_timeIntervals = {}, _defineProperty(_timeIntervals, YEAR, year), _defineProperty(_timeIntervals, QUARTER, month.every(3)), _defineProperty(_timeIntervals, MONTH, month), _defineProperty(_timeIntervals, WEEK, sunday), _defineProperty(_timeIntervals, DATE, day), _defineProperty(_timeIntervals, DAY, day), _defineProperty(_timeIntervals, HOURS, hour), _defineProperty(_timeIntervals, MINUTES, minute), _defineProperty(_timeIntervals, SECONDS, second), _defineProperty(_timeIntervals, MILLISECONDS, millisecond), _timeIntervals); |
|
var utcIntervals = (_utcIntervals = {}, _defineProperty(_utcIntervals, YEAR, utcYear), _defineProperty(_utcIntervals, QUARTER, utcMonth.every(3)), _defineProperty(_utcIntervals, MONTH, utcMonth), _defineProperty(_utcIntervals, WEEK, utcSunday), _defineProperty(_utcIntervals, DATE, utcDay), _defineProperty(_utcIntervals, DAY, utcDay), _defineProperty(_utcIntervals, HOURS, utcHour), _defineProperty(_utcIntervals, MINUTES, utcMinute), _defineProperty(_utcIntervals, SECONDS, second), _defineProperty(_utcIntervals, MILLISECONDS, millisecond), _utcIntervals); |
|
|
|
function timeInterval(unit) { |
|
return timeIntervals[unit]; |
|
} |
|
|
|
function utcInterval(unit) { |
|
return utcIntervals[unit]; |
|
} |
|
|
|
function offset(ival, date, step) { |
|
return ival ? ival.offset(date, step) : undefined; |
|
} |
|
|
|
function timeOffset(unit, date, step) { |
|
return offset(timeInterval(unit), date, step); |
|
} |
|
|
|
function utcOffset(unit, date, step) { |
|
return offset(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); |
|
} |
|
|
|
var defaultSpecifiers = (_defaultSpecifiers = {}, _defineProperty(_defaultSpecifiers, YEAR, '%Y '), _defineProperty(_defaultSpecifiers, QUARTER, 'Q%q '), _defineProperty(_defaultSpecifiers, MONTH, '%b '), _defineProperty(_defaultSpecifiers, DATE, '%d '), _defineProperty(_defaultSpecifiers, WEEK, 'W%U '), _defineProperty(_defaultSpecifiers, DAY, '%a '), _defineProperty(_defaultSpecifiers, HOURS, '%H:00'), _defineProperty(_defaultSpecifiers, MINUTES, '00:%M'), _defineProperty(_defaultSpecifiers, SECONDS, ':%S'), _defineProperty(_defaultSpecifiers, MILLISECONDS, '.%L'), _defineProperty(_defaultSpecifiers, "".concat(YEAR, "-").concat(MONTH), '%Y-%m '), _defineProperty(_defaultSpecifiers, "".concat(YEAR, "-").concat(MONTH, "-").concat(DATE), '%Y-%m-%d '), _defineProperty(_defaultSpecifiers, "".concat(HOURS, "-").concat(MINUTES), '%H:%M'), _defaultSpecifiers); |
|
|
|
function timeUnitSpecifier(units, specifiers) { |
|
var s = extend({}, defaultSpecifiers, specifiers), |
|
u = timeUnits(units), |
|
n = u.length; |
|
var 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(); |
|
} |
|
|
|
function timeFormat$1(specifier) { |
|
return formatter(timeFormat, timeInterval, specifier); |
|
} |
|
|
|
function utcFormat$1(specifier) { |
|
return formatter(utcFormat, utcInterval, specifier); |
|
} |
|
|
|
function formatter(format, interval, specifier) { |
|
return isString(specifier) ? format(specifier) : multiFormat(format, interval, specifier); |
|
} |
|
|
|
function multiFormat(format, interval, spec) { |
|
spec = spec || {}; |
|
|
|
if (!isObject(spec)) { |
|
error("Invalid time multi-format specifier: ".concat(spec)); |
|
} |
|
|
|
var 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 function (date) { |
|
return (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); |
|
}; |
|
} |
|
|
|
var durationSecond$1 = 1000, |
|
durationMinute$1 = durationSecond$1 * 60, |
|
durationHour$1 = durationMinute$1 * 60, |
|
durationDay$1 = durationHour$1 * 24, |
|
durationWeek$1 = durationDay$1 * 7, |
|
durationMonth = durationDay$1 * 30, |
|
durationYear = durationDay$1 * 365; |
|
var 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]; |
|
var intervals = [[Seconds, 1, durationSecond$1], [Seconds, 5, 5 * durationSecond$1], [Seconds, 15, 15 * durationSecond$1], [Seconds, 30, 30 * durationSecond$1], [Minutes, 1, durationMinute$1], [Minutes, 5, 5 * durationMinute$1], [Minutes, 15, 15 * durationMinute$1], [Minutes, 30, 30 * durationMinute$1], [Hours, 1, durationHour$1], [Hours, 3, 3 * durationHour$1], [Hours, 6, 6 * durationHour$1], [Hours, 12, 12 * durationHour$1], [Day, 1, durationDay$1], [Week, 1, durationWeek$1], [Month, 1, durationMonth], [Month, 3, 3 * durationMonth], [Year, 1, durationYear]]; |
|
|
|
function timeBin(opt) { |
|
var ext = opt.extent, |
|
max = opt.maxbins || 40, |
|
target = Math.abs(span(ext)) / max; |
|
var i = bisector(function (i) { |
|
return i[2]; |
|
}).right(intervals, target), |
|
units, |
|
step; |
|
|
|
if (i === intervals.length) { |
|
units = Year, step = 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(tickStep(ext[0], ext[1], max), 1); |
|
} |
|
|
|
return { |
|
units: units, |
|
step: step |
|
}; |
|
} |
|
/** |
|
* Discretize dates to specific time units. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The data field containing date/time values. |
|
*/ |
|
|
|
|
|
function TimeUnit(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var OUTPUT = ['unit0', 'unit1']; |
|
TimeUnit.Definition = { |
|
"type": "TimeUnit", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "interval", |
|
"type": "boolean", |
|
"default": true |
|
}, { |
|
"name": "units", |
|
"type": "string", |
|
"array": true |
|
}, { |
|
"name": "step", |
|
"type": "number", |
|
"default": 1 |
|
}, { |
|
"name": "timezone", |
|
"type": "enum", |
|
"default": "local", |
|
"values": ["local", "utc"] |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": 2, |
|
"default": OUTPUT |
|
}] |
|
}; |
|
var prototype$B = inherits(TimeUnit, Transform); |
|
|
|
prototype$B.transform = function (_, pulse) { |
|
var field = _.field, |
|
band = _.interval !== false, |
|
utc = _.timezone === 'utc', |
|
floor = this._floor(_, pulse), |
|
offset = (utc ? utcInterval : timeInterval)(floor.unit).offset, |
|
as = _.as || OUTPUT, |
|
u0 = as[0], |
|
u1 = as[1], |
|
min = floor.start || Infinity, |
|
max = floor.stop || -Infinity, |
|
step = floor.step, |
|
flag = pulse.ADD; |
|
|
|
if (_.modified() || pulse.modified(accessorFields(_.field))) { |
|
pulse = pulse.reflow(true); |
|
flag = pulse.SOURCE; |
|
min = Infinity; |
|
max = -Infinity; |
|
} |
|
|
|
pulse.visit(flag, function (t) { |
|
var v = field(t), |
|
a, |
|
b; |
|
|
|
if (v == null) { |
|
t[u0] = null; |
|
if (band) t[u1] = null; |
|
} else { |
|
t[u0] = a = b = floor(v); |
|
if (band) t[u1] = b = offset(a, step); |
|
if (a < min) min = a; |
|
if (b > max) max = b; |
|
} |
|
}); |
|
floor.start = min; |
|
floor.stop = max; |
|
return pulse.modifies(band ? as : u0); |
|
}; |
|
|
|
prototype$B._floor = function (_, pulse) { |
|
var utc = _.timezone === 'utc'; // get parameters |
|
|
|
var _ref2 = _.units ? { |
|
units: _.units, |
|
step: _.step || 1 |
|
} : timeBin({ |
|
extent: extent(pulse.materialize(pulse.SOURCE).source, _.field), |
|
maxbins: _.maxbins |
|
}), |
|
units = _ref2.units, |
|
step = _ref2.step; // check / standardize time units |
|
|
|
|
|
units = timeUnits(units); |
|
var prev = this.value || {}, |
|
floor = (utc ? utcFloor : timeFloor)(units, step); |
|
floor.unit = peek(units); |
|
floor.units = units; |
|
floor.step = step; |
|
floor.start = prev.start; |
|
floor.stop = prev.stop; |
|
return this.value = floor; |
|
}; |
|
/** |
|
* An index that maps from unique, string-coerced, field values to tuples. |
|
* Assumes that the field serves as a unique key with no duplicate values. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The field accessor to index. |
|
*/ |
|
|
|
|
|
function TupleIndex(params) { |
|
Transform.call(this, fastmap(), params); |
|
} |
|
|
|
var prototype$C = inherits(TupleIndex, Transform); |
|
|
|
prototype$C.transform = function (_, pulse) { |
|
var df = pulse.dataflow, |
|
field = _.field, |
|
index = this.value, |
|
mod = true; |
|
|
|
function set(t) { |
|
index.set(field(t), t); |
|
} |
|
|
|
if (_.modified('field') || pulse.modified(field.fields)) { |
|
index.clear(); |
|
pulse.visit(pulse.SOURCE, set); |
|
} else if (pulse.changed()) { |
|
pulse.visit(pulse.REM, function (t) { |
|
index.delete(field(t)); |
|
}); |
|
pulse.visit(pulse.ADD, set); |
|
} else { |
|
mod = false; |
|
} |
|
|
|
this.modified(mod); |
|
if (index.empty > df.cleanThreshold) df.runAfter(index.clean); |
|
return pulse.fork(); |
|
}; |
|
/** |
|
* Extracts an array of values. Assumes the source data has already been |
|
* reduced as needed (e.g., by an upstream Aggregate transform). |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The domain field to extract. |
|
* @param {function(*,*): number} [params.sort] - An optional |
|
* comparator function for sorting the values. The comparator will be |
|
* applied to backing tuples prior to value extraction. |
|
*/ |
|
|
|
|
|
function Values(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$D = inherits(Values, Transform); |
|
|
|
prototype$D.transform = function (_, pulse) { |
|
var run = !this.value || _.modified('field') || _.modified('sort') || pulse.changed() || _.sort && pulse.modified(_.sort.fields); |
|
|
|
if (run) { |
|
this.value = (_.sort ? pulse.source.slice().sort(stableCompare(_.sort)) : pulse.source).map(_.field); |
|
} |
|
}; |
|
|
|
function WindowOp(op, field, param, as) { |
|
var fn = WindowOps[op](field, param); |
|
return { |
|
init: fn.init || zero, |
|
update: function update(w, t) { |
|
t[as] = fn.next(w); |
|
} |
|
}; |
|
} |
|
|
|
var WindowOps = { |
|
row_number: function row_number() { |
|
return { |
|
next: function next(w) { |
|
return w.index + 1; |
|
} |
|
}; |
|
}, |
|
rank: function rank() { |
|
var rank; |
|
return { |
|
init: function init() { |
|
return rank = 1; |
|
}, |
|
next: function next(w) { |
|
var i = w.index, |
|
data = w.data; |
|
return i && w.compare(data[i - 1], data[i]) ? rank = i + 1 : rank; |
|
} |
|
}; |
|
}, |
|
dense_rank: function dense_rank() { |
|
var drank; |
|
return { |
|
init: function init() { |
|
return drank = 1; |
|
}, |
|
next: function next(w) { |
|
var i = w.index, |
|
d = w.data; |
|
return i && w.compare(d[i - 1], d[i]) ? ++drank : drank; |
|
} |
|
}; |
|
}, |
|
percent_rank: function percent_rank() { |
|
var rank = WindowOps.rank(), |
|
_next2 = rank.next; |
|
return { |
|
init: rank.init, |
|
next: function next(w) { |
|
return (_next2(w) - 1) / (w.data.length - 1); |
|
} |
|
}; |
|
}, |
|
cume_dist: function cume_dist() { |
|
var cume; |
|
return { |
|
init: function init() { |
|
return cume = 0; |
|
}, |
|
next: function next(w) { |
|
var i = w.index, |
|
d = w.data, |
|
c = w.compare; |
|
|
|
if (cume < i) { |
|
while (i + 1 < d.length && !c(d[i], d[i + 1])) { |
|
++i; |
|
} |
|
|
|
cume = i; |
|
} |
|
|
|
return (1 + cume) / d.length; |
|
} |
|
}; |
|
}, |
|
ntile: function ntile(field, num) { |
|
num = +num; |
|
if (!(num > 0)) error('ntile num must be greater than zero.'); |
|
var cume = WindowOps.cume_dist(), |
|
_next3 = cume.next; |
|
return { |
|
init: cume.init, |
|
next: function next(w) { |
|
return Math.ceil(num * _next3(w)); |
|
} |
|
}; |
|
}, |
|
lag: function lag(field, offset) { |
|
offset = +offset || 1; |
|
return { |
|
next: function next(w) { |
|
var i = w.index - offset; |
|
return i >= 0 ? field(w.data[i]) : null; |
|
} |
|
}; |
|
}, |
|
lead: function lead(field, offset) { |
|
offset = +offset || 1; |
|
return { |
|
next: function next(w) { |
|
var i = w.index + offset, |
|
d = w.data; |
|
return i < d.length ? field(d[i]) : null; |
|
} |
|
}; |
|
}, |
|
first_value: function first_value(field) { |
|
return { |
|
next: function next(w) { |
|
return field(w.data[w.i0]); |
|
} |
|
}; |
|
}, |
|
last_value: function last_value(field) { |
|
return { |
|
next: function next(w) { |
|
return field(w.data[w.i1 - 1]); |
|
} |
|
}; |
|
}, |
|
nth_value: function nth_value(field, nth) { |
|
nth = +nth; |
|
if (!(nth > 0)) error('nth_value nth must be greater than zero.'); |
|
return { |
|
next: function next(w) { |
|
var i = w.i0 + (nth - 1); |
|
return i < w.i1 ? field(w.data[i]) : null; |
|
} |
|
}; |
|
}, |
|
prev_value: function prev_value(field) { |
|
var prev = null; |
|
return { |
|
next: function next(w) { |
|
var v = field(w.data[w.index]); |
|
return v != null ? prev = v : prev; |
|
} |
|
}; |
|
}, |
|
next_value: function next_value(field) { |
|
var v = null, |
|
i = -1; |
|
return { |
|
next: function next(w) { |
|
var d = w.data; |
|
return w.index <= i ? v : (i = find(field, d, w.index)) < 0 ? (i = d.length, v = null) : v = field(d[i]); |
|
} |
|
}; |
|
} |
|
}; |
|
|
|
function find(field, data, index) { |
|
for (var n = data.length; index < n; ++index) { |
|
var _v2 = field(data[index]); |
|
|
|
if (_v2 != null) return index; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
var ValidWindowOps = Object.keys(WindowOps); |
|
|
|
function WindowState(_) { |
|
var self = this, |
|
ops = array(_.ops), |
|
fields = array(_.fields), |
|
params = array(_.params), |
|
as = array(_.as), |
|
outputs = self.outputs = [], |
|
windows = self.windows = [], |
|
inputs = {}, |
|
map = {}, |
|
countOnly = true, |
|
counts = [], |
|
measures = []; |
|
|
|
function visitInputs(f) { |
|
array(accessorFields(f)).forEach(function (_) { |
|
return inputs[_] = 1; |
|
}); |
|
} |
|
|
|
visitInputs(_.sort); |
|
ops.forEach(function (op, i) { |
|
var field = fields[i], |
|
mname = accessorName(field), |
|
name = measureName(op, mname, as[i]); |
|
visitInputs(field); |
|
outputs.push(name); // Window operation |
|
|
|
if (hasOwnProperty(WindowOps, op)) { |
|
windows.push(WindowOp(op, fields[i], params[i], name)); |
|
} // Aggregate operation |
|
else { |
|
if (field == null && op !== 'count') { |
|
error('Null aggregate field specified.'); |
|
} |
|
|
|
if (op === 'count') { |
|
counts.push(name); |
|
return; |
|
} |
|
|
|
countOnly = false; |
|
var m = map[mname]; |
|
|
|
if (!m) { |
|
m = map[mname] = []; |
|
m.field = field; |
|
measures.push(m); |
|
} |
|
|
|
m.push(createMeasure(op, name)); |
|
} |
|
}); |
|
|
|
if (counts.length || measures.length) { |
|
self.cell = cell(measures, counts, countOnly); |
|
} |
|
|
|
self.inputs = Object.keys(inputs); |
|
} |
|
|
|
var prototype$E = WindowState.prototype; |
|
|
|
prototype$E.init = function () { |
|
this.windows.forEach(function (_) { |
|
return _.init(); |
|
}); |
|
if (this.cell) this.cell.init(); |
|
}; |
|
|
|
prototype$E.update = function (w, t) { |
|
var self = this, |
|
cell = self.cell, |
|
wind = self.windows, |
|
data = w.data, |
|
m = wind && wind.length, |
|
j; |
|
|
|
if (cell) { |
|
for (j = w.p0; j < w.i0; ++j) { |
|
cell.rem(data[j]); |
|
} |
|
|
|
for (j = w.p1; j < w.i1; ++j) { |
|
cell.add(data[j]); |
|
} |
|
|
|
cell.set(t); |
|
} |
|
|
|
for (j = 0; j < m; ++j) { |
|
wind[j].update(w, t); |
|
} |
|
}; |
|
|
|
function cell(measures, counts, countOnly) { |
|
measures = measures.map(function (m) { |
|
return compileMeasures(m, m.field); |
|
}); |
|
var cell = { |
|
num: 0, |
|
agg: null, |
|
store: false, |
|
count: counts |
|
}; |
|
|
|
if (!countOnly) { |
|
var n = measures.length, |
|
a = cell.agg = Array(n), |
|
i = 0; |
|
|
|
for (; i < n; ++i) { |
|
a[i] = new measures[i](cell); |
|
} |
|
} |
|
|
|
if (cell.store) { |
|
var store = cell.data = new TupleStore(); |
|
} |
|
|
|
cell.add = function (t) { |
|
cell.num += 1; |
|
if (countOnly) return; |
|
if (store) store.add(t); |
|
|
|
for (var _i5 = 0; _i5 < n; ++_i5) { |
|
a[_i5].add(a[_i5].get(t), t); |
|
} |
|
}; |
|
|
|
cell.rem = function (t) { |
|
cell.num -= 1; |
|
if (countOnly) return; |
|
if (store) store.rem(t); |
|
|
|
for (var _i6 = 0; _i6 < n; ++_i6) { |
|
a[_i6].rem(a[_i6].get(t), t); |
|
} |
|
}; |
|
|
|
cell.set = function (t) { |
|
var i, n; // consolidate stored values |
|
|
|
if (store) store.values(); // update tuple properties |
|
|
|
for (i = 0, n = counts.length; i < n; ++i) { |
|
t[counts[i]] = cell.num; |
|
} |
|
|
|
if (!countOnly) for (i = 0, n = a.length; i < n; ++i) { |
|
a[i].set(t); |
|
} |
|
}; |
|
|
|
cell.init = function () { |
|
cell.num = 0; |
|
if (store) store.reset(); |
|
|
|
for (var _i7 = 0; _i7 < n; ++_i7) { |
|
a[_i7].init(); |
|
} |
|
}; |
|
|
|
return cell; |
|
} |
|
/** |
|
* Perform window calculations and write results to the input stream. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(*,*): number} [params.sort] - A comparator function for sorting tuples within a window. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors by which to partition tuples into separate windows. |
|
* @param {Array<string>} params.ops - An array of strings indicating window operations to perform. |
|
* @param {Array<function(object): *>} [params.fields] - An array of accessors |
|
* for data fields to use as inputs to window operations. |
|
* @param {Array<*>} [params.params] - An array of parameter values for window operations. |
|
* @param {Array<string>} [params.as] - An array of output field names for window operations. |
|
* @param {Array<number>} [params.frame] - Window frame definition as two-element array. |
|
* @param {boolean} [params.ignorePeers=false] - If true, base window frame boundaries on row |
|
* number alone, ignoring peers with identical sort values. If false (default), |
|
* the window boundaries will be adjusted to include peer values. |
|
*/ |
|
|
|
|
|
function Window(params) { |
|
Transform.call(this, {}, params); |
|
this._mlen = 0; |
|
this._mods = []; |
|
} |
|
|
|
Window.Definition = { |
|
"type": "Window", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "sort", |
|
"type": "compare" |
|
}, { |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "ops", |
|
"type": "enum", |
|
"array": true, |
|
"values": ValidWindowOps.concat(ValidAggregateOps) |
|
}, { |
|
"name": "params", |
|
"type": "number", |
|
"null": true, |
|
"array": true |
|
}, { |
|
"name": "fields", |
|
"type": "field", |
|
"null": true, |
|
"array": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"null": true, |
|
"array": true |
|
}, { |
|
"name": "frame", |
|
"type": "number", |
|
"null": true, |
|
"array": true, |
|
"length": 2, |
|
"default": [null, 0] |
|
}, { |
|
"name": "ignorePeers", |
|
"type": "boolean", |
|
"default": false |
|
}] |
|
}; |
|
var prototype$F = inherits(Window, Transform); |
|
|
|
prototype$F.transform = function (_, pulse) { |
|
var self = this, |
|
state = self.state, |
|
mod = _.modified(), |
|
cmp = stableCompare(_.sort), |
|
i, |
|
n; |
|
|
|
this.stamp = pulse.stamp; // initialize window state |
|
|
|
if (!state || mod) { |
|
state = self.state = new WindowState(_); |
|
} // retrieve group for a tuple |
|
|
|
|
|
var key = groupkey(_.groupby); |
|
|
|
function group(t) { |
|
return self.group(key(t)); |
|
} // partition input tuples |
|
|
|
|
|
if (mod || pulse.modified(state.inputs)) { |
|
self.value = {}; |
|
pulse.visit(pulse.SOURCE, function (t) { |
|
group(t).add(t); |
|
}); |
|
} else { |
|
pulse.visit(pulse.REM, function (t) { |
|
group(t).remove(t); |
|
}); |
|
pulse.visit(pulse.ADD, function (t) { |
|
group(t).add(t); |
|
}); |
|
} // perform window calculations for each modified partition |
|
|
|
|
|
for (i = 0, n = self._mlen; i < n; ++i) { |
|
processPartition(self._mods[i], state, cmp, _); |
|
} |
|
|
|
self._mlen = 0; |
|
self._mods = []; // TODO don't reflow everything? |
|
|
|
return pulse.reflow(mod).modifies(state.outputs); |
|
}; |
|
|
|
prototype$F.group = function (key) { |
|
var self = this, |
|
group = self.value[key]; |
|
|
|
if (!group) { |
|
group = self.value[key] = SortedList(tupleid); |
|
group.stamp = -1; |
|
} |
|
|
|
if (group.stamp < self.stamp) { |
|
group.stamp = self.stamp; |
|
self._mods[self._mlen++] = group; |
|
} |
|
|
|
return group; |
|
}; |
|
|
|
function processPartition(list, state, cmp, _) { |
|
var sort = _.sort, |
|
range = sort && !_.ignorePeers, |
|
frame = _.frame || [null, 0], |
|
data = list.data(cmp), |
|
// use cmp for stable sort |
|
n = data.length, |
|
i = 0, |
|
b = range ? bisector(sort) : null, |
|
w = { |
|
i0: 0, |
|
i1: 0, |
|
p0: 0, |
|
p1: 0, |
|
index: 0, |
|
data: data, |
|
compare: sort || constant(-1) |
|
}; |
|
|
|
for (state.init(); i < n; ++i) { |
|
setWindow(w, frame, i, n); |
|
if (range) adjustRange(w, b); |
|
state.update(w, data[i]); |
|
} |
|
} |
|
|
|
function setWindow(w, f, i, n) { |
|
w.p0 = w.i0; |
|
w.p1 = w.i1; |
|
w.i0 = f[0] == null ? 0 : Math.max(0, i - Math.abs(f[0])); |
|
w.i1 = f[1] == null ? n : Math.min(n, i + Math.abs(f[1]) + 1); |
|
w.index = i; |
|
} // if frame type is 'range', adjust window for peer values |
|
|
|
|
|
function adjustRange(w, bisect) { |
|
var r0 = w.i0, |
|
r1 = w.i1 - 1, |
|
c = w.compare, |
|
d = w.data, |
|
n = d.length - 1; |
|
if (r0 > 0 && !c(d[r0], d[r0 - 1])) w.i0 = bisect.left(d, d[r0]); |
|
if (r1 < n && !c(d[r1], d[r1 + 1])) w.i1 = bisect.right(d, d[r1]); |
|
} |
|
|
|
var tx = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
aggregate: Aggregate, |
|
bin: Bin, |
|
collect: Collect, |
|
compare: Compare, |
|
countpattern: CountPattern, |
|
cross: Cross, |
|
density: Density, |
|
dotbin: DotBin, |
|
expression: Expression, |
|
extent: Extent, |
|
facet: Facet, |
|
field: Field, |
|
filter: Filter, |
|
flatten: Flatten, |
|
fold: Fold, |
|
formula: Formula, |
|
generate: Generate, |
|
impute: Impute, |
|
joinaggregate: JoinAggregate, |
|
kde: KDE, |
|
key: Key, |
|
load: Load, |
|
lookup: Lookup, |
|
multiextent: MultiExtent, |
|
multivalues: MultiValues, |
|
params: Params, |
|
pivot: Pivot, |
|
prefacet: PreFacet, |
|
project: Project, |
|
proxy: Proxy, |
|
quantile: Quantile, |
|
relay: Relay, |
|
sample: Sample, |
|
sequence: Sequence, |
|
sieve: Sieve, |
|
subflow: Subflow, |
|
timeunit: TimeUnit, |
|
tupleindex: TupleIndex, |
|
values: Values, |
|
window: Window |
|
}); |
|
var Top = 'top'; |
|
var Left = 'left'; |
|
var Right = 'right'; |
|
var Bottom = 'bottom'; |
|
var TopLeft = 'top-left'; |
|
var TopRight = 'top-right'; |
|
var BottomLeft = 'bottom-left'; |
|
var BottomRight = 'bottom-right'; |
|
var Start = 'start'; |
|
var Middle = 'middle'; |
|
var End = 'end'; |
|
var X = 'x'; |
|
var Y = 'y'; |
|
var Group = 'group'; |
|
var AxisRole = 'axis'; |
|
var TitleRole = 'title'; |
|
var FrameRole = 'frame'; |
|
var ScopeRole = 'scope'; |
|
var LegendRole = 'legend'; |
|
var RowHeader = 'row-header'; |
|
var RowFooter = 'row-footer'; |
|
var RowTitle = 'row-title'; |
|
var ColHeader = 'column-header'; |
|
var ColFooter = 'column-footer'; |
|
var ColTitle = 'column-title'; |
|
var Padding = 'padding'; |
|
var Symbols = 'symbol'; |
|
var Fit = 'fit'; |
|
var FitX = 'fit-x'; |
|
var FitY = 'fit-y'; |
|
var Pad = 'pad'; |
|
var None$1 = 'none'; |
|
var All = 'all'; |
|
var Each = 'each'; |
|
var Flush = 'flush'; |
|
var Column = 'column'; |
|
var Row = 'row'; |
|
|
|
function Bounds(b) { |
|
this.clear(); |
|
if (b) this.union(b); |
|
} |
|
|
|
var prototype$G = Bounds.prototype; |
|
|
|
prototype$G.clone = function () { |
|
return new Bounds(this); |
|
}; |
|
|
|
prototype$G.clear = function () { |
|
this.x1 = +Number.MAX_VALUE; |
|
this.y1 = +Number.MAX_VALUE; |
|
this.x2 = -Number.MAX_VALUE; |
|
this.y2 = -Number.MAX_VALUE; |
|
return this; |
|
}; |
|
|
|
prototype$G.empty = function () { |
|
return this.x1 === +Number.MAX_VALUE && this.y1 === +Number.MAX_VALUE && this.x2 === -Number.MAX_VALUE && this.y2 === -Number.MAX_VALUE; |
|
}; |
|
|
|
prototype$G.equals = function (b) { |
|
return this.x1 === b.x1 && this.y1 === b.y1 && this.x2 === b.x2 && this.y2 === b.y2; |
|
}; |
|
|
|
prototype$G.set = function (x1, y1, x2, y2) { |
|
if (x2 < x1) { |
|
this.x2 = x1; |
|
this.x1 = x2; |
|
} else { |
|
this.x1 = x1; |
|
this.x2 = x2; |
|
} |
|
|
|
if (y2 < y1) { |
|
this.y2 = y1; |
|
this.y1 = y2; |
|
} else { |
|
this.y1 = y1; |
|
this.y2 = y2; |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
prototype$G.add = function (x, y) { |
|
if (x < this.x1) this.x1 = x; |
|
if (y < this.y1) this.y1 = y; |
|
if (x > this.x2) this.x2 = x; |
|
if (y > this.y2) this.y2 = y; |
|
return this; |
|
}; |
|
|
|
prototype$G.expand = function (d) { |
|
this.x1 -= d; |
|
this.y1 -= d; |
|
this.x2 += d; |
|
this.y2 += d; |
|
return this; |
|
}; |
|
|
|
prototype$G.round = function () { |
|
this.x1 = Math.floor(this.x1); |
|
this.y1 = Math.floor(this.y1); |
|
this.x2 = Math.ceil(this.x2); |
|
this.y2 = Math.ceil(this.y2); |
|
return this; |
|
}; |
|
|
|
prototype$G.translate = function (dx, dy) { |
|
this.x1 += dx; |
|
this.x2 += dx; |
|
this.y1 += dy; |
|
this.y2 += dy; |
|
return this; |
|
}; |
|
|
|
prototype$G.rotate = function (angle, x, y) { |
|
var p = this.rotatedPoints(angle, x, y); |
|
return this.clear().add(p[0], p[1]).add(p[2], p[3]).add(p[4], p[5]).add(p[6], p[7]); |
|
}; |
|
|
|
prototype$G.rotatedPoints = function (angle, x, y) { |
|
var x1 = this.x1, |
|
y1 = this.y1, |
|
x2 = this.x2, |
|
y2 = this.y2, |
|
cos = Math.cos(angle), |
|
sin = Math.sin(angle), |
|
cx = x - x * cos + y * sin, |
|
cy = y - x * sin - y * cos; |
|
return [cos * x1 - sin * y1 + cx, sin * x1 + cos * y1 + cy, cos * x1 - sin * y2 + cx, sin * x1 + cos * y2 + cy, cos * x2 - sin * y1 + cx, sin * x2 + cos * y1 + cy, cos * x2 - sin * y2 + cx, sin * x2 + cos * y2 + cy]; |
|
}; |
|
|
|
prototype$G.union = function (b) { |
|
if (b.x1 < this.x1) this.x1 = b.x1; |
|
if (b.y1 < this.y1) this.y1 = b.y1; |
|
if (b.x2 > this.x2) this.x2 = b.x2; |
|
if (b.y2 > this.y2) this.y2 = b.y2; |
|
return this; |
|
}; |
|
|
|
prototype$G.intersect = function (b) { |
|
if (b.x1 > this.x1) this.x1 = b.x1; |
|
if (b.y1 > this.y1) this.y1 = b.y1; |
|
if (b.x2 < this.x2) this.x2 = b.x2; |
|
if (b.y2 < this.y2) this.y2 = b.y2; |
|
return this; |
|
}; |
|
|
|
prototype$G.encloses = function (b) { |
|
return b && this.x1 <= b.x1 && this.x2 >= b.x2 && this.y1 <= b.y1 && this.y2 >= b.y2; |
|
}; |
|
|
|
prototype$G.alignsWith = function (b) { |
|
return b && (this.x1 == b.x1 || this.x2 == b.x2 || this.y1 == b.y1 || this.y2 == b.y2); |
|
}; |
|
|
|
prototype$G.intersects = function (b) { |
|
return b && !(this.x2 < b.x1 || this.x1 > b.x2 || this.y2 < b.y1 || this.y1 > b.y2); |
|
}; |
|
|
|
prototype$G.contains = function (x, y) { |
|
return !(x < this.x1 || x > this.x2 || y < this.y1 || y > this.y2); |
|
}; |
|
|
|
prototype$G.width = function () { |
|
return this.x2 - this.x1; |
|
}; |
|
|
|
prototype$G.height = function () { |
|
return this.y2 - this.y1; |
|
}; |
|
|
|
var gradient_id = 0; |
|
var patternPrefix = 'p_'; |
|
|
|
function isGradient(value) { |
|
return value && value.gradient; |
|
} |
|
|
|
function gradientRef(g, defs, base) { |
|
var id = g.id, |
|
type = g.gradient, |
|
prefix = type === 'radial' ? patternPrefix : ''; // check id, assign default values as needed |
|
|
|
if (!id) { |
|
id = g.id = 'gradient_' + gradient_id++; |
|
|
|
if (type === 'radial') { |
|
g.x1 = get$1(g.x1, 0.5); |
|
g.y1 = get$1(g.y1, 0.5); |
|
g.r1 = get$1(g.r1, 0); |
|
g.x2 = get$1(g.x2, 0.5); |
|
g.y2 = get$1(g.y2, 0.5); |
|
g.r2 = get$1(g.r2, 0.5); |
|
prefix = patternPrefix; |
|
} else { |
|
g.x1 = get$1(g.x1, 0); |
|
g.y1 = get$1(g.y1, 0); |
|
g.x2 = get$1(g.x2, 1); |
|
g.y2 = get$1(g.y2, 0); |
|
} |
|
} // register definition |
|
|
|
|
|
defs[id] = g; // return url reference |
|
|
|
return 'url(' + (base || '') + '#' + prefix + id + ')'; |
|
} |
|
|
|
function get$1(val, def) { |
|
return val != null ? val : def; |
|
} |
|
|
|
function Gradient(p0, p1) { |
|
var stops = [], |
|
gradient; |
|
return gradient = { |
|
gradient: 'linear', |
|
x1: p0 ? p0[0] : 0, |
|
y1: p0 ? p0[1] : 0, |
|
x2: p1 ? p1[0] : 1, |
|
y2: p1 ? p1[1] : 0, |
|
stops: stops, |
|
stop: function stop(offset, color) { |
|
stops.push({ |
|
offset: offset, |
|
color: color |
|
}); |
|
return gradient; |
|
} |
|
}; |
|
} |
|
|
|
function Item(mark) { |
|
this.mark = mark; |
|
this.bounds = this.bounds || new Bounds(); |
|
} |
|
|
|
function GroupItem(mark) { |
|
Item.call(this, mark); |
|
this.items = this.items || []; |
|
} |
|
|
|
inherits(GroupItem, Item); |
|
|
|
function domCanvas(w, h) { |
|
if (typeof document !== 'undefined' && document.createElement) { |
|
var c = document.createElement('canvas'); |
|
|
|
if (c && c.getContext) { |
|
c.width = w; |
|
c.height = h; |
|
return c; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function domImage() { |
|
return typeof Image !== 'undefined' ? Image : null; |
|
} |
|
|
|
function ResourceLoader(customLoader) { |
|
this._pending = 0; |
|
this._loader = customLoader || loader(); |
|
} |
|
|
|
var prototype$H = ResourceLoader.prototype; |
|
|
|
prototype$H.pending = function () { |
|
return this._pending; |
|
}; |
|
|
|
function increment(loader) { |
|
loader._pending += 1; |
|
} |
|
|
|
function decrement(loader) { |
|
loader._pending -= 1; |
|
} |
|
|
|
prototype$H.sanitizeURL = function (uri) { |
|
var loader = this; |
|
increment(loader); |
|
return loader._loader.sanitize(uri, { |
|
context: 'href' |
|
}).then(function (opt) { |
|
decrement(loader); |
|
return opt; |
|
}).catch(function () { |
|
decrement(loader); |
|
return null; |
|
}); |
|
}; |
|
|
|
prototype$H.loadImage = function (uri) { |
|
var loader = this, |
|
Image = domImage(); |
|
increment(loader); |
|
return loader._loader.sanitize(uri, { |
|
context: 'image' |
|
}).then(function (opt) { |
|
var url = opt.href; |
|
if (!url || !Image) throw { |
|
url: url |
|
}; |
|
var img = new Image(); |
|
|
|
img.onload = function () { |
|
decrement(loader); |
|
}; |
|
|
|
img.onerror = function () { |
|
decrement(loader); |
|
}; |
|
|
|
img.src = url; |
|
return img; |
|
}).catch(function (e) { |
|
decrement(loader); |
|
return { |
|
complete: false, |
|
width: 0, |
|
height: 0, |
|
src: e && e.url || '' |
|
}; |
|
}); |
|
}; |
|
|
|
prototype$H.ready = function () { |
|
var loader = this; |
|
return new Promise(function (accept) { |
|
function poll(value) { |
|
if (!loader.pending()) accept(value);else setTimeout(function () { |
|
poll(true); |
|
}, 10); |
|
} |
|
|
|
poll(false); |
|
}); |
|
}; |
|
|
|
var pi = Math.PI, |
|
tau = 2 * pi, |
|
epsilon$1 = 1e-6, |
|
tauEpsilon = tau - epsilon$1; |
|
|
|
function Path() { |
|
this._x0 = this._y0 = // start of current subpath |
|
this._x1 = this._y1 = null; // end of current subpath |
|
|
|
this._ = ""; |
|
} |
|
|
|
function path() { |
|
return new Path(); |
|
} |
|
|
|
Path.prototype = path.prototype = { |
|
constructor: Path, |
|
moveTo: function moveTo(x, y) { |
|
this._ += "M" + (this._x0 = this._x1 = +x) + "," + (this._y0 = this._y1 = +y); |
|
}, |
|
closePath: function closePath() { |
|
if (this._x1 !== null) { |
|
this._x1 = this._x0, this._y1 = this._y0; |
|
this._ += "Z"; |
|
} |
|
}, |
|
lineTo: function lineTo(x, y) { |
|
this._ += "L" + (this._x1 = +x) + "," + (this._y1 = +y); |
|
}, |
|
quadraticCurveTo: function quadraticCurveTo(x1, y1, x, y) { |
|
this._ += "Q" + +x1 + "," + +y1 + "," + (this._x1 = +x) + "," + (this._y1 = +y); |
|
}, |
|
bezierCurveTo: function bezierCurveTo(x1, y1, x2, y2, x, y) { |
|
this._ += "C" + +x1 + "," + +y1 + "," + +x2 + "," + +y2 + "," + (this._x1 = +x) + "," + (this._y1 = +y); |
|
}, |
|
arcTo: function arcTo(x1, y1, x2, y2, r) { |
|
x1 = +x1, y1 = +y1, x2 = +x2, y2 = +y2, r = +r; |
|
var x0 = this._x1, |
|
y0 = this._y1, |
|
x21 = x2 - x1, |
|
y21 = y2 - y1, |
|
x01 = x0 - x1, |
|
y01 = y0 - y1, |
|
l01_2 = x01 * x01 + y01 * y01; // Is the radius negative? Error. |
|
|
|
if (r < 0) throw new Error("negative radius: " + r); // Is this path empty? Move to (x1,y1). |
|
|
|
if (this._x1 === null) { |
|
this._ += "M" + (this._x1 = x1) + "," + (this._y1 = y1); |
|
} // Or, is (x1,y1) coincident with (x0,y0)? Do nothing. |
|
else if (!(l01_2 > epsilon$1)) ; // Or, are (x0,y0), (x1,y1) and (x2,y2) collinear? |
|
// Equivalently, is (x1,y1) coincident with (x2,y2)? |
|
// Or, is the radius zero? Line to (x1,y1). |
|
else if (!(Math.abs(y01 * x21 - y21 * x01) > epsilon$1) || !r) { |
|
this._ += "L" + (this._x1 = x1) + "," + (this._y1 = y1); |
|
} // Otherwise, draw an arc! |
|
else { |
|
var x20 = x2 - x0, |
|
y20 = y2 - y0, |
|
l21_2 = x21 * x21 + y21 * y21, |
|
l20_2 = x20 * x20 + y20 * y20, |
|
l21 = Math.sqrt(l21_2), |
|
l01 = Math.sqrt(l01_2), |
|
l = r * Math.tan((pi - Math.acos((l21_2 + l01_2 - l20_2) / (2 * l21 * l01))) / 2), |
|
t01 = l / l01, |
|
t21 = l / l21; // If the start tangent is not coincident with (x0,y0), line to. |
|
|
|
if (Math.abs(t01 - 1) > epsilon$1) { |
|
this._ += "L" + (x1 + t01 * x01) + "," + (y1 + t01 * y01); |
|
} |
|
|
|
this._ += "A" + r + "," + r + ",0,0," + +(y01 * x20 > x01 * y20) + "," + (this._x1 = x1 + t21 * x21) + "," + (this._y1 = y1 + t21 * y21); |
|
} |
|
}, |
|
arc: function arc(x, y, r, a0, a1, ccw) { |
|
x = +x, y = +y, r = +r, ccw = !!ccw; |
|
var dx = r * Math.cos(a0), |
|
dy = r * Math.sin(a0), |
|
x0 = x + dx, |
|
y0 = y + dy, |
|
cw = 1 ^ ccw, |
|
da = ccw ? a0 - a1 : a1 - a0; // Is the radius negative? Error. |
|
|
|
if (r < 0) throw new Error("negative radius: " + r); // Is this path empty? Move to (x0,y0). |
|
|
|
if (this._x1 === null) { |
|
this._ += "M" + x0 + "," + y0; |
|
} // Or, is (x0,y0) not coincident with the previous point? Line to (x0,y0). |
|
else if (Math.abs(this._x1 - x0) > epsilon$1 || Math.abs(this._y1 - y0) > epsilon$1) { |
|
this._ += "L" + x0 + "," + y0; |
|
} // Is this arc empty? We’re done. |
|
|
|
|
|
if (!r) return; // Does the angle go the wrong way? Flip the direction. |
|
|
|
if (da < 0) da = da % tau + tau; // Is this a complete circle? Draw two arcs to complete the circle. |
|
|
|
if (da > tauEpsilon) { |
|
this._ += "A" + r + "," + r + ",0,1," + cw + "," + (x - dx) + "," + (y - dy) + "A" + r + "," + r + ",0,1," + cw + "," + (this._x1 = x0) + "," + (this._y1 = y0); |
|
} // Is this arc non-empty? Draw an arc! |
|
else if (da > epsilon$1) { |
|
this._ += "A" + r + "," + r + ",0," + +(da >= pi) + "," + cw + "," + (this._x1 = x + r * Math.cos(a1)) + "," + (this._y1 = y + r * Math.sin(a1)); |
|
} |
|
}, |
|
rect: function rect(x, y, w, h) { |
|
this._ += "M" + (this._x0 = this._x1 = +x) + "," + (this._y0 = this._y1 = +y) + "h" + +w + "v" + +h + "h" + -w + "Z"; |
|
}, |
|
toString: function toString() { |
|
return this._; |
|
} |
|
}; |
|
|
|
function constant$1(x) { |
|
return function constant() { |
|
return x; |
|
}; |
|
} |
|
|
|
var abs = Math.abs; |
|
var atan2 = Math.atan2; |
|
var cos = Math.cos; |
|
var max$1 = Math.max; |
|
var min$1 = Math.min; |
|
var sin = Math.sin; |
|
var sqrt = Math.sqrt; |
|
var epsilon$2 = 1e-12; |
|
var pi$1 = Math.PI; |
|
var halfPi = pi$1 / 2; |
|
var tau$1 = 2 * pi$1; |
|
|
|
function acos(x) { |
|
return x > 1 ? 0 : x < -1 ? pi$1 : Math.acos(x); |
|
} |
|
|
|
function asin(x) { |
|
return x >= 1 ? halfPi : x <= -1 ? -halfPi : Math.asin(x); |
|
} |
|
|
|
function arcInnerRadius(d) { |
|
return d.innerRadius; |
|
} |
|
|
|
function arcOuterRadius(d) { |
|
return d.outerRadius; |
|
} |
|
|
|
function arcStartAngle(d) { |
|
return d.startAngle; |
|
} |
|
|
|
function arcEndAngle(d) { |
|
return d.endAngle; |
|
} |
|
|
|
function arcPadAngle(d) { |
|
return d && d.padAngle; // Note: optional! |
|
} |
|
|
|
function intersect(x0, y0, x1, y1, x2, y2, x3, y3) { |
|
var x10 = x1 - x0, |
|
y10 = y1 - y0, |
|
x32 = x3 - x2, |
|
y32 = y3 - y2, |
|
t = y32 * x10 - x32 * y10; |
|
if (t * t < epsilon$2) return; |
|
t = (x32 * (y0 - y2) - y32 * (x0 - x2)) / t; |
|
return [x0 + t * x10, y0 + t * y10]; |
|
} // Compute perpendicular offset line of length rc. |
|
// http://mathworld.wolfram.com/Circle-LineIntersection.html |
|
|
|
|
|
function cornerTangents(x0, y0, x1, y1, r1, rc, cw) { |
|
var x01 = x0 - x1, |
|
y01 = y0 - y1, |
|
lo = (cw ? rc : -rc) / sqrt(x01 * x01 + y01 * y01), |
|
ox = lo * y01, |
|
oy = -lo * x01, |
|
x11 = x0 + ox, |
|
y11 = y0 + oy, |
|
x10 = x1 + ox, |
|
y10 = y1 + oy, |
|
x00 = (x11 + x10) / 2, |
|
y00 = (y11 + y10) / 2, |
|
dx = x10 - x11, |
|
dy = y10 - y11, |
|
d2 = dx * dx + dy * dy, |
|
r = r1 - rc, |
|
D = x11 * y10 - x10 * y11, |
|
d = (dy < 0 ? -1 : 1) * sqrt(max$1(0, r * r * d2 - D * D)), |
|
cx0 = (D * dy - dx * d) / d2, |
|
cy0 = (-D * dx - dy * d) / d2, |
|
cx1 = (D * dy + dx * d) / d2, |
|
cy1 = (-D * dx + dy * d) / d2, |
|
dx0 = cx0 - x00, |
|
dy0 = cy0 - y00, |
|
dx1 = cx1 - x00, |
|
dy1 = cy1 - y00; // Pick the closer of the two intersection points. |
|
// TODO Is there a faster way to determine which intersection to use? |
|
|
|
if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; |
|
return { |
|
cx: cx0, |
|
cy: cy0, |
|
x01: -ox, |
|
y01: -oy, |
|
x11: cx0 * (r1 / r - 1), |
|
y11: cy0 * (r1 / r - 1) |
|
}; |
|
} |
|
|
|
function d3_arc() { |
|
var innerRadius = arcInnerRadius, |
|
outerRadius = arcOuterRadius, |
|
cornerRadius = constant$1(0), |
|
padRadius = null, |
|
startAngle = arcStartAngle, |
|
endAngle = arcEndAngle, |
|
padAngle = arcPadAngle, |
|
context = null; |
|
|
|
function arc() { |
|
var buffer, |
|
r, |
|
r0 = +innerRadius.apply(this, arguments), |
|
r1 = +outerRadius.apply(this, arguments), |
|
a0 = startAngle.apply(this, arguments) - halfPi, |
|
a1 = endAngle.apply(this, arguments) - halfPi, |
|
da = abs(a1 - a0), |
|
cw = a1 > a0; |
|
if (!context) context = buffer = path(); // Ensure that the outer radius is always larger than the inner radius. |
|
|
|
if (r1 < r0) r = r1, r1 = r0, r0 = r; // Is it a point? |
|
|
|
if (!(r1 > epsilon$2)) context.moveTo(0, 0); // Or is it a circle or annulus? |
|
else if (da > tau$1 - epsilon$2) { |
|
context.moveTo(r1 * cos(a0), r1 * sin(a0)); |
|
context.arc(0, 0, r1, a0, a1, !cw); |
|
|
|
if (r0 > epsilon$2) { |
|
context.moveTo(r0 * cos(a1), r0 * sin(a1)); |
|
context.arc(0, 0, r0, a1, a0, cw); |
|
} |
|
} // Or is it a circular or annular sector? |
|
else { |
|
var a01 = a0, |
|
a11 = a1, |
|
a00 = a0, |
|
a10 = a1, |
|
da0 = da, |
|
da1 = da, |
|
ap = padAngle.apply(this, arguments) / 2, |
|
rp = ap > epsilon$2 && (padRadius ? +padRadius.apply(this, arguments) : sqrt(r0 * r0 + r1 * r1)), |
|
rc = min$1(abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments)), |
|
rc0 = rc, |
|
rc1 = rc, |
|
t0, |
|
t1; // Apply padding? Note that since r1 ≥ r0, da1 ≥ da0. |
|
|
|
if (rp > epsilon$2) { |
|
var p0 = asin(rp / r0 * sin(ap)), |
|
p1 = asin(rp / r1 * sin(ap)); |
|
if ((da0 -= p0 * 2) > epsilon$2) p0 *= cw ? 1 : -1, a00 += p0, a10 -= p0;else da0 = 0, a00 = a10 = (a0 + a1) / 2; |
|
if ((da1 -= p1 * 2) > epsilon$2) p1 *= cw ? 1 : -1, a01 += p1, a11 -= p1;else da1 = 0, a01 = a11 = (a0 + a1) / 2; |
|
} |
|
|
|
var x01 = r1 * cos(a01), |
|
y01 = r1 * sin(a01), |
|
x10 = r0 * cos(a10), |
|
y10 = r0 * sin(a10); // Apply rounded corners? |
|
|
|
if (rc > epsilon$2) { |
|
var x11 = r1 * cos(a11), |
|
y11 = r1 * sin(a11), |
|
x00 = r0 * cos(a00), |
|
y00 = r0 * sin(a00), |
|
oc; // Restrict the corner radius according to the sector angle. |
|
|
|
if (da < pi$1 && (oc = intersect(x01, y01, x00, y00, x11, y11, x10, y10))) { |
|
var ax = x01 - oc[0], |
|
ay = y01 - oc[1], |
|
bx = x11 - oc[0], |
|
by = y11 - oc[1], |
|
kc = 1 / sin(acos((ax * bx + ay * by) / (sqrt(ax * ax + ay * ay) * sqrt(bx * bx + by * by))) / 2), |
|
lc = sqrt(oc[0] * oc[0] + oc[1] * oc[1]); |
|
rc0 = min$1(rc, (r0 - lc) / (kc - 1)); |
|
rc1 = min$1(rc, (r1 - lc) / (kc + 1)); |
|
} |
|
} // Is the sector collapsed to a line? |
|
|
|
|
|
if (!(da1 > epsilon$2)) context.moveTo(x01, y01); // Does the sector’s outer ring have rounded corners? |
|
else if (rc1 > epsilon$2) { |
|
t0 = cornerTangents(x00, y00, x01, y01, r1, rc1, cw); |
|
t1 = cornerTangents(x11, y11, x10, y10, r1, rc1, cw); |
|
context.moveTo(t0.cx + t0.x01, t0.cy + t0.y01); // Have the corners merged? |
|
|
|
if (rc1 < rc) context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw); // Otherwise, draw the two corners and the ring. |
|
else { |
|
context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw); |
|
context.arc(0, 0, r1, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), !cw); |
|
context.arc(t1.cx, t1.cy, rc1, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw); |
|
} |
|
} // Or is the outer ring just a circular arc? |
|
else context.moveTo(x01, y01), context.arc(0, 0, r1, a01, a11, !cw); // Is there no inner ring, and it’s a circular sector? |
|
// Or perhaps it’s an annular sector collapsed due to padding? |
|
|
|
if (!(r0 > epsilon$2) || !(da0 > epsilon$2)) context.lineTo(x10, y10); // Does the sector’s inner ring (or point) have rounded corners? |
|
else if (rc0 > epsilon$2) { |
|
t0 = cornerTangents(x10, y10, x11, y11, r0, -rc0, cw); |
|
t1 = cornerTangents(x01, y01, x00, y00, r0, -rc0, cw); |
|
context.lineTo(t0.cx + t0.x01, t0.cy + t0.y01); // Have the corners merged? |
|
|
|
if (rc0 < rc) context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw); // Otherwise, draw the two corners and the ring. |
|
else { |
|
context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw); |
|
context.arc(0, 0, r0, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), cw); |
|
context.arc(t1.cx, t1.cy, rc0, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw); |
|
} |
|
} // Or is the inner ring just a circular arc? |
|
else context.arc(0, 0, r0, a10, a00, cw); |
|
} |
|
context.closePath(); |
|
if (buffer) return context = null, buffer + "" || null; |
|
} |
|
|
|
arc.centroid = function () { |
|
var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, |
|
a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - pi$1 / 2; |
|
return [cos(a) * r, sin(a) * r]; |
|
}; |
|
|
|
arc.innerRadius = function (_) { |
|
return arguments.length ? (innerRadius = typeof _ === "function" ? _ : constant$1(+_), arc) : innerRadius; |
|
}; |
|
|
|
arc.outerRadius = function (_) { |
|
return arguments.length ? (outerRadius = typeof _ === "function" ? _ : constant$1(+_), arc) : outerRadius; |
|
}; |
|
|
|
arc.cornerRadius = function (_) { |
|
return arguments.length ? (cornerRadius = typeof _ === "function" ? _ : constant$1(+_), arc) : cornerRadius; |
|
}; |
|
|
|
arc.padRadius = function (_) { |
|
return arguments.length ? (padRadius = _ == null ? null : typeof _ === "function" ? _ : constant$1(+_), arc) : padRadius; |
|
}; |
|
|
|
arc.startAngle = function (_) { |
|
return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant$1(+_), arc) : startAngle; |
|
}; |
|
|
|
arc.endAngle = function (_) { |
|
return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant$1(+_), arc) : endAngle; |
|
}; |
|
|
|
arc.padAngle = function (_) { |
|
return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant$1(+_), arc) : padAngle; |
|
}; |
|
|
|
arc.context = function (_) { |
|
return arguments.length ? (context = _ == null ? null : _, arc) : context; |
|
}; |
|
|
|
return arc; |
|
} |
|
|
|
function Linear(context) { |
|
this._context = context; |
|
} |
|
|
|
Linear.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath(); |
|
this._line = 1 - this._line; |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
// proceed |
|
|
|
default: |
|
this._context.lineTo(x, y); |
|
|
|
break; |
|
} |
|
} |
|
}; |
|
|
|
function curveLinear(context) { |
|
return new Linear(context); |
|
} |
|
|
|
function x(p) { |
|
return p[0]; |
|
} |
|
|
|
function y(p) { |
|
return p[1]; |
|
} |
|
|
|
function d3_line() { |
|
var x$1 = x, |
|
y$1 = y, |
|
defined = constant$1(true), |
|
context = null, |
|
curve = curveLinear, |
|
output = null; |
|
|
|
function line(data) { |
|
var i, |
|
n = data.length, |
|
d, |
|
defined0 = false, |
|
buffer; |
|
if (context == null) output = curve(buffer = path()); |
|
|
|
for (i = 0; i <= n; ++i) { |
|
if (!(i < n && defined(d = data[i], i, data)) === defined0) { |
|
if (defined0 = !defined0) output.lineStart();else output.lineEnd(); |
|
} |
|
|
|
if (defined0) output.point(+x$1(d, i, data), +y$1(d, i, data)); |
|
} |
|
|
|
if (buffer) return output = null, buffer + "" || null; |
|
} |
|
|
|
line.x = function (_) { |
|
return arguments.length ? (x$1 = typeof _ === "function" ? _ : constant$1(+_), line) : x$1; |
|
}; |
|
|
|
line.y = function (_) { |
|
return arguments.length ? (y$1 = typeof _ === "function" ? _ : constant$1(+_), line) : y$1; |
|
}; |
|
|
|
line.defined = function (_) { |
|
return arguments.length ? (defined = typeof _ === "function" ? _ : constant$1(!!_), line) : defined; |
|
}; |
|
|
|
line.curve = function (_) { |
|
return arguments.length ? (curve = _, context != null && (output = curve(context)), line) : curve; |
|
}; |
|
|
|
line.context = function (_) { |
|
return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), line) : context; |
|
}; |
|
|
|
return line; |
|
} |
|
|
|
function d3_area() { |
|
var x0 = x, |
|
x1 = null, |
|
y0 = constant$1(0), |
|
y1 = y, |
|
defined = constant$1(true), |
|
context = null, |
|
curve = curveLinear, |
|
output = null; |
|
|
|
function area(data) { |
|
var i, |
|
j, |
|
k, |
|
n = data.length, |
|
d, |
|
defined0 = false, |
|
buffer, |
|
x0z = new Array(n), |
|
y0z = new Array(n); |
|
if (context == null) output = curve(buffer = path()); |
|
|
|
for (i = 0; i <= n; ++i) { |
|
if (!(i < n && defined(d = data[i], i, data)) === defined0) { |
|
if (defined0 = !defined0) { |
|
j = i; |
|
output.areaStart(); |
|
output.lineStart(); |
|
} else { |
|
output.lineEnd(); |
|
output.lineStart(); |
|
|
|
for (k = i - 1; k >= j; --k) { |
|
output.point(x0z[k], y0z[k]); |
|
} |
|
|
|
output.lineEnd(); |
|
output.areaEnd(); |
|
} |
|
} |
|
|
|
if (defined0) { |
|
x0z[i] = +x0(d, i, data), y0z[i] = +y0(d, i, data); |
|
output.point(x1 ? +x1(d, i, data) : x0z[i], y1 ? +y1(d, i, data) : y0z[i]); |
|
} |
|
} |
|
|
|
if (buffer) return output = null, buffer + "" || null; |
|
} |
|
|
|
function arealine() { |
|
return d3_line().defined(defined).curve(curve).context(context); |
|
} |
|
|
|
area.x = function (_) { |
|
return arguments.length ? (x0 = typeof _ === "function" ? _ : constant$1(+_), x1 = null, area) : x0; |
|
}; |
|
|
|
area.x0 = function (_) { |
|
return arguments.length ? (x0 = typeof _ === "function" ? _ : constant$1(+_), area) : x0; |
|
}; |
|
|
|
area.x1 = function (_) { |
|
return arguments.length ? (x1 = _ == null ? null : typeof _ === "function" ? _ : constant$1(+_), area) : x1; |
|
}; |
|
|
|
area.y = function (_) { |
|
return arguments.length ? (y0 = typeof _ === "function" ? _ : constant$1(+_), y1 = null, area) : y0; |
|
}; |
|
|
|
area.y0 = function (_) { |
|
return arguments.length ? (y0 = typeof _ === "function" ? _ : constant$1(+_), area) : y0; |
|
}; |
|
|
|
area.y1 = function (_) { |
|
return arguments.length ? (y1 = _ == null ? null : typeof _ === "function" ? _ : constant$1(+_), area) : y1; |
|
}; |
|
|
|
area.lineX0 = area.lineY0 = function () { |
|
return arealine().x(x0).y(y0); |
|
}; |
|
|
|
area.lineY1 = function () { |
|
return arealine().x(x0).y(y1); |
|
}; |
|
|
|
area.lineX1 = function () { |
|
return arealine().x(x1).y(y0); |
|
}; |
|
|
|
area.defined = function (_) { |
|
return arguments.length ? (defined = typeof _ === "function" ? _ : constant$1(!!_), area) : defined; |
|
}; |
|
|
|
area.curve = function (_) { |
|
return arguments.length ? (curve = _, context != null && (output = curve(context)), area) : curve; |
|
}; |
|
|
|
area.context = function (_) { |
|
return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), area) : context; |
|
}; |
|
|
|
return area; |
|
} |
|
|
|
var circle = { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size / pi$1); |
|
context.moveTo(r, 0); |
|
context.arc(0, 0, r, 0, tau$1); |
|
} |
|
}; |
|
|
|
function d3_symbol() { |
|
var type = constant$1(circle), |
|
size = constant$1(64), |
|
context = null; |
|
|
|
function symbol() { |
|
var buffer; |
|
if (!context) context = buffer = path(); |
|
type.apply(this, arguments).draw(context, +size.apply(this, arguments)); |
|
if (buffer) return context = null, buffer + "" || null; |
|
} |
|
|
|
symbol.type = function (_) { |
|
return arguments.length ? (type = typeof _ === "function" ? _ : constant$1(_), symbol) : type; |
|
}; |
|
|
|
symbol.size = function (_) { |
|
return arguments.length ? (size = typeof _ === "function" ? _ : constant$1(+_), symbol) : size; |
|
}; |
|
|
|
symbol.context = function (_) { |
|
return arguments.length ? (context = _ == null ? null : _, symbol) : context; |
|
}; |
|
|
|
return symbol; |
|
} |
|
|
|
function noop() {} |
|
|
|
function _point(that, x, y) { |
|
that._context.bezierCurveTo((2 * that._x0 + that._x1) / 3, (2 * that._y0 + that._y1) / 3, (that._x0 + 2 * that._x1) / 3, (that._y0 + 2 * that._y1) / 3, (that._x0 + 4 * that._x1 + x) / 6, (that._y0 + 4 * that._y1 + y) / 6); |
|
} |
|
|
|
function Basis(context) { |
|
this._context = context; |
|
} |
|
|
|
Basis.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._y0 = this._y1 = NaN; |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
switch (this._point) { |
|
case 3: |
|
_point(this, this._x1, this._y1); |
|
|
|
// proceed |
|
|
|
case 2: |
|
this._context.lineTo(this._x1, this._y1); |
|
|
|
break; |
|
} |
|
|
|
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath(); |
|
this._line = 1 - this._line; |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
|
|
this._context.lineTo((5 * this._x0 + this._x1) / 6, (5 * this._y0 + this._y1) / 6); |
|
|
|
// proceed |
|
|
|
default: |
|
_point(this, x, y); |
|
|
|
break; |
|
} |
|
|
|
this._x0 = this._x1, this._x1 = x; |
|
this._y0 = this._y1, this._y1 = y; |
|
} |
|
}; |
|
|
|
function curveBasis(context) { |
|
return new Basis(context); |
|
} |
|
|
|
function BasisClosed(context) { |
|
this._context = context; |
|
} |
|
|
|
BasisClosed.prototype = { |
|
areaStart: noop, |
|
areaEnd: noop, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._x2 = this._x3 = this._x4 = this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = NaN; |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
switch (this._point) { |
|
case 1: |
|
{ |
|
this._context.moveTo(this._x2, this._y2); |
|
|
|
this._context.closePath(); |
|
|
|
break; |
|
} |
|
|
|
case 2: |
|
{ |
|
this._context.moveTo((this._x2 + 2 * this._x3) / 3, (this._y2 + 2 * this._y3) / 3); |
|
|
|
this._context.lineTo((this._x3 + 2 * this._x2) / 3, (this._y3 + 2 * this._y2) / 3); |
|
|
|
this._context.closePath(); |
|
|
|
break; |
|
} |
|
|
|
case 3: |
|
{ |
|
this.point(this._x2, this._y2); |
|
this.point(this._x3, this._y3); |
|
this.point(this._x4, this._y4); |
|
break; |
|
} |
|
} |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
this._x2 = x, this._y2 = y; |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
this._x3 = x, this._y3 = y; |
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
this._x4 = x, this._y4 = y; |
|
|
|
this._context.moveTo((this._x0 + 4 * this._x1 + x) / 6, (this._y0 + 4 * this._y1 + y) / 6); |
|
|
|
break; |
|
|
|
default: |
|
_point(this, x, y); |
|
|
|
break; |
|
} |
|
|
|
this._x0 = this._x1, this._x1 = x; |
|
this._y0 = this._y1, this._y1 = y; |
|
} |
|
}; |
|
|
|
function curveBasisClosed(context) { |
|
return new BasisClosed(context); |
|
} |
|
|
|
function BasisOpen(context) { |
|
this._context = context; |
|
} |
|
|
|
BasisOpen.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._y0 = this._y1 = NaN; |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (this._line || this._line !== 0 && this._point === 3) this._context.closePath(); |
|
this._line = 1 - this._line; |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
var x0 = (this._x0 + 4 * this._x1 + x) / 6, |
|
y0 = (this._y0 + 4 * this._y1 + y) / 6; |
|
this._line ? this._context.lineTo(x0, y0) : this._context.moveTo(x0, y0); |
|
break; |
|
|
|
case 3: |
|
this._point = 4; |
|
// proceed |
|
|
|
default: |
|
_point(this, x, y); |
|
|
|
break; |
|
} |
|
|
|
this._x0 = this._x1, this._x1 = x; |
|
this._y0 = this._y1, this._y1 = y; |
|
} |
|
}; |
|
|
|
function curveBasisOpen(context) { |
|
return new BasisOpen(context); |
|
} |
|
|
|
function Bundle(context, beta) { |
|
this._basis = new Basis(context); |
|
this._beta = beta; |
|
} |
|
|
|
Bundle.prototype = { |
|
lineStart: function lineStart() { |
|
this._x = []; |
|
this._y = []; |
|
|
|
this._basis.lineStart(); |
|
}, |
|
lineEnd: function lineEnd() { |
|
var x = this._x, |
|
y = this._y, |
|
j = x.length - 1; |
|
|
|
if (j > 0) { |
|
var x0 = x[0], |
|
y0 = y[0], |
|
dx = x[j] - x0, |
|
dy = y[j] - y0, |
|
i = -1, |
|
t; |
|
|
|
while (++i <= j) { |
|
t = i / j; |
|
|
|
this._basis.point(this._beta * x[i] + (1 - this._beta) * (x0 + t * dx), this._beta * y[i] + (1 - this._beta) * (y0 + t * dy)); |
|
} |
|
} |
|
|
|
this._x = this._y = null; |
|
|
|
this._basis.lineEnd(); |
|
}, |
|
point: function point(x, y) { |
|
this._x.push(+x); |
|
|
|
this._y.push(+y); |
|
} |
|
}; |
|
|
|
var curveBundle = function custom(beta) { |
|
function bundle(context) { |
|
return beta === 1 ? new Basis(context) : new Bundle(context, beta); |
|
} |
|
|
|
bundle.beta = function (beta) { |
|
return custom(+beta); |
|
}; |
|
|
|
return bundle; |
|
}(0.85); |
|
|
|
function point$1(that, x, y) { |
|
that._context.bezierCurveTo(that._x1 + that._k * (that._x2 - that._x0), that._y1 + that._k * (that._y2 - that._y0), that._x2 + that._k * (that._x1 - x), that._y2 + that._k * (that._y1 - y), that._x2, that._y2); |
|
} |
|
|
|
function Cardinal(context, tension) { |
|
this._context = context; |
|
this._k = (1 - tension) / 6; |
|
} |
|
|
|
Cardinal.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._x2 = this._y0 = this._y1 = this._y2 = NaN; |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
switch (this._point) { |
|
case 2: |
|
this._context.lineTo(this._x2, this._y2); |
|
|
|
break; |
|
|
|
case 3: |
|
point$1(this, this._x1, this._y1); |
|
break; |
|
} |
|
|
|
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath(); |
|
this._line = 1 - this._line; |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
this._x1 = x, this._y1 = y; |
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
// proceed |
|
|
|
default: |
|
point$1(this, x, y); |
|
break; |
|
} |
|
|
|
this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; |
|
this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; |
|
} |
|
}; |
|
|
|
var curveCardinal = function custom(tension) { |
|
function cardinal(context) { |
|
return new Cardinal(context, tension); |
|
} |
|
|
|
cardinal.tension = function (tension) { |
|
return custom(+tension); |
|
}; |
|
|
|
return cardinal; |
|
}(0); |
|
|
|
function CardinalClosed(context, tension) { |
|
this._context = context; |
|
this._k = (1 - tension) / 6; |
|
} |
|
|
|
CardinalClosed.prototype = { |
|
areaStart: noop, |
|
areaEnd: noop, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._x2 = this._x3 = this._x4 = this._x5 = this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = this._y5 = NaN; |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
switch (this._point) { |
|
case 1: |
|
{ |
|
this._context.moveTo(this._x3, this._y3); |
|
|
|
this._context.closePath(); |
|
|
|
break; |
|
} |
|
|
|
case 2: |
|
{ |
|
this._context.lineTo(this._x3, this._y3); |
|
|
|
this._context.closePath(); |
|
|
|
break; |
|
} |
|
|
|
case 3: |
|
{ |
|
this.point(this._x3, this._y3); |
|
this.point(this._x4, this._y4); |
|
this.point(this._x5, this._y5); |
|
break; |
|
} |
|
} |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
this._x3 = x, this._y3 = y; |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
|
|
this._context.moveTo(this._x4 = x, this._y4 = y); |
|
|
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
this._x5 = x, this._y5 = y; |
|
break; |
|
|
|
default: |
|
point$1(this, x, y); |
|
break; |
|
} |
|
|
|
this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; |
|
this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; |
|
} |
|
}; |
|
|
|
var curveCardinalClosed = function custom(tension) { |
|
function cardinal(context) { |
|
return new CardinalClosed(context, tension); |
|
} |
|
|
|
cardinal.tension = function (tension) { |
|
return custom(+tension); |
|
}; |
|
|
|
return cardinal; |
|
}(0); |
|
|
|
function CardinalOpen(context, tension) { |
|
this._context = context; |
|
this._k = (1 - tension) / 6; |
|
} |
|
|
|
CardinalOpen.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._x2 = this._y0 = this._y1 = this._y2 = NaN; |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (this._line || this._line !== 0 && this._point === 3) this._context.closePath(); |
|
this._line = 1 - this._line; |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
this._line ? this._context.lineTo(this._x2, this._y2) : this._context.moveTo(this._x2, this._y2); |
|
break; |
|
|
|
case 3: |
|
this._point = 4; |
|
// proceed |
|
|
|
default: |
|
point$1(this, x, y); |
|
break; |
|
} |
|
|
|
this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; |
|
this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; |
|
} |
|
}; |
|
|
|
var curveCardinalOpen = function custom(tension) { |
|
function cardinal(context) { |
|
return new CardinalOpen(context, tension); |
|
} |
|
|
|
cardinal.tension = function (tension) { |
|
return custom(+tension); |
|
}; |
|
|
|
return cardinal; |
|
}(0); |
|
|
|
function point$2(that, x, y) { |
|
var x1 = that._x1, |
|
y1 = that._y1, |
|
x2 = that._x2, |
|
y2 = that._y2; |
|
|
|
if (that._l01_a > epsilon$2) { |
|
var a = 2 * that._l01_2a + 3 * that._l01_a * that._l12_a + that._l12_2a, |
|
n = 3 * that._l01_a * (that._l01_a + that._l12_a); |
|
x1 = (x1 * a - that._x0 * that._l12_2a + that._x2 * that._l01_2a) / n; |
|
y1 = (y1 * a - that._y0 * that._l12_2a + that._y2 * that._l01_2a) / n; |
|
} |
|
|
|
if (that._l23_a > epsilon$2) { |
|
var b = 2 * that._l23_2a + 3 * that._l23_a * that._l12_a + that._l12_2a, |
|
m = 3 * that._l23_a * (that._l23_a + that._l12_a); |
|
x2 = (x2 * b + that._x1 * that._l23_2a - x * that._l12_2a) / m; |
|
y2 = (y2 * b + that._y1 * that._l23_2a - y * that._l12_2a) / m; |
|
} |
|
|
|
that._context.bezierCurveTo(x1, y1, x2, y2, that._x2, that._y2); |
|
} |
|
|
|
function CatmullRom(context, alpha) { |
|
this._context = context; |
|
this._alpha = alpha; |
|
} |
|
|
|
CatmullRom.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._x2 = this._y0 = this._y1 = this._y2 = NaN; |
|
this._l01_a = this._l12_a = this._l23_a = this._l01_2a = this._l12_2a = this._l23_2a = this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
switch (this._point) { |
|
case 2: |
|
this._context.lineTo(this._x2, this._y2); |
|
|
|
break; |
|
|
|
case 3: |
|
this.point(this._x2, this._y2); |
|
break; |
|
} |
|
|
|
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath(); |
|
this._line = 1 - this._line; |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
if (this._point) { |
|
var x23 = this._x2 - x, |
|
y23 = this._y2 - y; |
|
this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha)); |
|
} |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
// proceed |
|
|
|
default: |
|
point$2(this, x, y); |
|
break; |
|
} |
|
|
|
this._l01_a = this._l12_a, this._l12_a = this._l23_a; |
|
this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a; |
|
this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; |
|
this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; |
|
} |
|
}; |
|
|
|
var curveCatmullRom = function custom(alpha) { |
|
function catmullRom(context) { |
|
return alpha ? new CatmullRom(context, alpha) : new Cardinal(context, 0); |
|
} |
|
|
|
catmullRom.alpha = function (alpha) { |
|
return custom(+alpha); |
|
}; |
|
|
|
return catmullRom; |
|
}(0.5); |
|
|
|
function CatmullRomClosed(context, alpha) { |
|
this._context = context; |
|
this._alpha = alpha; |
|
} |
|
|
|
CatmullRomClosed.prototype = { |
|
areaStart: noop, |
|
areaEnd: noop, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._x2 = this._x3 = this._x4 = this._x5 = this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = this._y5 = NaN; |
|
this._l01_a = this._l12_a = this._l23_a = this._l01_2a = this._l12_2a = this._l23_2a = this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
switch (this._point) { |
|
case 1: |
|
{ |
|
this._context.moveTo(this._x3, this._y3); |
|
|
|
this._context.closePath(); |
|
|
|
break; |
|
} |
|
|
|
case 2: |
|
{ |
|
this._context.lineTo(this._x3, this._y3); |
|
|
|
this._context.closePath(); |
|
|
|
break; |
|
} |
|
|
|
case 3: |
|
{ |
|
this.point(this._x3, this._y3); |
|
this.point(this._x4, this._y4); |
|
this.point(this._x5, this._y5); |
|
break; |
|
} |
|
} |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
if (this._point) { |
|
var x23 = this._x2 - x, |
|
y23 = this._y2 - y; |
|
this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha)); |
|
} |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
this._x3 = x, this._y3 = y; |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
|
|
this._context.moveTo(this._x4 = x, this._y4 = y); |
|
|
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
this._x5 = x, this._y5 = y; |
|
break; |
|
|
|
default: |
|
point$2(this, x, y); |
|
break; |
|
} |
|
|
|
this._l01_a = this._l12_a, this._l12_a = this._l23_a; |
|
this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a; |
|
this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; |
|
this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; |
|
} |
|
}; |
|
|
|
var curveCatmullRomClosed = function custom(alpha) { |
|
function catmullRom(context) { |
|
return alpha ? new CatmullRomClosed(context, alpha) : new CardinalClosed(context, 0); |
|
} |
|
|
|
catmullRom.alpha = function (alpha) { |
|
return custom(+alpha); |
|
}; |
|
|
|
return catmullRom; |
|
}(0.5); |
|
|
|
function CatmullRomOpen(context, alpha) { |
|
this._context = context; |
|
this._alpha = alpha; |
|
} |
|
|
|
CatmullRomOpen.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._x2 = this._y0 = this._y1 = this._y2 = NaN; |
|
this._l01_a = this._l12_a = this._l23_a = this._l01_2a = this._l12_2a = this._l23_2a = this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (this._line || this._line !== 0 && this._point === 3) this._context.closePath(); |
|
this._line = 1 - this._line; |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
if (this._point) { |
|
var x23 = this._x2 - x, |
|
y23 = this._y2 - y; |
|
this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha)); |
|
} |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
this._line ? this._context.lineTo(this._x2, this._y2) : this._context.moveTo(this._x2, this._y2); |
|
break; |
|
|
|
case 3: |
|
this._point = 4; |
|
// proceed |
|
|
|
default: |
|
point$2(this, x, y); |
|
break; |
|
} |
|
|
|
this._l01_a = this._l12_a, this._l12_a = this._l23_a; |
|
this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a; |
|
this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; |
|
this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; |
|
} |
|
}; |
|
|
|
var curveCatmullRomOpen = function custom(alpha) { |
|
function catmullRom(context) { |
|
return alpha ? new CatmullRomOpen(context, alpha) : new CardinalOpen(context, 0); |
|
} |
|
|
|
catmullRom.alpha = function (alpha) { |
|
return custom(+alpha); |
|
}; |
|
|
|
return catmullRom; |
|
}(0.5); |
|
|
|
function LinearClosed(context) { |
|
this._context = context; |
|
} |
|
|
|
LinearClosed.prototype = { |
|
areaStart: noop, |
|
areaEnd: noop, |
|
lineStart: function lineStart() { |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (this._point) this._context.closePath(); |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
if (this._point) this._context.lineTo(x, y);else this._point = 1, this._context.moveTo(x, y); |
|
} |
|
}; |
|
|
|
function curveLinearClosed(context) { |
|
return new LinearClosed(context); |
|
} |
|
|
|
function sign(x) { |
|
return x < 0 ? -1 : 1; |
|
} // Calculate the slopes of the tangents (Hermite-type interpolation) based on |
|
// the following paper: Steffen, M. 1990. A Simple Method for Monotonic |
|
// Interpolation in One Dimension. Astronomy and Astrophysics, Vol. 239, NO. |
|
// NOV(II), P. 443, 1990. |
|
|
|
|
|
function slope3(that, x2, y2) { |
|
var h0 = that._x1 - that._x0, |
|
h1 = x2 - that._x1, |
|
s0 = (that._y1 - that._y0) / (h0 || h1 < 0 && -0), |
|
s1 = (y2 - that._y1) / (h1 || h0 < 0 && -0), |
|
p = (s0 * h1 + s1 * h0) / (h0 + h1); |
|
return (sign(s0) + sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), 0.5 * Math.abs(p)) || 0; |
|
} // Calculate a one-sided slope. |
|
|
|
|
|
function slope2(that, t) { |
|
var h = that._x1 - that._x0; |
|
return h ? (3 * (that._y1 - that._y0) / h - t) / 2 : t; |
|
} // According to https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Representations |
|
// "you can express cubic Hermite interpolation in terms of cubic Bézier curves |
|
// with respect to the four values p0, p0 + m0 / 3, p1 - m1 / 3, p1". |
|
|
|
|
|
function point$3(that, t0, t1) { |
|
var x0 = that._x0, |
|
y0 = that._y0, |
|
x1 = that._x1, |
|
y1 = that._y1, |
|
dx = (x1 - x0) / 3; |
|
|
|
that._context.bezierCurveTo(x0 + dx, y0 + dx * t0, x1 - dx, y1 - dx * t1, x1, y1); |
|
} |
|
|
|
function MonotoneX(context) { |
|
this._context = context; |
|
} |
|
|
|
MonotoneX.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._x0 = this._x1 = this._y0 = this._y1 = this._t0 = NaN; |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
switch (this._point) { |
|
case 2: |
|
this._context.lineTo(this._x1, this._y1); |
|
|
|
break; |
|
|
|
case 3: |
|
point$3(this, this._t0, slope2(this, this._t0)); |
|
break; |
|
} |
|
|
|
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath(); |
|
this._line = 1 - this._line; |
|
}, |
|
point: function point(x, y) { |
|
var t1 = NaN; |
|
x = +x, y = +y; |
|
if (x === this._x1 && y === this._y1) return; // Ignore coincident points. |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
break; |
|
|
|
case 2: |
|
this._point = 3; |
|
point$3(this, slope2(this, t1 = slope3(this, x, y)), t1); |
|
break; |
|
|
|
default: |
|
point$3(this, this._t0, t1 = slope3(this, x, y)); |
|
break; |
|
} |
|
|
|
this._x0 = this._x1, this._x1 = x; |
|
this._y0 = this._y1, this._y1 = y; |
|
this._t0 = t1; |
|
} |
|
}; |
|
|
|
function MonotoneY(context) { |
|
this._context = new ReflectContext(context); |
|
} |
|
|
|
(MonotoneY.prototype = Object.create(MonotoneX.prototype)).point = function (x, y) { |
|
MonotoneX.prototype.point.call(this, y, x); |
|
}; |
|
|
|
function ReflectContext(context) { |
|
this._context = context; |
|
} |
|
|
|
ReflectContext.prototype = { |
|
moveTo: function moveTo(x, y) { |
|
this._context.moveTo(y, x); |
|
}, |
|
closePath: function closePath() { |
|
this._context.closePath(); |
|
}, |
|
lineTo: function lineTo(x, y) { |
|
this._context.lineTo(y, x); |
|
}, |
|
bezierCurveTo: function bezierCurveTo(x1, y1, x2, y2, x, y) { |
|
this._context.bezierCurveTo(y1, x1, y2, x2, y, x); |
|
} |
|
}; |
|
|
|
function monotoneX(context) { |
|
return new MonotoneX(context); |
|
} |
|
|
|
function monotoneY(context) { |
|
return new MonotoneY(context); |
|
} |
|
|
|
function Natural(context) { |
|
this._context = context; |
|
} |
|
|
|
Natural.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._x = []; |
|
this._y = []; |
|
}, |
|
lineEnd: function lineEnd() { |
|
var x = this._x, |
|
y = this._y, |
|
n = x.length; |
|
|
|
if (n) { |
|
this._line ? this._context.lineTo(x[0], y[0]) : this._context.moveTo(x[0], y[0]); |
|
|
|
if (n === 2) { |
|
this._context.lineTo(x[1], y[1]); |
|
} else { |
|
var px = controlPoints(x), |
|
py = controlPoints(y); |
|
|
|
for (var i0 = 0, i1 = 1; i1 < n; ++i0, ++i1) { |
|
this._context.bezierCurveTo(px[0][i0], py[0][i0], px[1][i0], py[1][i0], x[i1], y[i1]); |
|
} |
|
} |
|
} |
|
|
|
if (this._line || this._line !== 0 && n === 1) this._context.closePath(); |
|
this._line = 1 - this._line; |
|
this._x = this._y = null; |
|
}, |
|
point: function point(x, y) { |
|
this._x.push(+x); |
|
|
|
this._y.push(+y); |
|
} |
|
}; // See https://www.particleincell.com/2012/bezier-splines/ for derivation. |
|
|
|
function controlPoints(x) { |
|
var i, |
|
n = x.length - 1, |
|
m, |
|
a = new Array(n), |
|
b = new Array(n), |
|
r = new Array(n); |
|
a[0] = 0, b[0] = 2, r[0] = x[0] + 2 * x[1]; |
|
|
|
for (i = 1; i < n - 1; ++i) { |
|
a[i] = 1, b[i] = 4, r[i] = 4 * x[i] + 2 * x[i + 1]; |
|
} |
|
|
|
a[n - 1] = 2, b[n - 1] = 7, r[n - 1] = 8 * x[n - 1] + x[n]; |
|
|
|
for (i = 1; i < n; ++i) { |
|
m = a[i] / b[i - 1], b[i] -= m, r[i] -= m * r[i - 1]; |
|
} |
|
|
|
a[n - 1] = r[n - 1] / b[n - 1]; |
|
|
|
for (i = n - 2; i >= 0; --i) { |
|
a[i] = (r[i] - a[i + 1]) / b[i]; |
|
} |
|
|
|
b[n - 1] = (x[n] + a[n - 1]) / 2; |
|
|
|
for (i = 0; i < n - 1; ++i) { |
|
b[i] = 2 * x[i + 1] - a[i + 1]; |
|
} |
|
|
|
return [a, b]; |
|
} |
|
|
|
function curveNatural(context) { |
|
return new Natural(context); |
|
} |
|
|
|
function Step(context, t) { |
|
this._context = context; |
|
this._t = t; |
|
} |
|
|
|
Step.prototype = { |
|
areaStart: function areaStart() { |
|
this._line = 0; |
|
}, |
|
areaEnd: function areaEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._x = this._y = NaN; |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (0 < this._t && this._t < 1 && this._point === 2) this._context.lineTo(this._x, this._y); |
|
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath(); |
|
if (this._line >= 0) this._t = 1 - this._t, this._line = 1 - this._line; |
|
}, |
|
point: function point(x, y) { |
|
x = +x, y = +y; |
|
|
|
switch (this._point) { |
|
case 0: |
|
this._point = 1; |
|
this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); |
|
break; |
|
|
|
case 1: |
|
this._point = 2; |
|
// proceed |
|
|
|
default: |
|
{ |
|
if (this._t <= 0) { |
|
this._context.lineTo(this._x, y); |
|
|
|
this._context.lineTo(x, y); |
|
} else { |
|
var x1 = this._x * (1 - this._t) + x * this._t; |
|
|
|
this._context.lineTo(x1, this._y); |
|
|
|
this._context.lineTo(x1, y); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
|
|
this._x = x, this._y = y; |
|
} |
|
}; |
|
|
|
function curveStep(context) { |
|
return new Step(context, 0.5); |
|
} |
|
|
|
function stepBefore(context) { |
|
return new Step(context, 0); |
|
} |
|
|
|
function stepAfter(context) { |
|
return new Step(context, 1); |
|
} |
|
|
|
var lookup = { |
|
'basis': { |
|
curve: curveBasis |
|
}, |
|
'basis-closed': { |
|
curve: curveBasisClosed |
|
}, |
|
'basis-open': { |
|
curve: curveBasisOpen |
|
}, |
|
'bundle': { |
|
curve: curveBundle, |
|
tension: 'beta', |
|
value: 0.85 |
|
}, |
|
'cardinal': { |
|
curve: curveCardinal, |
|
tension: 'tension', |
|
value: 0 |
|
}, |
|
'cardinal-open': { |
|
curve: curveCardinalOpen, |
|
tension: 'tension', |
|
value: 0 |
|
}, |
|
'cardinal-closed': { |
|
curve: curveCardinalClosed, |
|
tension: 'tension', |
|
value: 0 |
|
}, |
|
'catmull-rom': { |
|
curve: curveCatmullRom, |
|
tension: 'alpha', |
|
value: 0.5 |
|
}, |
|
'catmull-rom-closed': { |
|
curve: curveCatmullRomClosed, |
|
tension: 'alpha', |
|
value: 0.5 |
|
}, |
|
'catmull-rom-open': { |
|
curve: curveCatmullRomOpen, |
|
tension: 'alpha', |
|
value: 0.5 |
|
}, |
|
'linear': { |
|
curve: curveLinear |
|
}, |
|
'linear-closed': { |
|
curve: curveLinearClosed |
|
}, |
|
'monotone': { |
|
horizontal: monotoneY, |
|
vertical: monotoneX |
|
}, |
|
'natural': { |
|
curve: curveNatural |
|
}, |
|
'step': { |
|
curve: curveStep |
|
}, |
|
'step-after': { |
|
curve: stepAfter |
|
}, |
|
'step-before': { |
|
curve: stepBefore |
|
} |
|
}; |
|
|
|
function curves(type, orientation, tension) { |
|
var entry = hasOwnProperty(lookup, type) && lookup[type], |
|
curve = null; |
|
|
|
if (entry) { |
|
curve = entry.curve || entry[orientation || 'vertical']; |
|
|
|
if (entry.tension && tension != null) { |
|
curve = curve[entry.tension](tension); |
|
} |
|
} |
|
|
|
return curve; |
|
} // Path parsing and rendering code adapted from fabric.js -- Thanks! |
|
|
|
|
|
var cmdlen = { |
|
m: 2, |
|
l: 2, |
|
h: 1, |
|
v: 1, |
|
c: 6, |
|
s: 4, |
|
q: 4, |
|
t: 2, |
|
a: 7 |
|
}, |
|
regexp = [/([MLHVCSQTAZmlhvcsqtaz])/g, /###/, /(\d)([-+])/g, /\s|,|###/]; |
|
|
|
function pathParse(pathstr) { |
|
var result = [], |
|
path, |
|
curr, |
|
chunks, |
|
parsed, |
|
param, |
|
cmd, |
|
len, |
|
i, |
|
j, |
|
n, |
|
m; // First, break path into command sequence |
|
|
|
path = pathstr.slice().replace(regexp[0], '###$1').split(regexp[1]).slice(1); // Next, parse each command in turn |
|
|
|
for (i = 0, n = path.length; i < n; ++i) { |
|
curr = path[i]; |
|
chunks = curr.slice(1).trim().replace(regexp[2], '$1###$2').split(regexp[3]); |
|
cmd = curr.charAt(0); |
|
parsed = [cmd]; |
|
|
|
for (j = 0, m = chunks.length; j < m; ++j) { |
|
if ((param = +chunks[j]) === param) { |
|
// not NaN |
|
parsed.push(param); |
|
} |
|
} |
|
|
|
len = cmdlen[cmd.toLowerCase()]; |
|
|
|
if (parsed.length - 1 > len) { |
|
for (j = 1, m = parsed.length; j < m; j += len) { |
|
result.push([cmd].concat(parsed.slice(j, j + len))); |
|
} |
|
} else { |
|
result.push(parsed); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
var DegToRad = Math.PI / 180; |
|
var Epsilon = 1e-14; |
|
var HalfPi = Math.PI / 2; |
|
var Tau = Math.PI * 2; |
|
var HalfSqrt3 = Math.sqrt(3) / 2; |
|
var segmentCache = {}; |
|
var bezierCache = {}; |
|
var join = [].join; // Copied from Inkscape svgtopdf, thanks! |
|
|
|
function segments(x, y, rx, ry, large, sweep, rotateX, ox, oy) { |
|
var key = join.call(arguments); |
|
|
|
if (segmentCache[key]) { |
|
return segmentCache[key]; |
|
} |
|
|
|
var th = rotateX * DegToRad; |
|
var sin_th = Math.sin(th); |
|
var cos_th = Math.cos(th); |
|
rx = Math.abs(rx); |
|
ry = Math.abs(ry); |
|
var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5; |
|
var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5; |
|
var pl = px * px / (rx * rx) + py * py / (ry * ry); |
|
|
|
if (pl > 1) { |
|
pl = Math.sqrt(pl); |
|
rx *= pl; |
|
ry *= pl; |
|
} |
|
|
|
var a00 = cos_th / rx; |
|
var a01 = sin_th / rx; |
|
var a10 = -sin_th / ry; |
|
var a11 = cos_th / ry; |
|
var x0 = a00 * ox + a01 * oy; |
|
var y0 = a10 * ox + a11 * oy; |
|
var x1 = a00 * x + a01 * y; |
|
var y1 = a10 * x + a11 * y; |
|
var d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); |
|
var sfactor_sq = 1 / d - 0.25; |
|
if (sfactor_sq < 0) sfactor_sq = 0; |
|
var sfactor = Math.sqrt(sfactor_sq); |
|
if (sweep == large) sfactor = -sfactor; |
|
var xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); |
|
var yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); |
|
var th0 = Math.atan2(y0 - yc, x0 - xc); |
|
var th1 = Math.atan2(y1 - yc, x1 - xc); |
|
var th_arc = th1 - th0; |
|
|
|
if (th_arc < 0 && sweep === 1) { |
|
th_arc += Tau; |
|
} else if (th_arc > 0 && sweep === 0) { |
|
th_arc -= Tau; |
|
} |
|
|
|
var segs = Math.ceil(Math.abs(th_arc / (HalfPi + 0.001))); |
|
var result = []; |
|
|
|
for (var i = 0; i < segs; ++i) { |
|
var th2 = th0 + i * th_arc / segs; |
|
var th3 = th0 + (i + 1) * th_arc / segs; |
|
result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th]; |
|
} |
|
|
|
return segmentCache[key] = result; |
|
} |
|
|
|
function bezier(params) { |
|
var key = join.call(params); |
|
|
|
if (bezierCache[key]) { |
|
return bezierCache[key]; |
|
} |
|
|
|
var cx = params[0], |
|
cy = params[1], |
|
th0 = params[2], |
|
th1 = params[3], |
|
rx = params[4], |
|
ry = params[5], |
|
sin_th = params[6], |
|
cos_th = params[7]; |
|
var a00 = cos_th * rx; |
|
var a01 = -sin_th * ry; |
|
var a10 = sin_th * rx; |
|
var a11 = cos_th * ry; |
|
var cos_th0 = Math.cos(th0); |
|
var sin_th0 = Math.sin(th0); |
|
var cos_th1 = Math.cos(th1); |
|
var sin_th1 = Math.sin(th1); |
|
var th_half = 0.5 * (th1 - th0); |
|
var sin_th_h2 = Math.sin(th_half * 0.5); |
|
var t = 8 / 3 * sin_th_h2 * sin_th_h2 / Math.sin(th_half); |
|
var x1 = cx + cos_th0 - t * sin_th0; |
|
var y1 = cy + sin_th0 + t * cos_th0; |
|
var x3 = cx + cos_th1; |
|
var y3 = cy + sin_th1; |
|
var x2 = x3 + t * sin_th1; |
|
var y2 = y3 - t * cos_th1; |
|
return bezierCache[key] = [a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, a00 * x3 + a01 * y3, a10 * x3 + a11 * y3]; |
|
} |
|
|
|
var temp = ['l', 0, 0, 0, 0, 0, 0, 0]; |
|
|
|
function scale(current, sX, sY) { |
|
var c = temp[0] = current[0]; |
|
|
|
if (c === 'a' || c === 'A') { |
|
temp[1] = sX * current[1]; |
|
temp[2] = sY * current[2]; |
|
temp[3] = current[3]; |
|
temp[4] = current[4]; |
|
temp[5] = current[5]; |
|
temp[6] = sX * current[6]; |
|
temp[7] = sY * current[7]; |
|
} else if (c === 'h' || c === 'H') { |
|
temp[1] = sX * current[1]; |
|
} else if (c === 'v' || c === 'V') { |
|
temp[1] = sY * current[1]; |
|
} else { |
|
for (var i = 1, n = current.length; i < n; ++i) { |
|
temp[i] = (i % 2 == 1 ? sX : sY) * current[i]; |
|
} |
|
} |
|
|
|
return temp; |
|
} |
|
|
|
function pathRender(context, path, l, t, sX, sY) { |
|
var current, |
|
// current instruction |
|
previous = null, |
|
x = 0, |
|
// current x |
|
y = 0, |
|
// current y |
|
controlX = 0, |
|
// current control point x |
|
controlY = 0, |
|
// current control point y |
|
tempX, |
|
tempY, |
|
tempControlX, |
|
tempControlY; |
|
if (l == null) l = 0; |
|
if (t == null) t = 0; |
|
if (sX == null) sX = 1; |
|
if (sY == null) sY = sX; |
|
if (context.beginPath) context.beginPath(); |
|
|
|
for (var i = 0, len = path.length; i < len; ++i) { |
|
current = path[i]; |
|
|
|
if (sX !== 1 || sY !== 1) { |
|
current = scale(current, sX, sY); |
|
} |
|
|
|
switch (current[0]) { |
|
// first letter |
|
case 'l': |
|
// lineto, relative |
|
x += current[1]; |
|
y += current[2]; |
|
context.lineTo(x + l, y + t); |
|
break; |
|
|
|
case 'L': |
|
// lineto, absolute |
|
x = current[1]; |
|
y = current[2]; |
|
context.lineTo(x + l, y + t); |
|
break; |
|
|
|
case 'h': |
|
// horizontal lineto, relative |
|
x += current[1]; |
|
context.lineTo(x + l, y + t); |
|
break; |
|
|
|
case 'H': |
|
// horizontal lineto, absolute |
|
x = current[1]; |
|
context.lineTo(x + l, y + t); |
|
break; |
|
|
|
case 'v': |
|
// vertical lineto, relative |
|
y += current[1]; |
|
context.lineTo(x + l, y + t); |
|
break; |
|
|
|
case 'V': |
|
// verical lineto, absolute |
|
y = current[1]; |
|
context.lineTo(x + l, y + t); |
|
break; |
|
|
|
case 'm': |
|
// moveTo, relative |
|
x += current[1]; |
|
y += current[2]; |
|
context.moveTo(x + l, y + t); |
|
break; |
|
|
|
case 'M': |
|
// moveTo, absolute |
|
x = current[1]; |
|
y = current[2]; |
|
context.moveTo(x + l, y + t); |
|
break; |
|
|
|
case 'c': |
|
// bezierCurveTo, relative |
|
tempX = x + current[5]; |
|
tempY = y + current[6]; |
|
controlX = x + current[3]; |
|
controlY = y + current[4]; |
|
context.bezierCurveTo(x + current[1] + l, // x1 |
|
y + current[2] + t, // y1 |
|
controlX + l, // x2 |
|
controlY + t, // y2 |
|
tempX + l, tempY + t); |
|
x = tempX; |
|
y = tempY; |
|
break; |
|
|
|
case 'C': |
|
// bezierCurveTo, absolute |
|
x = current[5]; |
|
y = current[6]; |
|
controlX = current[3]; |
|
controlY = current[4]; |
|
context.bezierCurveTo(current[1] + l, current[2] + t, controlX + l, controlY + t, x + l, y + t); |
|
break; |
|
|
|
case 's': |
|
// shorthand cubic bezierCurveTo, relative |
|
// transform to absolute x,y |
|
tempX = x + current[3]; |
|
tempY = y + current[4]; // calculate reflection of previous control points |
|
|
|
controlX = 2 * x - controlX; |
|
controlY = 2 * y - controlY; |
|
context.bezierCurveTo(controlX + l, controlY + t, x + current[1] + l, y + current[2] + t, tempX + l, tempY + t); // set control point to 2nd one of this command |
|
// the first control point is assumed to be the reflection of |
|
// the second control point on the previous command relative |
|
// to the current point. |
|
|
|
controlX = x + current[1]; |
|
controlY = y + current[2]; |
|
x = tempX; |
|
y = tempY; |
|
break; |
|
|
|
case 'S': |
|
// shorthand cubic bezierCurveTo, absolute |
|
tempX = current[3]; |
|
tempY = current[4]; // calculate reflection of previous control points |
|
|
|
controlX = 2 * x - controlX; |
|
controlY = 2 * y - controlY; |
|
context.bezierCurveTo(controlX + l, controlY + t, current[1] + l, current[2] + t, tempX + l, tempY + t); |
|
x = tempX; |
|
y = tempY; // set control point to 2nd one of this command |
|
// the first control point is assumed to be the reflection of |
|
// the second control point on the previous command relative |
|
// to the current point. |
|
|
|
controlX = current[1]; |
|
controlY = current[2]; |
|
break; |
|
|
|
case 'q': |
|
// quadraticCurveTo, relative |
|
// transform to absolute x,y |
|
tempX = x + current[3]; |
|
tempY = y + current[4]; |
|
controlX = x + current[1]; |
|
controlY = y + current[2]; |
|
context.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t); |
|
x = tempX; |
|
y = tempY; |
|
break; |
|
|
|
case 'Q': |
|
// quadraticCurveTo, absolute |
|
tempX = current[3]; |
|
tempY = current[4]; |
|
context.quadraticCurveTo(current[1] + l, current[2] + t, tempX + l, tempY + t); |
|
x = tempX; |
|
y = tempY; |
|
controlX = current[1]; |
|
controlY = current[2]; |
|
break; |
|
|
|
case 't': |
|
// shorthand quadraticCurveTo, relative |
|
// transform to absolute x,y |
|
tempX = x + current[1]; |
|
tempY = y + current[2]; |
|
|
|
if (previous[0].match(/[QqTt]/) === null) { |
|
// If there is no previous command or if the previous command was not a Q, q, T or t, |
|
// assume the control point is coincident with the current point |
|
controlX = x; |
|
controlY = y; |
|
} else if (previous[0] === 't') { |
|
// calculate reflection of previous control points for t |
|
controlX = 2 * x - tempControlX; |
|
controlY = 2 * y - tempControlY; |
|
} else if (previous[0] === 'q') { |
|
// calculate reflection of previous control points for q |
|
controlX = 2 * x - controlX; |
|
controlY = 2 * y - controlY; |
|
} |
|
|
|
tempControlX = controlX; |
|
tempControlY = controlY; |
|
context.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t); |
|
x = tempX; |
|
y = tempY; |
|
controlX = x + current[1]; |
|
controlY = y + current[2]; |
|
break; |
|
|
|
case 'T': |
|
tempX = current[1]; |
|
tempY = current[2]; // calculate reflection of previous control points |
|
|
|
controlX = 2 * x - controlX; |
|
controlY = 2 * y - controlY; |
|
context.quadraticCurveTo(controlX + l, controlY + t, tempX + l, tempY + t); |
|
x = tempX; |
|
y = tempY; |
|
break; |
|
|
|
case 'a': |
|
drawArc(context, x + l, y + t, [current[1], current[2], current[3], current[4], current[5], current[6] + x + l, current[7] + y + t]); |
|
x += current[6]; |
|
y += current[7]; |
|
break; |
|
|
|
case 'A': |
|
drawArc(context, x + l, y + t, [current[1], current[2], current[3], current[4], current[5], current[6] + l, current[7] + t]); |
|
x = current[6]; |
|
y = current[7]; |
|
break; |
|
|
|
case 'z': |
|
case 'Z': |
|
context.closePath(); |
|
break; |
|
} |
|
|
|
previous = current; |
|
} |
|
} |
|
|
|
function drawArc(context, x, y, coords) { |
|
var seg = segments(coords[5], // end x |
|
coords[6], // end y |
|
coords[0], // radius x |
|
coords[1], // radius y |
|
coords[3], // large flag |
|
coords[4], // sweep flag |
|
coords[2], // rotation |
|
x, y); |
|
|
|
for (var i = 0; i < seg.length; ++i) { |
|
var bez = bezier(seg[i]); |
|
context.bezierCurveTo(bez[0], bez[1], bez[2], bez[3], bez[4], bez[5]); |
|
} |
|
} |
|
|
|
var Tan30 = 0.5773502691896257; |
|
var builtins = { |
|
'circle': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2; |
|
context.moveTo(r, 0); |
|
context.arc(0, 0, r, 0, Tau); |
|
} |
|
}, |
|
'cross': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2, |
|
s = r / 2.5; |
|
context.moveTo(-r, -s); |
|
context.lineTo(-r, s); |
|
context.lineTo(-s, s); |
|
context.lineTo(-s, r); |
|
context.lineTo(s, r); |
|
context.lineTo(s, s); |
|
context.lineTo(r, s); |
|
context.lineTo(r, -s); |
|
context.lineTo(s, -s); |
|
context.lineTo(s, -r); |
|
context.lineTo(-s, -r); |
|
context.lineTo(-s, -s); |
|
context.closePath(); |
|
} |
|
}, |
|
'diamond': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2; |
|
context.moveTo(-r, 0); |
|
context.lineTo(0, -r); |
|
context.lineTo(r, 0); |
|
context.lineTo(0, r); |
|
context.closePath(); |
|
} |
|
}, |
|
'square': { |
|
draw: function draw(context, size) { |
|
var w = Math.sqrt(size), |
|
x = -w / 2; |
|
context.rect(x, x, w, w); |
|
} |
|
}, |
|
'arrow': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2, |
|
s = r / 7, |
|
t = r / 2.5, |
|
v = r / 8; |
|
context.moveTo(-s, r); |
|
context.lineTo(s, r); |
|
context.lineTo(s, -v); |
|
context.lineTo(t, -v); |
|
context.lineTo(0, -r); |
|
context.lineTo(-t, -v); |
|
context.lineTo(-s, -v); |
|
context.closePath(); |
|
} |
|
}, |
|
'wedge': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2, |
|
h = HalfSqrt3 * r, |
|
o = h - r * Tan30, |
|
b = r / 4; |
|
context.moveTo(0, -h - o); |
|
context.lineTo(-b, h - o); |
|
context.lineTo(b, h - o); |
|
context.closePath(); |
|
} |
|
}, |
|
'triangle': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2, |
|
h = HalfSqrt3 * r, |
|
o = h - r * Tan30; |
|
context.moveTo(0, -h - o); |
|
context.lineTo(-r, h - o); |
|
context.lineTo(r, h - o); |
|
context.closePath(); |
|
} |
|
}, |
|
'triangle-up': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2, |
|
h = HalfSqrt3 * r; |
|
context.moveTo(0, -h); |
|
context.lineTo(-r, h); |
|
context.lineTo(r, h); |
|
context.closePath(); |
|
} |
|
}, |
|
'triangle-down': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2, |
|
h = HalfSqrt3 * r; |
|
context.moveTo(0, h); |
|
context.lineTo(-r, -h); |
|
context.lineTo(r, -h); |
|
context.closePath(); |
|
} |
|
}, |
|
'triangle-right': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2, |
|
h = HalfSqrt3 * r; |
|
context.moveTo(h, 0); |
|
context.lineTo(-h, -r); |
|
context.lineTo(-h, r); |
|
context.closePath(); |
|
} |
|
}, |
|
'triangle-left': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2, |
|
h = HalfSqrt3 * r; |
|
context.moveTo(-h, 0); |
|
context.lineTo(h, -r); |
|
context.lineTo(h, r); |
|
context.closePath(); |
|
} |
|
}, |
|
'stroke': { |
|
draw: function draw(context, size) { |
|
var r = Math.sqrt(size) / 2; |
|
context.moveTo(-r, 0); |
|
context.lineTo(r, 0); |
|
} |
|
} |
|
}; |
|
|
|
function symbols(_) { |
|
return hasOwnProperty(builtins, _) ? builtins[_] : customSymbol(_); |
|
} |
|
|
|
var custom = {}; |
|
|
|
function customSymbol(path) { |
|
if (!hasOwnProperty(custom, path)) { |
|
var parsed = pathParse(path); |
|
custom[path] = { |
|
draw: function draw(context, size) { |
|
pathRender(context, parsed, 0, 0, Math.sqrt(size) / 2); |
|
} |
|
}; |
|
} |
|
|
|
return custom[path]; |
|
} // See http://spencermortensen.com/articles/bezier-circle/ |
|
|
|
|
|
var C = 0.448084975506; // C = 1 - c |
|
|
|
function rectangleX(d) { |
|
return d.x; |
|
} |
|
|
|
function rectangleY(d) { |
|
return d.y; |
|
} |
|
|
|
function rectangleWidth(d) { |
|
return d.width; |
|
} |
|
|
|
function rectangleHeight(d) { |
|
return d.height; |
|
} |
|
|
|
function number$1(_) { |
|
return typeof _ === 'function' ? _ : function () { |
|
return +_; |
|
}; |
|
} |
|
|
|
function clamp(value, min, max) { |
|
return Math.max(min, Math.min(value, max)); |
|
} |
|
|
|
function vg_rect() { |
|
var x = rectangleX, |
|
y = rectangleY, |
|
width = rectangleWidth, |
|
height = rectangleHeight, |
|
crTL = number$1(0), |
|
crTR = crTL, |
|
crBL = crTL, |
|
crBR = crTL, |
|
context = null; |
|
|
|
function rectangle(_, x0, y0) { |
|
var buffer, |
|
x1 = x0 != null ? x0 : +x.call(this, _), |
|
y1 = y0 != null ? y0 : +y.call(this, _), |
|
w = +width.call(this, _), |
|
h = +height.call(this, _), |
|
s = Math.min(w, h) / 2, |
|
tl = clamp(+crTL.call(this, _), 0, s), |
|
tr = clamp(+crTR.call(this, _), 0, s), |
|
bl = clamp(+crBL.call(this, _), 0, s), |
|
br = clamp(+crBR.call(this, _), 0, s); |
|
if (!context) context = buffer = path(); |
|
|
|
if (tl <= 0 && tr <= 0 && bl <= 0 && br <= 0) { |
|
context.rect(x1, y1, w, h); |
|
} else { |
|
var x2 = x1 + w, |
|
y2 = y1 + h; |
|
context.moveTo(x1 + tl, y1); |
|
context.lineTo(x2 - tr, y1); |
|
context.bezierCurveTo(x2 - C * tr, y1, x2, y1 + C * tr, x2, y1 + tr); |
|
context.lineTo(x2, y2 - br); |
|
context.bezierCurveTo(x2, y2 - C * br, x2 - C * br, y2, x2 - br, y2); |
|
context.lineTo(x1 + bl, y2); |
|
context.bezierCurveTo(x1 + C * bl, y2, x1, y2 - C * bl, x1, y2 - bl); |
|
context.lineTo(x1, y1 + tl); |
|
context.bezierCurveTo(x1, y1 + C * tl, x1 + C * tl, y1, x1 + tl, y1); |
|
context.closePath(); |
|
} |
|
|
|
if (buffer) { |
|
context = null; |
|
return buffer + '' || null; |
|
} |
|
} |
|
|
|
rectangle.x = function (_) { |
|
if (arguments.length) { |
|
x = number$1(_); |
|
return rectangle; |
|
} else { |
|
return x; |
|
} |
|
}; |
|
|
|
rectangle.y = function (_) { |
|
if (arguments.length) { |
|
y = number$1(_); |
|
return rectangle; |
|
} else { |
|
return y; |
|
} |
|
}; |
|
|
|
rectangle.width = function (_) { |
|
if (arguments.length) { |
|
width = number$1(_); |
|
return rectangle; |
|
} else { |
|
return width; |
|
} |
|
}; |
|
|
|
rectangle.height = function (_) { |
|
if (arguments.length) { |
|
height = number$1(_); |
|
return rectangle; |
|
} else { |
|
return height; |
|
} |
|
}; |
|
|
|
rectangle.cornerRadius = function (tl, tr, br, bl) { |
|
if (arguments.length) { |
|
crTL = number$1(tl); |
|
crTR = tr != null ? number$1(tr) : crTL; |
|
crBR = br != null ? number$1(br) : crTL; |
|
crBL = bl != null ? number$1(bl) : crTR; |
|
return rectangle; |
|
} else { |
|
return crTL; |
|
} |
|
}; |
|
|
|
rectangle.context = function (_) { |
|
if (arguments.length) { |
|
context = _ == null ? null : _; |
|
return rectangle; |
|
} else { |
|
return context; |
|
} |
|
}; |
|
|
|
return rectangle; |
|
} |
|
|
|
function vg_trail() { |
|
var x, |
|
y, |
|
size, |
|
defined, |
|
context = null, |
|
ready, |
|
x1, |
|
y1, |
|
r1; |
|
|
|
function point(x2, y2, w2) { |
|
var r2 = w2 / 2; |
|
|
|
if (ready) { |
|
var ux = y1 - y2, |
|
uy = x2 - x1; |
|
|
|
if (ux || uy) { |
|
// get normal vector |
|
var ud = Math.sqrt(ux * ux + uy * uy), |
|
rx = (ux /= ud) * r1, |
|
ry = (uy /= ud) * r1, |
|
t = Math.atan2(uy, ux); // draw segment |
|
|
|
context.moveTo(x1 - rx, y1 - ry); |
|
context.lineTo(x2 - ux * r2, y2 - uy * r2); |
|
context.arc(x2, y2, r2, t - Math.PI, t); |
|
context.lineTo(x1 + rx, y1 + ry); |
|
context.arc(x1, y1, r1, t, t + Math.PI); |
|
} else { |
|
context.arc(x2, y2, r2, 0, Tau); |
|
} |
|
|
|
context.closePath(); |
|
} else { |
|
ready = 1; |
|
} |
|
|
|
x1 = x2; |
|
y1 = y2; |
|
r1 = r2; |
|
} |
|
|
|
function trail(data) { |
|
var i, |
|
n = data.length, |
|
d, |
|
defined0 = false, |
|
buffer; |
|
if (context == null) context = buffer = path(); |
|
|
|
for (i = 0; i <= n; ++i) { |
|
if (!(i < n && defined(d = data[i], i, data)) === defined0) { |
|
if (defined0 = !defined0) ready = 0; |
|
} |
|
|
|
if (defined0) point(+x(d, i, data), +y(d, i, data), +size(d, i, data)); |
|
} |
|
|
|
if (buffer) { |
|
context = null; |
|
return buffer + '' || null; |
|
} |
|
} |
|
|
|
trail.x = function (_) { |
|
if (arguments.length) { |
|
x = _; |
|
return trail; |
|
} else { |
|
return x; |
|
} |
|
}; |
|
|
|
trail.y = function (_) { |
|
if (arguments.length) { |
|
y = _; |
|
return trail; |
|
} else { |
|
return y; |
|
} |
|
}; |
|
|
|
trail.size = function (_) { |
|
if (arguments.length) { |
|
size = _; |
|
return trail; |
|
} else { |
|
return size; |
|
} |
|
}; |
|
|
|
trail.defined = function (_) { |
|
if (arguments.length) { |
|
defined = _; |
|
return trail; |
|
} else { |
|
return defined; |
|
} |
|
}; |
|
|
|
trail.context = function (_) { |
|
if (arguments.length) { |
|
if (_ == null) { |
|
context = null; |
|
} else { |
|
context = _; |
|
} |
|
|
|
return trail; |
|
} else { |
|
return context; |
|
} |
|
}; |
|
|
|
return trail; |
|
} |
|
|
|
function value(a, b) { |
|
return a != null ? a : b; |
|
} |
|
|
|
var x$1 = function x$1(item) { |
|
return item.x || 0; |
|
}, |
|
y$1 = function y$1(item) { |
|
return item.y || 0; |
|
}, |
|
w = function w(item) { |
|
return item.width || 0; |
|
}, |
|
h = function h(item) { |
|
return item.height || 0; |
|
}, |
|
xw = function xw(item) { |
|
return (item.x || 0) + (item.width || 0); |
|
}, |
|
yh = function yh(item) { |
|
return (item.y || 0) + (item.height || 0); |
|
}, |
|
sa = function sa(item) { |
|
return item.startAngle || 0; |
|
}, |
|
ea = function ea(item) { |
|
return item.endAngle || 0; |
|
}, |
|
pa = function pa(item) { |
|
return item.padAngle || 0; |
|
}, |
|
ir = function ir(item) { |
|
return item.innerRadius || 0; |
|
}, |
|
or = function or(item) { |
|
return item.outerRadius || 0; |
|
}, |
|
cr = function cr(item) { |
|
return item.cornerRadius || 0; |
|
}, |
|
tl = function tl(item) { |
|
return value(item.cornerRadiusTopLeft, item.cornerRadius) || 0; |
|
}, |
|
tr = function tr(item) { |
|
return value(item.cornerRadiusTopRight, item.cornerRadius) || 0; |
|
}, |
|
br = function br(item) { |
|
return value(item.cornerRadiusBottomRight, item.cornerRadius) || 0; |
|
}, |
|
bl = function bl(item) { |
|
return value(item.cornerRadiusBottomLeft, item.cornerRadius) || 0; |
|
}, |
|
sz = function sz(item) { |
|
return value(item.size, 64); |
|
}, |
|
ts = function ts(item) { |
|
return item.size || 1; |
|
}, |
|
def = function def(item) { |
|
return !(item.defined === false); |
|
}, |
|
type = function type(item) { |
|
return symbols(item.shape || 'circle'); |
|
}; |
|
|
|
var arcShape = d3_arc().startAngle(sa).endAngle(ea).padAngle(pa).innerRadius(ir).outerRadius(or).cornerRadius(cr), |
|
areavShape = d3_area().x(x$1).y1(y$1).y0(yh).defined(def), |
|
areahShape = d3_area().y(y$1).x1(x$1).x0(xw).defined(def), |
|
lineShape = d3_line().x(x$1).y(y$1).defined(def), |
|
rectShape = vg_rect().x(x$1).y(y$1).width(w).height(h).cornerRadius(tl, tr, br, bl), |
|
symbolShape = d3_symbol().type(type).size(sz), |
|
trailShape = vg_trail().x(x$1).y(y$1).defined(def).size(ts); |
|
|
|
function hasCornerRadius(item) { |
|
return item.cornerRadius || item.cornerRadiusTopLeft || item.cornerRadiusTopRight || item.cornerRadiusBottomRight || item.cornerRadiusBottomLeft; |
|
} |
|
|
|
function arc(context, item) { |
|
return arcShape.context(context)(item); |
|
} |
|
|
|
function area(context, items) { |
|
var item = items[0], |
|
interp = item.interpolate || 'linear'; |
|
return (item.orient === 'horizontal' ? areahShape : areavShape).curve(curves(interp, item.orient, item.tension)).context(context)(items); |
|
} |
|
|
|
function line(context, items) { |
|
var item = items[0], |
|
interp = item.interpolate || 'linear'; |
|
return lineShape.curve(curves(interp, item.orient, item.tension)).context(context)(items); |
|
} |
|
|
|
function rectangle(context, item, x, y) { |
|
return rectShape.context(context)(item, x, y); |
|
} |
|
|
|
function shape(context, item) { |
|
return (item.mark.shape || item.shape).context(context)(item); |
|
} |
|
|
|
function symbol(context, item) { |
|
return symbolShape.context(context)(item); |
|
} |
|
|
|
function trail(context, items) { |
|
return trailShape.context(context)(items); |
|
} |
|
|
|
function boundStroke(bounds, item, miter) { |
|
if (item.stroke && item.opacity !== 0 && item.strokeOpacity !== 0) { |
|
var sw = item.strokeWidth != null ? +item.strokeWidth : 1; |
|
bounds.expand(sw + (miter ? miterAdjustment(item, sw) : 0)); |
|
} |
|
|
|
return bounds; |
|
} |
|
|
|
function miterAdjustment(item, strokeWidth) { |
|
// TODO: more sophisticated adjustment? Or miter support in boundContext? |
|
return item.strokeJoin && item.strokeJoin !== 'miter' ? 0 : strokeWidth; |
|
} |
|
|
|
var bounds, |
|
lx, |
|
ly, |
|
circleThreshold = Tau - 1e-8; |
|
|
|
function context(_) { |
|
bounds = _; |
|
return context; |
|
} |
|
|
|
function noop$1() {} |
|
|
|
function add$1(x, y) { |
|
bounds.add(x, y); |
|
} |
|
|
|
function addL(x, y) { |
|
add$1(lx = x, ly = y); |
|
} |
|
|
|
function addX(x) { |
|
add$1(x, bounds.y1); |
|
} |
|
|
|
function addY(y) { |
|
add$1(bounds.x1, y); |
|
} |
|
|
|
context.beginPath = noop$1; |
|
context.closePath = noop$1; |
|
context.moveTo = addL; |
|
context.lineTo = addL; |
|
|
|
context.rect = function (x, y, w, h) { |
|
add$1(x + w, y + h); |
|
addL(x, y); |
|
}; |
|
|
|
context.quadraticCurveTo = function (x1, y1, x2, y2) { |
|
quadExtrema(lx, x1, x2, addX); |
|
quadExtrema(ly, y1, y2, addY); |
|
addL(x2, y2); |
|
}; |
|
|
|
function quadExtrema(x0, x1, x2, cb) { |
|
var t = (x0 - x1) / (x0 + x2 - 2 * x1); |
|
if (0 < t && t < 1) cb(x0 + (x1 - x0) * t); |
|
} |
|
|
|
context.bezierCurveTo = function (x1, y1, x2, y2, x3, y3) { |
|
cubicExtrema(lx, x1, x2, x3, addX); |
|
cubicExtrema(ly, y1, y2, y3, addY); |
|
addL(x3, y3); |
|
}; |
|
|
|
function cubicExtrema(x0, x1, x2, x3, cb) { |
|
var a = x3 - x0 + 3 * x1 - 3 * x2, |
|
b = x0 + x2 - 2 * x1, |
|
c = x0 - x1; |
|
var t0 = 0, |
|
t1 = 0, |
|
r; // solve for parameter t |
|
|
|
if (Math.abs(a) > Epsilon) { |
|
// quadratic equation |
|
r = b * b + c * a; |
|
|
|
if (r >= 0) { |
|
r = Math.sqrt(r); |
|
t0 = (-b + r) / a; |
|
t1 = (-b - r) / a; |
|
} |
|
} else { |
|
// linear equation |
|
t0 = 0.5 * c / b; |
|
} // calculate position |
|
|
|
|
|
if (0 < t0 && t0 < 1) cb(cubic(t0, x0, x1, x2, x3)); |
|
if (0 < t1 && t1 < 1) cb(cubic(t1, x0, x1, x2, x3)); |
|
} |
|
|
|
function cubic(t, x0, x1, x2, x3) { |
|
var s = 1 - t, |
|
s2 = s * s, |
|
t2 = t * t; |
|
return s2 * s * x0 + 3 * s2 * t * x1 + 3 * s * t2 * x2 + t2 * t * x3; |
|
} |
|
|
|
context.arc = function (cx, cy, r, sa, ea, ccw) { |
|
// store last point on path |
|
lx = r * Math.cos(ea) + cx; |
|
ly = r * Math.sin(ea) + cy; |
|
|
|
if (Math.abs(ea - sa) > circleThreshold) { |
|
// treat as full circle |
|
add$1(cx - r, cy - r); |
|
add$1(cx + r, cy + r); |
|
} else { |
|
var _update = function _update(a) { |
|
return add$1(r * Math.cos(a) + cx, r * Math.sin(a) + cy); |
|
}; |
|
|
|
var s, i; // sample end points |
|
|
|
_update(sa); |
|
|
|
_update(ea); // sample interior points aligned with 90 degrees |
|
|
|
|
|
if (ea !== sa) { |
|
sa = sa % Tau; |
|
if (sa < 0) sa += Tau; |
|
ea = ea % Tau; |
|
if (ea < 0) ea += Tau; |
|
|
|
if (ea < sa) { |
|
ccw = !ccw; // flip direction |
|
|
|
s = sa; |
|
sa = ea; |
|
ea = s; // swap end-points |
|
} |
|
|
|
if (ccw) { |
|
ea -= Tau; |
|
s = sa - sa % HalfPi; |
|
|
|
for (i = 0; i < 4 && s > ea; ++i, s -= HalfPi) { |
|
_update(s); |
|
} |
|
} else { |
|
s = sa - sa % HalfPi + HalfPi; |
|
|
|
for (i = 0; i < 4 && s < ea; ++i, s = s + HalfPi) { |
|
_update(s); |
|
} |
|
} |
|
} |
|
} |
|
}; |
|
|
|
var context$1 = (context$1 = domCanvas(1, 1)) ? context$1.getContext('2d') : null; |
|
var b = new Bounds(); |
|
|
|
function intersectPath(draw) { |
|
return function (item, brush) { |
|
// rely on (inaccurate) bounds intersection if no context |
|
if (!context$1) return true; // add path to offscreen graphics context |
|
|
|
draw(context$1, item); // get bounds intersection region |
|
|
|
b.clear().union(item.bounds).intersect(brush).round(); |
|
var x1 = b.x1, |
|
y1 = b.y1, |
|
x2 = b.x2, |
|
y2 = b.y2; // iterate over intersection region |
|
// perform fine grained inclusion test |
|
|
|
for (var _y = y1; _y <= y2; ++_y) { |
|
for (var _x20 = x1; _x20 <= x2; ++_x20) { |
|
if (context$1.isPointInPath(_x20, _y)) { |
|
return true; |
|
} |
|
} |
|
} // false if no hits in intersection region |
|
|
|
|
|
return false; |
|
}; |
|
} |
|
|
|
function intersectPoint(item, box) { |
|
return box.contains(item.x || 0, item.y || 0); |
|
} |
|
|
|
function intersectRect(item, box) { |
|
var x = item.x || 0, |
|
y = item.y || 0, |
|
w = item.width || 0, |
|
h = item.height || 0; |
|
return box.intersects(b.set(x, y, x + w, y + h)); |
|
} |
|
|
|
function intersectRule(item, box) { |
|
var x = item.x || 0, |
|
y = item.y || 0, |
|
x2 = item.x2 != null ? item.x2 : x, |
|
y2 = item.y2 != null ? item.y2 : y; |
|
return intersectBoxLine(box, x, y, x2, y2); |
|
} |
|
|
|
function intersectBoxLine(box, x, y, u, v) { |
|
var x1 = box.x1, |
|
y1 = box.y1, |
|
x2 = box.x2, |
|
y2 = box.y2, |
|
dx = u - x, |
|
dy = v - y; |
|
var t0 = 0, |
|
t1 = 1, |
|
p, |
|
q, |
|
r, |
|
e; |
|
|
|
for (e = 0; e < 4; ++e) { |
|
if (e === 0) { |
|
p = -dx; |
|
q = -(x1 - x); |
|
} |
|
|
|
if (e === 1) { |
|
p = dx; |
|
q = x2 - x; |
|
} |
|
|
|
if (e === 2) { |
|
p = -dy; |
|
q = -(y1 - y); |
|
} |
|
|
|
if (e === 3) { |
|
p = dy; |
|
q = y2 - y; |
|
} |
|
|
|
if (Math.abs(p) < 1e-10 && q < 0) return false; |
|
r = q / p; |
|
|
|
if (p < 0) { |
|
if (r > t1) return false;else if (r > t0) t0 = r; |
|
} else if (p > 0) { |
|
if (r < t0) return false;else if (r < t1) t1 = r; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function gradient(context, gradient, bounds) { |
|
var w = bounds.width(), |
|
h = bounds.height(), |
|
stop = gradient.stops, |
|
n = stop.length; |
|
var canvasGradient = gradient.gradient === 'radial' ? context.createRadialGradient(bounds.x1 + v(gradient.x1, 0.5) * w, bounds.y1 + v(gradient.y1, 0.5) * h, Math.max(w, h) * v(gradient.r1, 0), bounds.x1 + v(gradient.x2, 0.5) * w, bounds.y1 + v(gradient.y2, 0.5) * h, Math.max(w, h) * v(gradient.r2, 0.5)) : context.createLinearGradient(bounds.x1 + v(gradient.x1, 0) * w, bounds.y1 + v(gradient.y1, 0) * h, bounds.x1 + v(gradient.x2, 1) * w, bounds.y1 + v(gradient.y2, 0) * h); |
|
|
|
for (var i = 0; i < n; ++i) { |
|
canvasGradient.addColorStop(stop[i].offset, stop[i].color); |
|
} |
|
|
|
return canvasGradient; |
|
} |
|
|
|
function v(value, dflt) { |
|
return value == null ? dflt : value; |
|
} |
|
|
|
function color(context, item, value) { |
|
return isGradient(value) ? gradient(context, value, item.bounds) : value; |
|
} |
|
|
|
function fill(context, item, opacity) { |
|
opacity *= item.fillOpacity == null ? 1 : item.fillOpacity; |
|
|
|
if (opacity > 0) { |
|
context.globalAlpha = opacity; |
|
context.fillStyle = color(context, item, item.fill); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
var Empty$1 = []; |
|
|
|
function stroke(context, item, opacity) { |
|
var lw = (lw = item.strokeWidth) != null ? lw : 1; |
|
if (lw <= 0) return false; |
|
opacity *= item.strokeOpacity == null ? 1 : item.strokeOpacity; |
|
|
|
if (opacity > 0) { |
|
context.globalAlpha = opacity; |
|
context.strokeStyle = color(context, item, item.stroke); |
|
context.lineWidth = lw; |
|
context.lineCap = item.strokeCap || 'butt'; |
|
context.lineJoin = item.strokeJoin || 'miter'; |
|
context.miterLimit = item.strokeMiterLimit || 10; |
|
|
|
if (context.setLineDash) { |
|
context.setLineDash(item.strokeDash || Empty$1); |
|
context.lineDashOffset = item.strokeDashOffset || 0; |
|
} |
|
|
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
function compare$1(a, b) { |
|
return a.zindex - b.zindex || a.index - b.index; |
|
} |
|
|
|
function zorder(scene) { |
|
if (!scene.zdirty) return scene.zitems; |
|
var items = scene.items, |
|
output = [], |
|
item, |
|
i, |
|
n; |
|
|
|
for (i = 0, n = items.length; i < n; ++i) { |
|
item = items[i]; |
|
item.index = i; |
|
if (item.zindex) output.push(item); |
|
} |
|
|
|
scene.zdirty = false; |
|
return scene.zitems = output.sort(compare$1); |
|
} |
|
|
|
function visit(scene, visitor) { |
|
var items = scene.items, |
|
i, |
|
n; |
|
if (!items || !items.length) return; |
|
var zitems = zorder(scene); |
|
|
|
if (zitems && zitems.length) { |
|
for (i = 0, n = items.length; i < n; ++i) { |
|
if (!items[i].zindex) visitor(items[i]); |
|
} |
|
|
|
items = zitems; |
|
} |
|
|
|
for (i = 0, n = items.length; i < n; ++i) { |
|
visitor(items[i]); |
|
} |
|
} |
|
|
|
function pickVisit(scene, visitor) { |
|
var items = scene.items, |
|
hit, |
|
i; |
|
if (!items || !items.length) return null; |
|
var zitems = zorder(scene); |
|
if (zitems && zitems.length) items = zitems; |
|
|
|
for (i = items.length; --i >= 0;) { |
|
if (hit = visitor(items[i])) return hit; |
|
} |
|
|
|
if (items === zitems) { |
|
for (items = scene.items, i = items.length; --i >= 0;) { |
|
if (!items[i].zindex) { |
|
if (hit = visitor(items[i])) return hit; |
|
} |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function drawAll(path) { |
|
return function (context, scene, bounds) { |
|
visit(scene, function (item) { |
|
if (!bounds || bounds.intersects(item.bounds)) { |
|
drawPath(path, context, item, item); |
|
} |
|
}); |
|
}; |
|
} |
|
|
|
function drawOne(path) { |
|
return function (context, scene, bounds) { |
|
if (scene.items.length && (!bounds || bounds.intersects(scene.bounds))) { |
|
drawPath(path, context, scene.items[0], scene.items); |
|
} |
|
}; |
|
} |
|
|
|
function drawPath(path, context, item, items) { |
|
var opacity = item.opacity == null ? 1 : item.opacity; |
|
if (opacity === 0) return; |
|
if (path(context, items)) return; |
|
|
|
if (item.fill && fill(context, item, opacity)) { |
|
context.fill(); |
|
} |
|
|
|
if (item.stroke && stroke(context, item, opacity)) { |
|
context.stroke(); |
|
} |
|
} |
|
|
|
function pick(test) { |
|
test = test || truthy; |
|
return function (context, scene, x, y, gx, gy) { |
|
x *= context.pixelRatio; |
|
y *= context.pixelRatio; |
|
return pickVisit(scene, function (item) { |
|
var b = item.bounds; // first hit test against bounding box |
|
|
|
if (b && !b.contains(gx, gy) || !b) return; // if in bounding box, perform more careful test |
|
|
|
if (test(context, item, x, y, gx, gy)) return item; |
|
}); |
|
}; |
|
} |
|
|
|
function hitPath(path, filled) { |
|
return function (context, o, x, y) { |
|
var item = Array.isArray(o) ? o[0] : o, |
|
fill = filled == null ? item.fill : filled, |
|
stroke = item.stroke && context.isPointInStroke, |
|
lw, |
|
lc; |
|
|
|
if (stroke) { |
|
lw = item.strokeWidth; |
|
lc = item.strokeCap; |
|
context.lineWidth = lw != null ? lw : 1; |
|
context.lineCap = lc != null ? lc : 'butt'; |
|
} |
|
|
|
return path(context, o) ? false : fill && context.isPointInPath(x, y) || stroke && context.isPointInStroke(x, y); |
|
}; |
|
} |
|
|
|
function pickPath(path) { |
|
return pick(hitPath(path)); |
|
} |
|
|
|
function translate(x, y) { |
|
return 'translate(' + x + ',' + y + ')'; |
|
} |
|
|
|
function rotate(a) { |
|
return 'rotate(' + a + ')'; |
|
} |
|
|
|
function scale$1(scaleX, scaleY) { |
|
return 'scale(' + scaleX + ',' + scaleY + ')'; |
|
} |
|
|
|
function translateItem(item) { |
|
return translate(item.x || 0, item.y || 0); |
|
} |
|
|
|
function transformItem(item) { |
|
return translate(item.x || 0, item.y || 0) + (item.angle ? ' ' + rotate(item.angle) : '') + (item.scaleX || item.scaleY ? ' ' + scale$1(item.scaleX || 1, item.scaleY || 1) : ''); |
|
} |
|
|
|
function markItemPath(type, shape, isect) { |
|
function attr(emit, item) { |
|
emit('transform', transformItem(item)); |
|
emit('d', shape(null, item)); |
|
} |
|
|
|
function bound(bounds, item) { |
|
var x = item.x || 0, |
|
y = item.y || 0; |
|
shape(context(bounds), item); |
|
boundStroke(bounds, item).translate(x, y); |
|
|
|
if (item.angle) { |
|
bounds.rotate(item.angle * DegToRad, x, y); |
|
} |
|
|
|
return bounds; |
|
} |
|
|
|
function draw(context, item) { |
|
var x = item.x || 0, |
|
y = item.y || 0, |
|
a = item.angle || 0; |
|
context.translate(x, y); |
|
if (a) context.rotate(a *= DegToRad); |
|
context.beginPath(); |
|
shape(context, item); |
|
if (a) context.rotate(-a); |
|
context.translate(-x, -y); |
|
} |
|
|
|
return { |
|
type: type, |
|
tag: 'path', |
|
nested: false, |
|
attr: attr, |
|
bound: bound, |
|
draw: drawAll(draw), |
|
pick: pickPath(draw), |
|
isect: isect || intersectPath(draw) |
|
}; |
|
} |
|
|
|
var arc$1 = markItemPath('arc', arc); |
|
|
|
function pickArea(a, p) { |
|
var v = a[0].orient === 'horizontal' ? p[1] : p[0], |
|
z = a[0].orient === 'horizontal' ? 'y' : 'x', |
|
i = a.length, |
|
min = +Infinity, |
|
hit, |
|
d; |
|
|
|
while (--i >= 0) { |
|
if (a[i].defined === false) continue; |
|
d = Math.abs(a[i][z] - v); |
|
|
|
if (d < min) { |
|
min = d; |
|
hit = a[i]; |
|
} |
|
} |
|
|
|
return hit; |
|
} |
|
|
|
function pickLine(a, p) { |
|
var t = Math.pow(a[0].strokeWidth || 1, 2), |
|
i = a.length, |
|
dx, |
|
dy, |
|
dd; |
|
|
|
while (--i >= 0) { |
|
if (a[i].defined === false) continue; |
|
dx = a[i].x - p[0]; |
|
dy = a[i].y - p[1]; |
|
dd = dx * dx + dy * dy; |
|
if (dd < t) return a[i]; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function pickTrail(a, p) { |
|
var i = a.length, |
|
dx, |
|
dy, |
|
dd; |
|
|
|
while (--i >= 0) { |
|
if (a[i].defined === false) continue; |
|
dx = a[i].x - p[0]; |
|
dy = a[i].y - p[1]; |
|
dd = dx * dx + dy * dy; |
|
dx = a[i].size || 1; |
|
if (dd < dx * dx) return a[i]; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function markMultiItemPath(type, shape, tip) { |
|
function attr(emit, item) { |
|
var items = item.mark.items; |
|
if (items.length) emit('d', shape(null, items)); |
|
} |
|
|
|
function bound(bounds, mark) { |
|
var items = mark.items; |
|
|
|
if (items.length === 0) { |
|
return bounds; |
|
} else { |
|
shape(context(bounds), items); |
|
return boundStroke(bounds, items[0]); |
|
} |
|
} |
|
|
|
function draw(context, items) { |
|
context.beginPath(); |
|
shape(context, items); |
|
} |
|
|
|
var hit = hitPath(draw); |
|
|
|
function pick(context, scene, x, y, gx, gy) { |
|
var items = scene.items, |
|
b = scene.bounds; |
|
|
|
if (!items || !items.length || b && !b.contains(gx, gy)) { |
|
return null; |
|
} |
|
|
|
x *= context.pixelRatio; |
|
y *= context.pixelRatio; |
|
return hit(context, items, x, y) ? items[0] : null; |
|
} |
|
|
|
return { |
|
type: type, |
|
tag: 'path', |
|
nested: true, |
|
attr: attr, |
|
bound: bound, |
|
draw: drawOne(draw), |
|
pick: pick, |
|
isect: intersectPoint, |
|
tip: tip |
|
}; |
|
} |
|
|
|
var area$1 = markMultiItemPath('area', area, pickArea); |
|
|
|
function clip(context, scene) { |
|
var clip = scene.clip; |
|
context.save(); |
|
|
|
if (isFunction(clip)) { |
|
context.beginPath(); |
|
clip(context); |
|
context.clip(); |
|
} else { |
|
clipGroup(context, scene.group); |
|
} |
|
} |
|
|
|
function clipGroup(context, group) { |
|
context.beginPath(); |
|
hasCornerRadius(group) ? rectangle(context, group, 0, 0) : context.rect(0, 0, group.width || 0, group.height || 0); |
|
context.clip(); |
|
} |
|
|
|
var clip_id = 1; |
|
|
|
function resetSVGClipId() { |
|
clip_id = 1; |
|
} |
|
|
|
function clip$1(renderer, item, size) { |
|
var clip = item.clip, |
|
defs = renderer._defs, |
|
id = item.clip_id || (item.clip_id = 'clip' + clip_id++), |
|
c = defs.clipping[id] || (defs.clipping[id] = { |
|
id: id |
|
}); |
|
|
|
if (isFunction(clip)) { |
|
c.path = clip(null); |
|
} else if (hasCornerRadius(size)) { |
|
c.path = rectangle(null, size, 0, 0); |
|
} else { |
|
c.width = size.width || 0; |
|
c.height = size.height || 0; |
|
} |
|
|
|
return 'url(#' + id + ')'; |
|
} |
|
|
|
function offset$1(item) { |
|
var sw = (sw = item.strokeWidth) != null ? sw : 1; |
|
return item.strokeOffset != null ? item.strokeOffset : item.stroke && sw > 0.5 && sw < 1.5 ? 0.5 - Math.abs(sw - 1) : 0; |
|
} |
|
|
|
function attr(emit, item) { |
|
emit('transform', translateItem(item)); |
|
} |
|
|
|
function emitRectangle(emit, item) { |
|
var off = offset$1(item); |
|
emit('d', rectangle(null, item, off, off)); |
|
} |
|
|
|
function background(emit, item) { |
|
emit('class', 'background'); |
|
emitRectangle(emit, item); |
|
} |
|
|
|
function foreground(emit, item) { |
|
emit('class', 'foreground'); |
|
|
|
if (item.strokeForeground) { |
|
emitRectangle(emit, item); |
|
} else { |
|
emit('d', ''); |
|
} |
|
} |
|
|
|
function content(emit, item, renderer) { |
|
var url = item.clip ? clip$1(renderer, item, item) : null; |
|
emit('clip-path', url); |
|
} |
|
|
|
function bound(bounds, group) { |
|
if (!group.clip && group.items) { |
|
var items = group.items; |
|
|
|
for (var j = 0, m = items.length; j < m; ++j) { |
|
bounds.union(items[j].bounds); |
|
} |
|
} |
|
|
|
if ((group.clip || group.width || group.height) && !group.noBound) { |
|
bounds.add(0, 0).add(group.width || 0, group.height || 0); |
|
} |
|
|
|
boundStroke(bounds, group); |
|
return bounds.translate(group.x || 0, group.y || 0); |
|
} |
|
|
|
function rectanglePath(context, group, x, y) { |
|
var off = offset$1(group); |
|
context.beginPath(); |
|
rectangle(context, group, (x || 0) + off, (y || 0) + off); |
|
} |
|
|
|
var hitBackground = hitPath(rectanglePath); |
|
var hitForeground = hitPath(rectanglePath, false); |
|
|
|
function draw(context, scene, bounds) { |
|
var renderer = this; |
|
visit(scene, function (group) { |
|
var gx = group.x || 0, |
|
gy = group.y || 0, |
|
fore = group.strokeForeground, |
|
opacity = group.opacity == null ? 1 : group.opacity; // draw group background |
|
|
|
if ((group.stroke || group.fill) && opacity) { |
|
rectanglePath(context, group, gx, gy); |
|
|
|
if (group.fill && fill(context, group, opacity)) { |
|
context.fill(); |
|
} |
|
|
|
if (group.stroke && !fore && stroke(context, group, opacity)) { |
|
context.stroke(); |
|
} |
|
} // setup graphics context, set clip and bounds |
|
|
|
|
|
context.save(); |
|
context.translate(gx, gy); |
|
if (group.clip) clipGroup(context, group); |
|
if (bounds) bounds.translate(-gx, -gy); // draw group contents |
|
|
|
visit(group, function (item) { |
|
renderer.draw(context, item, bounds); |
|
}); // restore graphics context |
|
|
|
if (bounds) bounds.translate(gx, gy); |
|
context.restore(); // draw group foreground |
|
|
|
if (fore && group.stroke && opacity) { |
|
rectanglePath(context, group, gx, gy); |
|
|
|
if (stroke(context, group, opacity)) { |
|
context.stroke(); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
function pick$1(context, scene, x, y, gx, gy) { |
|
if (scene.bounds && !scene.bounds.contains(gx, gy) || !scene.items) { |
|
return null; |
|
} |
|
|
|
var handler = this, |
|
cx = x * context.pixelRatio, |
|
cy = y * context.pixelRatio; |
|
return pickVisit(scene, function (group) { |
|
var hit, fore, ix, dx, dy, dw, dh, b, c; // first hit test bounding box |
|
|
|
b = group.bounds; |
|
if (b && !b.contains(gx, gy)) return; // passed bounds check, test rectangular clip |
|
|
|
dx = group.x || 0; |
|
dy = group.y || 0; |
|
dw = dx + (group.width || 0); |
|
dh = dy + (group.height || 0); |
|
c = group.clip; |
|
if (c && (gx < dx || gx > dw || gy < dx || gy > dh)) return; // adjust coordinate system |
|
|
|
context.save(); |
|
context.translate(dx, dy); |
|
dx = gx - dx; |
|
dy = gy - dy; // test background for rounded corner clip |
|
|
|
if (c && hasCornerRadius(group) && !hitBackground(context, group, cx, cy)) { |
|
context.restore(); |
|
return null; |
|
} |
|
|
|
fore = group.strokeForeground; |
|
ix = scene.interactive !== false; // hit test against group foreground |
|
|
|
if (ix && fore && group.stroke && hitForeground(context, group, cx, cy)) { |
|
context.restore(); |
|
return group; |
|
} // hit test against contained marks |
|
|
|
|
|
hit = pickVisit(group, function (mark) { |
|
return pickMark(mark, dx, dy) ? handler.pick(mark, x, y, dx, dy) : null; |
|
}); // hit test against group background |
|
|
|
if (!hit && ix && (group.fill || !fore && group.stroke) && hitBackground(context, group, cx, cy)) { |
|
hit = group; |
|
} // restore state and return |
|
|
|
|
|
context.restore(); |
|
return hit || null; |
|
}); |
|
} |
|
|
|
function pickMark(mark, x, y) { |
|
return (mark.interactive !== false || mark.marktype === 'group') && mark.bounds && mark.bounds.contains(x, y); |
|
} |
|
|
|
var group = { |
|
type: 'group', |
|
tag: 'g', |
|
nested: false, |
|
attr: attr, |
|
bound: bound, |
|
draw: draw, |
|
pick: pick$1, |
|
isect: intersectRect, |
|
content: content, |
|
background: background, |
|
foreground: foreground |
|
}; |
|
|
|
function getImage(item, renderer) { |
|
var image = item.image; |
|
|
|
if (!image || item.url && item.url !== image.url) { |
|
image = { |
|
complete: false, |
|
width: 0, |
|
height: 0 |
|
}; |
|
renderer.loadImage(item.url).then(function (image) { |
|
item.image = image; |
|
item.image.url = item.url; |
|
}); |
|
} |
|
|
|
return image; |
|
} |
|
|
|
function imageWidth(item, image) { |
|
return item.width != null ? item.width : !image || !image.width ? 0 : item.aspect !== false && item.height ? item.height * image.width / image.height : image.width; |
|
} |
|
|
|
function imageHeight(item, image) { |
|
return item.height != null ? item.height : !image || !image.height ? 0 : item.aspect !== false && item.width ? item.width * image.height / image.width : image.height; |
|
} |
|
|
|
function imageXOffset(align, w) { |
|
return align === 'center' ? w / 2 : align === 'right' ? w : 0; |
|
} |
|
|
|
function imageYOffset(baseline, h) { |
|
return baseline === 'middle' ? h / 2 : baseline === 'bottom' ? h : 0; |
|
} |
|
|
|
function attr$1(emit, item, renderer) { |
|
var image = getImage(item, renderer), |
|
x = item.x || 0, |
|
y = item.y || 0, |
|
w = imageWidth(item, image), |
|
h = imageHeight(item, image), |
|
a = item.aspect === false ? 'none' : 'xMidYMid'; |
|
x -= imageXOffset(item.align, w); |
|
y -= imageYOffset(item.baseline, h); |
|
|
|
if (!image.src && image.toDataURL) { |
|
emit('href', image.toDataURL(), 'http://www.w3.org/1999/xlink', 'xlink:href'); |
|
} else { |
|
emit('href', image.src || '', 'http://www.w3.org/1999/xlink', 'xlink:href'); |
|
} |
|
|
|
emit('transform', translate(x, y)); |
|
emit('width', w); |
|
emit('height', h); |
|
emit('preserveAspectRatio', a); |
|
} |
|
|
|
function bound$1(bounds, item) { |
|
var image = item.image, |
|
x = item.x || 0, |
|
y = item.y || 0, |
|
w = imageWidth(item, image), |
|
h = imageHeight(item, image); |
|
x -= imageXOffset(item.align, w); |
|
y -= imageYOffset(item.baseline, h); |
|
return bounds.set(x, y, x + w, y + h); |
|
} |
|
|
|
function draw$1(context, scene, bounds) { |
|
var renderer = this; |
|
visit(scene, function (item) { |
|
if (bounds && !bounds.intersects(item.bounds)) return; // bounds check |
|
|
|
var image = getImage(item, renderer), |
|
x = item.x || 0, |
|
y = item.y || 0, |
|
w = imageWidth(item, image), |
|
h = imageHeight(item, image), |
|
opacity, |
|
ar0, |
|
ar1, |
|
t; |
|
x -= imageXOffset(item.align, w); |
|
y -= imageYOffset(item.baseline, h); |
|
|
|
if (item.aspect !== false) { |
|
ar0 = image.width / image.height; |
|
ar1 = item.width / item.height; |
|
|
|
if (ar0 === ar0 && ar1 === ar1 && ar0 !== ar1) { |
|
if (ar1 < ar0) { |
|
t = w / ar0; |
|
y += (h - t) / 2; |
|
h = t; |
|
} else { |
|
t = h * ar0; |
|
x += (w - t) / 2; |
|
w = t; |
|
} |
|
} |
|
} |
|
|
|
if (image.complete || image.toDataURL) { |
|
context.globalAlpha = (opacity = item.opacity) != null ? opacity : 1; |
|
context.imageSmoothingEnabled = item.smooth !== false; |
|
context.drawImage(image, x, y, w, h); |
|
} |
|
}); |
|
} |
|
|
|
var image = { |
|
type: 'image', |
|
tag: 'image', |
|
nested: false, |
|
attr: attr$1, |
|
bound: bound$1, |
|
draw: draw$1, |
|
pick: pick(), |
|
isect: truthy, |
|
// bounds check is sufficient |
|
get: getImage, |
|
xOffset: imageXOffset, |
|
yOffset: imageYOffset |
|
}; |
|
var line$1 = markMultiItemPath('line', line, pickLine); |
|
|
|
function attr$2(emit, item) { |
|
var sx = item.scaleX || 1, |
|
sy = item.scaleY || 1; |
|
|
|
if (sx !== 1 || sy !== 1) { |
|
emit('vector-effect', 'non-scaling-stroke'); |
|
} |
|
|
|
emit('transform', transformItem(item)); |
|
emit('d', item.path); |
|
} |
|
|
|
function path$1(context, item) { |
|
var path = item.path; |
|
if (path == null) return true; |
|
var x = item.x || 0, |
|
y = item.y || 0, |
|
sx = item.scaleX || 1, |
|
sy = item.scaleY || 1, |
|
a = (item.angle || 0) * DegToRad, |
|
cache = item.pathCache; |
|
|
|
if (!cache || cache.path !== path) { |
|
(item.pathCache = cache = pathParse(path)).path = path; |
|
} |
|
|
|
if (a && context.rotate && context.translate) { |
|
context.translate(x, y); |
|
context.rotate(a); |
|
pathRender(context, cache, 0, 0, sx, sy); |
|
context.rotate(-a); |
|
context.translate(-x, -y); |
|
} else { |
|
pathRender(context, cache, x, y, sx, sy); |
|
} |
|
} |
|
|
|
function bound$2(bounds, item) { |
|
path$1(context(bounds), item) ? bounds.set(0, 0, 0, 0) : boundStroke(bounds, item, true); |
|
|
|
if (item.angle) { |
|
bounds.rotate(item.angle * DegToRad, item.x || 0, item.y || 0); |
|
} |
|
|
|
return bounds; |
|
} |
|
|
|
var path$2 = { |
|
type: 'path', |
|
tag: 'path', |
|
nested: false, |
|
attr: attr$2, |
|
bound: bound$2, |
|
draw: drawAll(path$1), |
|
pick: pickPath(path$1), |
|
isect: intersectPath(path$1) |
|
}; |
|
|
|
function attr$3(emit, item) { |
|
emit('d', rectangle(null, item)); |
|
} |
|
|
|
function bound$3(bounds, item) { |
|
var x, y; |
|
return boundStroke(bounds.set(x = item.x || 0, y = item.y || 0, x + item.width || 0, y + item.height || 0), item); |
|
} |
|
|
|
function draw$2(context, item) { |
|
context.beginPath(); |
|
rectangle(context, item); |
|
} |
|
|
|
var rect = { |
|
type: 'rect', |
|
tag: 'path', |
|
nested: false, |
|
attr: attr$3, |
|
bound: bound$3, |
|
draw: drawAll(draw$2), |
|
pick: pickPath(draw$2), |
|
isect: intersectRect |
|
}; |
|
|
|
function attr$4(emit, item) { |
|
emit('transform', translateItem(item)); |
|
emit('x2', item.x2 != null ? item.x2 - (item.x || 0) : 0); |
|
emit('y2', item.y2 != null ? item.y2 - (item.y || 0) : 0); |
|
} |
|
|
|
function bound$4(bounds, item) { |
|
var x1, y1; |
|
return boundStroke(bounds.set(x1 = item.x || 0, y1 = item.y || 0, item.x2 != null ? item.x2 : x1, item.y2 != null ? item.y2 : y1), item); |
|
} |
|
|
|
function path$3(context, item, opacity) { |
|
var x1, y1, x2, y2; |
|
|
|
if (item.stroke && stroke(context, item, opacity)) { |
|
x1 = item.x || 0; |
|
y1 = item.y || 0; |
|
x2 = item.x2 != null ? item.x2 : x1; |
|
y2 = item.y2 != null ? item.y2 : y1; |
|
context.beginPath(); |
|
context.moveTo(x1, y1); |
|
context.lineTo(x2, y2); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function draw$3(context, scene, bounds) { |
|
visit(scene, function (item) { |
|
if (bounds && !bounds.intersects(item.bounds)) return; // bounds check |
|
|
|
var opacity = item.opacity == null ? 1 : item.opacity; |
|
|
|
if (opacity && path$3(context, item, opacity)) { |
|
context.stroke(); |
|
} |
|
}); |
|
} |
|
|
|
function hit(context, item, x, y) { |
|
if (!context.isPointInStroke) return false; |
|
return path$3(context, item, 1) && context.isPointInStroke(x, y); |
|
} |
|
|
|
var rule = { |
|
type: 'rule', |
|
tag: 'line', |
|
nested: false, |
|
attr: attr$4, |
|
bound: bound$4, |
|
draw: draw$3, |
|
pick: pick(hit), |
|
isect: intersectRule |
|
}; |
|
var shape$1 = markItemPath('shape', shape); |
|
var symbol$1 = markItemPath('symbol', symbol, intersectPoint); |
|
var currFontHeight; |
|
var textMetrics = { |
|
height: fontSize, |
|
measureWidth: measureWidth, |
|
estimateWidth: estimateWidth, |
|
width: estimateWidth, |
|
canvas: useCanvas |
|
}; |
|
useCanvas(true); // make dumb, simple estimate if no canvas is available |
|
|
|
function estimateWidth(item, text) { |
|
currFontHeight = fontSize(item); |
|
return estimate(textValue(item, text)); |
|
} |
|
|
|
function estimate(text) { |
|
return ~~(0.8 * text.length * currFontHeight); |
|
} // measure text width if canvas is available |
|
|
|
|
|
function measureWidth(item, text) { |
|
return fontSize(item) <= 0 ? 0 : (context$1.font = font(item), measure$1(textValue(item, text))); |
|
} |
|
|
|
function measure$1(text) { |
|
return context$1.measureText(text).width; |
|
} |
|
|
|
function fontSize(item) { |
|
return item.fontSize != null ? item.fontSize : 11; |
|
} |
|
|
|
function useCanvas(use) { |
|
textMetrics.width = use && context$1 ? measureWidth : estimateWidth; |
|
} |
|
|
|
function lineHeight(item) { |
|
return item.lineHeight != null ? item.lineHeight : fontSize(item) + 2; |
|
} |
|
|
|
function lineArray(_) { |
|
return isArray(_) ? _.length > 1 ? _ : _[0] : _; |
|
} |
|
|
|
function textLines(item) { |
|
return lineArray(item.lineBreak && item.text && !isArray(item.text) ? item.text.split(item.lineBreak) : item.text); |
|
} |
|
|
|
function multiLineOffset(item) { |
|
var tl = textLines(item); |
|
return (isArray(tl) ? tl.length - 1 : 0) * lineHeight(item); |
|
} |
|
|
|
function textValue(item, line) { |
|
return line == null ? '' : item.limit > 0 ? truncate$1(item, line) : line + ''; |
|
} |
|
|
|
function truncate$1(item, line) { |
|
var limit = +item.limit, |
|
text = line + '', |
|
width; |
|
|
|
if (textMetrics.width === measureWidth) { |
|
// we are using canvas |
|
context$1.font = font(item); |
|
width = measure$1; |
|
} else { |
|
// we are relying on estimates |
|
currFontHeight = fontSize(item); |
|
width = estimate; |
|
} |
|
|
|
if (width(text) < limit) return text; |
|
var ellipsis = item.ellipsis || "\u2026", |
|
rtl = item.dir === 'rtl', |
|
lo = 0, |
|
hi = text.length, |
|
mid; |
|
limit -= width(ellipsis); |
|
|
|
if (rtl) { |
|
while (lo < hi) { |
|
mid = lo + hi >>> 1; |
|
if (width(text.slice(mid)) > limit) lo = mid + 1;else hi = mid; |
|
} |
|
|
|
return ellipsis + text.slice(lo); |
|
} else { |
|
while (lo < hi) { |
|
mid = 1 + (lo + hi >>> 1); |
|
if (width(text.slice(0, mid)) < limit) lo = mid;else hi = mid - 1; |
|
} |
|
|
|
return text.slice(0, lo) + ellipsis; |
|
} |
|
} |
|
|
|
function fontFamily(item, quote) { |
|
var font = item.font; |
|
return (quote && font ? String(font).replace(/"/g, '\'') : font) || 'sans-serif'; |
|
} |
|
|
|
function font(item, quote) { |
|
return '' + (item.fontStyle ? item.fontStyle + ' ' : '') + (item.fontVariant ? item.fontVariant + ' ' : '') + (item.fontWeight ? item.fontWeight + ' ' : '') + fontSize(item) + 'px ' + fontFamily(item, quote); |
|
} |
|
|
|
function offset$2(item) { |
|
// perform our own font baseline calculation |
|
// why? not all browsers support SVG 1.1 'alignment-baseline' :( |
|
var baseline = item.baseline, |
|
h = fontSize(item); |
|
return Math.round(baseline === 'top' ? 0.79 * h : baseline === 'middle' ? 0.30 * h : baseline === 'bottom' ? -0.21 * h : 0); |
|
} |
|
|
|
var textAlign = { |
|
'left': 'start', |
|
'center': 'middle', |
|
'right': 'end' |
|
}; |
|
var tempBounds = new Bounds(); |
|
|
|
function anchorPoint(item) { |
|
var x = item.x || 0, |
|
y = item.y || 0, |
|
r = item.radius || 0, |
|
t; |
|
|
|
if (r) { |
|
t = (item.theta || 0) - HalfPi; |
|
x += r * Math.cos(t); |
|
y += r * Math.sin(t); |
|
} |
|
|
|
tempBounds.x1 = x; |
|
tempBounds.y1 = y; |
|
return tempBounds; |
|
} |
|
|
|
function attr$5(emit, item) { |
|
var dx = item.dx || 0, |
|
dy = (item.dy || 0) + offset$2(item), |
|
p = anchorPoint(item), |
|
x = p.x1, |
|
y = p.y1, |
|
a = item.angle || 0, |
|
t; |
|
emit('text-anchor', textAlign[item.align] || 'start'); |
|
|
|
if (a) { |
|
t = translate(x, y) + ' ' + rotate(a); |
|
if (dx || dy) t += ' ' + translate(dx, dy); |
|
} else { |
|
t = translate(x + dx, y + dy); |
|
} |
|
|
|
emit('transform', t); |
|
} |
|
|
|
function bound$5(bounds, item, mode) { |
|
var h = textMetrics.height(item), |
|
a = item.align, |
|
p = anchorPoint(item), |
|
x = p.x1, |
|
y = p.y1, |
|
dx = item.dx || 0, |
|
dy = (item.dy || 0) + offset$2(item) - Math.round(0.8 * h), |
|
// use 4/5 offset |
|
tl = textLines(item), |
|
w; // get dimensions |
|
|
|
if (isArray(tl)) { |
|
// multi-line text |
|
h += lineHeight(item) * (tl.length - 1); |
|
w = tl.reduce(function (w, t) { |
|
return Math.max(w, textMetrics.width(item, t)); |
|
}, 0); |
|
} else { |
|
// single-line text |
|
w = textMetrics.width(item, tl); |
|
} // horizontal alignment |
|
|
|
|
|
if (a === 'center') { |
|
dx -= w / 2; |
|
} else if (a === 'right') { |
|
dx -= w; |
|
} |
|
|
|
bounds.set(dx += x, dy += y, dx + w, dy + h); |
|
|
|
if (item.angle && !mode) { |
|
bounds.rotate(item.angle * DegToRad, x, y); |
|
} else if (mode === 2) { |
|
return bounds.rotatedPoints(item.angle * DegToRad, x, y); |
|
} |
|
|
|
return bounds; |
|
} |
|
|
|
function draw$4(context, scene, bounds) { |
|
visit(scene, function (item) { |
|
var opacity = item.opacity == null ? 1 : item.opacity, |
|
p, |
|
x, |
|
y, |
|
i, |
|
lh, |
|
tl, |
|
str; |
|
if (bounds && !bounds.intersects(item.bounds) || // bounds check |
|
opacity === 0 || item.fontSize <= 0 || item.text == null || item.text.length === 0) return; |
|
context.font = font(item); |
|
context.textAlign = item.align || 'left'; |
|
p = anchorPoint(item); |
|
x = p.x1, y = p.y1; |
|
|
|
if (item.angle) { |
|
context.save(); |
|
context.translate(x, y); |
|
context.rotate(item.angle * DegToRad); |
|
x = y = 0; // reset x, y |
|
} |
|
|
|
x += item.dx || 0; |
|
y += (item.dy || 0) + offset$2(item); |
|
tl = textLines(item); |
|
|
|
if (isArray(tl)) { |
|
lh = lineHeight(item); |
|
|
|
for (i = 0; i < tl.length; ++i) { |
|
str = textValue(item, tl[i]); |
|
|
|
if (item.fill && fill(context, item, opacity)) { |
|
context.fillText(str, x, y); |
|
} |
|
|
|
if (item.stroke && stroke(context, item, opacity)) { |
|
context.strokeText(str, x, y); |
|
} |
|
|
|
y += lh; |
|
} |
|
} else { |
|
str = textValue(item, tl); |
|
|
|
if (item.fill && fill(context, item, opacity)) { |
|
context.fillText(str, x, y); |
|
} |
|
|
|
if (item.stroke && stroke(context, item, opacity)) { |
|
context.strokeText(str, x, y); |
|
} |
|
} |
|
|
|
if (item.angle) context.restore(); |
|
}); |
|
} |
|
|
|
function hit$1(context, item, x, y, gx, gy) { |
|
if (item.fontSize <= 0) return false; |
|
if (!item.angle) return true; // bounds sufficient if no rotation |
|
// project point into space of unrotated bounds |
|
|
|
var p = anchorPoint(item), |
|
ax = p.x1, |
|
ay = p.y1, |
|
b = bound$5(tempBounds, item, 1), |
|
a = -item.angle * DegToRad, |
|
cos = Math.cos(a), |
|
sin = Math.sin(a), |
|
px = cos * gx - sin * gy + (ax - cos * ax + sin * ay), |
|
py = sin * gx + cos * gy + (ay - sin * ax - cos * ay); |
|
return b.contains(px, py); |
|
} |
|
|
|
function intersectText(item, box) { |
|
var p = bound$5(tempBounds, item, 2); |
|
return intersectBoxLine(box, p[0], p[1], p[2], p[3]) || intersectBoxLine(box, p[0], p[1], p[4], p[5]) || intersectBoxLine(box, p[4], p[5], p[6], p[7]) || intersectBoxLine(box, p[2], p[3], p[6], p[7]); |
|
} |
|
|
|
var text = { |
|
type: 'text', |
|
tag: 'text', |
|
nested: false, |
|
attr: attr$5, |
|
bound: bound$5, |
|
draw: draw$4, |
|
pick: pick(hit$1), |
|
isect: intersectText |
|
}; |
|
var trail$1 = markMultiItemPath('trail', trail, pickTrail); |
|
var Marks = { |
|
arc: arc$1, |
|
area: area$1, |
|
group: group, |
|
image: image, |
|
line: line$1, |
|
path: path$2, |
|
rect: rect, |
|
rule: rule, |
|
shape: shape$1, |
|
symbol: symbol$1, |
|
text: text, |
|
trail: trail$1 |
|
}; |
|
|
|
function boundItem(item, func, opt) { |
|
var type = Marks[item.mark.marktype], |
|
bound = func || type.bound; |
|
if (type.nested) item = item.mark; |
|
return bound(item.bounds || (item.bounds = new Bounds()), item, opt); |
|
} |
|
|
|
var DUMMY = { |
|
mark: null |
|
}; |
|
|
|
function boundMark(mark, bounds, opt) { |
|
var type = Marks[mark.marktype], |
|
bound = type.bound, |
|
items = mark.items, |
|
hasItems = items && items.length, |
|
i, |
|
n, |
|
item, |
|
b; |
|
|
|
if (type.nested) { |
|
if (hasItems) { |
|
item = items[0]; |
|
} else { |
|
// no items, fake it |
|
DUMMY.mark = mark; |
|
item = DUMMY; |
|
} |
|
|
|
b = boundItem(item, bound, opt); |
|
bounds = bounds && bounds.union(b) || b; |
|
return bounds; |
|
} |
|
|
|
bounds = bounds || mark.bounds && mark.bounds.clear() || new Bounds(); |
|
|
|
if (hasItems) { |
|
for (i = 0, n = items.length; i < n; ++i) { |
|
bounds.union(boundItem(items[i], bound, opt)); |
|
} |
|
} |
|
|
|
return mark.bounds = bounds; |
|
} |
|
|
|
var keys = ['marktype', 'name', 'role', 'interactive', 'clip', 'items', 'zindex', 'x', 'y', 'width', 'height', 'align', 'baseline', // layout |
|
'fill', 'fillOpacity', 'opacity', // fill |
|
'stroke', 'strokeOpacity', 'strokeWidth', 'strokeCap', // stroke |
|
'strokeDash', 'strokeDashOffset', // stroke dash |
|
'strokeForeground', 'strokeOffset', // group |
|
'startAngle', 'endAngle', 'innerRadius', 'outerRadius', // arc |
|
'cornerRadius', 'padAngle', // arc, rect |
|
'cornerRadiusTopLeft', 'cornerRadiusTopRight', // rect, group |
|
'cornerRadiusBottomLeft', 'cornerRadiusBottomRight', 'interpolate', 'tension', 'orient', 'defined', // area, line |
|
'url', 'aspect', 'smooth', // image |
|
'path', 'scaleX', 'scaleY', // path |
|
'x2', 'y2', // rule |
|
'size', 'shape', // symbol |
|
'text', 'angle', 'theta', 'radius', 'dir', 'dx', 'dy', // text |
|
'ellipsis', 'limit', 'lineBreak', 'lineHeight', 'font', 'fontSize', 'fontWeight', 'fontStyle', 'fontVariant' // font |
|
]; |
|
|
|
function sceneToJSON(scene, indent) { |
|
return JSON.stringify(scene, keys, indent); |
|
} |
|
|
|
function sceneFromJSON(json) { |
|
var scene = typeof json === 'string' ? JSON.parse(json) : json; |
|
return initialize(scene); |
|
} |
|
|
|
function initialize(scene) { |
|
var type = scene.marktype, |
|
items = scene.items, |
|
parent, |
|
i, |
|
n; |
|
|
|
if (items) { |
|
for (i = 0, n = items.length; i < n; ++i) { |
|
parent = type ? 'mark' : 'group'; |
|
items[i][parent] = scene; |
|
if (items[i].zindex) items[i][parent].zdirty = true; |
|
if ('group' === (type || parent)) initialize(items[i]); |
|
} |
|
} |
|
|
|
if (type) boundMark(scene); |
|
return scene; |
|
} |
|
|
|
function Scenegraph(scene) { |
|
if (arguments.length) { |
|
this.root = sceneFromJSON(scene); |
|
} else { |
|
this.root = createMark({ |
|
marktype: 'group', |
|
name: 'root', |
|
role: 'frame' |
|
}); |
|
this.root.items = [new GroupItem(this.root)]; |
|
} |
|
} |
|
|
|
var prototype$I = Scenegraph.prototype; |
|
|
|
prototype$I.toJSON = function (indent) { |
|
return sceneToJSON(this.root, indent || 0); |
|
}; |
|
|
|
prototype$I.mark = function (markdef, group, index) { |
|
group = group || this.root.items[0]; |
|
var mark = createMark(markdef, group); |
|
group.items[index] = mark; |
|
if (mark.zindex) mark.group.zdirty = true; |
|
return mark; |
|
}; |
|
|
|
function createMark(def, group) { |
|
return { |
|
bounds: new Bounds(), |
|
clip: !!def.clip, |
|
group: group, |
|
interactive: def.interactive === false ? false : true, |
|
items: [], |
|
marktype: def.marktype, |
|
name: def.name || undefined, |
|
role: def.role || undefined, |
|
zindex: def.zindex || 0 |
|
}; |
|
} // create a new DOM element |
|
|
|
|
|
function domCreate(doc, tag, ns) { |
|
if (!doc && typeof document !== 'undefined' && document.createElement) { |
|
doc = document; |
|
} |
|
|
|
return doc ? ns ? doc.createElementNS(ns, tag) : doc.createElement(tag) : null; |
|
} // find first child element with matching tag |
|
|
|
|
|
function domFind(el, tag) { |
|
tag = tag.toLowerCase(); |
|
var nodes = el.childNodes, |
|
i = 0, |
|
n = nodes.length; |
|
|
|
for (; i < n; ++i) { |
|
if (nodes[i].tagName.toLowerCase() === tag) { |
|
return nodes[i]; |
|
} |
|
} |
|
} // retrieve child element at given index |
|
// create & insert if doesn't exist or if tags do not match |
|
|
|
|
|
function domChild(el, index, tag, ns) { |
|
var a = el.childNodes[index], |
|
b; |
|
|
|
if (!a || a.tagName.toLowerCase() !== tag.toLowerCase()) { |
|
b = a || null; |
|
a = domCreate(el.ownerDocument, tag, ns); |
|
el.insertBefore(a, b); |
|
} |
|
|
|
return a; |
|
} // remove all child elements at or above the given index |
|
|
|
|
|
function domClear(el, index) { |
|
var nodes = el.childNodes, |
|
curr = nodes.length; |
|
|
|
while (curr > index) { |
|
el.removeChild(nodes[--curr]); |
|
} |
|
|
|
return el; |
|
} // generate css class name for mark |
|
|
|
|
|
function cssClass(mark) { |
|
return 'mark-' + mark.marktype + (mark.role ? ' role-' + mark.role : '') + (mark.name ? ' ' + mark.name : ''); |
|
} |
|
|
|
function point$4(event, el) { |
|
var rect = el.getBoundingClientRect(); |
|
return [event.clientX - rect.left - (el.clientLeft || 0), event.clientY - rect.top - (el.clientTop || 0)]; |
|
} |
|
|
|
function resolveItem(item, event, el, origin) { |
|
var mark = item && item.mark, |
|
mdef, |
|
p; |
|
|
|
if (mark && (mdef = Marks[mark.marktype]).tip) { |
|
p = point$4(event, el); |
|
p[0] -= origin[0]; |
|
p[1] -= origin[1]; |
|
|
|
while (item = item.mark.group) { |
|
p[0] -= item.x || 0; |
|
p[1] -= item.y || 0; |
|
} |
|
|
|
item = mdef.tip(mark.items, p); |
|
} |
|
|
|
return item; |
|
} |
|
/** |
|
* Create a new Handler instance. |
|
* @param {object} [customLoader] - Optional loader instance for |
|
* href URL sanitization. If not specified, a standard loader |
|
* instance will be generated. |
|
* @param {function} [customTooltip] - Optional tooltip handler |
|
* function for custom tooltip display. |
|
* @constructor |
|
*/ |
|
|
|
|
|
function Handler(customLoader, customTooltip) { |
|
this._active = null; |
|
this._handlers = {}; |
|
this._loader = customLoader || loader(); |
|
this._tooltip = customTooltip || defaultTooltip; |
|
} // The default tooltip display handler. |
|
// Sets the HTML title attribute on the visualization container. |
|
|
|
|
|
function defaultTooltip(handler, event, item, value) { |
|
handler.element().setAttribute('title', value || ''); |
|
} |
|
|
|
var prototype$J = Handler.prototype; |
|
/** |
|
* Initialize a new Handler instance. |
|
* @param {DOMElement} el - The containing DOM element for the display. |
|
* @param {Array<number>} origin - The origin of the display, in pixels. |
|
* The coordinate system will be translated to this point. |
|
* @param {object} [obj] - Optional context object that should serve as |
|
* the "this" context for event callbacks. |
|
* @return {Handler} - This handler instance. |
|
*/ |
|
|
|
prototype$J.initialize = function (el, origin, obj) { |
|
this._el = el; |
|
this._obj = obj || null; |
|
return this.origin(origin); |
|
}; |
|
/** |
|
* Returns the parent container element for a visualization. |
|
* @return {DOMElement} - The containing DOM element. |
|
*/ |
|
|
|
|
|
prototype$J.element = function () { |
|
return this._el; |
|
}; |
|
/** |
|
* Returns the scene element (e.g., canvas or SVG) of the visualization |
|
* Subclasses must override if the first child is not the scene element. |
|
* @return {DOMElement} - The scene (e.g., canvas or SVG) element. |
|
*/ |
|
|
|
|
|
prototype$J.canvas = function () { |
|
return this._el && this._el.firstChild; |
|
}; |
|
/** |
|
* Get / set the origin coordinates of the visualization. |
|
*/ |
|
|
|
|
|
prototype$J.origin = function (origin) { |
|
if (arguments.length) { |
|
this._origin = origin || [0, 0]; |
|
return this; |
|
} else { |
|
return this._origin.slice(); |
|
} |
|
}; |
|
/** |
|
* Get / set the scenegraph root. |
|
*/ |
|
|
|
|
|
prototype$J.scene = function (scene) { |
|
if (!arguments.length) return this._scene; |
|
this._scene = scene; |
|
return this; |
|
}; |
|
/** |
|
* Add an event handler. Subclasses should override this method. |
|
*/ |
|
|
|
|
|
prototype$J.on = function () |
|
/*type, handler*/ |
|
{}; |
|
/** |
|
* Remove an event handler. Subclasses should override this method. |
|
*/ |
|
|
|
|
|
prototype$J.off = function () |
|
/*type, handler*/ |
|
{}; |
|
/** |
|
* Utility method for finding the array index of an event handler. |
|
* @param {Array} h - An array of registered event handlers. |
|
* @param {string} type - The event type. |
|
* @param {function} handler - The event handler instance to find. |
|
* @return {number} - The handler's array index or -1 if not registered. |
|
*/ |
|
|
|
|
|
prototype$J._handlerIndex = function (h, type, handler) { |
|
for (var i = h ? h.length : 0; --i >= 0;) { |
|
if (h[i].type === type && (!handler || h[i].handler === handler)) { |
|
return i; |
|
} |
|
} |
|
|
|
return -1; |
|
}; |
|
/** |
|
* Returns an array with registered event handlers. |
|
* @param {string} [type] - The event type to query. Any annotations |
|
* are ignored; for example, for the argument "click.foo", ".foo" will |
|
* be ignored and the method returns all "click" handlers. If type is |
|
* null or unspecified, this method returns handlers for all types. |
|
* @return {Array} - A new array containing all registered event handlers. |
|
*/ |
|
|
|
|
|
prototype$J.handlers = function (type) { |
|
var h = this._handlers, |
|
a = [], |
|
k; |
|
|
|
if (type) { |
|
a.push.apply(a, h[this.eventName(type)]); |
|
} else { |
|
for (k in h) { |
|
a.push.apply(a, h[k]); |
|
} |
|
} |
|
|
|
return a; |
|
}; |
|
/** |
|
* Parses an event name string to return the specific event type. |
|
* For example, given "click.foo" returns "click" |
|
* @param {string} name - The input event type string. |
|
* @return {string} - A string with the event type only. |
|
*/ |
|
|
|
|
|
prototype$J.eventName = function (name) { |
|
var i = name.indexOf('.'); |
|
return i < 0 ? name : name.slice(0, i); |
|
}; |
|
/** |
|
* Handle hyperlink navigation in response to an item.href value. |
|
* @param {Event} event - The event triggering hyperlink navigation. |
|
* @param {Item} item - The scenegraph item. |
|
* @param {string} href - The URL to navigate to. |
|
*/ |
|
|
|
|
|
prototype$J.handleHref = function (event, item, href) { |
|
this._loader.sanitize(href, { |
|
context: 'href' |
|
}).then(function (opt) { |
|
var e = new MouseEvent(event.type, event), |
|
a = domCreate(null, 'a'); |
|
|
|
for (var name in opt) { |
|
a.setAttribute(name, opt[name]); |
|
} |
|
|
|
a.dispatchEvent(e); |
|
}).catch(function () { |
|
/* do nothing */ |
|
}); |
|
}; |
|
/** |
|
* Handle tooltip display in response to an item.tooltip value. |
|
* @param {Event} event - The event triggering tooltip display. |
|
* @param {Item} item - The scenegraph item. |
|
* @param {boolean} show - A boolean flag indicating whether |
|
* to show or hide a tooltip for the given item. |
|
*/ |
|
|
|
|
|
prototype$J.handleTooltip = function (event, item, show) { |
|
if (item && item.tooltip != null) { |
|
item = resolveItem(item, event, this.canvas(), this._origin); |
|
var value = show && item && item.tooltip || null; |
|
|
|
this._tooltip.call(this._obj, this, event, item, value); |
|
} |
|
}; |
|
/** |
|
* Returns the size of a scenegraph item and its position relative |
|
* to the viewport. |
|
* @param {Item} item - The scenegraph item. |
|
* @return {object} - A bounding box object (compatible with the |
|
* DOMRect type) consisting of x, y, width, heigh, top, left, |
|
* right, and bottom properties. |
|
*/ |
|
|
|
|
|
prototype$J.getItemBoundingClientRect = function (item) { |
|
if (!(el = this.canvas())) return; |
|
var el, |
|
rect = el.getBoundingClientRect(), |
|
origin = this._origin, |
|
itemBounds = item.bounds, |
|
x = itemBounds.x1 + origin[0] + rect.left, |
|
y = itemBounds.y1 + origin[1] + rect.top, |
|
w = itemBounds.width(), |
|
h = itemBounds.height(); // translate coordinate for each parent group |
|
|
|
while (item.mark && (item = item.mark.group)) { |
|
x += item.x || 0; |
|
y += item.y || 0; |
|
} // return DOMRect-compatible bounding box |
|
|
|
|
|
return { |
|
x: x, |
|
y: y, |
|
width: w, |
|
height: h, |
|
left: x, |
|
top: y, |
|
right: x + w, |
|
bottom: y + h |
|
}; |
|
}; |
|
/** |
|
* Create a new Renderer instance. |
|
* @param {object} [loader] - Optional loader instance for |
|
* image and href URL sanitization. If not specified, a |
|
* standard loader instance will be generated. |
|
* @constructor |
|
*/ |
|
|
|
|
|
function Renderer(loader) { |
|
this._el = null; |
|
this._bgcolor = null; |
|
this._loader = new ResourceLoader(loader); |
|
} |
|
|
|
var prototype$K = Renderer.prototype; |
|
/** |
|
* Initialize a new Renderer instance. |
|
* @param {DOMElement} el - The containing DOM element for the display. |
|
* @param {number} width - The coordinate width of the display, in pixels. |
|
* @param {number} height - The coordinate height of the display, in pixels. |
|
* @param {Array<number>} origin - The origin of the display, in pixels. |
|
* The coordinate system will be translated to this point. |
|
* @param {number} [scaleFactor=1] - Optional scaleFactor by which to multiply |
|
* the width and height to determine the final pixel size. |
|
* @return {Renderer} - This renderer instance. |
|
*/ |
|
|
|
prototype$K.initialize = function (el, width, height, origin, scaleFactor) { |
|
this._el = el; |
|
return this.resize(width, height, origin, scaleFactor); |
|
}; |
|
/** |
|
* Returns the parent container element for a visualization. |
|
* @return {DOMElement} - The containing DOM element. |
|
*/ |
|
|
|
|
|
prototype$K.element = function () { |
|
return this._el; |
|
}; |
|
/** |
|
* Returns the scene element (e.g., canvas or SVG) of the visualization |
|
* Subclasses must override if the first child is not the scene element. |
|
* @return {DOMElement} - The scene (e.g., canvas or SVG) element. |
|
*/ |
|
|
|
|
|
prototype$K.canvas = function () { |
|
return this._el && this._el.firstChild; |
|
}; |
|
/** |
|
* Get / set the background color. |
|
*/ |
|
|
|
|
|
prototype$K.background = function (bgcolor) { |
|
if (arguments.length === 0) return this._bgcolor; |
|
this._bgcolor = bgcolor; |
|
return this; |
|
}; |
|
/** |
|
* Resize the display. |
|
* @param {number} width - The new coordinate width of the display, in pixels. |
|
* @param {number} height - The new coordinate height of the display, in pixels. |
|
* @param {Array<number>} origin - The new origin of the display, in pixels. |
|
* The coordinate system will be translated to this point. |
|
* @param {number} [scaleFactor=1] - Optional scaleFactor by which to multiply |
|
* the width and height to determine the final pixel size. |
|
* @return {Renderer} - This renderer instance; |
|
*/ |
|
|
|
|
|
prototype$K.resize = function (width, height, origin, scaleFactor) { |
|
this._width = width; |
|
this._height = height; |
|
this._origin = origin || [0, 0]; |
|
this._scale = scaleFactor || 1; |
|
return this; |
|
}; |
|
/** |
|
* Report a dirty item whose bounds should be redrawn. |
|
* This base class method does nothing. Subclasses that perform |
|
* incremental should implement this method. |
|
* @param {Item} item - The dirty item whose bounds should be redrawn. |
|
*/ |
|
|
|
|
|
prototype$K.dirty = function () |
|
/*item*/ |
|
{}; |
|
/** |
|
* Render an input scenegraph, potentially with a set of dirty items. |
|
* This method will perform an immediate rendering with available resources. |
|
* The renderer may also need to perform image loading to perform a complete |
|
* render. This process can lead to asynchronous re-rendering of the scene |
|
* after this method returns. To receive notification when rendering is |
|
* complete, use the renderAsync method instead. |
|
* @param {object} scene - The root mark of a scenegraph to render. |
|
* @return {Renderer} - This renderer instance. |
|
*/ |
|
|
|
|
|
prototype$K.render = function (scene) { |
|
var r = this; // bind arguments into a render call, and cache it |
|
// this function may be subsequently called for async redraw |
|
|
|
r._call = function () { |
|
r._render(scene); |
|
}; // invoke the renderer |
|
|
|
|
|
r._call(); // clear the cached call for garbage collection |
|
// async redraws will stash their own copy |
|
|
|
|
|
r._call = null; |
|
return r; |
|
}; |
|
/** |
|
* Internal rendering method. Renderer subclasses should override this |
|
* method to actually perform rendering. |
|
* @param {object} scene - The root mark of a scenegraph to render. |
|
*/ |
|
|
|
|
|
prototype$K._render = function () |
|
/*scene*/ |
|
{// subclasses to override |
|
}; |
|
/** |
|
* Asynchronous rendering method. Similar to render, but returns a Promise |
|
* that resolves when all rendering is completed. Sometimes a renderer must |
|
* perform image loading to get a complete rendering. The returned |
|
* Promise will not resolve until this process completes. |
|
* @param {object} scene - The root mark of a scenegraph to render. |
|
* @return {Promise} - A Promise that resolves when rendering is complete. |
|
*/ |
|
|
|
|
|
prototype$K.renderAsync = function (scene) { |
|
var r = this.render(scene); |
|
return this._ready ? this._ready.then(function () { |
|
return r; |
|
}) : Promise.resolve(r); |
|
}; |
|
/** |
|
* Internal method for asynchronous resource loading. |
|
* Proxies method calls to the ImageLoader, and tracks loading |
|
* progress to invoke a re-render once complete. |
|
* @param {string} method - The method name to invoke on the ImageLoader. |
|
* @param {string} uri - The URI for the requested resource. |
|
* @return {Promise} - A Promise that resolves to the requested resource. |
|
*/ |
|
|
|
|
|
prototype$K._load = function (method, uri) { |
|
var r = this, |
|
p = r._loader[method](uri); |
|
|
|
if (!r._ready) { |
|
// re-render the scene when loading completes |
|
var call = r._call; |
|
r._ready = r._loader.ready().then(function (redraw) { |
|
if (redraw) call(); |
|
r._ready = null; |
|
}); |
|
} |
|
|
|
return p; |
|
}; |
|
/** |
|
* Sanitize a URL to include as a hyperlink in the rendered scene. |
|
* This method proxies a call to ImageLoader.sanitizeURL, but also tracks |
|
* image loading progress and invokes a re-render once complete. |
|
* @param {string} uri - The URI string to sanitize. |
|
* @return {Promise} - A Promise that resolves to the sanitized URL. |
|
*/ |
|
|
|
|
|
prototype$K.sanitizeURL = function (uri) { |
|
return this._load('sanitizeURL', uri); |
|
}; |
|
/** |
|
* Requests an image to include in the rendered scene. |
|
* This method proxies a call to ImageLoader.loadImage, but also tracks |
|
* image loading progress and invokes a re-render once complete. |
|
* @param {string} uri - The URI string of the image. |
|
* @return {Promise} - A Promise that resolves to the loaded Image. |
|
*/ |
|
|
|
|
|
prototype$K.loadImage = function (uri) { |
|
return this._load('loadImage', uri); |
|
}; |
|
|
|
var Events = ['keydown', 'keypress', 'keyup', 'dragenter', 'dragleave', 'dragover', 'mousedown', 'mouseup', 'mousemove', 'mouseout', 'mouseover', 'click', 'dblclick', 'wheel', 'mousewheel', 'touchstart', 'touchmove', 'touchend']; |
|
var TooltipShowEvent = 'mousemove'; |
|
var TooltipHideEvent = 'mouseout'; |
|
var HrefEvent = 'click'; |
|
|
|
function CanvasHandler(loader, tooltip) { |
|
Handler.call(this, loader, tooltip); |
|
this._down = null; |
|
this._touch = null; |
|
this._first = true; |
|
} |
|
|
|
var prototype$L = inherits(CanvasHandler, Handler); |
|
|
|
prototype$L.initialize = function (el, origin, obj) { |
|
// add event listeners |
|
var canvas = this._canvas = el && domFind(el, 'canvas'); |
|
|
|
if (canvas) { |
|
var that = this; |
|
this.events.forEach(function (type) { |
|
canvas.addEventListener(type, function (evt) { |
|
if (prototype$L[type]) { |
|
prototype$L[type].call(that, evt); |
|
} else { |
|
that.fire(type, evt); |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
return Handler.prototype.initialize.call(this, el, origin, obj); |
|
}; // return the backing canvas instance |
|
|
|
|
|
prototype$L.canvas = function () { |
|
return this._canvas; |
|
}; // retrieve the current canvas context |
|
|
|
|
|
prototype$L.context = function () { |
|
return this._canvas.getContext('2d'); |
|
}; // supported events |
|
|
|
|
|
prototype$L.events = Events; // to keep old versions of firefox happy |
|
|
|
prototype$L.DOMMouseScroll = function (evt) { |
|
this.fire('mousewheel', evt); |
|
}; |
|
|
|
function move(moveEvent, overEvent, outEvent) { |
|
return function (evt) { |
|
var a = this._active, |
|
p = this.pickEvent(evt); |
|
|
|
if (p === a) { |
|
// active item and picked item are the same |
|
this.fire(moveEvent, evt); // fire move |
|
} else { |
|
// active item and picked item are different |
|
if (!a || !a.exit) { |
|
// fire out for prior active item |
|
// suppress if active item was removed from scene |
|
this.fire(outEvent, evt); |
|
} |
|
|
|
this._active = p; // set new active item |
|
|
|
this.fire(overEvent, evt); // fire over for new active item |
|
|
|
this.fire(moveEvent, evt); // fire move for new active item |
|
} |
|
}; |
|
} |
|
|
|
function inactive(type) { |
|
return function (evt) { |
|
this.fire(type, evt); |
|
this._active = null; |
|
}; |
|
} |
|
|
|
prototype$L.mousemove = move('mousemove', 'mouseover', 'mouseout'); |
|
prototype$L.dragover = move('dragover', 'dragenter', 'dragleave'); |
|
prototype$L.mouseout = inactive('mouseout'); |
|
prototype$L.dragleave = inactive('dragleave'); |
|
|
|
prototype$L.mousedown = function (evt) { |
|
this._down = this._active; |
|
this.fire('mousedown', evt); |
|
}; |
|
|
|
prototype$L.click = function (evt) { |
|
if (this._down === this._active) { |
|
this.fire('click', evt); |
|
this._down = null; |
|
} |
|
}; |
|
|
|
prototype$L.touchstart = function (evt) { |
|
this._touch = this.pickEvent(evt.changedTouches[0]); |
|
|
|
if (this._first) { |
|
this._active = this._touch; |
|
this._first = false; |
|
} |
|
|
|
this.fire('touchstart', evt, true); |
|
}; |
|
|
|
prototype$L.touchmove = function (evt) { |
|
this.fire('touchmove', evt, true); |
|
}; |
|
|
|
prototype$L.touchend = function (evt) { |
|
this.fire('touchend', evt, true); |
|
this._touch = null; |
|
}; // fire an event |
|
|
|
|
|
prototype$L.fire = function (type, evt, touch) { |
|
var a = touch ? this._touch : this._active, |
|
h = this._handlers[type], |
|
i, |
|
len; // set event type relative to scenegraph items |
|
|
|
evt.vegaType = type; // handle hyperlinks and tooltips first |
|
|
|
if (type === HrefEvent && a && a.href) { |
|
this.handleHref(evt, a, a.href); |
|
} else if (type === TooltipShowEvent || type === TooltipHideEvent) { |
|
this.handleTooltip(evt, a, type !== TooltipHideEvent); |
|
} // invoke all registered handlers |
|
|
|
|
|
if (h) { |
|
for (i = 0, len = h.length; i < len; ++i) { |
|
h[i].handler.call(this._obj, evt, a); |
|
} |
|
} |
|
}; // add an event handler |
|
|
|
|
|
prototype$L.on = function (type, handler) { |
|
var name = this.eventName(type), |
|
h = this._handlers, |
|
i = this._handlerIndex(h[name], type, handler); |
|
|
|
if (i < 0) { |
|
(h[name] || (h[name] = [])).push({ |
|
type: type, |
|
handler: handler |
|
}); |
|
} |
|
|
|
return this; |
|
}; // remove an event handler |
|
|
|
|
|
prototype$L.off = function (type, handler) { |
|
var name = this.eventName(type), |
|
h = this._handlers[name], |
|
i = this._handlerIndex(h, type, handler); |
|
|
|
if (i >= 0) { |
|
h.splice(i, 1); |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
prototype$L.pickEvent = function (evt) { |
|
var p = point$4(evt, this._canvas), |
|
o = this._origin; |
|
return this.pick(this._scene, p[0], p[1], p[0] - o[0], p[1] - o[1]); |
|
}; // find the scenegraph item at the current mouse position |
|
// x, y -- the absolute x, y mouse coordinates on the canvas element |
|
// gx, gy -- the relative coordinates within the current group |
|
|
|
|
|
prototype$L.pick = function (scene, x, y, gx, gy) { |
|
var g = this.context(), |
|
mark = Marks[scene.marktype]; |
|
return mark.pick.call(this, g, scene, x, y, gx, gy); |
|
}; |
|
|
|
function devicePixelRatio() { |
|
return typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1; |
|
} |
|
|
|
var pixelRatio = devicePixelRatio(); |
|
|
|
function resize(canvas, width, height, origin, scaleFactor, opt) { |
|
var inDOM = typeof HTMLElement !== 'undefined' && canvas instanceof HTMLElement && canvas.parentNode != null; |
|
var context = canvas.getContext('2d'), |
|
ratio = inDOM ? pixelRatio : scaleFactor, |
|
key; |
|
canvas.width = width * ratio; |
|
canvas.height = height * ratio; |
|
|
|
for (key in opt) { |
|
context[key] = opt[key]; |
|
} |
|
|
|
if (inDOM && ratio !== 1) { |
|
canvas.style.width = width + 'px'; |
|
canvas.style.height = height + 'px'; |
|
} |
|
|
|
context.pixelRatio = ratio; |
|
context.setTransform(ratio, 0, 0, ratio, ratio * origin[0], ratio * origin[1]); |
|
return canvas; |
|
} |
|
|
|
function CanvasRenderer(loader) { |
|
Renderer.call(this, loader); |
|
this._redraw = false; |
|
this._dirty = new Bounds(); |
|
} |
|
|
|
var prototype$M = inherits(CanvasRenderer, Renderer), |
|
base = Renderer.prototype, |
|
tempBounds$1 = new Bounds(); |
|
|
|
prototype$M.initialize = function (el, width, height, origin, scaleFactor, options) { |
|
this._options = options; |
|
this._canvas = domCanvas(1, 1, options && options.type); // instantiate a small canvas |
|
|
|
if (el) { |
|
domClear(el, 0).appendChild(this._canvas); |
|
|
|
this._canvas.setAttribute('class', 'marks'); |
|
} // this method will invoke resize to size the canvas appropriately |
|
|
|
|
|
return base.initialize.call(this, el, width, height, origin, scaleFactor); |
|
}; |
|
|
|
prototype$M.resize = function (width, height, origin, scaleFactor) { |
|
base.resize.call(this, width, height, origin, scaleFactor); |
|
resize(this._canvas, this._width, this._height, this._origin, this._scale, this._options && this._options.context); |
|
this._redraw = true; |
|
return this; |
|
}; |
|
|
|
prototype$M.canvas = function () { |
|
return this._canvas; |
|
}; |
|
|
|
prototype$M.context = function () { |
|
return this._canvas ? this._canvas.getContext('2d') : null; |
|
}; |
|
|
|
prototype$M.dirty = function (item) { |
|
var b = translate$1(item.bounds, item.mark.group); |
|
|
|
this._dirty.union(b); |
|
}; |
|
|
|
function clipToBounds(g, b, origin) { |
|
// expand bounds by 1 pixel, then round to pixel boundaries |
|
b.expand(1).round(); // to avoid artifacts translate if origin has fractional pixels |
|
|
|
b.translate(-(origin[0] % 1), -(origin[1] % 1)); // set clipping path |
|
|
|
g.beginPath(); |
|
g.rect(b.x1, b.y1, b.width(), b.height()); |
|
g.clip(); |
|
return b; |
|
} |
|
|
|
function viewBounds(origin, width, height) { |
|
return tempBounds$1.set(0, 0, width, height).translate(-origin[0], -origin[1]); |
|
} |
|
|
|
function translate$1(bounds, group) { |
|
if (group == null) return bounds; |
|
var b = tempBounds$1.clear().union(bounds); |
|
|
|
for (; group != null; group = group.mark.group) { |
|
b.translate(group.x || 0, group.y || 0); |
|
} |
|
|
|
return b; |
|
} |
|
|
|
prototype$M._render = function (scene) { |
|
var g = this.context(), |
|
o = this._origin, |
|
w = this._width, |
|
h = this._height, |
|
b = this._dirty; // setup |
|
|
|
g.save(); |
|
|
|
if (this._redraw || b.empty()) { |
|
this._redraw = false; |
|
b = viewBounds(o, w, h).expand(1); |
|
} else { |
|
b = clipToBounds(g, b.intersect(viewBounds(o, w, h)), o); |
|
} |
|
|
|
this.clear(-o[0], -o[1], w, h); // render |
|
|
|
this.draw(g, scene, b); // takedown |
|
|
|
g.restore(); |
|
|
|
this._dirty.clear(); |
|
|
|
return this; |
|
}; |
|
|
|
prototype$M.draw = function (ctx, scene, bounds) { |
|
var mark = Marks[scene.marktype]; |
|
if (scene.clip) clip(ctx, scene); |
|
mark.draw.call(this, ctx, scene, bounds); |
|
if (scene.clip) ctx.restore(); |
|
}; |
|
|
|
prototype$M.clear = function (x, y, w, h) { |
|
var g = this.context(); |
|
g.clearRect(x, y, w, h); |
|
|
|
if (this._bgcolor != null) { |
|
g.fillStyle = this._bgcolor; |
|
g.fillRect(x, y, w, h); |
|
} |
|
}; |
|
|
|
function SVGHandler(loader, tooltip) { |
|
Handler.call(this, loader, tooltip); |
|
var h = this; |
|
h._hrefHandler = listener(h, function (evt, item) { |
|
if (item && item.href) h.handleHref(evt, item, item.href); |
|
}); |
|
h._tooltipHandler = listener(h, function (evt, item) { |
|
h.handleTooltip(evt, item, evt.type !== TooltipHideEvent); |
|
}); |
|
} |
|
|
|
var prototype$N = inherits(SVGHandler, Handler); |
|
|
|
prototype$N.initialize = function (el, origin, obj) { |
|
var svg = this._svg; |
|
|
|
if (svg) { |
|
svg.removeEventListener(HrefEvent, this._hrefHandler); |
|
svg.removeEventListener(TooltipShowEvent, this._tooltipHandler); |
|
svg.removeEventListener(TooltipHideEvent, this._tooltipHandler); |
|
} |
|
|
|
this._svg = svg = el && domFind(el, 'svg'); |
|
|
|
if (svg) { |
|
svg.addEventListener(HrefEvent, this._hrefHandler); |
|
svg.addEventListener(TooltipShowEvent, this._tooltipHandler); |
|
svg.addEventListener(TooltipHideEvent, this._tooltipHandler); |
|
} |
|
|
|
return Handler.prototype.initialize.call(this, el, origin, obj); |
|
}; |
|
|
|
prototype$N.canvas = function () { |
|
return this._svg; |
|
}; // wrap an event listener for the SVG DOM |
|
|
|
|
|
function listener(context, handler) { |
|
return function (evt) { |
|
var target = evt.target, |
|
item = target.__data__; |
|
evt.vegaType = evt.type; |
|
item = Array.isArray(item) ? item[0] : item; |
|
handler.call(context._obj, evt, item); |
|
}; |
|
} // add an event handler |
|
|
|
|
|
prototype$N.on = function (type, handler) { |
|
var name = this.eventName(type), |
|
h = this._handlers, |
|
i = this._handlerIndex(h[name], type, handler); |
|
|
|
if (i < 0) { |
|
var x = { |
|
type: type, |
|
handler: handler, |
|
listener: listener(this, handler) |
|
}; |
|
(h[name] || (h[name] = [])).push(x); |
|
|
|
if (this._svg) { |
|
this._svg.addEventListener(name, x.listener); |
|
} |
|
} |
|
|
|
return this; |
|
}; // remove an event handler |
|
|
|
|
|
prototype$N.off = function (type, handler) { |
|
var name = this.eventName(type), |
|
h = this._handlers[name], |
|
i = this._handlerIndex(h, type, handler); |
|
|
|
if (i >= 0) { |
|
if (this._svg) { |
|
this._svg.removeEventListener(name, h[i].listener); |
|
} |
|
|
|
h.splice(i, 1); |
|
} |
|
|
|
return this; |
|
}; // generate string for an opening xml tag |
|
// tag: the name of the xml tag |
|
// attr: hash of attribute name-value pairs to include |
|
// raw: additional raw string to include in tag markup |
|
|
|
|
|
function openTag(tag, attr, raw) { |
|
var s = '<' + tag, |
|
key, |
|
val; |
|
|
|
if (attr) { |
|
for (key in attr) { |
|
val = attr[key]; |
|
|
|
if (val != null) { |
|
s += ' ' + key + '="' + val + '"'; |
|
} |
|
} |
|
} |
|
|
|
if (raw) s += ' ' + raw; |
|
return s + '>'; |
|
} // generate string for closing xml tag |
|
// tag: the name of the xml tag |
|
|
|
|
|
function closeTag(tag) { |
|
return '</' + tag + '>'; |
|
} |
|
|
|
var metadata = { |
|
'version': '1.1', |
|
'xmlns': 'http://www.w3.org/2000/svg', |
|
'xmlns:xlink': 'http://www.w3.org/1999/xlink' |
|
}; |
|
var styles = { |
|
'fill': 'fill', |
|
'fillOpacity': 'fill-opacity', |
|
'stroke': 'stroke', |
|
'strokeOpacity': 'stroke-opacity', |
|
'strokeWidth': 'stroke-width', |
|
'strokeCap': 'stroke-linecap', |
|
'strokeJoin': 'stroke-linejoin', |
|
'strokeDash': 'stroke-dasharray', |
|
'strokeDashOffset': 'stroke-dashoffset', |
|
'strokeMiterLimit': 'stroke-miterlimit', |
|
'opacity': 'opacity' |
|
}; |
|
var styleProperties = Object.keys(styles); |
|
var ns = metadata.xmlns; |
|
|
|
function SVGRenderer(loader) { |
|
Renderer.call(this, loader); |
|
this._dirtyID = 0; |
|
this._dirty = []; |
|
this._svg = null; |
|
this._root = null; |
|
this._defs = null; |
|
} |
|
|
|
var prototype$O = inherits(SVGRenderer, Renderer); |
|
var base$1 = Renderer.prototype; |
|
|
|
prototype$O.initialize = function (el, width, height, padding) { |
|
if (el) { |
|
this._svg = domChild(el, 0, 'svg', ns); |
|
|
|
this._svg.setAttribute('class', 'marks'); |
|
|
|
domClear(el, 1); // set the svg root group |
|
|
|
this._root = domChild(this._svg, 0, 'g', ns); |
|
domClear(this._svg, 1); |
|
} // create the svg definitions cache |
|
|
|
|
|
this._defs = { |
|
gradient: {}, |
|
clipping: {} |
|
}; // set background color if defined |
|
|
|
this.background(this._bgcolor); |
|
return base$1.initialize.call(this, el, width, height, padding); |
|
}; |
|
|
|
prototype$O.background = function (bgcolor) { |
|
if (arguments.length && this._svg) { |
|
this._svg.style.setProperty('background-color', bgcolor); |
|
} |
|
|
|
return base$1.background.apply(this, arguments); |
|
}; |
|
|
|
prototype$O.resize = function (width, height, origin, scaleFactor) { |
|
base$1.resize.call(this, width, height, origin, scaleFactor); |
|
|
|
if (this._svg) { |
|
this._svg.setAttribute('width', this._width * this._scale); |
|
|
|
this._svg.setAttribute('height', this._height * this._scale); |
|
|
|
this._svg.setAttribute('viewBox', '0 0 ' + this._width + ' ' + this._height); |
|
|
|
this._root.setAttribute('transform', 'translate(' + this._origin + ')'); |
|
} |
|
|
|
this._dirty = []; |
|
return this; |
|
}; |
|
|
|
prototype$O.canvas = function () { |
|
return this._svg; |
|
}; |
|
|
|
prototype$O.svg = function () { |
|
if (!this._svg) return null; |
|
var attr = { |
|
class: 'marks', |
|
width: this._width * this._scale, |
|
height: this._height * this._scale, |
|
viewBox: '0 0 ' + this._width + ' ' + this._height |
|
}; |
|
|
|
for (var key in metadata) { |
|
attr[key] = metadata[key]; |
|
} |
|
|
|
var bg = !this._bgcolor ? '' : openTag('rect', { |
|
width: this._width, |
|
height: this._height, |
|
style: 'fill: ' + this._bgcolor + ';' |
|
}) + closeTag('rect'); |
|
return openTag('svg', attr) + bg + this._svg.innerHTML + closeTag('svg'); |
|
}; // -- Render entry point -- |
|
|
|
|
|
prototype$O._render = function (scene) { |
|
// perform spot updates and re-render markup |
|
if (this._dirtyCheck()) { |
|
if (this._dirtyAll) this._resetDefs(); |
|
this.draw(this._root, scene); |
|
domClear(this._root, 1); |
|
} |
|
|
|
this.updateDefs(); |
|
this._dirty = []; |
|
++this._dirtyID; |
|
return this; |
|
}; // -- Manage SVG definitions ('defs') block -- |
|
|
|
|
|
prototype$O.updateDefs = function () { |
|
var svg = this._svg, |
|
defs = this._defs, |
|
el = defs.el, |
|
index = 0, |
|
id; |
|
|
|
for (id in defs.gradient) { |
|
if (!el) defs.el = el = domChild(svg, 0, 'defs', ns); |
|
index = updateGradient(el, defs.gradient[id], index); |
|
} |
|
|
|
for (id in defs.clipping) { |
|
if (!el) defs.el = el = domChild(svg, 0, 'defs', ns); |
|
index = updateClipping(el, defs.clipping[id], index); |
|
} // clean-up |
|
|
|
|
|
if (el) { |
|
if (index === 0) { |
|
svg.removeChild(el); |
|
defs.el = null; |
|
} else { |
|
domClear(el, index); |
|
} |
|
} |
|
}; |
|
|
|
function updateGradient(el, grad, index) { |
|
var i, n, stop; |
|
|
|
if (grad.gradient === 'radial') { |
|
// SVG radial gradients automatically transform to normalized bbox |
|
// coordinates, in a way that is cumbersome to replicate in canvas. |
|
// So we wrap the radial gradient in a pattern element, allowing us |
|
// to mantain a circular gradient that matches what canvas provides. |
|
var pt = domChild(el, index++, 'pattern', ns); |
|
pt.setAttribute('id', patternPrefix + grad.id); |
|
pt.setAttribute('viewBox', '0,0,1,1'); |
|
pt.setAttribute('width', '100%'); |
|
pt.setAttribute('height', '100%'); |
|
pt.setAttribute('preserveAspectRatio', 'xMidYMid slice'); |
|
pt = domChild(pt, 0, 'rect', ns); |
|
pt.setAttribute('width', '1'); |
|
pt.setAttribute('height', '1'); |
|
pt.setAttribute('fill', 'url(' + href() + '#' + grad.id + ')'); |
|
el = domChild(el, index++, 'radialGradient', ns); |
|
el.setAttribute('id', grad.id); |
|
el.setAttribute('fx', grad.x1); |
|
el.setAttribute('fy', grad.y1); |
|
el.setAttribute('fr', grad.r1); |
|
el.setAttribute('cx', grad.x2); |
|
el.setAttribute('cy', grad.y2); |
|
el.setAttribute('r', grad.r2); |
|
} else { |
|
el = domChild(el, index++, 'linearGradient', ns); |
|
el.setAttribute('id', grad.id); |
|
el.setAttribute('x1', grad.x1); |
|
el.setAttribute('x2', grad.x2); |
|
el.setAttribute('y1', grad.y1); |
|
el.setAttribute('y2', grad.y2); |
|
} |
|
|
|
for (i = 0, n = grad.stops.length; i < n; ++i) { |
|
stop = domChild(el, i, 'stop', ns); |
|
stop.setAttribute('offset', grad.stops[i].offset); |
|
stop.setAttribute('stop-color', grad.stops[i].color); |
|
} |
|
|
|
domClear(el, i); |
|
return index; |
|
} |
|
|
|
function updateClipping(el, clip, index) { |
|
var mask; |
|
el = domChild(el, index, 'clipPath', ns); |
|
el.setAttribute('id', clip.id); |
|
|
|
if (clip.path) { |
|
mask = domChild(el, 0, 'path', ns); |
|
mask.setAttribute('d', clip.path); |
|
} else { |
|
mask = domChild(el, 0, 'rect', ns); |
|
mask.setAttribute('x', 0); |
|
mask.setAttribute('y', 0); |
|
mask.setAttribute('width', clip.width); |
|
mask.setAttribute('height', clip.height); |
|
} |
|
|
|
domClear(el, 1); |
|
return index + 1; |
|
} |
|
|
|
prototype$O._resetDefs = function () { |
|
var def = this._defs; |
|
def.gradient = {}; |
|
def.clipping = {}; |
|
}; // -- Manage rendering of items marked as dirty -- |
|
|
|
|
|
prototype$O.dirty = function (item) { |
|
if (item.dirty !== this._dirtyID) { |
|
item.dirty = this._dirtyID; |
|
|
|
this._dirty.push(item); |
|
} |
|
}; |
|
|
|
prototype$O.isDirty = function (item) { |
|
return this._dirtyAll || !item._svg || item.dirty === this._dirtyID; |
|
}; |
|
|
|
prototype$O._dirtyCheck = function () { |
|
this._dirtyAll = true; |
|
var items = this._dirty; |
|
if (!items.length || !this._dirtyID) return true; |
|
var id = ++this._dirtyID, |
|
item, |
|
mark, |
|
type, |
|
mdef, |
|
i, |
|
n, |
|
o; |
|
|
|
for (i = 0, n = items.length; i < n; ++i) { |
|
item = items[i]; |
|
mark = item.mark; |
|
|
|
if (mark.marktype !== type) { |
|
// memoize mark instance lookup |
|
type = mark.marktype; |
|
mdef = Marks[type]; |
|
} |
|
|
|
if (mark.zdirty && mark.dirty !== id) { |
|
this._dirtyAll = false; |
|
dirtyParents(item, id); |
|
mark.items.forEach(function (i) { |
|
i.dirty = id; |
|
}); |
|
} |
|
|
|
if (mark.zdirty) continue; // handle in standard drawing pass |
|
|
|
if (item.exit) { |
|
// EXIT |
|
if (mdef.nested && mark.items.length) { |
|
// if nested mark with remaining points, update instead |
|
o = mark.items[0]; |
|
if (o._svg) this._update(mdef, o._svg, o); |
|
} else if (item._svg) { |
|
// otherwise remove from DOM |
|
o = item._svg.parentNode; |
|
if (o) o.removeChild(item._svg); |
|
} |
|
|
|
item._svg = null; |
|
continue; |
|
} |
|
|
|
item = mdef.nested ? mark.items[0] : item; |
|
if (item._update === id) continue; // already visited |
|
|
|
if (!item._svg || !item._svg.ownerSVGElement) { |
|
// ENTER |
|
this._dirtyAll = false; |
|
dirtyParents(item, id); |
|
} else { |
|
// IN-PLACE UPDATE |
|
this._update(mdef, item._svg, item); |
|
} |
|
|
|
item._update = id; |
|
} |
|
|
|
return !this._dirtyAll; |
|
}; |
|
|
|
function dirtyParents(item, id) { |
|
for (; item && item.dirty !== id; item = item.mark.group) { |
|
item.dirty = id; |
|
|
|
if (item.mark && item.mark.dirty !== id) { |
|
item.mark.dirty = id; |
|
} else return; |
|
} |
|
} // -- Construct & maintain scenegraph to SVG mapping --- |
|
// Draw a mark container. |
|
|
|
|
|
prototype$O.draw = function (el, scene, prev) { |
|
if (!this.isDirty(scene)) return scene._svg; |
|
var renderer = this, |
|
svg = this._svg, |
|
mdef = Marks[scene.marktype], |
|
events = scene.interactive === false ? 'none' : null, |
|
isGroup = mdef.tag === 'g', |
|
sibling = null, |
|
i = 0, |
|
parent; |
|
parent = bind(scene, el, prev, 'g', svg); |
|
parent.setAttribute('class', cssClass(scene)); |
|
|
|
if (!isGroup) { |
|
parent.style.setProperty('pointer-events', events); |
|
} |
|
|
|
if (scene.clip) { |
|
parent.setAttribute('clip-path', clip$1(renderer, scene, scene.group)); |
|
} else { |
|
parent.removeAttribute('clip-path'); |
|
} |
|
|
|
function process(item) { |
|
var dirty = renderer.isDirty(item), |
|
node = bind(item, parent, sibling, mdef.tag, svg); |
|
|
|
if (dirty) { |
|
renderer._update(mdef, node, item); |
|
|
|
if (isGroup) recurse(renderer, node, item); |
|
} |
|
|
|
sibling = node; |
|
++i; |
|
} |
|
|
|
if (mdef.nested) { |
|
if (scene.items.length) process(scene.items[0]); |
|
} else { |
|
visit(scene, process); |
|
} |
|
|
|
domClear(parent, i); |
|
return parent; |
|
}; // Recursively process group contents. |
|
|
|
|
|
function recurse(renderer, el, group) { |
|
el = el.lastChild.previousSibling; |
|
var prev, |
|
idx = 0; |
|
visit(group, function (item) { |
|
prev = renderer.draw(el, item, prev); |
|
++idx; |
|
}); // remove any extraneous DOM elements |
|
|
|
domClear(el, 1 + idx); |
|
} // Bind a scenegraph item to an SVG DOM element. |
|
// Create new SVG elements as needed. |
|
|
|
|
|
function bind(item, el, sibling, tag, svg) { |
|
var node = item._svg, |
|
doc; // create a new dom node if needed |
|
|
|
if (!node) { |
|
doc = el.ownerDocument; |
|
node = domCreate(doc, tag, ns); |
|
item._svg = node; |
|
|
|
if (item.mark) { |
|
node.__data__ = item; |
|
node.__values__ = { |
|
fill: 'default' |
|
}; // if group, create background, content, and foreground elements |
|
|
|
if (tag === 'g') { |
|
var bg = domCreate(doc, 'path', ns); |
|
node.appendChild(bg); |
|
bg.__data__ = item; |
|
var cg = domCreate(doc, 'g', ns); |
|
node.appendChild(cg); |
|
cg.__data__ = item; |
|
var fg = domCreate(doc, 'path', ns); |
|
node.appendChild(fg); |
|
fg.__data__ = item; |
|
fg.__values__ = { |
|
fill: 'default' |
|
}; |
|
} |
|
} |
|
} // (re-)insert if (a) not contained in SVG or (b) sibling order has changed |
|
|
|
|
|
if (node.ownerSVGElement !== svg || siblingCheck(node, sibling)) { |
|
el.insertBefore(node, sibling ? sibling.nextSibling : el.firstChild); |
|
} |
|
|
|
return node; |
|
} |
|
|
|
function siblingCheck(node, sibling) { |
|
return node.parentNode && node.parentNode.childNodes.length > 1 && node.previousSibling != sibling; // treat null/undefined the same |
|
} // -- Set attributes & styles on SVG elements --- |
|
|
|
|
|
var element = null, |
|
// temp var for current SVG element |
|
values = null; // temp var for current values hash |
|
// Extra configuration for certain mark types |
|
|
|
var mark_extras = { |
|
group: function group(mdef, el, item) { |
|
var fg, bg; |
|
element = fg = el.childNodes[2]; |
|
values = fg.__values__; |
|
mdef.foreground(emit, item, this); |
|
values = el.__values__; // use parent's values hash |
|
|
|
element = el.childNodes[1]; |
|
mdef.content(emit, item, this); |
|
element = bg = el.childNodes[0]; |
|
mdef.background(emit, item, this); |
|
var value = item.mark.interactive === false ? 'none' : null; |
|
|
|
if (value !== values.events) { |
|
fg.style.setProperty('pointer-events', value); |
|
bg.style.setProperty('pointer-events', value); |
|
values.events = value; |
|
} |
|
|
|
if (item.strokeForeground && item.stroke) { |
|
var _fill = item.fill; |
|
fg.style.removeProperty('display'); // set style of background |
|
|
|
this.style(bg, item); |
|
bg.style.removeProperty('stroke'); // set style of foreground |
|
|
|
if (_fill) item.fill = null; |
|
values = fg.__values__; |
|
this.style(fg, item); |
|
if (_fill) item.fill = _fill; // leave element null to prevent downstream styling |
|
|
|
element = null; |
|
} else { |
|
// ensure foreground is ignored |
|
fg.style.setProperty('display', 'none'); |
|
fg.style.setProperty('fill', 'none'); |
|
} |
|
}, |
|
image: function image(mdef, el, item) { |
|
if (item.smooth === false) { |
|
setStyle(el, 'image-rendering', 'optimizeSpeed'); |
|
setStyle(el, 'image-rendering', 'pixelated'); |
|
} else { |
|
setStyle(el, 'image-rendering', null); |
|
} |
|
}, |
|
text: function text(mdef, el, item) { |
|
var tl = textLines(item), |
|
key, |
|
value, |
|
doc, |
|
lh; |
|
|
|
if (isArray(tl)) { |
|
// multi-line text |
|
value = tl.map(function (_) { |
|
return textValue(item, _); |
|
}); |
|
key = value.join('\n'); // content cache key |
|
|
|
if (key !== values.text) { |
|
domClear(el, 0); |
|
doc = el.ownerDocument; |
|
lh = lineHeight(item); |
|
value.forEach(function (t, i) { |
|
var ts = domCreate(doc, 'tspan', ns); |
|
ts.__data__ = item; // data binding |
|
|
|
ts.textContent = t; |
|
|
|
if (i) { |
|
ts.setAttribute('x', 0); |
|
ts.setAttribute('dy', lh); |
|
} |
|
|
|
el.appendChild(ts); |
|
}); |
|
values.text = key; |
|
} |
|
} else { |
|
// single-line text |
|
value = textValue(item, tl); |
|
|
|
if (value !== values.text) { |
|
el.textContent = value; |
|
values.text = value; |
|
} |
|
} |
|
|
|
setStyle(el, 'font-family', fontFamily(item)); |
|
setStyle(el, 'font-size', fontSize(item) + 'px'); |
|
setStyle(el, 'font-style', item.fontStyle); |
|
setStyle(el, 'font-variant', item.fontVariant); |
|
setStyle(el, 'font-weight', item.fontWeight); |
|
} |
|
}; |
|
|
|
function setStyle(el, name, value) { |
|
if (value !== values[name]) { |
|
if (value == null) { |
|
el.style.removeProperty(name); |
|
} else { |
|
el.style.setProperty(name, value + ''); |
|
} |
|
|
|
values[name] = value; |
|
} |
|
} |
|
|
|
prototype$O._update = function (mdef, el, item) { |
|
// set dom element and values cache |
|
// provides access to emit method |
|
element = el; |
|
values = el.__values__; // apply svg attributes |
|
|
|
mdef.attr(emit, item, this); // some marks need special treatment |
|
|
|
var extra = mark_extras[mdef.type]; |
|
if (extra) extra.call(this, mdef, el, item); // apply svg css styles |
|
// note: element may be modified by 'extra' method |
|
|
|
if (element) this.style(element, item); |
|
}; |
|
|
|
function emit(name, value, ns) { |
|
// early exit if value is unchanged |
|
if (value === values[name]) return; |
|
|
|
if (value != null) { |
|
// if value is provided, update DOM attribute |
|
if (ns) { |
|
element.setAttributeNS(ns, name, value); |
|
} else { |
|
element.setAttribute(name, value); |
|
} |
|
} else { |
|
// else remove DOM attribute |
|
if (ns) { |
|
element.removeAttributeNS(ns, name); |
|
} else { |
|
element.removeAttribute(name); |
|
} |
|
} // note current value for future comparison |
|
|
|
|
|
values[name] = value; |
|
} |
|
|
|
prototype$O.style = function (el, o) { |
|
if (o == null) return; |
|
var i, n, prop, name, value; |
|
|
|
for (i = 0, n = styleProperties.length; i < n; ++i) { |
|
prop = styleProperties[i]; |
|
value = o[prop]; |
|
|
|
if (prop === 'font') { |
|
value = fontFamily(o); |
|
} |
|
|
|
if (value === values[prop]) continue; |
|
name = styles[prop]; |
|
|
|
if (value == null) { |
|
if (name === 'fill') { |
|
el.style.setProperty(name, 'none'); |
|
} else { |
|
el.style.removeProperty(name); |
|
} |
|
} else { |
|
if (isGradient(value)) { |
|
value = gradientRef(value, this._defs.gradient, href()); |
|
} |
|
|
|
el.style.setProperty(name, value + ''); |
|
} |
|
|
|
values[prop] = value; |
|
} |
|
}; |
|
|
|
function href() { |
|
var loc; |
|
return typeof window === 'undefined' ? '' : (loc = window.location).hash ? loc.href.slice(0, -loc.hash.length) : loc.href; |
|
} |
|
|
|
function SVGStringRenderer(loader) { |
|
Renderer.call(this, loader); |
|
this._text = { |
|
head: '', |
|
bg: '', |
|
root: '', |
|
foot: '', |
|
defs: '', |
|
body: '' |
|
}; |
|
this._defs = { |
|
gradient: {}, |
|
clipping: {} |
|
}; |
|
} |
|
|
|
var prototype$P = inherits(SVGStringRenderer, Renderer); |
|
var base$2 = Renderer.prototype; |
|
|
|
prototype$P.resize = function (width, height, origin, scaleFactor) { |
|
base$2.resize.call(this, width, height, origin, scaleFactor); |
|
var o = this._origin, |
|
t = this._text; |
|
var attr = { |
|
class: 'marks', |
|
width: this._width * this._scale, |
|
height: this._height * this._scale, |
|
viewBox: '0 0 ' + this._width + ' ' + this._height |
|
}; |
|
|
|
for (var key in metadata) { |
|
attr[key] = metadata[key]; |
|
} |
|
|
|
t.head = openTag('svg', attr); |
|
var bg = this._bgcolor; |
|
if (bg === 'transparent' || bg === 'none') bg = null; |
|
|
|
if (bg) { |
|
t.bg = openTag('rect', { |
|
width: this._width, |
|
height: this._height, |
|
style: 'fill: ' + bg + ';' |
|
}) + closeTag('rect'); |
|
} else { |
|
t.bg = ''; |
|
} |
|
|
|
t.root = openTag('g', { |
|
transform: 'translate(' + o + ')' |
|
}); |
|
t.foot = closeTag('g') + closeTag('svg'); |
|
return this; |
|
}; |
|
|
|
prototype$P.background = function () { |
|
var rv = base$2.background.apply(this, arguments); |
|
|
|
if (arguments.length && this._text.head) { |
|
this.resize(this._width, this._height, this._origin, this._scale); |
|
} |
|
|
|
return rv; |
|
}; |
|
|
|
prototype$P.svg = function () { |
|
var t = this._text; |
|
return t.head + t.bg + t.defs + t.root + t.body + t.foot; |
|
}; |
|
|
|
prototype$P._render = function (scene) { |
|
this._text.body = this.mark(scene); |
|
this._text.defs = this.buildDefs(); |
|
return this; |
|
}; |
|
|
|
prototype$P.buildDefs = function () { |
|
var all = this._defs, |
|
defs = '', |
|
i, |
|
id, |
|
def, |
|
tag, |
|
stops; |
|
|
|
for (id in all.gradient) { |
|
def = all.gradient[id]; |
|
stops = def.stops; |
|
|
|
if (def.gradient === 'radial') { |
|
// SVG radial gradients automatically transform to normalized bbox |
|
// coordinates, in a way that is cumbersome to replicate in canvas. |
|
// So we wrap the radial gradient in a pattern element, allowing us |
|
// to mantain a circular gradient that matches what canvas provides. |
|
defs += openTag(tag = 'pattern', { |
|
id: patternPrefix + id, |
|
viewBox: '0,0,1,1', |
|
width: '100%', |
|
height: '100%', |
|
preserveAspectRatio: 'xMidYMid slice' |
|
}); |
|
defs += openTag('rect', { |
|
width: '1', |
|
height: '1', |
|
fill: 'url(#' + id + ')' |
|
}) + closeTag('rect'); |
|
defs += closeTag(tag); |
|
defs += openTag(tag = 'radialGradient', { |
|
id: id, |
|
fx: def.x1, |
|
fy: def.y1, |
|
fr: def.r1, |
|
cx: def.x2, |
|
cy: def.y2, |
|
r: def.r2 |
|
}); |
|
} else { |
|
defs += openTag(tag = 'linearGradient', { |
|
id: id, |
|
x1: def.x1, |
|
x2: def.x2, |
|
y1: def.y1, |
|
y2: def.y2 |
|
}); |
|
} |
|
|
|
for (i = 0; i < stops.length; ++i) { |
|
defs += openTag('stop', { |
|
offset: stops[i].offset, |
|
'stop-color': stops[i].color |
|
}) + closeTag('stop'); |
|
} |
|
|
|
defs += closeTag(tag); |
|
} |
|
|
|
for (id in all.clipping) { |
|
def = all.clipping[id]; |
|
defs += openTag('clipPath', { |
|
id: id |
|
}); |
|
|
|
if (def.path) { |
|
defs += openTag('path', { |
|
d: def.path |
|
}) + closeTag('path'); |
|
} else { |
|
defs += openTag('rect', { |
|
x: 0, |
|
y: 0, |
|
width: def.width, |
|
height: def.height |
|
}) + closeTag('rect'); |
|
} |
|
|
|
defs += closeTag('clipPath'); |
|
} |
|
|
|
return defs.length > 0 ? openTag('defs') + defs + closeTag('defs') : ''; |
|
}; |
|
|
|
var object$1; |
|
|
|
function emit$1(name, value, ns, prefixed) { |
|
object$1[prefixed || name] = value; |
|
} |
|
|
|
prototype$P.attributes = function (attr, item) { |
|
object$1 = {}; |
|
attr(emit$1, item, this); |
|
return object$1; |
|
}; |
|
|
|
prototype$P.href = function (item) { |
|
var that = this, |
|
href = item.href, |
|
attr; |
|
|
|
if (href) { |
|
if (attr = that._hrefs && that._hrefs[href]) { |
|
return attr; |
|
} else { |
|
that.sanitizeURL(href).then(function (attr) { |
|
// rewrite to use xlink namespace |
|
// note that this will be deprecated in SVG 2.0 |
|
attr['xlink:href'] = attr.href; |
|
attr.href = null; |
|
(that._hrefs || (that._hrefs = {}))[href] = attr; |
|
}); |
|
} |
|
} |
|
|
|
return null; |
|
}; |
|
|
|
prototype$P.mark = function (scene) { |
|
var renderer = this, |
|
mdef = Marks[scene.marktype], |
|
tag = mdef.tag, |
|
defs = this._defs, |
|
str = '', |
|
style; |
|
|
|
if (tag !== 'g' && scene.interactive === false) { |
|
style = 'style="pointer-events: none;"'; |
|
} // render opening group tag |
|
|
|
|
|
str += openTag('g', { |
|
'class': cssClass(scene), |
|
'clip-path': scene.clip ? clip$1(renderer, scene, scene.group) : null |
|
}, style); // render contained elements |
|
|
|
function process(item) { |
|
var href = renderer.href(item); |
|
if (href) str += openTag('a', href); |
|
style = tag !== 'g' ? applyStyles(item, scene, tag, defs) : null; |
|
str += openTag(tag, renderer.attributes(mdef.attr, item), style); |
|
|
|
if (tag === 'text') { |
|
var _tl = textLines(item); |
|
|
|
if (isArray(_tl)) { |
|
// multi-line text |
|
var attrs = { |
|
x: 0, |
|
dy: lineHeight(item) |
|
}; |
|
|
|
for (var i = 0; i < _tl.length; ++i) { |
|
str += openTag('tspan', i ? attrs : null) + escape_text(textValue(item, _tl[i])) + closeTag('tspan'); |
|
} |
|
} else { |
|
// single-line text |
|
str += escape_text(textValue(item, _tl)); |
|
} |
|
} else if (tag === 'g') { |
|
var fore = item.strokeForeground, |
|
_fill2 = item.fill, |
|
_stroke = item.stroke; |
|
|
|
if (fore && _stroke) { |
|
item.stroke = null; |
|
} |
|
|
|
str += openTag('path', renderer.attributes(mdef.background, item), applyStyles(item, scene, 'bgrect', defs)) + closeTag('path'); |
|
str += openTag('g', renderer.attributes(mdef.content, item)) + renderer.markGroup(item) + closeTag('g'); |
|
|
|
if (fore && _stroke) { |
|
if (_fill2) item.fill = null; |
|
item.stroke = _stroke; |
|
str += openTag('path', renderer.attributes(mdef.foreground, item), applyStyles(item, scene, 'bgrect', defs)) + closeTag('path'); |
|
if (_fill2) item.fill = _fill2; |
|
} else { |
|
str += openTag('path', renderer.attributes(mdef.foreground, item), applyStyles({}, scene, 'bgfore', defs)) + closeTag('path'); |
|
} |
|
} |
|
|
|
str += closeTag(tag); |
|
if (href) str += closeTag('a'); |
|
} |
|
|
|
if (mdef.nested) { |
|
if (scene.items && scene.items.length) process(scene.items[0]); |
|
} else { |
|
visit(scene, process); |
|
} // render closing group tag |
|
|
|
|
|
return str + closeTag('g'); |
|
}; |
|
|
|
prototype$P.markGroup = function (scene) { |
|
var renderer = this, |
|
str = ''; |
|
visit(scene, function (item) { |
|
str += renderer.mark(item); |
|
}); |
|
return str; |
|
}; |
|
|
|
function applyStyles(o, mark, tag, defs) { |
|
if (o == null) return ''; |
|
var i, |
|
n, |
|
prop, |
|
name, |
|
value, |
|
s = ''; |
|
|
|
if (tag === 'bgrect' && mark.interactive === false) { |
|
s += 'pointer-events: none; '; |
|
} |
|
|
|
if (tag === 'bgfore') { |
|
if (mark.interactive === false) { |
|
s += 'pointer-events: none; '; |
|
} |
|
|
|
s += 'display: none; '; |
|
} |
|
|
|
if (tag === 'image') { |
|
if (o.smooth === false) { |
|
s += 'image-rendering: optimizeSpeed; image-rendering: pixelated; '; |
|
} |
|
} |
|
|
|
if (tag === 'text') { |
|
s += 'font-family: ' + fontFamily(o) + '; '; |
|
s += 'font-size: ' + fontSize(o) + 'px; '; |
|
if (o.fontStyle) s += 'font-style: ' + o.fontStyle + '; '; |
|
if (o.fontVariant) s += 'font-variant: ' + o.fontVariant + '; '; |
|
if (o.fontWeight) s += 'font-weight: ' + o.fontWeight + '; '; |
|
} |
|
|
|
for (i = 0, n = styleProperties.length; i < n; ++i) { |
|
prop = styleProperties[i]; |
|
name = styles[prop]; |
|
value = o[prop]; |
|
|
|
if (value == null) { |
|
if (name === 'fill') { |
|
s += 'fill: none; '; |
|
} |
|
} else if (value === 'transparent' && (name === 'fill' || name === 'stroke')) { |
|
// transparent is not a legal SVG value, so map to none instead |
|
s += name + ': none; '; |
|
} else { |
|
if (isGradient(value)) { |
|
value = gradientRef(value, defs.gradient, ''); |
|
} |
|
|
|
s += name + ': ' + value + '; '; |
|
} |
|
} |
|
|
|
return s ? 'style="' + s.trim() + '"' : null; |
|
} |
|
|
|
function escape_text(s) { |
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); |
|
} |
|
|
|
var Canvas = 'canvas'; |
|
var PNG = 'png'; |
|
var SVG = 'svg'; |
|
var None$2 = 'none'; |
|
var RenderType = { |
|
Canvas: Canvas, |
|
PNG: PNG, |
|
SVG: SVG, |
|
None: None$2 |
|
}; |
|
var modules = {}; |
|
modules[Canvas] = modules[PNG] = { |
|
renderer: CanvasRenderer, |
|
headless: CanvasRenderer, |
|
handler: CanvasHandler |
|
}; |
|
modules[SVG] = { |
|
renderer: SVGRenderer, |
|
headless: SVGStringRenderer, |
|
handler: SVGHandler |
|
}; |
|
modules[None$2] = {}; |
|
|
|
function renderModule(name, _) { |
|
name = String(name || '').toLowerCase(); |
|
|
|
if (arguments.length > 1) { |
|
modules[name] = _; |
|
return this; |
|
} else { |
|
return modules[name]; |
|
} |
|
} |
|
|
|
function intersect$1(scene, bounds, filter) { |
|
var hits = [], |
|
// intersection results |
|
box = new Bounds().union(bounds), |
|
// defensive copy |
|
type = scene.marktype; |
|
return type ? intersectMark(scene, box, filter, hits) : type === 'group' ? intersectGroup(scene, box, filter, hits) : error('Intersect scene must be mark node or group item.'); |
|
} |
|
|
|
function intersectMark(mark, box, filter, hits) { |
|
if (visitMark(mark, box, filter)) { |
|
var items = mark.items, |
|
_type = mark.marktype, |
|
n = items.length; |
|
var i = 0; |
|
|
|
if (_type === 'group') { |
|
for (; i < n; ++i) { |
|
intersectGroup(items[i], box, filter, hits); |
|
} |
|
} else { |
|
for (var test = Marks[_type].isect; i < n; ++i) { |
|
var item = items[i]; |
|
if (intersectItem(item, box, test)) hits.push(item); |
|
} |
|
} |
|
} |
|
|
|
return hits; |
|
} |
|
|
|
function visitMark(mark, box, filter) { |
|
// process if bounds intersect and if |
|
// (1) mark is a group mark (so we must recurse), or |
|
// (2) mark is interactive and passes filter |
|
return mark.bounds && box.intersects(mark.bounds) && (mark.marktype === 'group' || mark.interactive !== false && (!filter || filter(mark))); |
|
} |
|
|
|
function intersectGroup(group, box, filter, hits) { |
|
// test intersect against group |
|
// skip groups by default unless filter says otherwise |
|
if (filter && filter(group.mark) && intersectItem(group, box, Marks.group.isect)) { |
|
hits.push(group); |
|
} // recursively test children marks |
|
// translate box to group coordinate space |
|
|
|
|
|
var marks = group.items, |
|
n = marks && marks.length; |
|
|
|
if (n) { |
|
var _x21 = group.x || 0, |
|
_y2 = group.y || 0; |
|
|
|
box.translate(-_x21, -_y2); |
|
|
|
for (var i = 0; i < n; ++i) { |
|
intersectMark(marks[i], box, filter, hits); |
|
} |
|
|
|
box.translate(_x21, _y2); |
|
} |
|
|
|
return hits; |
|
} |
|
|
|
function intersectItem(item, box, test) { |
|
// test bounds enclosure, bounds intersection, then detailed test |
|
var bounds = item.bounds; |
|
return box.encloses(bounds) || box.intersects(bounds) && test(item, box); |
|
} |
|
|
|
var clipBounds = new Bounds(); |
|
|
|
function boundClip(mark) { |
|
var clip = mark.clip; |
|
|
|
if (isFunction(clip)) { |
|
clip(context(clipBounds.clear())); |
|
} else if (clip) { |
|
clipBounds.set(0, 0, mark.group.width, mark.group.height); |
|
} else return; |
|
|
|
mark.bounds.intersect(clipBounds); |
|
} |
|
|
|
var TOLERANCE = 1e-9; |
|
|
|
function sceneEqual(a, b, key) { |
|
return a === b ? true : key === 'path' ? pathEqual(a, b) : a instanceof Date && b instanceof Date ? +a === +b : isNumber(a) && isNumber(b) ? Math.abs(a - b) <= TOLERANCE : !a || !b || !isObject(a) && !isObject(b) ? a == b : a == null || b == null ? false : objectEqual(a, b); |
|
} |
|
|
|
function pathEqual(a, b) { |
|
return sceneEqual(pathParse(a), pathParse(b)); |
|
} |
|
|
|
function objectEqual(a, b) { |
|
var ka = Object.keys(a), |
|
kb = Object.keys(b), |
|
key, |
|
i; |
|
if (ka.length !== kb.length) return false; |
|
ka.sort(); |
|
kb.sort(); |
|
|
|
for (i = ka.length - 1; i >= 0; i--) { |
|
if (ka[i] != kb[i]) return false; |
|
} |
|
|
|
for (i = ka.length - 1; i >= 0; i--) { |
|
key = ka[i]; |
|
if (!sceneEqual(a[key], b[key], key)) return false; |
|
} |
|
|
|
return _typeof(a) === _typeof(b); |
|
} |
|
/** |
|
* Calculate bounding boxes for scenegraph items. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {object} params.mark - The scenegraph mark instance to bound. |
|
*/ |
|
|
|
|
|
function Bound(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$Q = inherits(Bound, Transform); |
|
|
|
prototype$Q.transform = function (_, pulse) { |
|
var view = pulse.dataflow, |
|
mark = _.mark, |
|
type = mark.marktype, |
|
entry = Marks[type], |
|
bound = entry.bound, |
|
markBounds = mark.bounds, |
|
rebound; |
|
|
|
if (entry.nested) { |
|
// multi-item marks have a single bounds instance |
|
if (mark.items.length) view.dirty(mark.items[0]); |
|
markBounds = boundItem$1(mark, bound); |
|
mark.items.forEach(function (item) { |
|
item.bounds.clear().union(markBounds); |
|
}); |
|
} else if (type === Group || _.modified()) { |
|
// operator parameters modified -> re-bound all items |
|
// updates group bounds in response to modified group content |
|
pulse.visit(pulse.MOD, function (item) { |
|
view.dirty(item); |
|
}); |
|
markBounds.clear(); |
|
mark.items.forEach(function (item) { |
|
markBounds.union(boundItem$1(item, bound)); |
|
}); // force reflow for axes/legends/titles to propagate any layout changes |
|
|
|
switch (mark.role) { |
|
case AxisRole: |
|
case LegendRole: |
|
case TitleRole: |
|
pulse.reflow(); |
|
} |
|
} else { |
|
// incrementally update bounds, re-bound mark as needed |
|
rebound = pulse.changed(pulse.REM); |
|
pulse.visit(pulse.ADD, function (item) { |
|
markBounds.union(boundItem$1(item, bound)); |
|
}); |
|
pulse.visit(pulse.MOD, function (item) { |
|
rebound = rebound || markBounds.alignsWith(item.bounds); |
|
view.dirty(item); |
|
markBounds.union(boundItem$1(item, bound)); |
|
}); |
|
|
|
if (rebound) { |
|
markBounds.clear(); |
|
mark.items.forEach(function (item) { |
|
markBounds.union(item.bounds); |
|
}); |
|
} |
|
} // ensure mark bounds do not exceed any clipping region |
|
|
|
|
|
boundClip(mark); |
|
return pulse.modifies('bounds'); |
|
}; |
|
|
|
function boundItem$1(item, bound, opt) { |
|
return bound(item.bounds.clear(), item, opt); |
|
} |
|
|
|
var COUNTER_NAME = ':vega_identifier:'; |
|
/** |
|
* Adds a unique identifier to all added tuples. |
|
* This transform creates a new signal that serves as an id counter. |
|
* As a result, the id counter is shared across all instances of this |
|
* transform, generating unique ids across multiple data streams. In |
|
* addition, this signal value can be included in a snapshot of the |
|
* dataflow state, enabling correct resumption of id allocation. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {string} params.as - The field name for the generated identifier. |
|
*/ |
|
|
|
function Identifier(params) { |
|
Transform.call(this, 0, params); |
|
} |
|
|
|
Identifier.Definition = { |
|
"type": "Identifier", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "as", |
|
"type": "string", |
|
"required": true |
|
}] |
|
}; |
|
var prototype$R = inherits(Identifier, Transform); |
|
|
|
prototype$R.transform = function (_, pulse) { |
|
var counter = getCounter(pulse.dataflow), |
|
id = counter.value, |
|
as = _.as; |
|
pulse.visit(pulse.ADD, function (t) { |
|
if (!t[as]) t[as] = ++id; |
|
}); |
|
counter.set(this.value = id); |
|
return pulse; |
|
}; |
|
|
|
function getCounter(view) { |
|
var counter = view._signals[COUNTER_NAME]; |
|
|
|
if (!counter) { |
|
view._signals[COUNTER_NAME] = counter = view.add(0); |
|
} |
|
|
|
return counter; |
|
} |
|
/** |
|
* Bind scenegraph items to a scenegraph mark instance. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {object} params.markdef - The mark definition for creating the mark. |
|
* This is an object of legal scenegraph mark properties which *must* include |
|
* the 'marktype' property. |
|
*/ |
|
|
|
|
|
function Mark(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$S = inherits(Mark, Transform); |
|
|
|
prototype$S.transform = function (_, pulse) { |
|
var mark = this.value; // acquire mark on first invocation, bind context and group |
|
|
|
if (!mark) { |
|
mark = pulse.dataflow.scenegraph().mark(_.markdef, lookup$1(_), _.index); |
|
mark.group.context = _.context; |
|
if (!_.context.group) _.context.group = mark.group; |
|
mark.source = this.source; // point to upstream collector |
|
|
|
mark.clip = _.clip; |
|
mark.interactive = _.interactive; |
|
this.value = mark; |
|
} // initialize entering items |
|
|
|
|
|
var Init = mark.marktype === Group ? GroupItem : Item; |
|
pulse.visit(pulse.ADD, function (item) { |
|
Init.call(item, mark); |
|
}); // update clipping and/or interactive status |
|
|
|
if (_.modified('clip') || _.modified('interactive')) { |
|
mark.clip = _.clip; |
|
mark.interactive = !!_.interactive; |
|
mark.zdirty = true; // force scenegraph re-eval |
|
|
|
pulse.reflow(); |
|
} // bind items array to scenegraph mark |
|
|
|
|
|
mark.items = pulse.source; |
|
return pulse; |
|
}; |
|
|
|
function lookup$1(_) { |
|
var g = _.groups, |
|
p = _.parent; |
|
return g && g.size === 1 ? g.get(Object.keys(g.object)[0]) : g && p ? g.lookup(p) : null; |
|
} |
|
/** |
|
* Analyze items for overlap, changing opacity to hide items with |
|
* overlapping bounding boxes. This transform will preserve at least |
|
* two items (e.g., first and last) even if overlap persists. |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(*,*): number} [params.sort] - A comparator |
|
* function for sorting items. |
|
* @param {object} [params.method] - The overlap removal method to apply. |
|
* One of 'parity' (default, hide every other item until there is no |
|
* more overlap) or 'greedy' (sequentially scan and hide and items that |
|
* overlap with the last visible item). |
|
* @param {object} [params.boundScale] - A scale whose range should be used |
|
* to bound the items. Items exceeding the bounds of the scale range |
|
* will be treated as overlapping. If null or undefined, no bounds check |
|
* will be applied. |
|
* @param {object} [params.boundOrient] - The orientation of the scale |
|
* (top, bottom, left, or right) used to bound items. This parameter is |
|
* ignored if boundScale is null or undefined. |
|
* @param {object} [params.boundTolerance] - The tolerance in pixels for |
|
* bound inclusion testing (default 1). This specifies by how many pixels |
|
* an item's bounds may exceed the scale range bounds and not be culled. |
|
* @constructor |
|
*/ |
|
|
|
|
|
function Overlap(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$T = inherits(Overlap, Transform); |
|
var methods = { |
|
parity: function parity(items) { |
|
return items.filter(function (item, i) { |
|
return i % 2 ? item.opacity = 0 : 1; |
|
}); |
|
}, |
|
greedy: function greedy(items, sep) { |
|
var a; |
|
return items.filter(function (b, i) { |
|
if (!i || !intersect$2(a.bounds, b.bounds, sep)) { |
|
a = b; |
|
return 1; |
|
} else { |
|
return b.opacity = 0; |
|
} |
|
}); |
|
} |
|
}; // compute bounding box intersection |
|
// including padding pixels of separation |
|
|
|
function intersect$2(a, b, sep) { |
|
return sep > Math.max(b.x1 - a.x2, a.x1 - b.x2, b.y1 - a.y2, a.y1 - b.y2); |
|
} |
|
|
|
function hasOverlap(items, pad) { |
|
for (var i = 1, n = items.length, a = items[0].bounds, b; i < n; a = b, ++i) { |
|
if (intersect$2(a, b = items[i].bounds, pad)) return true; |
|
} |
|
} |
|
|
|
function hasBounds(item) { |
|
var b = item.bounds; |
|
return b.width() > 1 && b.height() > 1; |
|
} |
|
|
|
function boundTest(scale, orient, tolerance) { |
|
var range = scale.range(), |
|
b = new Bounds(); |
|
|
|
if (orient === Top || orient === Bottom) { |
|
b.set(range[0], -Infinity, range[1], +Infinity); |
|
} else { |
|
b.set(-Infinity, range[0], +Infinity, range[1]); |
|
} |
|
|
|
b.expand(tolerance || 1); |
|
return function (item) { |
|
return b.encloses(item.bounds); |
|
}; |
|
} // reset all items to be fully opaque |
|
|
|
|
|
function reset(source) { |
|
source.forEach(function (item) { |
|
return item.opacity = 1; |
|
}); |
|
return source; |
|
} // add all tuples to mod, fork pulse if parameters were modified |
|
// fork prevents cross-stream tuple pollution (e.g., pulse from scale) |
|
|
|
|
|
function reflow(pulse, _) { |
|
return pulse.reflow(_.modified()).modifies('opacity'); |
|
} |
|
|
|
prototype$T.transform = function (_, pulse) { |
|
var reduce = methods[_.method] || methods.parity, |
|
source = pulse.materialize(pulse.SOURCE).source, |
|
sep = _.separation || 0, |
|
items, |
|
test, |
|
bounds; |
|
if (!source || !source.length) return; |
|
|
|
if (!_.method) { |
|
// early exit if method is falsy |
|
if (_.modified('method')) { |
|
reset(source); |
|
pulse = reflow(pulse, _); |
|
} |
|
|
|
return pulse; |
|
} |
|
|
|
if (_.sort) { |
|
source = source.slice().sort(_.sort); |
|
} // skip labels with no content |
|
|
|
|
|
source = source.filter(hasBounds); |
|
items = reset(source); |
|
pulse = reflow(pulse, _); |
|
|
|
if (items.length >= 3 && hasOverlap(items, sep)) { |
|
do { |
|
items = reduce(items, sep); |
|
} while (items.length >= 3 && hasOverlap(items, sep)); |
|
|
|
if (items.length < 3 && !peek(source).opacity) { |
|
if (items.length > 1) peek(items).opacity = 0; |
|
peek(source).opacity = 1; |
|
} |
|
} |
|
|
|
if (_.boundScale && _.boundTolerance >= 0) { |
|
test = boundTest(_.boundScale, _.boundOrient, +_.boundTolerance); |
|
source.forEach(function (item) { |
|
if (!test(item)) item.opacity = 0; |
|
}); |
|
} // re-calculate mark bounds |
|
|
|
|
|
bounds = items[0].mark.bounds.clear(); |
|
source.forEach(function (item) { |
|
if (item.opacity) bounds.union(item.bounds); |
|
}); |
|
return pulse; |
|
}; |
|
/** |
|
* Queue modified scenegraph items for rendering. |
|
* @constructor |
|
*/ |
|
|
|
|
|
function Render(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$U = inherits(Render, Transform); |
|
|
|
prototype$U.transform = function (_, pulse) { |
|
var view = pulse.dataflow; |
|
pulse.visit(pulse.ALL, function (item) { |
|
view.dirty(item); |
|
}); // set z-index dirty flag as needed |
|
|
|
if (pulse.fields && pulse.fields['zindex']) { |
|
var item = pulse.source && pulse.source[0]; |
|
if (item) item.mark.zdirty = true; |
|
} |
|
}; |
|
|
|
var tempBounds$2 = new Bounds(); |
|
|
|
function set(item, property, value) { |
|
return item[property] === value ? 0 : (item[property] = value, 1); |
|
} |
|
|
|
function isYAxis(mark) { |
|
var orient = mark.items[0].datum.orient; |
|
return orient === Left || orient === Right; |
|
} |
|
|
|
function axisIndices(datum) { |
|
var index = +datum.grid; |
|
return [datum.ticks ? index++ : -1, // ticks index |
|
datum.labels ? index++ : -1, // labels index |
|
index + +datum.domain // title index |
|
]; |
|
} |
|
|
|
function axisLayout(view, axis, width, height) { |
|
var item = axis.items[0], |
|
datum = item.datum, |
|
orient = datum.orient, |
|
delta = datum.translate != null ? datum.translate : 0.5, |
|
indices = axisIndices(datum), |
|
range = item.range, |
|
offset = item.offset, |
|
position = item.position, |
|
minExtent = item.minExtent, |
|
maxExtent = item.maxExtent, |
|
title = datum.title && item.items[indices[2]].items[0], |
|
titlePadding = item.titlePadding, |
|
bounds = item.bounds, |
|
dl = title && multiLineOffset(title), |
|
x = 0, |
|
y = 0, |
|
i, |
|
s; |
|
tempBounds$2.clear().union(bounds); |
|
bounds.clear(); |
|
if ((i = indices[0]) > -1) bounds.union(item.items[i].bounds); |
|
if ((i = indices[1]) > -1) bounds.union(item.items[i].bounds); // position axis group and title |
|
|
|
switch (orient) { |
|
case Top: |
|
x = position || 0; |
|
y = -offset; |
|
s = Math.max(minExtent, Math.min(maxExtent, -bounds.y1)); |
|
if (title) s = axisTitleLayout(view, title, s, titlePadding, dl, 0, -1, bounds); |
|
bounds.add(0, -s).add(range, 0); |
|
break; |
|
|
|
case Left: |
|
x = -offset; |
|
y = position || 0; |
|
s = Math.max(minExtent, Math.min(maxExtent, -bounds.x1)); |
|
if (title) s = axisTitleLayout(view, title, s, titlePadding, dl, 1, -1, bounds); |
|
bounds.add(-s, 0).add(0, range); |
|
break; |
|
|
|
case Right: |
|
x = width + offset; |
|
y = position || 0; |
|
s = Math.max(minExtent, Math.min(maxExtent, bounds.x2)); |
|
if (title) s = axisTitleLayout(view, title, s, titlePadding, dl, 1, 1, bounds); |
|
bounds.add(0, 0).add(s, range); |
|
break; |
|
|
|
case Bottom: |
|
x = position || 0; |
|
y = height + offset; |
|
s = Math.max(minExtent, Math.min(maxExtent, bounds.y2)); |
|
if (title) s = axisTitleLayout(view, title, s, titlePadding, 0, 0, 1, bounds); |
|
bounds.add(0, 0).add(range, s); |
|
break; |
|
|
|
default: |
|
x = item.x; |
|
y = item.y; |
|
} // update bounds |
|
|
|
|
|
boundStroke(bounds.translate(x, y), item); |
|
|
|
if (set(item, 'x', x + delta) | set(item, 'y', y + delta)) { |
|
item.bounds = tempBounds$2; |
|
view.dirty(item); |
|
item.bounds = bounds; |
|
view.dirty(item); |
|
} |
|
|
|
return item.mark.bounds.clear().union(bounds); |
|
} |
|
|
|
function axisTitleLayout(view, title, offset, pad, dl, isYAxis, sign, bounds) { |
|
var b = title.bounds, |
|
dx = 0, |
|
dy = 0; |
|
|
|
if (title.auto) { |
|
view.dirty(title); |
|
offset += pad; |
|
isYAxis ? dx = (title.x || 0) - (title.x = sign * (offset + dl)) : dy = (title.y || 0) - (title.y = sign * (offset + dl)); |
|
title.mark.bounds.clear().union(b.translate(-dx, -dy)); |
|
view.dirty(title); |
|
|
|
if (isYAxis) { |
|
bounds.add(0, b.y1).add(0, b.y2); |
|
offset += b.width(); |
|
} else { |
|
bounds.add(b.x1, 0).add(b.x2, 0); |
|
offset += b.height(); |
|
} |
|
} else { |
|
bounds.union(b); |
|
} |
|
|
|
return offset; |
|
} |
|
|
|
function gridLayoutGroups(group) { |
|
var _views$rowheaders, _views$rowfooters, _views$colheaders, _views$colfooters, _views$marks; |
|
|
|
var groups = group.items, |
|
n = groups.length, |
|
i = 0, |
|
mark, |
|
items; |
|
var views = { |
|
marks: [], |
|
rowheaders: [], |
|
rowfooters: [], |
|
colheaders: [], |
|
colfooters: [], |
|
rowtitle: null, |
|
coltitle: null |
|
}; // layout axes, gather legends, collect bounds |
|
|
|
for (; i < n; ++i) { |
|
mark = groups[i]; |
|
items = mark.items; |
|
|
|
if (mark.marktype === Group) { |
|
switch (mark.role) { |
|
case AxisRole: |
|
case LegendRole: |
|
case TitleRole: |
|
break; |
|
|
|
case RowHeader: |
|
(_views$rowheaders = views.rowheaders).push.apply(_views$rowheaders, _toConsumableArray(items)); |
|
|
|
break; |
|
|
|
case RowFooter: |
|
(_views$rowfooters = views.rowfooters).push.apply(_views$rowfooters, _toConsumableArray(items)); |
|
|
|
break; |
|
|
|
case ColHeader: |
|
(_views$colheaders = views.colheaders).push.apply(_views$colheaders, _toConsumableArray(items)); |
|
|
|
break; |
|
|
|
case ColFooter: |
|
(_views$colfooters = views.colfooters).push.apply(_views$colfooters, _toConsumableArray(items)); |
|
|
|
break; |
|
|
|
case RowTitle: |
|
views.rowtitle = items[0]; |
|
break; |
|
|
|
case ColTitle: |
|
views.coltitle = items[0]; |
|
break; |
|
|
|
default: |
|
(_views$marks = views.marks).push.apply(_views$marks, _toConsumableArray(items)); |
|
|
|
} |
|
} |
|
} |
|
|
|
return views; |
|
} |
|
|
|
function bboxFlush(item) { |
|
return new Bounds().set(0, 0, item.width || 0, item.height || 0); |
|
} |
|
|
|
function bboxFull(item) { |
|
var b = item.bounds.clone(); |
|
return b.empty() ? b.set(0, 0, 0, 0) : b.translate(-(item.x || 0), -(item.y || 0)); |
|
} |
|
|
|
function get$2(opt, key, d) { |
|
var v = isObject(opt) ? opt[key] : opt; |
|
return v != null ? v : d !== undefined ? d : 0; |
|
} |
|
|
|
function offsetValue(v) { |
|
return v < 0 ? Math.ceil(-v) : 0; |
|
} |
|
|
|
function gridLayout(view, groups, opt) { |
|
var dirty = !opt.nodirty, |
|
bbox = opt.bounds === Flush ? bboxFlush : bboxFull, |
|
bounds = tempBounds$2.set(0, 0, 0, 0), |
|
alignCol = get$2(opt.align, Column), |
|
alignRow = get$2(opt.align, Row), |
|
padCol = get$2(opt.padding, Column), |
|
padRow = get$2(opt.padding, Row), |
|
ncols = opt.columns || groups.length, |
|
nrows = ncols < 0 ? 1 : Math.ceil(groups.length / ncols), |
|
n = groups.length, |
|
xOffset = Array(n), |
|
xExtent = Array(ncols), |
|
xMax = 0, |
|
yOffset = Array(n), |
|
yExtent = Array(nrows), |
|
yMax = 0, |
|
dx = Array(n), |
|
dy = Array(n), |
|
boxes = Array(n), |
|
m, |
|
i, |
|
c, |
|
r, |
|
b, |
|
g, |
|
px, |
|
py, |
|
x, |
|
y, |
|
offset; |
|
|
|
for (i = 0; i < ncols; ++i) { |
|
xExtent[i] = 0; |
|
} |
|
|
|
for (i = 0; i < nrows; ++i) { |
|
yExtent[i] = 0; |
|
} // determine offsets for each group |
|
|
|
|
|
for (i = 0; i < n; ++i) { |
|
g = groups[i]; |
|
b = boxes[i] = bbox(g); |
|
g.x = g.x || 0; |
|
dx[i] = 0; |
|
g.y = g.y || 0; |
|
dy[i] = 0; |
|
c = i % ncols; |
|
r = ~~(i / ncols); |
|
xMax = Math.max(xMax, px = Math.ceil(b.x2)); |
|
yMax = Math.max(yMax, py = Math.ceil(b.y2)); |
|
xExtent[c] = Math.max(xExtent[c], px); |
|
yExtent[r] = Math.max(yExtent[r], py); |
|
xOffset[i] = padCol + offsetValue(b.x1); |
|
yOffset[i] = padRow + offsetValue(b.y1); |
|
if (dirty) view.dirty(groups[i]); |
|
} // set initial alignment offsets |
|
|
|
|
|
for (i = 0; i < n; ++i) { |
|
if (i % ncols === 0) xOffset[i] = 0; |
|
if (i < ncols) yOffset[i] = 0; |
|
} // enforce column alignment constraints |
|
|
|
|
|
if (alignCol === Each) { |
|
for (c = 1; c < ncols; ++c) { |
|
for (offset = 0, i = c; i < n; i += ncols) { |
|
if (offset < xOffset[i]) offset = xOffset[i]; |
|
} |
|
|
|
for (i = c; i < n; i += ncols) { |
|
xOffset[i] = offset + xExtent[c - 1]; |
|
} |
|
} |
|
} else if (alignCol === All) { |
|
for (offset = 0, i = 0; i < n; ++i) { |
|
if (i % ncols && offset < xOffset[i]) offset = xOffset[i]; |
|
} |
|
|
|
for (i = 0; i < n; ++i) { |
|
if (i % ncols) xOffset[i] = offset + xMax; |
|
} |
|
} else { |
|
for (alignCol = false, c = 1; c < ncols; ++c) { |
|
for (i = c; i < n; i += ncols) { |
|
xOffset[i] += xExtent[c - 1]; |
|
} |
|
} |
|
} // enforce row alignment constraints |
|
|
|
|
|
if (alignRow === Each) { |
|
for (r = 1; r < nrows; ++r) { |
|
for (offset = 0, i = r * ncols, m = i + ncols; i < m; ++i) { |
|
if (offset < yOffset[i]) offset = yOffset[i]; |
|
} |
|
|
|
for (i = r * ncols; i < m; ++i) { |
|
yOffset[i] = offset + yExtent[r - 1]; |
|
} |
|
} |
|
} else if (alignRow === All) { |
|
for (offset = 0, i = ncols; i < n; ++i) { |
|
if (offset < yOffset[i]) offset = yOffset[i]; |
|
} |
|
|
|
for (i = ncols; i < n; ++i) { |
|
yOffset[i] = offset + yMax; |
|
} |
|
} else { |
|
for (alignRow = false, r = 1; r < nrows; ++r) { |
|
for (i = r * ncols, m = i + ncols; i < m; ++i) { |
|
yOffset[i] += yExtent[r - 1]; |
|
} |
|
} |
|
} // perform horizontal grid layout |
|
|
|
|
|
for (x = 0, i = 0; i < n; ++i) { |
|
x = xOffset[i] + (i % ncols ? x : 0); |
|
dx[i] += x - groups[i].x; |
|
} // perform vertical grid layout |
|
|
|
|
|
for (c = 0; c < ncols; ++c) { |
|
for (y = 0, i = c; i < n; i += ncols) { |
|
y += yOffset[i]; |
|
dy[i] += y - groups[i].y; |
|
} |
|
} // perform horizontal centering |
|
|
|
|
|
if (alignCol && get$2(opt.center, Column) && nrows > 1) { |
|
for (i = 0; i < n; ++i) { |
|
b = alignCol === All ? xMax : xExtent[i % ncols]; |
|
x = b - boxes[i].x2 - groups[i].x - dx[i]; |
|
if (x > 0) dx[i] += x / 2; |
|
} |
|
} // perform vertical centering |
|
|
|
|
|
if (alignRow && get$2(opt.center, Row) && ncols !== 1) { |
|
for (i = 0; i < n; ++i) { |
|
b = alignRow === All ? yMax : yExtent[~~(i / ncols)]; |
|
y = b - boxes[i].y2 - groups[i].y - dy[i]; |
|
if (y > 0) dy[i] += y / 2; |
|
} |
|
} // position grid relative to anchor |
|
|
|
|
|
for (i = 0; i < n; ++i) { |
|
bounds.union(boxes[i].translate(dx[i], dy[i])); |
|
} |
|
|
|
x = get$2(opt.anchor, X); |
|
y = get$2(opt.anchor, Y); |
|
|
|
switch (get$2(opt.anchor, Column)) { |
|
case End: |
|
x -= bounds.width(); |
|
break; |
|
|
|
case Middle: |
|
x -= bounds.width() / 2; |
|
} |
|
|
|
switch (get$2(opt.anchor, Row)) { |
|
case End: |
|
y -= bounds.height(); |
|
break; |
|
|
|
case Middle: |
|
y -= bounds.height() / 2; |
|
} |
|
|
|
x = Math.round(x); |
|
y = Math.round(y); // update mark positions, bounds, dirty |
|
|
|
bounds.clear(); |
|
|
|
for (i = 0; i < n; ++i) { |
|
groups[i].mark.bounds.clear(); |
|
} |
|
|
|
for (i = 0; i < n; ++i) { |
|
g = groups[i]; |
|
g.x += dx[i] += x; |
|
g.y += dy[i] += y; |
|
bounds.union(g.mark.bounds.union(g.bounds.translate(dx[i], dy[i]))); |
|
if (dirty) view.dirty(g); |
|
} |
|
|
|
return bounds; |
|
} |
|
|
|
function trellisLayout(view, group, opt) { |
|
var views = gridLayoutGroups(group), |
|
groups = views.marks, |
|
bbox = opt.bounds === Flush ? boundFlush : boundFull, |
|
off = opt.offset, |
|
ncols = opt.columns || groups.length, |
|
nrows = ncols < 0 ? 1 : Math.ceil(groups.length / ncols), |
|
cells = nrows * ncols, |
|
x, |
|
y, |
|
x2, |
|
y2, |
|
anchor, |
|
band, |
|
offset; // -- initial grid layout |
|
|
|
var bounds = gridLayout(view, groups, opt); // -- layout grid headers and footers -- |
|
// perform row header layout |
|
|
|
if (views.rowheaders) { |
|
band = get$2(opt.headerBand, Row, null); |
|
x = layoutHeaders(view, views.rowheaders, groups, ncols, nrows, -get$2(off, 'rowHeader'), min$2, 0, bbox, 'x1', 0, ncols, 1, band); |
|
} // perform column header layout |
|
|
|
|
|
if (views.colheaders) { |
|
band = get$2(opt.headerBand, Column, null); |
|
y = layoutHeaders(view, views.colheaders, groups, ncols, ncols, -get$2(off, 'columnHeader'), min$2, 1, bbox, 'y1', 0, 1, ncols, band); |
|
} // perform row footer layout |
|
|
|
|
|
if (views.rowfooters) { |
|
band = get$2(opt.footerBand, Row, null); |
|
x2 = layoutHeaders(view, views.rowfooters, groups, ncols, nrows, get$2(off, 'rowFooter'), max$2, 0, bbox, 'x2', ncols - 1, ncols, 1, band); |
|
} // perform column footer layout |
|
|
|
|
|
if (views.colfooters) { |
|
band = get$2(opt.footerBand, Column, null); |
|
y2 = layoutHeaders(view, views.colfooters, groups, ncols, ncols, get$2(off, 'columnFooter'), max$2, 1, bbox, 'y2', cells - ncols, 1, ncols, band); |
|
} // perform row title layout |
|
|
|
|
|
if (views.rowtitle) { |
|
anchor = get$2(opt.titleAnchor, Row); |
|
offset = get$2(off, 'rowTitle'); |
|
offset = anchor === End ? x2 + offset : x - offset; |
|
band = get$2(opt.titleBand, Row, 0.5); |
|
layoutTitle(view, views.rowtitle, offset, 0, bounds, band); |
|
} // perform column title layout |
|
|
|
|
|
if (views.coltitle) { |
|
anchor = get$2(opt.titleAnchor, Column); |
|
offset = get$2(off, 'columnTitle'); |
|
offset = anchor === End ? y2 + offset : y - offset; |
|
band = get$2(opt.titleBand, Column, 0.5); |
|
layoutTitle(view, views.coltitle, offset, 1, bounds, band); |
|
} |
|
} |
|
|
|
function boundFlush(item, field) { |
|
return field === 'x1' ? item.x || 0 : field === 'y1' ? item.y || 0 : field === 'x2' ? (item.x || 0) + (item.width || 0) : field === 'y2' ? (item.y || 0) + (item.height || 0) : undefined; |
|
} |
|
|
|
function boundFull(item, field) { |
|
return item.bounds[field]; |
|
} // aggregation functions for grid margin determination |
|
|
|
|
|
function min$2(a, b) { |
|
return Math.floor(Math.min(a, b)); |
|
} |
|
|
|
function max$2(a, b) { |
|
return Math.ceil(Math.max(a, b)); |
|
} |
|
|
|
function layoutHeaders(view, headers, groups, ncols, limit, offset, agg, isX, bound, bf, start, stride, back, band) { |
|
var n = groups.length, |
|
init = 0, |
|
edge = 0, |
|
i, |
|
j, |
|
k, |
|
m, |
|
b, |
|
h, |
|
g, |
|
x, |
|
y; // if no groups, early exit and return 0 |
|
|
|
if (!n) return init; // compute margin |
|
|
|
for (i = start; i < n; i += stride) { |
|
if (groups[i]) init = agg(init, bound(groups[i], bf)); |
|
} // if no headers, return margin calculation |
|
|
|
|
|
if (!headers.length) return init; // check if number of headers exceeds number of rows or columns |
|
|
|
if (headers.length > limit) { |
|
view.warn('Grid headers exceed limit: ' + limit); |
|
headers = headers.slice(0, limit); |
|
} // apply offset |
|
|
|
|
|
init += offset; // clear mark bounds for all headers |
|
|
|
for (j = 0, m = headers.length; j < m; ++j) { |
|
view.dirty(headers[j]); |
|
headers[j].mark.bounds.clear(); |
|
} // layout each header |
|
|
|
|
|
for (i = start, j = 0, m = headers.length; j < m; ++j, i += stride) { |
|
h = headers[j]; |
|
b = h.mark.bounds; // search for nearest group to align to |
|
// necessary if table has empty cells |
|
|
|
for (k = i; k >= 0 && (g = groups[k]) == null; k -= back) { |
|
; |
|
} // assign coordinates and update bounds |
|
|
|
|
|
if (isX) { |
|
x = band == null ? g.x : Math.round(g.bounds.x1 + band * g.bounds.width()); |
|
y = init; |
|
} else { |
|
x = init; |
|
y = band == null ? g.y : Math.round(g.bounds.y1 + band * g.bounds.height()); |
|
} |
|
|
|
b.union(h.bounds.translate(x - (h.x || 0), y - (h.y || 0))); |
|
h.x = x; |
|
h.y = y; |
|
view.dirty(h); // update current edge of layout bounds |
|
|
|
edge = agg(edge, b[bf]); |
|
} |
|
|
|
return edge; |
|
} |
|
|
|
function layoutTitle(view, g, offset, isX, bounds, band) { |
|
if (!g) return; |
|
view.dirty(g); // compute title coordinates |
|
|
|
var x = offset, |
|
y = offset; |
|
isX ? x = Math.round(bounds.x1 + band * bounds.width()) : y = Math.round(bounds.y1 + band * bounds.height()); // assign coordinates and update bounds |
|
|
|
g.bounds.translate(x - (g.x || 0), y - (g.y || 0)); |
|
g.mark.bounds.clear().union(g.bounds); |
|
g.x = x; |
|
g.y = y; // queue title for redraw |
|
|
|
view.dirty(g); |
|
} // utility for looking up legend layout configuration |
|
|
|
|
|
function lookup$2(config, orient) { |
|
var opt = config[orient] || {}; |
|
return function (key, d) { |
|
return opt[key] != null ? opt[key] : config[key] != null ? config[key] : d; |
|
}; |
|
} // if legends specify offset directly, use the maximum specified value |
|
|
|
|
|
function offsets(legends, value) { |
|
var max = -Infinity; |
|
legends.forEach(function (item) { |
|
if (item.offset != null) max = Math.max(max, item.offset); |
|
}); |
|
return max > -Infinity ? max : value; |
|
} |
|
|
|
function legendParams(g, orient, config, xb, yb, w, h) { |
|
var _ = lookup$2(config, orient), |
|
offset = offsets(g, _('offset', 0)), |
|
anchor = _('anchor', Start), |
|
mult = anchor === End ? 1 : anchor === Middle ? 0.5 : 0; |
|
|
|
var p = { |
|
align: Each, |
|
bounds: _('bounds', Flush), |
|
columns: _('direction') === 'vertical' ? 1 : g.length, |
|
padding: _('margin', 8), |
|
center: _('center'), |
|
nodirty: true |
|
}; |
|
|
|
switch (orient) { |
|
case Left: |
|
p.anchor = { |
|
x: Math.floor(xb.x1) - offset, |
|
column: End, |
|
y: mult * (h || xb.height() + 2 * xb.y1), |
|
row: anchor |
|
}; |
|
break; |
|
|
|
case Right: |
|
p.anchor = { |
|
x: Math.ceil(xb.x2) + offset, |
|
y: mult * (h || xb.height() + 2 * xb.y1), |
|
row: anchor |
|
}; |
|
break; |
|
|
|
case Top: |
|
p.anchor = { |
|
y: Math.floor(yb.y1) - offset, |
|
row: End, |
|
x: mult * (w || yb.width() + 2 * yb.x1), |
|
column: anchor |
|
}; |
|
break; |
|
|
|
case Bottom: |
|
p.anchor = { |
|
y: Math.ceil(yb.y2) + offset, |
|
x: mult * (w || yb.width() + 2 * yb.x1), |
|
column: anchor |
|
}; |
|
break; |
|
|
|
case TopLeft: |
|
p.anchor = { |
|
x: offset, |
|
y: offset |
|
}; |
|
break; |
|
|
|
case TopRight: |
|
p.anchor = { |
|
x: w - offset, |
|
y: offset, |
|
column: End |
|
}; |
|
break; |
|
|
|
case BottomLeft: |
|
p.anchor = { |
|
x: offset, |
|
y: h - offset, |
|
row: End |
|
}; |
|
break; |
|
|
|
case BottomRight: |
|
p.anchor = { |
|
x: w - offset, |
|
y: h - offset, |
|
column: End, |
|
row: End |
|
}; |
|
break; |
|
} |
|
|
|
return p; |
|
} |
|
|
|
function legendLayout(view, legend) { |
|
var item = legend.items[0], |
|
datum = item.datum, |
|
orient = item.orient, |
|
bounds = item.bounds, |
|
x = item.x, |
|
y = item.y, |
|
w, |
|
h; // cache current bounds for later comparison |
|
|
|
item._bounds ? item._bounds.clear().union(bounds) : item._bounds = bounds.clone(); |
|
bounds.clear(); // adjust legend to accommodate padding and title |
|
|
|
legendGroupLayout(view, item, item.items[0].items[0]); // aggregate bounds to determine size, and include origin |
|
|
|
bounds = legendBounds(item, bounds); |
|
w = 2 * item.padding; |
|
h = 2 * item.padding; |
|
|
|
if (!bounds.empty()) { |
|
w = Math.ceil(bounds.width() + w); |
|
h = Math.ceil(bounds.height() + h); |
|
} |
|
|
|
if (datum.type === Symbols) { |
|
legendEntryLayout(item.items[0].items[0].items[0].items); |
|
} |
|
|
|
if (orient !== None$1) { |
|
item.x = x = 0; |
|
item.y = y = 0; |
|
} |
|
|
|
item.width = w; |
|
item.height = h; |
|
boundStroke(bounds.set(x, y, x + w, y + h), item); |
|
item.mark.bounds.clear().union(bounds); |
|
return item; |
|
} |
|
|
|
function legendBounds(item, b) { |
|
// aggregate item bounds |
|
item.items.forEach(function (_) { |
|
return b.union(_.bounds); |
|
}); // anchor to legend origin |
|
|
|
b.x1 = item.padding; |
|
b.y1 = item.padding; |
|
return b; |
|
} |
|
|
|
function legendGroupLayout(view, item, entry) { |
|
var pad = item.padding, |
|
ex = pad - entry.x, |
|
ey = pad - entry.y; |
|
|
|
if (!item.datum.title) { |
|
if (ex || ey) translate$2(view, entry, ex, ey); |
|
} else { |
|
var title = item.items[1].items[0], |
|
anchor = title.anchor, |
|
tpad = item.titlePadding || 0, |
|
tx = pad - title.x, |
|
ty = pad - title.y; |
|
|
|
switch (title.orient) { |
|
case Left: |
|
ex += Math.ceil(title.bounds.width()) + tpad; |
|
break; |
|
|
|
case Right: |
|
case Bottom: |
|
break; |
|
|
|
default: |
|
ey += title.bounds.height() + tpad; |
|
} |
|
|
|
if (ex || ey) translate$2(view, entry, ex, ey); |
|
|
|
switch (title.orient) { |
|
case Left: |
|
ty += legendTitleOffset(item, entry, title, anchor, 1, 1); |
|
break; |
|
|
|
case Right: |
|
tx += legendTitleOffset(item, entry, title, End, 0, 0) + tpad; |
|
ty += legendTitleOffset(item, entry, title, anchor, 1, 1); |
|
break; |
|
|
|
case Bottom: |
|
tx += legendTitleOffset(item, entry, title, anchor, 0, 0); |
|
ty += legendTitleOffset(item, entry, title, End, -1, 0, 1) + tpad; |
|
break; |
|
|
|
default: |
|
tx += legendTitleOffset(item, entry, title, anchor, 0, 0); |
|
} |
|
|
|
if (tx || ty) translate$2(view, title, tx, ty); // translate legend if title pushes into negative coordinates |
|
|
|
if ((tx = Math.round(title.bounds.x1 - pad)) < 0) { |
|
translate$2(view, entry, -tx, 0); |
|
translate$2(view, title, -tx, 0); |
|
} |
|
} |
|
} |
|
|
|
function legendTitleOffset(item, entry, title, anchor, y, lr, noBar) { |
|
var grad = item.datum.type !== 'symbol', |
|
vgrad = title.datum.vgrad, |
|
e = grad && (lr || !vgrad) && !noBar ? entry.items[0] : entry, |
|
s = e.bounds[y ? 'y2' : 'x2'] - item.padding, |
|
u = vgrad && lr ? s : 0, |
|
v = vgrad && lr ? 0 : s, |
|
o = y <= 0 ? 0 : multiLineOffset(title); |
|
return Math.round(anchor === Start ? u : anchor === End ? v - o : 0.5 * (s - o)); |
|
} |
|
|
|
function translate$2(view, item, dx, dy) { |
|
item.x += dx; |
|
item.y += dy; |
|
item.bounds.translate(dx, dy); |
|
item.mark.bounds.translate(dx, dy); |
|
view.dirty(item); |
|
} |
|
|
|
function legendEntryLayout(entries) { |
|
// get max widths for each column |
|
var widths = entries.reduce(function (w, g) { |
|
w[g.column] = Math.max(g.bounds.x2 - g.x, w[g.column] || 0); |
|
return w; |
|
}, {}); // set dimensions of legend entry groups |
|
|
|
entries.forEach(function (g) { |
|
g.width = widths[g.column]; |
|
g.height = g.bounds.y2 - g.y; |
|
}); |
|
} |
|
|
|
function titleLayout(view, mark, width, height, viewBounds) { |
|
var group = mark.items[0], |
|
frame = group.frame, |
|
orient = group.orient, |
|
anchor = group.anchor, |
|
offset = group.offset, |
|
padding = group.padding, |
|
title = group.items[0].items[0], |
|
subtitle = group.items[1] && group.items[1].items[0], |
|
end = orient === Left || orient === Right ? height : width, |
|
start = 0, |
|
x = 0, |
|
y = 0, |
|
sx = 0, |
|
sy = 0, |
|
pos; |
|
|
|
if (frame !== Group) { |
|
orient === Left ? (start = viewBounds.y2, end = viewBounds.y1) : orient === Right ? (start = viewBounds.y1, end = viewBounds.y2) : (start = viewBounds.x1, end = viewBounds.x2); |
|
} else if (orient === Left) { |
|
start = height, end = 0; |
|
} |
|
|
|
pos = anchor === Start ? start : anchor === End ? end : (start + end) / 2; |
|
|
|
if (subtitle && subtitle.text) { |
|
// position subtitle |
|
switch (orient) { |
|
case Top: |
|
case Bottom: |
|
sy = title.bounds.height() + padding; |
|
break; |
|
|
|
case Left: |
|
sx = title.bounds.width() + padding; |
|
break; |
|
|
|
case Right: |
|
sx = -title.bounds.width() - padding; |
|
break; |
|
} |
|
|
|
tempBounds$2.clear().union(subtitle.bounds); |
|
tempBounds$2.translate(sx - (subtitle.x || 0), sy - (subtitle.y || 0)); |
|
|
|
if (set(subtitle, 'x', sx) | set(subtitle, 'y', sy)) { |
|
view.dirty(subtitle); |
|
subtitle.bounds.clear().union(tempBounds$2); |
|
subtitle.mark.bounds.clear().union(tempBounds$2); |
|
view.dirty(subtitle); |
|
} |
|
|
|
tempBounds$2.clear().union(subtitle.bounds); |
|
} else { |
|
tempBounds$2.clear(); |
|
} |
|
|
|
tempBounds$2.union(title.bounds); // position title group |
|
|
|
switch (orient) { |
|
case Top: |
|
x = pos; |
|
y = viewBounds.y1 - tempBounds$2.height() - offset; |
|
break; |
|
|
|
case Left: |
|
x = viewBounds.x1 - tempBounds$2.width() - offset; |
|
y = pos; |
|
break; |
|
|
|
case Right: |
|
x = viewBounds.x2 + tempBounds$2.width() + offset; |
|
y = pos; |
|
break; |
|
|
|
case Bottom: |
|
x = pos; |
|
y = viewBounds.y2 + offset; |
|
break; |
|
|
|
default: |
|
x = group.x; |
|
y = group.y; |
|
} |
|
|
|
if (set(group, 'x', x) | set(group, 'y', y)) { |
|
tempBounds$2.translate(x, y); |
|
view.dirty(group); |
|
group.bounds.clear().union(tempBounds$2); |
|
mark.bounds.clear().union(tempBounds$2); |
|
view.dirty(group); |
|
} |
|
|
|
return group.bounds; |
|
} |
|
/** |
|
* Layout view elements such as axes and legends. |
|
* Also performs size adjustments. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {object} params.mark - Scenegraph mark of groups to layout. |
|
*/ |
|
|
|
|
|
function ViewLayout(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$V = inherits(ViewLayout, Transform); |
|
|
|
prototype$V.transform = function (_, pulse) { |
|
// TODO incremental update, output? |
|
var view = pulse.dataflow; |
|
|
|
_.mark.items.forEach(function (group) { |
|
if (_.layout) trellisLayout(view, group, _.layout); |
|
layoutGroup(view, group, _); |
|
}); |
|
|
|
if (_.modified()) pulse.reflow(); |
|
return pulse; |
|
}; |
|
|
|
function layoutGroup(view, group, _) { |
|
var items = group.items, |
|
width = Math.max(0, group.width || 0), |
|
height = Math.max(0, group.height || 0), |
|
viewBounds = new Bounds().set(0, 0, width, height), |
|
xBounds = viewBounds.clone(), |
|
yBounds = viewBounds.clone(), |
|
legends = [], |
|
title, |
|
mark, |
|
orient, |
|
b, |
|
i, |
|
n; // layout axes, gather legends, collect bounds |
|
|
|
for (i = 0, n = items.length; i < n; ++i) { |
|
mark = items[i]; |
|
|
|
switch (mark.role) { |
|
case AxisRole: |
|
b = isYAxis(mark) ? xBounds : yBounds; |
|
b.union(axisLayout(view, mark, width, height)); |
|
break; |
|
|
|
case TitleRole: |
|
title = mark; |
|
break; |
|
|
|
case LegendRole: |
|
legends.push(legendLayout(view, mark)); |
|
break; |
|
|
|
case FrameRole: |
|
case ScopeRole: |
|
case RowHeader: |
|
case RowFooter: |
|
case RowTitle: |
|
case ColHeader: |
|
case ColFooter: |
|
case ColTitle: |
|
xBounds.union(mark.bounds); |
|
yBounds.union(mark.bounds); |
|
break; |
|
|
|
default: |
|
viewBounds.union(mark.bounds); |
|
} |
|
} // layout legends, adjust viewBounds |
|
|
|
|
|
if (legends.length) { |
|
// group legends by orient |
|
var l = {}; |
|
legends.forEach(function (item) { |
|
orient = item.orient || Right; |
|
if (orient !== None$1) (l[orient] || (l[orient] = [])).push(item); |
|
}); // perform grid layout for each orient group |
|
|
|
for (var _orient in l) { |
|
var g = l[_orient]; |
|
gridLayout(view, g, legendParams(g, _orient, _.legends, xBounds, yBounds, width, height)); |
|
} // update view bounds |
|
|
|
|
|
legends.forEach(function (item) { |
|
var b = item.bounds; |
|
|
|
if (!b.equals(item._bounds)) { |
|
item.bounds = item._bounds; |
|
view.dirty(item); // dirty previous location |
|
|
|
item.bounds = b; |
|
view.dirty(item); |
|
} |
|
|
|
if (_.autosize && _.autosize.type === Fit) { |
|
// For autosize fit, incorporate the orthogonal dimension only. |
|
// Legends that overrun the chart area will then be clipped; |
|
// otherwise the chart area gets reduced to nothing! |
|
switch (item.orient) { |
|
case Left: |
|
case Right: |
|
viewBounds.add(b.x1, 0).add(b.x2, 0); |
|
break; |
|
|
|
case Top: |
|
case Bottom: |
|
viewBounds.add(0, b.y1).add(0, b.y2); |
|
} |
|
} else { |
|
viewBounds.union(b); |
|
} |
|
}); |
|
} // combine bounding boxes |
|
|
|
|
|
viewBounds.union(xBounds).union(yBounds); // layout title, adjust bounds |
|
|
|
if (title) { |
|
viewBounds.union(titleLayout(view, title, width, height, viewBounds)); |
|
} // override aggregated view bounds if content is clipped |
|
|
|
|
|
if (group.clip) { |
|
viewBounds.set(0, 0, group.width || 0, group.height || 0); |
|
} // perform size adjustment |
|
|
|
|
|
viewSizeLayout(view, group, viewBounds, _); |
|
} |
|
|
|
function viewSizeLayout(view, group, viewBounds, _) { |
|
var auto = _.autosize || {}, |
|
type = auto.type, |
|
viewWidth = view._width, |
|
viewHeight = view._height, |
|
padding = view.padding(); |
|
if (view._autosize < 1 || !type) return; |
|
var width = Math.max(0, group.width || 0), |
|
left = Math.max(0, Math.ceil(-viewBounds.x1)), |
|
right = Math.max(0, Math.ceil(viewBounds.x2 - width)), |
|
height = Math.max(0, group.height || 0), |
|
top = Math.max(0, Math.ceil(-viewBounds.y1)), |
|
bottom = Math.max(0, Math.ceil(viewBounds.y2 - height)); |
|
|
|
if (auto.contains === Padding) { |
|
viewWidth -= padding.left + padding.right; |
|
viewHeight -= padding.top + padding.bottom; |
|
} |
|
|
|
if (type === None$1) { |
|
left = 0; |
|
top = 0; |
|
width = viewWidth; |
|
height = viewHeight; |
|
} else if (type === Fit) { |
|
width = Math.max(0, viewWidth - left - right); |
|
height = Math.max(0, viewHeight - top - bottom); |
|
} else if (type === FitX) { |
|
width = Math.max(0, viewWidth - left - right); |
|
viewHeight = height + top + bottom; |
|
} else if (type === FitY) { |
|
viewWidth = width + left + right; |
|
height = Math.max(0, viewHeight - top - bottom); |
|
} else if (type === Pad) { |
|
viewWidth = width + left + right; |
|
viewHeight = height + top + bottom; |
|
} |
|
|
|
view._resizeView(viewWidth, viewHeight, width, height, [left, top], auto.resize); |
|
} |
|
|
|
var vtx = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
bound: Bound, |
|
identifier: Identifier, |
|
mark: Mark, |
|
overlap: Overlap, |
|
render: Render, |
|
viewlayout: ViewLayout |
|
}); |
|
|
|
function bandSpace(count, paddingInner, paddingOuter) { |
|
var space = count - paddingInner + paddingOuter * 2; |
|
return count ? space > 0 ? space : 1 : 0; |
|
} |
|
|
|
var Identity = 'identity'; |
|
var Linear$1 = 'linear'; |
|
var Log = 'log'; |
|
var Pow = 'pow'; |
|
var Sqrt = 'sqrt'; |
|
var Symlog = 'symlog'; |
|
var Time = 'time'; |
|
var UTC = 'utc'; |
|
var Sequential = 'sequential'; |
|
var Diverging = 'diverging'; |
|
var Quantile$1 = 'quantile'; |
|
var Quantize = 'quantize'; |
|
var Threshold = 'threshold'; |
|
var Ordinal = 'ordinal'; |
|
var Point = 'point'; |
|
var Band = 'band'; |
|
var BinOrdinal = 'bin-ordinal'; // categories |
|
|
|
var Continuous = 'continuous'; |
|
var Discrete = 'discrete'; |
|
var Discretizing = 'discretizing'; |
|
var Interpolating = 'interpolating'; |
|
var Temporal = 'temporal'; |
|
|
|
function invertRange(scale) { |
|
return function (_) { |
|
var lo = _[0], |
|
hi = _[1], |
|
t; |
|
|
|
if (hi < lo) { |
|
t = lo; |
|
lo = hi; |
|
hi = t; |
|
} |
|
|
|
return [scale.invert(lo), scale.invert(hi)]; |
|
}; |
|
} |
|
|
|
function invertRangeExtent(scale) { |
|
return function (_) { |
|
var range = scale.range(), |
|
lo = _[0], |
|
hi = _[1], |
|
min = -1, |
|
max, |
|
t, |
|
i, |
|
n; |
|
|
|
if (hi < lo) { |
|
t = lo; |
|
lo = hi; |
|
hi = t; |
|
} |
|
|
|
for (i = 0, n = range.length; i < n; ++i) { |
|
if (range[i] >= lo && range[i] <= hi) { |
|
if (min < 0) min = i; |
|
max = i; |
|
} |
|
} |
|
|
|
if (min < 0) return undefined; |
|
lo = scale.invertExtent(range[min]); |
|
hi = scale.invertExtent(range[max]); |
|
return [lo[0] === undefined ? lo[1] : lo[0], hi[1] === undefined ? hi[0] : hi[1]]; |
|
}; |
|
} |
|
|
|
function initRange(domain, range) { |
|
switch (arguments.length) { |
|
case 0: |
|
break; |
|
|
|
case 1: |
|
this.range(domain); |
|
break; |
|
|
|
default: |
|
this.range(range).domain(domain); |
|
break; |
|
} |
|
|
|
return this; |
|
} |
|
|
|
function initInterpolator(domain, interpolator) { |
|
switch (arguments.length) { |
|
case 0: |
|
break; |
|
|
|
case 1: |
|
{ |
|
if (typeof domain === "function") this.interpolator(domain);else this.range(domain); |
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
this.domain(domain); |
|
if (typeof interpolator === "function") this.interpolator(interpolator);else this.range(interpolator); |
|
break; |
|
} |
|
} |
|
|
|
return this; |
|
} |
|
|
|
var implicit = Symbol("implicit"); |
|
|
|
function ordinal() { |
|
var index = new Map(), |
|
domain = [], |
|
range = [], |
|
unknown = implicit; |
|
|
|
function scale(d) { |
|
var key = d + "", |
|
i = index.get(key); |
|
|
|
if (!i) { |
|
if (unknown !== implicit) return unknown; |
|
index.set(key, i = domain.push(d)); |
|
} |
|
|
|
return range[(i - 1) % range.length]; |
|
} |
|
|
|
scale.domain = function (_) { |
|
if (!arguments.length) return domain.slice(); |
|
domain = [], index = new Map(); |
|
var _iteratorNormalCompletion18 = true; |
|
var _didIteratorError18 = false; |
|
var _iteratorError18 = undefined; |
|
|
|
try { |
|
for (var _iterator18 = _[Symbol.iterator](), _step18; !(_iteratorNormalCompletion18 = (_step18 = _iterator18.next()).done); _iteratorNormalCompletion18 = true) { |
|
var _value15 = _step18.value; |
|
|
|
var _key2 = _value15 + ""; |
|
|
|
if (index.has(_key2)) continue; |
|
index.set(_key2, domain.push(_value15)); |
|
} |
|
} catch (err) { |
|
_didIteratorError18 = true; |
|
_iteratorError18 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion18 && _iterator18.return != null) { |
|
_iterator18.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError18) { |
|
throw _iteratorError18; |
|
} |
|
} |
|
} |
|
|
|
return scale; |
|
}; |
|
|
|
scale.range = function (_) { |
|
return arguments.length ? (range = Array.from(_), scale) : range.slice(); |
|
}; |
|
|
|
scale.unknown = function (_) { |
|
return arguments.length ? (unknown = _, scale) : unknown; |
|
}; |
|
|
|
scale.copy = function () { |
|
return ordinal(domain, range).unknown(unknown); |
|
}; |
|
|
|
initRange.apply(scale, arguments); |
|
return scale; |
|
} |
|
|
|
function define(constructor, factory, prototype) { |
|
constructor.prototype = factory.prototype = prototype; |
|
prototype.constructor = constructor; |
|
} |
|
|
|
function extend$1(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$1, { |
|
copy: function copy(channels) { |
|
return Object.assign(new this.constructor(), this, channels); |
|
}, |
|
displayable: function displayable() { |
|
return this.rgb().displayable(); |
|
}, |
|
hex: color_formatHex, |
|
// Deprecated! Use color.formatHex. |
|
formatHex: color_formatHex, |
|
formatHsl: color_formatHsl, |
|
formatRgb: color_formatRgb, |
|
toString: color_formatRgb |
|
}); |
|
|
|
function color_formatHex() { |
|
return this.rgb().formatHex(); |
|
} |
|
|
|
function color_formatHsl() { |
|
return hslConvert(this).formatHsl(); |
|
} |
|
|
|
function color_formatRgb() { |
|
return this.rgb().formatRgb(); |
|
} |
|
|
|
function color$1(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 ? new Rgb(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000 |
|
: l === 4 ? new Rgb(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$1(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$1(Color, { |
|
brighter: function 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: function 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: function rgb() { |
|
return this; |
|
}, |
|
displayable: function 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, |
|
formatRgb: rgb_formatRgb, |
|
toString: rgb_formatRgb |
|
})); |
|
|
|
function rgb_formatHex() { |
|
return "#" + hex(this.r) + hex(this.g) + hex(this.b); |
|
} |
|
|
|
function rgb_formatRgb() { |
|
var a = this.opacity; |
|
a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a)); |
|
return (a === 1 ? "rgb(" : "rgba(") + Math.max(0, Math.min(255, Math.round(this.r) || 0)) + ", " + Math.max(0, Math.min(255, Math.round(this.g) || 0)) + ", " + Math.max(0, Math.min(255, Math.round(this.b) || 0)) + (a === 1 ? ")" : ", " + a + ")"); |
|
} |
|
|
|
function hex(value) { |
|
value = Math.max(0, Math.min(255, Math.round(value) || 0)); |
|
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$1(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$1(Color, { |
|
brighter: function brighter(k) { |
|
k = k == null ? _brighter : Math.pow(_brighter, k); |
|
return new Hsl(this.h, this.s, this.l * k, this.opacity); |
|
}, |
|
darker: function darker(k) { |
|
k = k == null ? _darker : Math.pow(_darker, k); |
|
return new Hsl(this.h, this.s, this.l * k, this.opacity); |
|
}, |
|
rgb: function 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); |
|
}, |
|
displayable: function displayable() { |
|
return (0 <= this.s && this.s <= 1 || isNaN(this.s)) && 0 <= this.l && this.l <= 1 && 0 <= this.opacity && this.opacity <= 1; |
|
}, |
|
formatHsl: function formatHsl() { |
|
var a = this.opacity; |
|
a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a)); |
|
return (a === 1 ? "hsl(" : "hsla(") + (this.h || 0) + ", " + (this.s || 0) * 100 + "%, " + (this.l || 0) * 100 + "%" + (a === 1 ? ")" : ", " + a + ")"); |
|
} |
|
})); |
|
/* 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; |
|
} |
|
|
|
var deg2rad = Math.PI / 180; |
|
var rad2deg = 180 / Math.PI; // https://observablehq.com/@mbostock/lab-and-rgb |
|
|
|
var K = 18, |
|
Xn = 0.96422, |
|
Yn = 1, |
|
Zn = 0.82521, |
|
t0$2 = 4 / 29, |
|
t1$1 = 6 / 29, |
|
t2 = 3 * t1$1 * t1$1, |
|
t3 = t1$1 * t1$1 * t1$1; |
|
|
|
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$1(Color, { |
|
brighter: function brighter(k) { |
|
return new Lab(this.l + K * (k == null ? 1 : k), this.a, this.b, this.opacity); |
|
}, |
|
darker: function darker(k) { |
|
return new Lab(this.l - K * (k == null ? 1 : k), this.a, this.b, this.opacity); |
|
}, |
|
rgb: function 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$2; |
|
} |
|
|
|
function lab2xyz(t) { |
|
return t > t1$1 ? t * t * t : t2 * (t - t0$2); |
|
} |
|
|
|
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) * rad2deg; |
|
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 * deg2rad; |
|
return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity); |
|
} |
|
|
|
define(Hcl, hcl, extend$1(Color, { |
|
brighter: function brighter(k) { |
|
return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity); |
|
}, |
|
darker: function darker(k) { |
|
return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity); |
|
}, |
|
rgb: function rgb() { |
|
return hcl2lab(this).rgb(); |
|
} |
|
})); |
|
var A = -0.14861, |
|
B = +1.78277, |
|
C$1 = -0.29227, |
|
D = -0.90649, |
|
E = +1.97294, |
|
ED = E * D, |
|
EB = E * B, |
|
BC_DA = B * C$1 - D * A; |
|
|
|
function cubehelixConvert(o) { |
|
if (o instanceof Cubehelix) return new Cubehelix(o.h, o.s, o.l, o.opacity); |
|
if (!(o instanceof Rgb)) o = rgbConvert(o); |
|
var r = o.r / 255, |
|
g = o.g / 255, |
|
b = o.b / 255, |
|
l = (BC_DA * b + ED * r - EB * g) / (BC_DA + ED - EB), |
|
bl = b - l, |
|
k = (E * (g - l) - C$1 * bl) / D, |
|
s = Math.sqrt(k * k + bl * bl) / (E * l * (1 - l)), |
|
// NaN if l=0 or l=1 |
|
h = s ? Math.atan2(k, bl) * rad2deg - 120 : NaN; |
|
return new Cubehelix(h < 0 ? h + 360 : h, s, l, o.opacity); |
|
} |
|
|
|
function cubehelix(h, s, l, opacity) { |
|
return arguments.length === 1 ? cubehelixConvert(h) : new Cubehelix(h, s, l, opacity == null ? 1 : opacity); |
|
} |
|
|
|
function Cubehelix(h, s, l, opacity) { |
|
this.h = +h; |
|
this.s = +s; |
|
this.l = +l; |
|
this.opacity = +opacity; |
|
} |
|
|
|
define(Cubehelix, cubehelix, extend$1(Color, { |
|
brighter: function brighter(k) { |
|
k = k == null ? _brighter : Math.pow(_brighter, k); |
|
return new Cubehelix(this.h, this.s, this.l * k, this.opacity); |
|
}, |
|
darker: function darker(k) { |
|
k = k == null ? _darker : Math.pow(_darker, k); |
|
return new Cubehelix(this.h, this.s, this.l * k, this.opacity); |
|
}, |
|
rgb: function rgb() { |
|
var h = isNaN(this.h) ? 0 : (this.h + 120) * deg2rad, |
|
l = +this.l, |
|
a = isNaN(this.s) ? 0 : this.s * l * (1 - l), |
|
cosh = Math.cos(h), |
|
sinh = Math.sin(h); |
|
return new Rgb(255 * (l + a * (A * cosh + B * sinh)), 255 * (l + a * (C$1 * cosh + D * sinh)), 255 * (l + a * (E * cosh)), this.opacity); |
|
} |
|
})); |
|
|
|
function basis(t1, v0, v1, v2, v3) { |
|
var t2 = t1 * t1, |
|
t3 = t2 * t1; |
|
return ((1 - 3 * t1 + 3 * t2 - t3) * v0 + (4 - 6 * t2 + 3 * t3) * v1 + (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 + t3 * v3) / 6; |
|
} |
|
|
|
function basis$1(values) { |
|
var n = values.length - 1; |
|
return function (t) { |
|
var i = t <= 0 ? t = 0 : t >= 1 ? (t = 1, n - 1) : Math.floor(t * n), |
|
v1 = values[i], |
|
v2 = values[i + 1], |
|
v0 = i > 0 ? values[i - 1] : 2 * v1 - v2, |
|
v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1; |
|
return basis((t - i / n) * n, v0, v1, v2, v3); |
|
}; |
|
} |
|
|
|
function basisClosed(values) { |
|
var n = values.length; |
|
return function (t) { |
|
var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n), |
|
v0 = values[(i + n - 1) % n], |
|
v1 = values[i % n], |
|
v2 = values[(i + 1) % n], |
|
v3 = values[(i + 2) % n]; |
|
return basis((t - i / n) * n, v0, v1, v2, v3); |
|
}; |
|
} |
|
|
|
function constant$2(x) { |
|
return function () { |
|
return x; |
|
}; |
|
} |
|
|
|
function linear(a, d) { |
|
return function (t) { |
|
return a + t * d; |
|
}; |
|
} |
|
|
|
function exponential(a, b, y) { |
|
return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function (t) { |
|
return Math.pow(a + t * b, y); |
|
}; |
|
} |
|
|
|
function hue(a, b) { |
|
var d = b - a; |
|
return d ? linear(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d) : constant$2(isNaN(a) ? b : a); |
|
} |
|
|
|
function gamma(y) { |
|
return (y = +y) === 1 ? nogamma : function (a, b) { |
|
return b - a ? exponential(a, b, y) : constant$2(isNaN(a) ? b : a); |
|
}; |
|
} |
|
|
|
function nogamma(a, b) { |
|
var d = b - a; |
|
return d ? linear(a, d) : constant$2(isNaN(a) ? b : a); |
|
} |
|
|
|
var rgb$1 = function rgbGamma(y) { |
|
var color = gamma(y); |
|
|
|
function rgb$1(start, end) { |
|
var r = color((start = rgb(start)).r, (end = rgb(end)).r), |
|
g = color(start.g, end.g), |
|
b = color(start.b, end.b), |
|
opacity = nogamma(start.opacity, end.opacity); |
|
return function (t) { |
|
start.r = r(t); |
|
start.g = g(t); |
|
start.b = b(t); |
|
start.opacity = opacity(t); |
|
return start + ""; |
|
}; |
|
} |
|
|
|
rgb$1.gamma = rgbGamma; |
|
return rgb$1; |
|
}(1); |
|
|
|
function rgbSpline(spline) { |
|
return function (colors) { |
|
var n = colors.length, |
|
r = new Array(n), |
|
g = new Array(n), |
|
b = new Array(n), |
|
i, |
|
color; |
|
|
|
for (i = 0; i < n; ++i) { |
|
color = rgb(colors[i]); |
|
r[i] = color.r || 0; |
|
g[i] = color.g || 0; |
|
b[i] = color.b || 0; |
|
} |
|
|
|
r = spline(r); |
|
g = spline(g); |
|
b = spline(b); |
|
color.opacity = 1; |
|
return function (t) { |
|
color.r = r(t); |
|
color.g = g(t); |
|
color.b = b(t); |
|
return color + ""; |
|
}; |
|
}; |
|
} |
|
|
|
var rgbBasis = rgbSpline(basis$1); |
|
var rgbBasisClosed = rgbSpline(basisClosed); |
|
|
|
function numberArray(a, b) { |
|
if (!b) b = []; |
|
var n = a ? Math.min(b.length, a.length) : 0, |
|
c = b.slice(), |
|
i; |
|
return function (t) { |
|
for (i = 0; i < n; ++i) { |
|
c[i] = a[i] * (1 - t) + b[i] * t; |
|
} |
|
|
|
return c; |
|
}; |
|
} |
|
|
|
function isNumberArray(x) { |
|
return ArrayBuffer.isView(x) && !(x instanceof DataView); |
|
} |
|
|
|
function array$1(a, b) { |
|
return (isNumberArray(b) ? numberArray : genericArray)(a, b); |
|
} |
|
|
|
function genericArray(a, b) { |
|
var nb = b ? b.length : 0, |
|
na = a ? Math.min(nb, a.length) : 0, |
|
x = new Array(na), |
|
c = new Array(nb), |
|
i; |
|
|
|
for (i = 0; i < na; ++i) { |
|
x[i] = interpolate(a[i], b[i]); |
|
} |
|
|
|
for (; i < nb; ++i) { |
|
c[i] = b[i]; |
|
} |
|
|
|
return function (t) { |
|
for (i = 0; i < na; ++i) { |
|
c[i] = x[i](t); |
|
} |
|
|
|
return c; |
|
}; |
|
} |
|
|
|
function date(a, b) { |
|
var d = new Date(); |
|
return a = +a, b = +b, function (t) { |
|
return d.setTime(a * (1 - t) + b * t), d; |
|
}; |
|
} |
|
|
|
function interpolateNumber(a, b) { |
|
return a = +a, b = +b, function (t) { |
|
return a * (1 - t) + b * t; |
|
}; |
|
} |
|
|
|
function object$2(a, b) { |
|
var i = {}, |
|
c = {}, |
|
k; |
|
if (a === null || _typeof(a) !== "object") a = {}; |
|
if (b === null || _typeof(b) !== "object") b = {}; |
|
|
|
for (k in b) { |
|
if (k in a) { |
|
i[k] = interpolate(a[k], b[k]); |
|
} else { |
|
c[k] = b[k]; |
|
} |
|
} |
|
|
|
return function (t) { |
|
for (k in i) { |
|
c[k] = i[k](t); |
|
} |
|
|
|
return c; |
|
}; |
|
} |
|
|
|
var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, |
|
reB = new RegExp(reA.source, "g"); |
|
|
|
function zero$1(b) { |
|
return function () { |
|
return b; |
|
}; |
|
} |
|
|
|
function one$1(b) { |
|
return function (t) { |
|
return b(t) + ""; |
|
}; |
|
} |
|
|
|
function string(a, b) { |
|
var bi = reA.lastIndex = reB.lastIndex = 0, |
|
// scan index for next number in b |
|
am, |
|
// current match in a |
|
bm, |
|
// current match in b |
|
bs, |
|
// string preceding current number in b, if any |
|
i = -1, |
|
// index in s |
|
s = [], |
|
// string constants and placeholders |
|
q = []; // number interpolators |
|
// Coerce inputs to strings. |
|
|
|
a = a + "", b = b + ""; // Interpolate pairs of numbers in a & b. |
|
|
|
while ((am = reA.exec(a)) && (bm = reB.exec(b))) { |
|
if ((bs = bm.index) > bi) { |
|
// a string precedes the next number in b |
|
bs = b.slice(bi, bs); |
|
if (s[i]) s[i] += bs; // coalesce with previous string |
|
else s[++i] = bs; |
|
} |
|
|
|
if ((am = am[0]) === (bm = bm[0])) { |
|
// numbers in a & b match |
|
if (s[i]) s[i] += bm; // coalesce with previous string |
|
else s[++i] = bm; |
|
} else { |
|
// interpolate non-matching numbers |
|
s[++i] = null; |
|
q.push({ |
|
i: i, |
|
x: interpolateNumber(am, bm) |
|
}); |
|
} |
|
|
|
bi = reB.lastIndex; |
|
} // Add remains of b. |
|
|
|
|
|
if (bi < b.length) { |
|
bs = b.slice(bi); |
|
if (s[i]) s[i] += bs; // coalesce with previous string |
|
else s[++i] = bs; |
|
} // Special optimization for only a single match. |
|
// Otherwise, interpolate each of the numbers and rejoin the string. |
|
|
|
|
|
return s.length < 2 ? q[0] ? one$1(q[0].x) : zero$1(b) : (b = q.length, function (t) { |
|
for (var i = 0, o; i < b; ++i) { |
|
s[(o = q[i]).i] = o.x(t); |
|
} |
|
|
|
return s.join(""); |
|
}); |
|
} |
|
|
|
function interpolate(a, b) { |
|
var t = _typeof(b), |
|
c; |
|
|
|
return b == null || t === "boolean" ? constant$2(b) : (t === "number" ? interpolateNumber : t === "string" ? (c = color$1(b)) ? (b = c, rgb$1) : string : b instanceof color$1 ? rgb$1 : b instanceof Date ? date : isNumberArray(b) ? numberArray : Array.isArray(b) ? genericArray : typeof b.valueOf !== "function" && typeof b.toString !== "function" || isNaN(b) ? object$2 : interpolateNumber)(a, b); |
|
} |
|
|
|
function discrete(range) { |
|
var n = range.length; |
|
return function (t) { |
|
return range[Math.max(0, Math.min(n - 1, Math.floor(t * n)))]; |
|
}; |
|
} |
|
|
|
function hue$1(a, b) { |
|
var i = hue(+a, +b); |
|
return function (t) { |
|
var x = i(t); |
|
return x - 360 * Math.floor(x / 360); |
|
}; |
|
} |
|
|
|
function interpolateRound(a, b) { |
|
return a = +a, b = +b, function (t) { |
|
return Math.round(a * (1 - t) + b * t); |
|
}; |
|
} |
|
|
|
var degrees = 180 / Math.PI; |
|
var identity$2 = { |
|
translateX: 0, |
|
translateY: 0, |
|
rotate: 0, |
|
skewX: 0, |
|
scaleX: 1, |
|
scaleY: 1 |
|
}; |
|
|
|
function decompose(a, b, c, d, e, f) { |
|
var scaleX, scaleY, skewX; |
|
if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX; |
|
if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX; |
|
if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY; |
|
if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX; |
|
return { |
|
translateX: e, |
|
translateY: f, |
|
rotate: Math.atan2(b, a) * degrees, |
|
skewX: Math.atan(skewX) * degrees, |
|
scaleX: scaleX, |
|
scaleY: scaleY |
|
}; |
|
} |
|
|
|
var cssNode, cssRoot, cssView, svgNode; |
|
|
|
function parseCss(value) { |
|
if (value === "none") return identity$2; |
|
if (!cssNode) cssNode = document.createElement("DIV"), cssRoot = document.documentElement, cssView = document.defaultView; |
|
cssNode.style.transform = value; |
|
value = cssView.getComputedStyle(cssRoot.appendChild(cssNode), null).getPropertyValue("transform"); |
|
cssRoot.removeChild(cssNode); |
|
value = value.slice(7, -1).split(","); |
|
return decompose(+value[0], +value[1], +value[2], +value[3], +value[4], +value[5]); |
|
} |
|
|
|
function parseSvg(value) { |
|
if (value == null) return identity$2; |
|
if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g"); |
|
svgNode.setAttribute("transform", value); |
|
if (!(value = svgNode.transform.baseVal.consolidate())) return identity$2; |
|
value = value.matrix; |
|
return decompose(value.a, value.b, value.c, value.d, value.e, value.f); |
|
} |
|
|
|
function interpolateTransform(parse, pxComma, pxParen, degParen) { |
|
function pop(s) { |
|
return s.length ? s.pop() + " " : ""; |
|
} |
|
|
|
function translate(xa, ya, xb, yb, s, q) { |
|
if (xa !== xb || ya !== yb) { |
|
var i = s.push("translate(", null, pxComma, null, pxParen); |
|
q.push({ |
|
i: i - 4, |
|
x: interpolateNumber(xa, xb) |
|
}, { |
|
i: i - 2, |
|
x: interpolateNumber(ya, yb) |
|
}); |
|
} else if (xb || yb) { |
|
s.push("translate(" + xb + pxComma + yb + pxParen); |
|
} |
|
} |
|
|
|
function rotate(a, b, s, q) { |
|
if (a !== b) { |
|
if (a - b > 180) b += 360;else if (b - a > 180) a += 360; // shortest path |
|
|
|
q.push({ |
|
i: s.push(pop(s) + "rotate(", null, degParen) - 2, |
|
x: interpolateNumber(a, b) |
|
}); |
|
} else if (b) { |
|
s.push(pop(s) + "rotate(" + b + degParen); |
|
} |
|
} |
|
|
|
function skewX(a, b, s, q) { |
|
if (a !== b) { |
|
q.push({ |
|
i: s.push(pop(s) + "skewX(", null, degParen) - 2, |
|
x: interpolateNumber(a, b) |
|
}); |
|
} else if (b) { |
|
s.push(pop(s) + "skewX(" + b + degParen); |
|
} |
|
} |
|
|
|
function scale(xa, ya, xb, yb, s, q) { |
|
if (xa !== xb || ya !== yb) { |
|
var i = s.push(pop(s) + "scale(", null, ",", null, ")"); |
|
q.push({ |
|
i: i - 4, |
|
x: interpolateNumber(xa, xb) |
|
}, { |
|
i: i - 2, |
|
x: interpolateNumber(ya, yb) |
|
}); |
|
} else if (xb !== 1 || yb !== 1) { |
|
s.push(pop(s) + "scale(" + xb + "," + yb + ")"); |
|
} |
|
} |
|
|
|
return function (a, b) { |
|
var s = [], |
|
// string constants and placeholders |
|
q = []; // number interpolators |
|
|
|
a = parse(a), b = parse(b); |
|
translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q); |
|
rotate(a.rotate, b.rotate, s, q); |
|
skewX(a.skewX, b.skewX, s, q); |
|
scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q); |
|
a = b = null; // gc |
|
|
|
return function (t) { |
|
var i = -1, |
|
n = q.length, |
|
o; |
|
|
|
while (++i < n) { |
|
s[(o = q[i]).i] = o.x(t); |
|
} |
|
|
|
return s.join(""); |
|
}; |
|
}; |
|
} |
|
|
|
var interpolateTransformCss = interpolateTransform(parseCss, "px, ", "px)", "deg)"); |
|
var interpolateTransformSvg = interpolateTransform(parseSvg, ", ", ")", ")"); |
|
var rho = Math.SQRT2, |
|
rho2 = 2, |
|
rho4 = 4, |
|
epsilon2 = 1e-12; |
|
|
|
function cosh(x) { |
|
return ((x = Math.exp(x)) + 1 / x) / 2; |
|
} |
|
|
|
function sinh(x) { |
|
return ((x = Math.exp(x)) - 1 / x) / 2; |
|
} |
|
|
|
function tanh(x) { |
|
return ((x = Math.exp(2 * x)) - 1) / (x + 1); |
|
} // p0 = [ux0, uy0, w0] |
|
// p1 = [ux1, uy1, w1] |
|
|
|
|
|
function zoom$1(p0, p1) { |
|
var ux0 = p0[0], |
|
uy0 = p0[1], |
|
w0 = p0[2], |
|
ux1 = p1[0], |
|
uy1 = p1[1], |
|
w1 = p1[2], |
|
dx = ux1 - ux0, |
|
dy = uy1 - uy0, |
|
d2 = dx * dx + dy * dy, |
|
i, |
|
S; // Special case for u0 ≅ u1. |
|
|
|
if (d2 < epsilon2) { |
|
S = Math.log(w1 / w0) / rho; |
|
|
|
i = function i(t) { |
|
return [ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(rho * t * S)]; |
|
}; |
|
} // General case. |
|
else { |
|
var d1 = Math.sqrt(d2), |
|
b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1), |
|
b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1), |
|
r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), |
|
r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1); |
|
S = (r1 - r0) / rho; |
|
|
|
i = function i(t) { |
|
var s = t * S, |
|
coshr0 = cosh(r0), |
|
u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0)); |
|
return [ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / cosh(rho * s + r0)]; |
|
}; |
|
} |
|
|
|
i.duration = S * 1000; |
|
return i; |
|
} |
|
|
|
function hsl$1(hue) { |
|
return function (start, end) { |
|
var h = hue((start = hsl(start)).h, (end = hsl(end)).h), |
|
s = nogamma(start.s, end.s), |
|
l = nogamma(start.l, end.l), |
|
opacity = nogamma(start.opacity, end.opacity); |
|
return function (t) { |
|
start.h = h(t); |
|
start.s = s(t); |
|
start.l = l(t); |
|
start.opacity = opacity(t); |
|
return start + ""; |
|
}; |
|
}; |
|
} |
|
|
|
var hsl$2 = hsl$1(hue); |
|
var hslLong = hsl$1(nogamma); |
|
|
|
function lab$1(start, end) { |
|
var l = nogamma((start = lab(start)).l, (end = lab(end)).l), |
|
a = nogamma(start.a, end.a), |
|
b = nogamma(start.b, end.b), |
|
opacity = nogamma(start.opacity, end.opacity); |
|
return function (t) { |
|
start.l = l(t); |
|
start.a = a(t); |
|
start.b = b(t); |
|
start.opacity = opacity(t); |
|
return start + ""; |
|
}; |
|
} |
|
|
|
function hcl$1(hue) { |
|
return function (start, end) { |
|
var h = hue((start = hcl(start)).h, (end = hcl(end)).h), |
|
c = nogamma(start.c, end.c), |
|
l = nogamma(start.l, end.l), |
|
opacity = nogamma(start.opacity, end.opacity); |
|
return function (t) { |
|
start.h = h(t); |
|
start.c = c(t); |
|
start.l = l(t); |
|
start.opacity = opacity(t); |
|
return start + ""; |
|
}; |
|
}; |
|
} |
|
|
|
var hcl$2 = hcl$1(hue); |
|
var hclLong = hcl$1(nogamma); |
|
|
|
function cubehelix$1(hue) { |
|
return function cubehelixGamma(y) { |
|
y = +y; |
|
|
|
function cubehelix$1(start, end) { |
|
var h = hue((start = cubehelix(start)).h, (end = cubehelix(end)).h), |
|
s = nogamma(start.s, end.s), |
|
l = nogamma(start.l, end.l), |
|
opacity = nogamma(start.opacity, end.opacity); |
|
return function (t) { |
|
start.h = h(t); |
|
start.s = s(t); |
|
start.l = l(Math.pow(t, y)); |
|
start.opacity = opacity(t); |
|
return start + ""; |
|
}; |
|
} |
|
|
|
cubehelix$1.gamma = cubehelixGamma; |
|
return cubehelix$1; |
|
}(1); |
|
} |
|
|
|
var cubehelix$2 = cubehelix$1(hue); |
|
var cubehelixLong = cubehelix$1(nogamma); |
|
|
|
function piecewise(interpolate, values) { |
|
var i = 0, |
|
n = values.length - 1, |
|
v = values[0], |
|
I = new Array(n < 0 ? 0 : n); |
|
|
|
while (i < n) { |
|
I[i] = interpolate(v, v = values[++i]); |
|
} |
|
|
|
return function (t) { |
|
var i = Math.max(0, Math.min(n - 1, Math.floor(t *= n))); |
|
return I[i](t - i); |
|
}; |
|
} |
|
|
|
function quantize(interpolator, n) { |
|
var samples = new Array(n); |
|
|
|
for (var i = 0; i < n; ++i) { |
|
samples[i] = interpolator(i / (n - 1)); |
|
} |
|
|
|
return samples; |
|
} |
|
|
|
var $$1 = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
interpolate: interpolate, |
|
interpolateArray: array$1, |
|
interpolateBasis: basis$1, |
|
interpolateBasisClosed: basisClosed, |
|
interpolateDate: date, |
|
interpolateDiscrete: discrete, |
|
interpolateHue: hue$1, |
|
interpolateNumber: interpolateNumber, |
|
interpolateNumberArray: numberArray, |
|
interpolateObject: object$2, |
|
interpolateRound: interpolateRound, |
|
interpolateString: string, |
|
interpolateTransformCss: interpolateTransformCss, |
|
interpolateTransformSvg: interpolateTransformSvg, |
|
interpolateZoom: zoom$1, |
|
interpolateRgb: rgb$1, |
|
interpolateRgbBasis: rgbBasis, |
|
interpolateRgbBasisClosed: rgbBasisClosed, |
|
interpolateHsl: hsl$2, |
|
interpolateHslLong: hslLong, |
|
interpolateLab: lab$1, |
|
interpolateHcl: hcl$2, |
|
interpolateHclLong: hclLong, |
|
interpolateCubehelix: cubehelix$2, |
|
interpolateCubehelixLong: cubehelixLong, |
|
piecewise: piecewise, |
|
quantize: quantize |
|
}); |
|
|
|
function constant$3(x) { |
|
return function () { |
|
return x; |
|
}; |
|
} |
|
|
|
function number$2(x) { |
|
return +x; |
|
} |
|
|
|
var unit = [0, 1]; |
|
|
|
function identity$3(x) { |
|
return x; |
|
} |
|
|
|
function normalize(a, b) { |
|
return (b -= a = +a) ? function (x) { |
|
return (x - a) / b; |
|
} : constant$3(isNaN(b) ? NaN : 0.5); |
|
} |
|
|
|
function clamper(a, b) { |
|
var t; |
|
if (a > b) t = a, a = b, b = t; |
|
return function (x) { |
|
return Math.max(a, Math.min(b, x)); |
|
}; |
|
} // normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1]. |
|
// interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b]. |
|
|
|
|
|
function bimap(domain, range, interpolate) { |
|
var d0 = domain[0], |
|
d1 = domain[1], |
|
r0 = range[0], |
|
r1 = range[1]; |
|
if (d1 < d0) d0 = normalize(d1, d0), r0 = interpolate(r1, r0);else d0 = normalize(d0, d1), r0 = interpolate(r0, r1); |
|
return function (x) { |
|
return r0(d0(x)); |
|
}; |
|
} |
|
|
|
function polymap(domain, range, interpolate) { |
|
var j = Math.min(domain.length, range.length) - 1, |
|
d = new Array(j), |
|
r = new Array(j), |
|
i = -1; // Reverse descending domains. |
|
|
|
if (domain[j] < domain[0]) { |
|
domain = domain.slice().reverse(); |
|
range = range.slice().reverse(); |
|
} |
|
|
|
while (++i < j) { |
|
d[i] = normalize(domain[i], domain[i + 1]); |
|
r[i] = interpolate(range[i], range[i + 1]); |
|
} |
|
|
|
return function (x) { |
|
var i = bisectRight(domain, x, 1, j) - 1; |
|
return r[i](d[i](x)); |
|
}; |
|
} |
|
|
|
function copy(source, target) { |
|
return target.domain(source.domain()).range(source.range()).interpolate(source.interpolate()).clamp(source.clamp()).unknown(source.unknown()); |
|
} |
|
|
|
function transformer() { |
|
var domain = unit, |
|
range = unit, |
|
interpolate$1 = interpolate, |
|
transform, |
|
untransform, |
|
unknown, |
|
clamp = identity$3, |
|
piecewise, |
|
output, |
|
input; |
|
|
|
function rescale() { |
|
var n = Math.min(domain.length, range.length); |
|
if (clamp !== identity$3) clamp = clamper(domain[0], domain[n - 1]); |
|
piecewise = n > 2 ? polymap : bimap; |
|
output = input = null; |
|
return scale; |
|
} |
|
|
|
function scale(x) { |
|
return isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate$1)))(transform(clamp(x))); |
|
} |
|
|
|
scale.invert = function (y) { |
|
return clamp(untransform((input || (input = piecewise(range, domain.map(transform), interpolateNumber)))(y))); |
|
}; |
|
|
|
scale.domain = function (_) { |
|
return arguments.length ? (domain = Array.from(_, number$2), rescale()) : domain.slice(); |
|
}; |
|
|
|
scale.range = function (_) { |
|
return arguments.length ? (range = Array.from(_), rescale()) : range.slice(); |
|
}; |
|
|
|
scale.rangeRound = function (_) { |
|
return range = Array.from(_), interpolate$1 = interpolateRound, rescale(); |
|
}; |
|
|
|
scale.clamp = function (_) { |
|
return arguments.length ? (clamp = _ ? true : identity$3, rescale()) : clamp !== identity$3; |
|
}; |
|
|
|
scale.interpolate = function (_) { |
|
return arguments.length ? (interpolate$1 = _, rescale()) : interpolate$1; |
|
}; |
|
|
|
scale.unknown = function (_) { |
|
return arguments.length ? (unknown = _, scale) : unknown; |
|
}; |
|
|
|
return function (t, u) { |
|
transform = t, untransform = u; |
|
return rescale(); |
|
}; |
|
} |
|
|
|
function continuous() { |
|
return transformer()(identity$3, identity$3); |
|
} // Computes the decimal coefficient and exponent of the specified number x with |
|
// significant digits p, where x is positive and p is in [1, 21] or undefined. |
|
// For example, formatDecimal(1.23) returns ["123", 0]. |
|
|
|
|
|
function formatDecimal(x, p) { |
|
if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity |
|
|
|
var i, |
|
coefficient = x.slice(0, i); // The string returned by toExponential either has the form \d\.\d+e[-+]\d+ |
|
// (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3). |
|
|
|
return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +x.slice(i + 1)]; |
|
} |
|
|
|
function exponent(x) { |
|
return x = formatDecimal(Math.abs(x)), x ? x[1] : NaN; |
|
} |
|
|
|
function formatGroup(grouping, thousands) { |
|
return function (value, width) { |
|
var i = value.length, |
|
t = [], |
|
j = 0, |
|
g = grouping[0], |
|
length = 0; |
|
|
|
while (i > 0 && g > 0) { |
|
if (length + g + 1 > width) g = Math.max(1, width - length); |
|
t.push(value.substring(i -= g, i + g)); |
|
if ((length += g + 1) > width) break; |
|
g = grouping[j = (j + 1) % grouping.length]; |
|
} |
|
|
|
return t.reverse().join(thousands); |
|
}; |
|
} |
|
|
|
function formatNumerals(numerals) { |
|
return function (value) { |
|
return value.replace(/[0-9]/g, function (i) { |
|
return numerals[+i]; |
|
}); |
|
}; |
|
} // [[fill]align][sign][symbol][0][width][,][.precision][~][type] |
|
|
|
|
|
var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i; |
|
|
|
function formatSpecifier(specifier) { |
|
if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier); |
|
var match; |
|
return new FormatSpecifier({ |
|
fill: match[1], |
|
align: match[2], |
|
sign: match[3], |
|
symbol: match[4], |
|
zero: match[5], |
|
width: match[6], |
|
comma: match[7], |
|
precision: match[8] && match[8].slice(1), |
|
trim: match[9], |
|
type: match[10] |
|
}); |
|
} |
|
|
|
formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof |
|
|
|
function FormatSpecifier(specifier) { |
|
this.fill = specifier.fill === undefined ? " " : specifier.fill + ""; |
|
this.align = specifier.align === undefined ? ">" : specifier.align + ""; |
|
this.sign = specifier.sign === undefined ? "-" : specifier.sign + ""; |
|
this.symbol = specifier.symbol === undefined ? "" : specifier.symbol + ""; |
|
this.zero = !!specifier.zero; |
|
this.width = specifier.width === undefined ? undefined : +specifier.width; |
|
this.comma = !!specifier.comma; |
|
this.precision = specifier.precision === undefined ? undefined : +specifier.precision; |
|
this.trim = !!specifier.trim; |
|
this.type = specifier.type === undefined ? "" : specifier.type + ""; |
|
} |
|
|
|
FormatSpecifier.prototype.toString = function () { |
|
return this.fill + this.align + this.sign + this.symbol + (this.zero ? "0" : "") + (this.width === undefined ? "" : Math.max(1, this.width | 0)) + (this.comma ? "," : "") + (this.precision === undefined ? "" : "." + Math.max(0, this.precision | 0)) + (this.trim ? "~" : "") + this.type; |
|
}; // Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k. |
|
|
|
|
|
function formatTrim(s) { |
|
out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) { |
|
switch (s[i]) { |
|
case ".": |
|
i0 = i1 = i; |
|
break; |
|
|
|
case "0": |
|
if (i0 === 0) i0 = i; |
|
i1 = i; |
|
break; |
|
|
|
default: |
|
if (!+s[i]) break out; |
|
if (i0 > 0) i0 = 0; |
|
break; |
|
} |
|
} |
|
|
|
return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s; |
|
} |
|
|
|
var prefixExponent; |
|
|
|
function formatPrefixAuto(x, p) { |
|
var d = formatDecimal(x, p); |
|
if (!d) return x + ""; |
|
var coefficient = d[0], |
|
exponent = d[1], |
|
i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, |
|
n = coefficient.length; |
|
return i === n ? coefficient : i > n ? coefficient + new Array(i - n + 1).join("0") : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) : "0." + new Array(1 - i).join("0") + formatDecimal(x, Math.max(0, p + i - 1))[0]; // less than 1y! |
|
} |
|
|
|
function formatRounded(x, p) { |
|
var d = formatDecimal(x, p); |
|
if (!d) return x + ""; |
|
var coefficient = d[0], |
|
exponent = d[1]; |
|
return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) : coefficient + new Array(exponent - coefficient.length + 2).join("0"); |
|
} |
|
|
|
var formatTypes = { |
|
"%": function _(x, p) { |
|
return (x * 100).toFixed(p); |
|
}, |
|
"b": function b(x) { |
|
return Math.round(x).toString(2); |
|
}, |
|
"c": function c(x) { |
|
return x + ""; |
|
}, |
|
"d": function d(x) { |
|
return Math.round(x).toString(10); |
|
}, |
|
"e": function e(x, p) { |
|
return x.toExponential(p); |
|
}, |
|
"f": function f(x, p) { |
|
return x.toFixed(p); |
|
}, |
|
"g": function g(x, p) { |
|
return x.toPrecision(p); |
|
}, |
|
"o": function o(x) { |
|
return Math.round(x).toString(8); |
|
}, |
|
"p": function p(x, _p) { |
|
return formatRounded(x * 100, _p); |
|
}, |
|
"r": formatRounded, |
|
"s": formatPrefixAuto, |
|
"X": function X(x) { |
|
return Math.round(x).toString(16).toUpperCase(); |
|
}, |
|
"x": function x(_x22) { |
|
return Math.round(_x22).toString(16); |
|
} |
|
}; |
|
|
|
function identity$4(x) { |
|
return x; |
|
} |
|
|
|
var map = Array.prototype.map, |
|
prefixes = ["y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"]; |
|
|
|
function formatLocale$1(locale) { |
|
var group = locale.grouping === undefined || locale.thousands === undefined ? identity$4 : formatGroup(map.call(locale.grouping, Number), locale.thousands + ""), |
|
currencyPrefix = locale.currency === undefined ? "" : locale.currency[0] + "", |
|
currencySuffix = locale.currency === undefined ? "" : locale.currency[1] + "", |
|
decimal = locale.decimal === undefined ? "." : locale.decimal + "", |
|
numerals = locale.numerals === undefined ? identity$4 : formatNumerals(map.call(locale.numerals, String)), |
|
percent = locale.percent === undefined ? "%" : locale.percent + "", |
|
minus = locale.minus === undefined ? "-" : locale.minus + "", |
|
nan = locale.nan === undefined ? "NaN" : locale.nan + ""; |
|
|
|
function newFormat(specifier) { |
|
specifier = formatSpecifier(specifier); |
|
var fill = specifier.fill, |
|
align = specifier.align, |
|
sign = specifier.sign, |
|
symbol = specifier.symbol, |
|
zero = specifier.zero, |
|
width = specifier.width, |
|
comma = specifier.comma, |
|
precision = specifier.precision, |
|
trim = specifier.trim, |
|
type = specifier.type; // The "n" type is an alias for ",g". |
|
|
|
if (type === "n") comma = true, type = "g"; // The "" type, and any invalid type, is an alias for ".12~g". |
|
else if (!formatTypes[type]) precision === undefined && (precision = 12), trim = true, type = "g"; // If zero fill is specified, padding goes after sign and before digits. |
|
|
|
if (zero || fill === "0" && align === "=") zero = true, fill = "0", align = "="; // Compute the prefix and suffix. |
|
// For SI-prefix, the suffix is lazily computed. |
|
|
|
var prefix = symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "", |
|
suffix = symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : ""; // What format function should we use? |
|
// Is this an integer type? |
|
// Can this type generate exponential notation? |
|
|
|
var formatType = formatTypes[type], |
|
maybeSuffix = /[defgprs%]/.test(type); // Set the default precision if not specified, |
|
// or clamp the specified precision to the supported range. |
|
// For significant precision, it must be in [1, 21]. |
|
// For fixed precision, it must be in [0, 20]. |
|
|
|
precision = precision === undefined ? 6 : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) : Math.max(0, Math.min(20, precision)); |
|
|
|
function format(value) { |
|
var valuePrefix = prefix, |
|
valueSuffix = suffix, |
|
i, |
|
n, |
|
c; |
|
|
|
if (type === "c") { |
|
valueSuffix = formatType(value) + valueSuffix; |
|
value = ""; |
|
} else { |
|
value = +value; // Perform the initial formatting. |
|
|
|
var valueNegative = value < 0; |
|
value = isNaN(value) ? nan : formatType(Math.abs(value), precision); // Trim insignificant zeros. |
|
|
|
if (trim) value = formatTrim(value); // If a negative value rounds to zero during formatting, treat as positive. |
|
|
|
if (valueNegative && +value === 0) valueNegative = false; // Compute the prefix and suffix. |
|
|
|
valuePrefix = (valueNegative ? sign === "(" ? sign : minus : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; |
|
valueSuffix = (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : ""); // Break the formatted value into the integer “value” part that can be |
|
// grouped, and fractional or exponential “suffix” part that is not. |
|
|
|
if (maybeSuffix) { |
|
i = -1, n = value.length; |
|
|
|
while (++i < n) { |
|
if (c = value.charCodeAt(i), 48 > c || c > 57) { |
|
valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix; |
|
value = value.slice(0, i); |
|
break; |
|
} |
|
} |
|
} |
|
} // If the fill character is not "0", grouping is applied before padding. |
|
|
|
|
|
if (comma && !zero) value = group(value, Infinity); // Compute the padding. |
|
|
|
var length = valuePrefix.length + value.length + valueSuffix.length, |
|
padding = length < width ? new Array(width - length + 1).join(fill) : ""; // If the fill character is "0", grouping is applied after padding. |
|
|
|
if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; // Reconstruct the final output based on the desired alignment. |
|
|
|
switch (align) { |
|
case "<": |
|
value = valuePrefix + value + valueSuffix + padding; |
|
break; |
|
|
|
case "=": |
|
value = valuePrefix + padding + value + valueSuffix; |
|
break; |
|
|
|
case "^": |
|
value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length); |
|
break; |
|
|
|
default: |
|
value = padding + valuePrefix + value + valueSuffix; |
|
break; |
|
} |
|
|
|
return numerals(value); |
|
} |
|
|
|
format.toString = function () { |
|
return specifier + ""; |
|
}; |
|
|
|
return format; |
|
} |
|
|
|
function formatPrefix(specifier, value) { |
|
var f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)), |
|
e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, |
|
k = Math.pow(10, -e), |
|
prefix = prefixes[8 + e / 3]; |
|
return function (value) { |
|
return f(k * value) + prefix; |
|
}; |
|
} |
|
|
|
return { |
|
format: newFormat, |
|
formatPrefix: formatPrefix |
|
}; |
|
} |
|
|
|
var locale$1; |
|
var format$1; |
|
var formatPrefix; |
|
defaultLocale$1({ |
|
decimal: ".", |
|
thousands: ",", |
|
grouping: [3], |
|
currency: ["$", ""], |
|
minus: "-" |
|
}); |
|
|
|
function defaultLocale$1(definition) { |
|
locale$1 = formatLocale$1(definition); |
|
format$1 = locale$1.format; |
|
formatPrefix = locale$1.formatPrefix; |
|
return locale$1; |
|
} |
|
|
|
function precisionFixed(step) { |
|
return Math.max(0, -exponent(Math.abs(step))); |
|
} |
|
|
|
function precisionPrefix(step, value) { |
|
return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step))); |
|
} |
|
|
|
function precisionRound(step, max) { |
|
step = Math.abs(step), max = Math.abs(max) - step; |
|
return Math.max(0, exponent(max) - exponent(step)) + 1; |
|
} |
|
|
|
function spanFormat(start, stop, count, specifier) { |
|
var step = tickStep(start, stop, count), |
|
precision; |
|
specifier = formatSpecifier(specifier == null ? ",f" : specifier); |
|
|
|
switch (specifier.type) { |
|
case "s": |
|
{ |
|
var value = Math.max(Math.abs(start), Math.abs(stop)); |
|
if (specifier.precision == null && !isNaN(precision = precisionPrefix(step, value))) specifier.precision = precision; |
|
return formatPrefix(specifier, value); |
|
} |
|
|
|
case "": |
|
case "e": |
|
case "g": |
|
case "p": |
|
case "r": |
|
{ |
|
if (specifier.precision == null && !isNaN(precision = precisionRound(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e"); |
|
break; |
|
} |
|
|
|
case "f": |
|
case "%": |
|
{ |
|
if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2; |
|
break; |
|
} |
|
} |
|
|
|
return format$1(specifier); |
|
} |
|
|
|
function linearish(scale) { |
|
var domain = scale.domain; |
|
|
|
scale.ticks = function (count) { |
|
var d = domain(); |
|
return ticks(d[0], d[d.length - 1], count == null ? 10 : count); |
|
}; |
|
|
|
scale.tickFormat = function (count, specifier) { |
|
var d = domain(); |
|
return spanFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier); |
|
}; |
|
|
|
scale.nice = function (count) { |
|
if (count == null) count = 10; |
|
var d = domain(), |
|
i0 = 0, |
|
i1 = d.length - 1, |
|
start = d[i0], |
|
stop = d[i1], |
|
step; |
|
|
|
if (stop < start) { |
|
step = start, start = stop, stop = step; |
|
step = i0, i0 = i1, i1 = step; |
|
} |
|
|
|
step = tickIncrement(start, stop, count); |
|
|
|
if (step > 0) { |
|
start = Math.floor(start / step) * step; |
|
stop = Math.ceil(stop / step) * step; |
|
step = tickIncrement(start, stop, count); |
|
} else if (step < 0) { |
|
start = Math.ceil(start * step) / step; |
|
stop = Math.floor(stop * step) / step; |
|
step = tickIncrement(start, stop, count); |
|
} |
|
|
|
if (step > 0) { |
|
d[i0] = Math.floor(start / step) * step; |
|
d[i1] = Math.ceil(stop / step) * step; |
|
domain(d); |
|
} else if (step < 0) { |
|
d[i0] = Math.ceil(start * step) / step; |
|
d[i1] = Math.floor(stop * step) / step; |
|
domain(d); |
|
} |
|
|
|
return scale; |
|
}; |
|
|
|
return scale; |
|
} |
|
|
|
function linear$1() { |
|
var scale = continuous(); |
|
|
|
scale.copy = function () { |
|
return copy(scale, linear$1()); |
|
}; |
|
|
|
initRange.apply(scale, arguments); |
|
return linearish(scale); |
|
} |
|
|
|
function identity$5(domain) { |
|
var unknown; |
|
|
|
function scale(x) { |
|
return isNaN(x = +x) ? unknown : x; |
|
} |
|
|
|
scale.invert = scale; |
|
|
|
scale.domain = scale.range = function (_) { |
|
return arguments.length ? (domain = Array.from(_, number$2), scale) : domain.slice(); |
|
}; |
|
|
|
scale.unknown = function (_) { |
|
return arguments.length ? (unknown = _, scale) : unknown; |
|
}; |
|
|
|
scale.copy = function () { |
|
return identity$5(domain).unknown(unknown); |
|
}; |
|
|
|
domain = arguments.length ? Array.from(domain, number$2) : [0, 1]; |
|
return linearish(scale); |
|
} |
|
|
|
function nice(domain, interval) { |
|
domain = domain.slice(); |
|
var i0 = 0, |
|
i1 = domain.length - 1, |
|
x0 = domain[i0], |
|
x1 = domain[i1], |
|
t; |
|
|
|
if (x1 < x0) { |
|
t = i0, i0 = i1, i1 = t; |
|
t = x0, x0 = x1, x1 = t; |
|
} |
|
|
|
domain[i0] = interval.floor(x0); |
|
domain[i1] = interval.ceil(x1); |
|
return domain; |
|
} |
|
|
|
function transformLog(x) { |
|
return Math.log(x); |
|
} |
|
|
|
function transformExp(x) { |
|
return Math.exp(x); |
|
} |
|
|
|
function transformLogn(x) { |
|
return -Math.log(-x); |
|
} |
|
|
|
function transformExpn(x) { |
|
return -Math.exp(-x); |
|
} |
|
|
|
function pow10(x) { |
|
return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x; |
|
} |
|
|
|
function powp(base) { |
|
return base === 10 ? pow10 : base === Math.E ? Math.exp : function (x) { |
|
return Math.pow(base, x); |
|
}; |
|
} |
|
|
|
function logp(base) { |
|
return base === Math.E ? Math.log : base === 10 && Math.log10 || base === 2 && Math.log2 || (base = Math.log(base), function (x) { |
|
return Math.log(x) / base; |
|
}); |
|
} |
|
|
|
function reflect(f) { |
|
return function (x) { |
|
return -f(-x); |
|
}; |
|
} |
|
|
|
function loggish(transform) { |
|
var scale = transform(transformLog, transformExp), |
|
domain = scale.domain, |
|
base = 10, |
|
logs, |
|
pows; |
|
|
|
function rescale() { |
|
logs = logp(base), pows = powp(base); |
|
|
|
if (domain()[0] < 0) { |
|
logs = reflect(logs), pows = reflect(pows); |
|
transform(transformLogn, transformExpn); |
|
} else { |
|
transform(transformLog, transformExp); |
|
} |
|
|
|
return scale; |
|
} |
|
|
|
scale.base = function (_) { |
|
return arguments.length ? (base = +_, rescale()) : base; |
|
}; |
|
|
|
scale.domain = function (_) { |
|
return arguments.length ? (domain(_), rescale()) : domain(); |
|
}; |
|
|
|
scale.ticks = function (count) { |
|
var d = domain(), |
|
u = d[0], |
|
v = d[d.length - 1], |
|
r; |
|
if (r = v < u) i = u, u = v, v = i; |
|
var i = logs(u), |
|
j = logs(v), |
|
p, |
|
k, |
|
t, |
|
n = count == null ? 10 : +count, |
|
z = []; |
|
|
|
if (!(base % 1) && j - i < n) { |
|
i = Math.floor(i), j = Math.ceil(j); |
|
if (u > 0) for (; i <= j; ++i) { |
|
for (k = 1, p = pows(i); k < base; ++k) { |
|
t = p * k; |
|
if (t < u) continue; |
|
if (t > v) break; |
|
z.push(t); |
|
} |
|
} else for (; i <= j; ++i) { |
|
for (k = base - 1, p = pows(i); k >= 1; --k) { |
|
t = p * k; |
|
if (t < u) continue; |
|
if (t > v) break; |
|
z.push(t); |
|
} |
|
} |
|
if (z.length * 2 < n) z = ticks(u, v, n); |
|
} else { |
|
z = ticks(i, j, Math.min(j - i, n)).map(pows); |
|
} |
|
|
|
return r ? z.reverse() : z; |
|
}; |
|
|
|
scale.tickFormat = function (count, specifier) { |
|
if (specifier == null) specifier = base === 10 ? ".0e" : ","; |
|
if (typeof specifier !== "function") specifier = format$1(specifier); |
|
if (count === Infinity) return specifier; |
|
if (count == null) count = 10; |
|
var k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate? |
|
|
|
return function (d) { |
|
var i = d / pows(Math.round(logs(d))); |
|
if (i * base < base - 0.5) i *= base; |
|
return i <= k ? specifier(d) : ""; |
|
}; |
|
}; |
|
|
|
scale.nice = function () { |
|
return domain(nice(domain(), { |
|
floor: function floor(x) { |
|
return pows(Math.floor(logs(x))); |
|
}, |
|
ceil: function ceil(x) { |
|
return pows(Math.ceil(logs(x))); |
|
} |
|
})); |
|
}; |
|
|
|
return scale; |
|
} |
|
|
|
function log$2() { |
|
var scale = loggish(transformer()).domain([1, 10]); |
|
|
|
scale.copy = function () { |
|
return copy(scale, log$2()).base(scale.base()); |
|
}; |
|
|
|
initRange.apply(scale, arguments); |
|
return scale; |
|
} |
|
|
|
function transformSymlog(c) { |
|
return function (x) { |
|
return Math.sign(x) * Math.log1p(Math.abs(x / c)); |
|
}; |
|
} |
|
|
|
function transformSymexp(c) { |
|
return function (x) { |
|
return Math.sign(x) * Math.expm1(Math.abs(x)) * c; |
|
}; |
|
} |
|
|
|
function symlogish(transform) { |
|
var c = 1, |
|
scale = transform(transformSymlog(c), transformSymexp(c)); |
|
|
|
scale.constant = function (_) { |
|
return arguments.length ? transform(transformSymlog(c = +_), transformSymexp(c)) : c; |
|
}; |
|
|
|
return linearish(scale); |
|
} |
|
|
|
function symlog$1() { |
|
var scale = symlogish(transformer()); |
|
|
|
scale.copy = function () { |
|
return copy(scale, symlog$1()).constant(scale.constant()); |
|
}; |
|
|
|
return initRange.apply(scale, arguments); |
|
} |
|
|
|
function transformPow(exponent) { |
|
return function (x) { |
|
return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent); |
|
}; |
|
} |
|
|
|
function transformSqrt(x) { |
|
return x < 0 ? -Math.sqrt(-x) : Math.sqrt(x); |
|
} |
|
|
|
function transformSquare(x) { |
|
return x < 0 ? -x * x : x * x; |
|
} |
|
|
|
function powish(transform) { |
|
var scale = transform(identity$3, identity$3), |
|
exponent = 1; |
|
|
|
function rescale() { |
|
return exponent === 1 ? transform(identity$3, identity$3) : exponent === 0.5 ? transform(transformSqrt, transformSquare) : transform(transformPow(exponent), transformPow(1 / exponent)); |
|
} |
|
|
|
scale.exponent = function (_) { |
|
return arguments.length ? (exponent = +_, rescale()) : exponent; |
|
}; |
|
|
|
return linearish(scale); |
|
} |
|
|
|
function pow$1() { |
|
var scale = powish(transformer()); |
|
|
|
scale.copy = function () { |
|
return copy(scale, pow$1()).exponent(scale.exponent()); |
|
}; |
|
|
|
initRange.apply(scale, arguments); |
|
return scale; |
|
} |
|
|
|
function sqrt$1() { |
|
return pow$1.apply(null, arguments).exponent(0.5); |
|
} |
|
|
|
function quantile$1() { |
|
var domain = [], |
|
range = [], |
|
thresholds = [], |
|
unknown; |
|
|
|
function rescale() { |
|
var i = 0, |
|
n = Math.max(1, range.length); |
|
thresholds = new Array(n - 1); |
|
|
|
while (++i < n) { |
|
thresholds[i - 1] = quantile(domain, i / n); |
|
} |
|
|
|
return scale; |
|
} |
|
|
|
function scale(x) { |
|
return isNaN(x = +x) ? unknown : range[bisectRight(thresholds, x)]; |
|
} |
|
|
|
scale.invertExtent = function (y) { |
|
var i = range.indexOf(y); |
|
return i < 0 ? [NaN, NaN] : [i > 0 ? thresholds[i - 1] : domain[0], i < thresholds.length ? thresholds[i] : domain[domain.length - 1]]; |
|
}; |
|
|
|
scale.domain = function (_) { |
|
if (!arguments.length) return domain.slice(); |
|
domain = []; |
|
var _iteratorNormalCompletion19 = true; |
|
var _didIteratorError19 = false; |
|
var _iteratorError19 = undefined; |
|
|
|
try { |
|
for (var _iterator19 = _[Symbol.iterator](), _step19; !(_iteratorNormalCompletion19 = (_step19 = _iterator19.next()).done); _iteratorNormalCompletion19 = true) { |
|
var d = _step19.value; |
|
if (d != null && !isNaN(d = +d)) domain.push(d); |
|
} |
|
} catch (err) { |
|
_didIteratorError19 = true; |
|
_iteratorError19 = err; |
|
} finally { |
|
try { |
|
if (!_iteratorNormalCompletion19 && _iterator19.return != null) { |
|
_iterator19.return(); |
|
} |
|
} finally { |
|
if (_didIteratorError19) { |
|
throw _iteratorError19; |
|
} |
|
} |
|
} |
|
|
|
domain.sort(ascending); |
|
return rescale(); |
|
}; |
|
|
|
scale.range = function (_) { |
|
return arguments.length ? (range = Array.from(_), rescale()) : range.slice(); |
|
}; |
|
|
|
scale.unknown = function (_) { |
|
return arguments.length ? (unknown = _, scale) : unknown; |
|
}; |
|
|
|
scale.quantiles = function () { |
|
return thresholds.slice(); |
|
}; |
|
|
|
scale.copy = function () { |
|
return quantile$1().domain(domain).range(range).unknown(unknown); |
|
}; |
|
|
|
return initRange.apply(scale, arguments); |
|
} |
|
|
|
function quantize$1() { |
|
var x0 = 0, |
|
x1 = 1, |
|
n = 1, |
|
domain = [0.5], |
|
range = [0, 1], |
|
unknown; |
|
|
|
function scale(x) { |
|
return x <= x ? range[bisectRight(domain, x, 0, n)] : unknown; |
|
} |
|
|
|
function rescale() { |
|
var i = -1; |
|
domain = new Array(n); |
|
|
|
while (++i < n) { |
|
domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1); |
|
} |
|
|
|
return scale; |
|
} |
|
|
|
scale.domain = function (_) { |
|
var _ref3, _ref4; |
|
|
|
return arguments.length ? ((_ref3 = _, _ref4 = _slicedToArray(_ref3, 2), x0 = _ref4[0], x1 = _ref4[1], _ref3), x0 = +x0, x1 = +x1, rescale()) : [x0, x1]; |
|
}; |
|
|
|
scale.range = function (_) { |
|
return arguments.length ? (n = (range = Array.from(_)).length - 1, rescale()) : range.slice(); |
|
}; |
|
|
|
scale.invertExtent = function (y) { |
|
var i = range.indexOf(y); |
|
return i < 0 ? [NaN, NaN] : i < 1 ? [x0, domain[0]] : i >= n ? [domain[n - 1], x1] : [domain[i - 1], domain[i]]; |
|
}; |
|
|
|
scale.unknown = function (_) { |
|
return arguments.length ? (unknown = _, scale) : scale; |
|
}; |
|
|
|
scale.thresholds = function () { |
|
return domain.slice(); |
|
}; |
|
|
|
scale.copy = function () { |
|
return quantize$1().domain([x0, x1]).range(range).unknown(unknown); |
|
}; |
|
|
|
return initRange.apply(linearish(scale), arguments); |
|
} |
|
|
|
function threshold() { |
|
var domain = [0.5], |
|
range = [0, 1], |
|
unknown, |
|
n = 1; |
|
|
|
function scale(x) { |
|
return x <= x ? range[bisectRight(domain, x, 0, n)] : unknown; |
|
} |
|
|
|
scale.domain = function (_) { |
|
return arguments.length ? (domain = Array.from(_), n = Math.min(domain.length, range.length - 1), scale) : domain.slice(); |
|
}; |
|
|
|
scale.range = function (_) { |
|
return arguments.length ? (range = Array.from(_), n = Math.min(domain.length, range.length - 1), scale) : range.slice(); |
|
}; |
|
|
|
scale.invertExtent = function (y) { |
|
var i = range.indexOf(y); |
|
return [domain[i - 1], domain[i]]; |
|
}; |
|
|
|
scale.unknown = function (_) { |
|
return arguments.length ? (unknown = _, scale) : unknown; |
|
}; |
|
|
|
scale.copy = function () { |
|
return threshold().domain(domain).range(range).unknown(unknown); |
|
}; |
|
|
|
return initRange.apply(scale, arguments); |
|
} |
|
|
|
var durationSecond$2 = 1000, |
|
durationMinute$2 = durationSecond$2 * 60, |
|
durationHour$2 = durationMinute$2 * 60, |
|
durationDay$2 = durationHour$2 * 24, |
|
durationWeek$2 = durationDay$2 * 7, |
|
durationMonth$1 = durationDay$2 * 30, |
|
durationYear$1 = durationDay$2 * 365; |
|
|
|
function date$1(t) { |
|
return new Date(t); |
|
} |
|
|
|
function number$3(t) { |
|
return t instanceof Date ? +t : +new Date(+t); |
|
} |
|
|
|
function calendar(year, month, week, day, hour, minute, second, millisecond, format) { |
|
var scale = continuous(), |
|
invert = scale.invert, |
|
domain = scale.domain; |
|
var formatMillisecond = format(".%L"), |
|
formatSecond = format(":%S"), |
|
formatMinute = format("%I:%M"), |
|
formatHour = format("%I %p"), |
|
formatDay = format("%a %d"), |
|
formatWeek = format("%b %d"), |
|
formatMonth = format("%B"), |
|
formatYear = format("%Y"); |
|
var tickIntervals = [[second, 1, durationSecond$2], [second, 5, 5 * durationSecond$2], [second, 15, 15 * durationSecond$2], [second, 30, 30 * durationSecond$2], [minute, 1, durationMinute$2], [minute, 5, 5 * durationMinute$2], [minute, 15, 15 * durationMinute$2], [minute, 30, 30 * durationMinute$2], [hour, 1, durationHour$2], [hour, 3, 3 * durationHour$2], [hour, 6, 6 * durationHour$2], [hour, 12, 12 * durationHour$2], [day, 1, durationDay$2], [day, 2, 2 * durationDay$2], [week, 1, durationWeek$2], [month, 1, durationMonth$1], [month, 3, 3 * durationMonth$1], [year, 1, durationYear$1]]; |
|
|
|
function tickFormat(date) { |
|
return (second(date) < date ? formatMillisecond : minute(date) < date ? formatSecond : hour(date) < date ? formatMinute : day(date) < date ? formatHour : month(date) < date ? week(date) < date ? formatDay : formatWeek : year(date) < date ? formatMonth : formatYear)(date); |
|
} |
|
|
|
function tickInterval(interval, start, stop) { |
|
if (interval == null) interval = 10; // If a desired tick count is specified, pick a reasonable tick interval |
|
// based on the extent of the domain and a rough estimate of tick size. |
|
// Otherwise, assume interval is already a time interval and use it. |
|
|
|
if (typeof interval === "number") { |
|
var target = Math.abs(stop - start) / interval, |
|
i = bisector(function (i) { |
|
return i[2]; |
|
}).right(tickIntervals, target), |
|
step; |
|
|
|
if (i === tickIntervals.length) { |
|
step = tickStep(start / durationYear$1, stop / durationYear$1, interval); |
|
interval = year; |
|
} else if (i) { |
|
i = tickIntervals[target / tickIntervals[i - 1][2] < tickIntervals[i][2] / target ? i - 1 : i]; |
|
step = i[1]; |
|
interval = i[0]; |
|
} else { |
|
step = Math.max(tickStep(start, stop, interval), 1); |
|
interval = millisecond; |
|
} |
|
|
|
return interval.every(step); |
|
} |
|
|
|
return interval; |
|
} |
|
|
|
scale.invert = function (y) { |
|
return new Date(invert(y)); |
|
}; |
|
|
|
scale.domain = function (_) { |
|
return arguments.length ? domain(Array.from(_, number$3)) : domain().map(date$1); |
|
}; |
|
|
|
scale.ticks = function (interval) { |
|
var d = domain(), |
|
t0 = d[0], |
|
t1 = d[d.length - 1], |
|
r = t1 < t0, |
|
t; |
|
if (r) t = t0, t0 = t1, t1 = t; |
|
t = tickInterval(interval, t0, t1); |
|
t = t ? t.range(t0, t1 + 1) : []; // inclusive stop |
|
|
|
return r ? t.reverse() : t; |
|
}; |
|
|
|
scale.tickFormat = function (count, specifier) { |
|
return specifier == null ? tickFormat : format(specifier); |
|
}; |
|
|
|
scale.nice = function (interval) { |
|
var d = domain(); |
|
return (interval = tickInterval(interval, d[0], d[d.length - 1])) ? domain(nice(d, interval)) : scale; |
|
}; |
|
|
|
scale.copy = function () { |
|
return copy(scale, calendar(year, month, week, day, hour, minute, second, millisecond, format)); |
|
}; |
|
|
|
return scale; |
|
} |
|
|
|
function time() { |
|
return initRange.apply(calendar(year, month, sunday, day, hour, minute, second, millisecond, timeFormat).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]), arguments); |
|
} |
|
|
|
function utcTime() { |
|
return initRange.apply(calendar(utcYear, utcMonth, utcSunday, utcDay, utcHour, utcMinute, second, millisecond, utcFormat).domain([Date.UTC(2000, 0, 1), Date.UTC(2000, 0, 2)]), arguments); |
|
} |
|
|
|
function transformer$1() { |
|
var x0 = 0, |
|
x1 = 1, |
|
t0, |
|
t1, |
|
k10, |
|
transform, |
|
interpolator = identity$3, |
|
clamp = false, |
|
unknown; |
|
|
|
function scale(x) { |
|
return isNaN(x = +x) ? unknown : interpolator(k10 === 0 ? 0.5 : (x = (transform(x) - t0) * k10, clamp ? Math.max(0, Math.min(1, x)) : x)); |
|
} |
|
|
|
scale.domain = function (_) { |
|
var _ref5, _ref6; |
|
|
|
return arguments.length ? ((_ref5 = _, _ref6 = _slicedToArray(_ref5, 2), x0 = _ref6[0], x1 = _ref6[1], _ref5), t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0), scale) : [x0, x1]; |
|
}; |
|
|
|
scale.clamp = function (_) { |
|
return arguments.length ? (clamp = !!_, scale) : clamp; |
|
}; |
|
|
|
scale.interpolator = function (_) { |
|
return arguments.length ? (interpolator = _, scale) : interpolator; |
|
}; |
|
|
|
function range(interpolate) { |
|
return function (_) { |
|
var _ref7, _ref8; |
|
|
|
var r0, r1; |
|
return arguments.length ? ((_ref7 = _, _ref8 = _slicedToArray(_ref7, 2), r0 = _ref8[0], r1 = _ref8[1], _ref7), interpolator = interpolate(r0, r1), scale) : [interpolator(0), interpolator(1)]; |
|
}; |
|
} |
|
|
|
scale.range = range(interpolate); |
|
scale.rangeRound = range(interpolateRound); |
|
|
|
scale.unknown = function (_) { |
|
return arguments.length ? (unknown = _, scale) : unknown; |
|
}; |
|
|
|
return function (t) { |
|
transform = t, t0 = t(x0), t1 = t(x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0); |
|
return scale; |
|
}; |
|
} |
|
|
|
function copy$1(source, target) { |
|
return target.domain(source.domain()).interpolator(source.interpolator()).clamp(source.clamp()).unknown(source.unknown()); |
|
} |
|
|
|
function sequential() { |
|
var scale = linearish(transformer$1()(identity$3)); |
|
|
|
scale.copy = function () { |
|
return copy$1(scale, sequential()); |
|
}; |
|
|
|
return initInterpolator.apply(scale, arguments); |
|
} |
|
|
|
function sequentialLog() { |
|
var scale = loggish(transformer$1()).domain([1, 10]); |
|
|
|
scale.copy = function () { |
|
return copy$1(scale, sequentialLog()).base(scale.base()); |
|
}; |
|
|
|
return initInterpolator.apply(scale, arguments); |
|
} |
|
|
|
function sequentialSymlog() { |
|
var scale = symlogish(transformer$1()); |
|
|
|
scale.copy = function () { |
|
return copy$1(scale, sequentialSymlog()).constant(scale.constant()); |
|
}; |
|
|
|
return initInterpolator.apply(scale, arguments); |
|
} |
|
|
|
function sequentialPow() { |
|
var scale = powish(transformer$1()); |
|
|
|
scale.copy = function () { |
|
return copy$1(scale, sequentialPow()).exponent(scale.exponent()); |
|
}; |
|
|
|
return initInterpolator.apply(scale, arguments); |
|
} |
|
|
|
function sequentialSqrt() { |
|
return sequentialPow.apply(null, arguments).exponent(0.5); |
|
} |
|
|
|
function transformer$2() { |
|
var x0 = 0, |
|
x1 = 0.5, |
|
x2 = 1, |
|
s = 1, |
|
t0, |
|
t1, |
|
t2, |
|
k10, |
|
k21, |
|
interpolator = identity$3, |
|
transform, |
|
clamp = false, |
|
unknown; |
|
|
|
function scale(x) { |
|
return isNaN(x = +x) ? unknown : (x = 0.5 + ((x = +transform(x)) - t1) * (s * x < s * t1 ? k10 : k21), interpolator(clamp ? Math.max(0, Math.min(1, x)) : x)); |
|
} |
|
|
|
scale.domain = function (_) { |
|
var _ref9, _ref10; |
|
|
|
return arguments.length ? ((_ref9 = _, _ref10 = _slicedToArray(_ref9, 3), x0 = _ref10[0], x1 = _ref10[1], x2 = _ref10[2], _ref9), t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), t2 = transform(x2 = +x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1, scale) : [x0, x1, x2]; |
|
}; |
|
|
|
scale.clamp = function (_) { |
|
return arguments.length ? (clamp = !!_, scale) : clamp; |
|
}; |
|
|
|
scale.interpolator = function (_) { |
|
return arguments.length ? (interpolator = _, scale) : interpolator; |
|
}; |
|
|
|
function range(interpolate) { |
|
return function (_) { |
|
var _ref11, _ref12; |
|
|
|
var r0, r1, r2; |
|
return arguments.length ? ((_ref11 = _, _ref12 = _slicedToArray(_ref11, 3), r0 = _ref12[0], r1 = _ref12[1], r2 = _ref12[2], _ref11), interpolator = piecewise(interpolate, [r0, r1, r2]), scale) : [interpolator(0), interpolator(0.5), interpolator(1)]; |
|
}; |
|
} |
|
|
|
scale.range = range(interpolate); |
|
scale.rangeRound = range(interpolateRound); |
|
|
|
scale.unknown = function (_) { |
|
return arguments.length ? (unknown = _, scale) : unknown; |
|
}; |
|
|
|
return function (t) { |
|
transform = t, t0 = t(x0), t1 = t(x1), t2 = t(x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1; |
|
return scale; |
|
}; |
|
} |
|
|
|
function diverging() { |
|
var scale = linearish(transformer$2()(identity$3)); |
|
|
|
scale.copy = function () { |
|
return copy$1(scale, diverging()); |
|
}; |
|
|
|
return initInterpolator.apply(scale, arguments); |
|
} |
|
|
|
function divergingLog() { |
|
var scale = loggish(transformer$2()).domain([0.1, 1, 10]); |
|
|
|
scale.copy = function () { |
|
return copy$1(scale, divergingLog()).base(scale.base()); |
|
}; |
|
|
|
return initInterpolator.apply(scale, arguments); |
|
} |
|
|
|
function divergingSymlog() { |
|
var scale = symlogish(transformer$2()); |
|
|
|
scale.copy = function () { |
|
return copy$1(scale, divergingSymlog()).constant(scale.constant()); |
|
}; |
|
|
|
return initInterpolator.apply(scale, arguments); |
|
} |
|
|
|
function divergingPow() { |
|
var scale = powish(transformer$2()); |
|
|
|
scale.copy = function () { |
|
return copy$1(scale, divergingPow()).exponent(scale.exponent()); |
|
}; |
|
|
|
return initInterpolator.apply(scale, arguments); |
|
} |
|
|
|
function divergingSqrt() { |
|
return divergingPow.apply(null, arguments).exponent(0.5); |
|
} |
|
|
|
function band() { |
|
var scale = ordinal().unknown(undefined), |
|
domain = scale.domain, |
|
ordinalRange = scale.range, |
|
range = [0, 1], |
|
step, |
|
bandwidth, |
|
round = false, |
|
paddingInner = 0, |
|
paddingOuter = 0, |
|
align = 0.5; |
|
delete scale.unknown; |
|
|
|
function rescale() { |
|
var n = domain().length, |
|
reverse = range[1] < range[0], |
|
start = range[reverse - 0], |
|
stop = range[1 - reverse], |
|
space = bandSpace(n, paddingInner, paddingOuter); |
|
step = (stop - start) / (space || 1); |
|
|
|
if (round) { |
|
step = Math.floor(step); |
|
} |
|
|
|
start += (stop - start - step * (n - paddingInner)) * align; |
|
bandwidth = step * (1 - paddingInner); |
|
|
|
if (round) { |
|
start = Math.round(start); |
|
bandwidth = Math.round(bandwidth); |
|
} |
|
|
|
var values = sequence(n).map(function (i) { |
|
return start + step * i; |
|
}); |
|
return ordinalRange(reverse ? values.reverse() : values); |
|
} |
|
|
|
scale.domain = function (_) { |
|
if (arguments.length) { |
|
domain(_); |
|
return rescale(); |
|
} else { |
|
return domain(); |
|
} |
|
}; |
|
|
|
scale.range = function (_) { |
|
if (arguments.length) { |
|
range = [+_[0], +_[1]]; |
|
return rescale(); |
|
} else { |
|
return range.slice(); |
|
} |
|
}; |
|
|
|
scale.rangeRound = function (_) { |
|
range = [+_[0], +_[1]]; |
|
round = true; |
|
return rescale(); |
|
}; |
|
|
|
scale.bandwidth = function () { |
|
return bandwidth; |
|
}; |
|
|
|
scale.step = function () { |
|
return step; |
|
}; |
|
|
|
scale.round = function (_) { |
|
if (arguments.length) { |
|
round = !!_; |
|
return rescale(); |
|
} else { |
|
return round; |
|
} |
|
}; |
|
|
|
scale.padding = function (_) { |
|
if (arguments.length) { |
|
paddingOuter = Math.max(0, Math.min(1, _)); |
|
paddingInner = paddingOuter; |
|
return rescale(); |
|
} else { |
|
return paddingInner; |
|
} |
|
}; |
|
|
|
scale.paddingInner = function (_) { |
|
if (arguments.length) { |
|
paddingInner = Math.max(0, Math.min(1, _)); |
|
return rescale(); |
|
} else { |
|
return paddingInner; |
|
} |
|
}; |
|
|
|
scale.paddingOuter = function (_) { |
|
if (arguments.length) { |
|
paddingOuter = Math.max(0, Math.min(1, _)); |
|
return rescale(); |
|
} else { |
|
return paddingOuter; |
|
} |
|
}; |
|
|
|
scale.align = function (_) { |
|
if (arguments.length) { |
|
align = Math.max(0, Math.min(1, _)); |
|
return rescale(); |
|
} else { |
|
return align; |
|
} |
|
}; |
|
|
|
scale.invertRange = function (_) { |
|
// bail if range has null or undefined values |
|
if (_[0] == null || _[1] == null) return; |
|
var lo = +_[0], |
|
hi = +_[1], |
|
reverse = range[1] < range[0], |
|
values = reverse ? ordinalRange().reverse() : ordinalRange(), |
|
n = values.length - 1, |
|
a, |
|
b, |
|
t; // bail if either range endpoint is invalid |
|
|
|
if (lo !== lo || hi !== hi) return; // order range inputs, bail if outside of scale range |
|
|
|
if (hi < lo) { |
|
t = lo; |
|
lo = hi; |
|
hi = t; |
|
} |
|
|
|
if (hi < values[0] || lo > range[1 - reverse]) return; // binary search to index into scale range |
|
|
|
a = Math.max(0, bisectRight(values, lo) - 1); |
|
b = lo === hi ? a : bisectRight(values, hi) - 1; // increment index a if lo is within padding gap |
|
|
|
if (lo - values[a] > bandwidth + 1e-10) ++a; |
|
|
|
if (reverse) { |
|
// map + swap |
|
t = a; |
|
a = n - b; |
|
b = n - t; |
|
} |
|
|
|
return a > b ? undefined : domain().slice(a, b + 1); |
|
}; |
|
|
|
scale.invert = function (_) { |
|
var value = scale.invertRange([_, _]); |
|
return value ? value[0] : value; |
|
}; |
|
|
|
scale.copy = function () { |
|
return band().domain(domain()).range(range).round(round).paddingInner(paddingInner).paddingOuter(paddingOuter).align(align); |
|
}; |
|
|
|
return rescale(); |
|
} |
|
|
|
function pointish(scale) { |
|
var copy = scale.copy; |
|
scale.padding = scale.paddingOuter; |
|
delete scale.paddingInner; |
|
|
|
scale.copy = function () { |
|
return pointish(copy()); |
|
}; |
|
|
|
return scale; |
|
} |
|
|
|
function point$5() { |
|
return pointish(band().paddingInner(1)); |
|
} |
|
|
|
var map$1 = Array.prototype.map; |
|
|
|
function numbers$2(_) { |
|
return map$1.call(_, function (x) { |
|
return +x; |
|
}); |
|
} |
|
|
|
var slice = Array.prototype.slice; |
|
|
|
function scaleBinOrdinal() { |
|
var domain = [], |
|
range = []; |
|
|
|
function scale(x) { |
|
return x == null || x !== x ? undefined : range[(bisectRight(domain, x) - 1) % range.length]; |
|
} |
|
|
|
scale.domain = function (_) { |
|
if (arguments.length) { |
|
domain = numbers$2(_); |
|
return scale; |
|
} else { |
|
return domain.slice(); |
|
} |
|
}; |
|
|
|
scale.range = function (_) { |
|
if (arguments.length) { |
|
range = slice.call(_); |
|
return scale; |
|
} else { |
|
return range.slice(); |
|
} |
|
}; |
|
|
|
scale.tickFormat = function (count, specifier) { |
|
return spanFormat(domain[0], peek(domain), count == null ? 10 : count, specifier); |
|
}; |
|
|
|
scale.copy = function () { |
|
return scaleBinOrdinal().domain(scale.domain()).range(scale.range()); |
|
}; |
|
|
|
return scale; |
|
} // scale registry |
|
|
|
|
|
var scales = {}; |
|
/** |
|
* Augment scales with their type and needed inverse methods. |
|
*/ |
|
|
|
function create(type, constructor, metadata) { |
|
var ctr = function scale() { |
|
var s = constructor(); |
|
|
|
if (!s.invertRange) { |
|
s.invertRange = s.invert ? invertRange(s) : s.invertExtent ? invertRangeExtent(s) : undefined; |
|
} |
|
|
|
s.type = type; |
|
return s; |
|
}; |
|
|
|
ctr.metadata = toSet(array(metadata)); |
|
return ctr; |
|
} |
|
|
|
function scale$2(type, scale, metadata) { |
|
if (arguments.length > 1) { |
|
scales[type] = create(type, scale, metadata); |
|
return this; |
|
} else { |
|
return isValidScaleType(type) ? scales[type] : undefined; |
|
} |
|
} // identity scale |
|
|
|
|
|
scale$2(Identity, identity$5); // continuous scales |
|
|
|
scale$2(Linear$1, linear$1, Continuous); |
|
scale$2(Log, log$2, [Continuous, Log]); |
|
scale$2(Pow, pow$1, Continuous); |
|
scale$2(Sqrt, sqrt$1, Continuous); |
|
scale$2(Symlog, symlog$1, Continuous); |
|
scale$2(Time, time, [Continuous, Temporal]); |
|
scale$2(UTC, utcTime, [Continuous, Temporal]); // sequential scales |
|
|
|
scale$2(Sequential, sequential, [Continuous, Interpolating]); // backwards compat |
|
|
|
scale$2("".concat(Sequential, "-").concat(Linear$1), sequential, [Continuous, Interpolating]); |
|
scale$2("".concat(Sequential, "-").concat(Log), sequentialLog, [Continuous, Interpolating, Log]); |
|
scale$2("".concat(Sequential, "-").concat(Pow), sequentialPow, [Continuous, Interpolating]); |
|
scale$2("".concat(Sequential, "-").concat(Sqrt), sequentialSqrt, [Continuous, Interpolating]); |
|
scale$2("".concat(Sequential, "-").concat(Symlog), sequentialSymlog, [Continuous, Interpolating]); // diverging scales |
|
|
|
scale$2("".concat(Diverging, "-").concat(Linear$1), diverging, [Continuous, Interpolating]); |
|
scale$2("".concat(Diverging, "-").concat(Log), divergingLog, [Continuous, Interpolating, Log]); |
|
scale$2("".concat(Diverging, "-").concat(Pow), divergingPow, [Continuous, Interpolating]); |
|
scale$2("".concat(Diverging, "-").concat(Sqrt), divergingSqrt, [Continuous, Interpolating]); |
|
scale$2("".concat(Diverging, "-").concat(Symlog), divergingSymlog, [Continuous, Interpolating]); // discretizing scales |
|
|
|
scale$2(Quantile$1, quantile$1, [Discretizing, Quantile$1]); |
|
scale$2(Quantize, quantize$1, Discretizing); |
|
scale$2(Threshold, threshold, Discretizing); // discrete scales |
|
|
|
scale$2(BinOrdinal, scaleBinOrdinal, [Discrete, Discretizing]); |
|
scale$2(Ordinal, ordinal, Discrete); |
|
scale$2(Band, band, Discrete); |
|
scale$2(Point, point$5, Discrete); |
|
|
|
function isValidScaleType(type) { |
|
return hasOwnProperty(scales, type); |
|
} |
|
|
|
function hasType(key, type) { |
|
var s = scales[key]; |
|
return s && s.metadata[type]; |
|
} |
|
|
|
function isContinuous(key) { |
|
return hasType(key, Continuous); |
|
} |
|
|
|
function isDiscrete(key) { |
|
return hasType(key, Discrete); |
|
} |
|
|
|
function isDiscretizing(key) { |
|
return hasType(key, Discretizing); |
|
} |
|
|
|
function isLogarithmic(key) { |
|
return hasType(key, Log); |
|
} |
|
|
|
function isInterpolating(key) { |
|
return hasType(key, Interpolating); |
|
} |
|
|
|
function isQuantile(key) { |
|
return hasType(key, Quantile$1); |
|
} |
|
|
|
var scaleProps = ['clamp', 'base', 'constant', 'exponent']; |
|
|
|
function interpolateRange(interpolator, range) { |
|
var start = range[0], |
|
span = peek(range) - start; |
|
return function (i) { |
|
return interpolator(start + i * span); |
|
}; |
|
} |
|
|
|
function interpolateColors(colors, type, gamma) { |
|
return piecewise(interpolate$1(type || 'rgb', gamma), colors); |
|
} |
|
|
|
function quantizeInterpolator(interpolator, count) { |
|
var samples = new Array(count), |
|
n = count + 1; |
|
|
|
for (var i = 0; i < count;) { |
|
samples[i] = interpolator(++i / n); |
|
} |
|
|
|
return samples; |
|
} |
|
|
|
function scaleFraction(scale, min, max) { |
|
var delta = max - min, |
|
i, |
|
t, |
|
s; |
|
|
|
if (!delta || !Number.isFinite(delta)) { |
|
return constant(0.5); |
|
} else { |
|
i = (t = scale.type).indexOf('-'); |
|
t = i < 0 ? t : t.slice(i + 1); |
|
s = scale$2(t)().domain([min, max]).range([0, 1]); |
|
scaleProps.forEach(function (m) { |
|
return scale[m] ? s[m](scale[m]()) : 0; |
|
}); |
|
return s; |
|
} |
|
} |
|
|
|
function interpolate$1(type, gamma) { |
|
var interp = $$1[method(type)]; |
|
return gamma != null && interp && interp.gamma ? interp.gamma(gamma) : interp; |
|
} |
|
|
|
function method(type) { |
|
return 'interpolate' + type.toLowerCase().split('-').map(function (s) { |
|
return s[0].toUpperCase() + s.slice(1); |
|
}).join(''); |
|
} |
|
|
|
var continuous$1 = { |
|
blues: 'cfe1f2bed8eca8cee58fc1de74b2d75ba3cf4592c63181bd206fb2125ca40a4a90', |
|
greens: 'd3eecdc0e6baabdda594d3917bc77d60ba6c46ab5e329a512089430e7735036429', |
|
greys: 'e2e2e2d4d4d4c4c4c4b1b1b19d9d9d8888887575756262624d4d4d3535351e1e1e', |
|
oranges: 'fdd8b3fdc998fdb87bfda55efc9244f87f2cf06b18e4580bd14904b93d029f3303', |
|
purples: 'e2e1efd4d4e8c4c5e0b4b3d6a3a0cc928ec3827cb97566ae684ea25c3696501f8c', |
|
reds: 'fdc9b4fcb49afc9e80fc8767fa7051f6573fec3f2fdc2a25c81b1db21218970b13', |
|
blueGreen: 'd5efedc1e8e0a7ddd18bd2be70c6a958ba9144ad77319c5d2089460e7736036429', |
|
bluePurple: 'ccddecbad0e4a8c2dd9ab0d4919cc98d85be8b6db28a55a6873c99822287730f71', |
|
greenBlue: 'd3eecec5e8c3b1e1bb9bd8bb82cec269c2ca51b2cd3c9fc7288abd1675b10b60a1', |
|
orangeRed: 'fddcaffdcf9bfdc18afdad77fb9562f67d53ee6545e24932d32d1ebf130da70403', |
|
purpleBlue: 'dbdaebc8cee4b1c3de97b7d87bacd15b9fc93a90c01e7fb70b70ab056199045281', |
|
purpleBlueGreen: 'dbd8eac8cee4b0c3de93b7d872acd1549fc83892bb1c88a3097f8702736b016353', |
|
purpleRed: 'dcc9e2d3b3d7ce9eccd186c0da6bb2e14da0e23189d91e6fc61159ab07498f023a', |
|
redPurple: 'fccfccfcbec0faa9b8f98faff571a5ec539ddb3695c41b8aa908808d0179700174', |
|
yellowGreen: 'e4f4acd1eca0b9e2949ed68880c97c62bb6e47aa5e3297502083440e723b036034', |
|
yellowOrangeBrown: 'feeaa1fedd84fecc63feb746fca031f68921eb7215db5e0bc54c05ab3d038f3204', |
|
yellowOrangeRed: 'fee087fed16ffebd59fea849fd903efc7335f9522bee3423de1b20ca0b22af0225', |
|
blueOrange: '134b852f78b35da2cb9dcae1d2e5eff2f0ebfce0bafbbf74e8932fc5690d994a07', |
|
brownBlueGreen: '704108a0651ac79548e3c78af3e6c6eef1eac9e9e48ed1c74da79e187a72025147', |
|
purpleGreen: '5b1667834792a67fb6c9aed3e6d6e8eff0efd9efd5aedda971bb75368e490e5e29', |
|
purpleOrange: '4114696647968f83b7b9b4d6dadbebf3eeeafce0bafbbf74e8932fc5690d994a07', |
|
redBlue: '8c0d25bf363adf745ef4ae91fbdbc9f2efeed2e5ef9dcae15da2cb2f78b3134b85', |
|
redGrey: '8c0d25bf363adf745ef4ae91fcdccbfaf4f1e2e2e2c0c0c0969696646464343434', |
|
yellowGreenBlue: 'eff9bddbf1b4bde5b594d5b969c5be45b4c22c9ec02182b82163aa23479c1c3185', |
|
redYellowBlue: 'a50026d4322cf16e43fcac64fedd90faf8c1dcf1ecabd6e875abd04a74b4313695', |
|
redYellowGreen: 'a50026d4322cf16e43fcac63fedd8df9f7aed7ee8ea4d86e64bc6122964f006837', |
|
pinkYellowGreen: '8e0152c0267edd72adf0b3d6faddedf5f3efe1f2cab6de8780bb474f9125276419', |
|
spectral: '9e0142d13c4bf0704afcac63fedd8dfbf8b0e0f3a1a9dda269bda94288b55e4fa2', |
|
viridis: '440154470e61481a6c482575472f7d443a834144873d4e8a39568c35608d31688e2d708e2a788e27818e23888e21918d1f988b1fa08822a8842ab07f35b77943bf7154c56866cc5d7ad1518fd744a5db36bcdf27d2e21be9e51afde725', |
|
magma: '0000040404130b0924150e3720114b2c11603b0f704a107957157e651a80721f817f24828c29819a2e80a8327db6377ac43c75d1426fde4968e95462f1605df76f5cfa7f5efc8f65fe9f6dfeaf78febf84fece91fddea0fcedaffcfdbf', |
|
inferno: '0000040403130c0826170c3b240c4f330a5f420a68500d6c5d126e6b176e781c6d86216b932667a12b62ae305cbb3755c73e4cd24644dd513ae65c30ed6925f3771af8850ffb9506fca50afcb519fac62df6d645f2e661f3f484fcffa4', |
|
plasma: '0d088723069033059742039d5002a25d01a66a00a87801a88405a7900da49c179ea72198b12a90ba3488c33d80cb4779d35171da5a69e16462e76e5bed7953f2834cf68f44fa9a3dfca636fdb32ffec029fcce25f9dc24f5ea27f0f921', |
|
rainbow: '6e40aa883eb1a43db3bf3cafd83fa4ee4395fe4b83ff576eff6659ff7847ff8c38f3a130e2b72fcfcc36bee044aff05b8ff4576ff65b52f6673af27828ea8d1ddfa319d0b81cbecb23abd82f96e03d82e14c6edb5a5dd0664dbf6e40aa', |
|
sinebow: 'ff4040fc582af47218e78d0bd5a703bfbf00a7d5038de70b72f41858fc2a40ff402afc5818f4720be78d03d5a700bfbf03a7d50b8de71872f42a58fc4040ff582afc7218f48d0be7a703d5bf00bfd503a7e70b8df41872fc2a58ff4040', |
|
browns: 'eedbbdecca96e9b97ae4a865dc9856d18954c7784cc0673fb85536ad44339f3632', |
|
tealBlues: 'bce4d89dd3d181c3cb65b3c245a2b9368fae347da0306a932c5985', |
|
teals: 'bbdfdfa2d4d58ac9c975bcbb61b0af4da5a43799982b8b8c1e7f7f127273006667', |
|
warmGreys: 'dcd4d0cec5c1c0b8b4b3aaa7a59c9998908c8b827f7e7673726866665c5a59504e', |
|
goldGreen: 'f4d166d5ca60b6c35c98bb597cb25760a6564b9c533f8f4f33834a257740146c36', |
|
goldOrange: 'f4d166f8be5cf8aa4cf5983bf3852aef701be2621fd65322c54923b142239e3a26', |
|
goldRed: 'f4d166f6be59f9aa51fc964ef6834bee734ae56249db5247cf4244c43141b71d3e', |
|
lightGreyRed: 'efe9e6e1dad7d5cbc8c8bdb9bbaea9cd967ddc7b43e15f19df4011dc000b', |
|
lightGreyTeal: 'e4eaead6dcddc8ced2b7c2c7a6b4bc64b0bf22a6c32295c11f85be1876bc', |
|
lightMulti: 'e0f1f2c4e9d0b0de9fd0e181f6e072f6c053f3993ef77440ef4a3c', |
|
lightOrange: 'f2e7daf7d5baf9c499fab184fa9c73f68967ef7860e8645bde515bd43d5b', |
|
lightTealBlue: 'e3e9e0c0dccf9aceca7abfc859afc0389fb9328dad2f7ca0276b95255988', |
|
darkBlue: '3232322d46681a5c930074af008cbf05a7ce25c0dd38daed50f3faffffff', |
|
darkGold: '3c3c3c584b37725e348c7631ae8b2bcfa424ecc31ef9de30fff184ffffff', |
|
darkGreen: '3a3a3a215748006f4d048942489e4276b340a6c63dd2d836ffeb2cffffaa', |
|
darkMulti: '3737371f5287197d8c29a86995ce3fffe800ffffff', |
|
darkRed: '3434347036339e3c38cc4037e75d1eec8620eeab29f0ce32ffeb2c' |
|
}; |
|
var discrete$1 = { |
|
category10: '1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf', |
|
category20: '1f77b4aec7e8ff7f0effbb782ca02c98df8ad62728ff98969467bdc5b0d58c564bc49c94e377c2f7b6d27f7f7fc7c7c7bcbd22dbdb8d17becf9edae5', |
|
category20b: '393b795254a36b6ecf9c9ede6379398ca252b5cf6bcedb9c8c6d31bd9e39e7ba52e7cb94843c39ad494ad6616be7969c7b4173a55194ce6dbdde9ed6', |
|
category20c: '3182bd6baed69ecae1c6dbefe6550dfd8d3cfdae6bfdd0a231a35474c476a1d99bc7e9c0756bb19e9ac8bcbddcdadaeb636363969696bdbdbdd9d9d9', |
|
tableau10: '4c78a8f58518e4575672b7b254a24beeca3bb279a2ff9da69d755dbab0ac', |
|
tableau20: '4c78a89ecae9f58518ffbf7954a24b88d27ab79a20f2cf5b43989483bcb6e45756ff9d9879706ebab0acd67195fcbfd2b279a2d6a5c99e765fd8b5a5', |
|
accent: '7fc97fbeaed4fdc086ffff99386cb0f0027fbf5b17666666', |
|
dark2: '1b9e77d95f027570b3e7298a66a61ee6ab02a6761d666666', |
|
paired: 'a6cee31f78b4b2df8a33a02cfb9a99e31a1cfdbf6fff7f00cab2d66a3d9affff99b15928', |
|
pastel1: 'fbb4aeb3cde3ccebc5decbe4fed9a6ffffcce5d8bdfddaecf2f2f2', |
|
pastel2: 'b3e2cdfdcdaccbd5e8f4cae4e6f5c9fff2aef1e2cccccccc', |
|
set1: 'e41a1c377eb84daf4a984ea3ff7f00ffff33a65628f781bf999999', |
|
set2: '66c2a5fc8d628da0cbe78ac3a6d854ffd92fe5c494b3b3b3', |
|
set3: '8dd3c7ffffb3bebadafb807280b1d3fdb462b3de69fccde5d9d9d9bc80bdccebc5ffed6f' |
|
}; |
|
|
|
function colors(palette) { |
|
var n = palette.length / 6 | 0, |
|
c = new Array(n), |
|
i = 0; |
|
|
|
while (i < n) { |
|
c[i] = '#' + palette.slice(i * 6, ++i * 6); |
|
} |
|
|
|
return c; |
|
} |
|
|
|
function apply(_, f) { |
|
for (var k in _) { |
|
scheme(k, f(_[k])); |
|
} |
|
} |
|
|
|
var schemes = {}; |
|
apply(discrete$1, colors); |
|
apply(continuous$1, function (_) { |
|
return interpolateColors(colors(_)); |
|
}); |
|
|
|
function scheme(name, scheme) { |
|
name = name && name.toLowerCase(); |
|
|
|
if (arguments.length > 1) { |
|
schemes[name] = scheme; |
|
return this; |
|
} else { |
|
return schemes[name]; |
|
} |
|
} |
|
/** |
|
* Determine the tick count or interval function. |
|
* @param {Scale} scale - The scale for which to generate tick values. |
|
* @param {*} count - The desired tick count or interval specifier. |
|
* @param {number} minStep - The desired minimum step between tick values. |
|
* @return {*} - The tick count or interval function. |
|
*/ |
|
|
|
|
|
function tickCount(scale, count, minStep) { |
|
var step; |
|
|
|
if (isNumber(count)) { |
|
if (scale.bins) { |
|
count = Math.max(count, scale.bins.length); |
|
} |
|
|
|
if (minStep != null) { |
|
count = Math.min(count, ~~(span(scale.domain()) / minStep) || 1); |
|
} |
|
} |
|
|
|
if (isObject(count)) { |
|
step = count.step; |
|
count = count.interval; |
|
} |
|
|
|
if (isString(count)) { |
|
count = scale.type === Time ? timeInterval(count) : scale.type == UTC ? utcInterval(count) : error('Only time and utc scales accept interval strings.'); |
|
if (step) count = count.every(step); |
|
} |
|
|
|
return count; |
|
} |
|
/** |
|
* Filter a set of candidate tick values, ensuring that only tick values |
|
* that lie within the scale range are included. |
|
* @param {Scale} scale - The scale for which to generate tick values. |
|
* @param {Array<*>} ticks - The candidate tick values. |
|
* @param {*} count - The tick count or interval function. |
|
* @return {Array<*>} - The filtered tick values. |
|
*/ |
|
|
|
|
|
function validTicks(scale, ticks, count) { |
|
var range = scale.range(), |
|
lo = Math.floor(range[0]), |
|
hi = Math.ceil(peek(range)); |
|
|
|
if (lo > hi) { |
|
range = hi; |
|
hi = lo; |
|
lo = range; |
|
} |
|
|
|
ticks = ticks.filter(function (v) { |
|
v = scale(v); |
|
return lo <= v && v <= hi; |
|
}); |
|
|
|
if (count > 0 && ticks.length > 1) { |
|
var endpoints = [ticks[0], peek(ticks)]; |
|
|
|
while (ticks.length > count && ticks.length >= 3) { |
|
ticks = ticks.filter(function (_, i) { |
|
return !(i % 2); |
|
}); |
|
} |
|
|
|
if (ticks.length < 3) { |
|
ticks = endpoints; |
|
} |
|
} |
|
|
|
return ticks; |
|
} |
|
/** |
|
* Generate tick values for the given scale and approximate tick count or |
|
* interval value. If the scale has a 'ticks' method, it will be used to |
|
* generate the ticks, with the count argument passed as a parameter. If the |
|
* scale lacks a 'ticks' method, the full scale domain will be returned. |
|
* @param {Scale} scale - The scale for which to generate tick values. |
|
* @param {*} [count] - The approximate number of desired ticks. |
|
* @return {Array<*>} - The generated tick values. |
|
*/ |
|
|
|
|
|
function tickValues(scale, count) { |
|
return scale.bins ? validTicks(scale, scale.bins) : scale.ticks ? scale.ticks(count) : scale.domain(); |
|
} |
|
/** |
|
* Generate a label format function for a scale. If the scale has a |
|
* 'tickFormat' method, it will be used to generate the formatter, with the |
|
* count and specifier arguments passed as parameters. If the scale lacks a |
|
* 'tickFormat' method, the returned formatter performs simple string coercion. |
|
* If the input scale is a logarithmic scale and the format specifier does not |
|
* indicate a desired decimal precision, a special variable precision formatter |
|
* that automatically trims trailing zeroes will be generated. |
|
* @param {Scale} scale - The scale for which to generate the label formatter. |
|
* @param {*} [count] - The approximate number of desired ticks. |
|
* @param {string} [specifier] - The format specifier. Must be a legal d3 |
|
* specifier string (see https://github.com/d3/d3-format#formatSpecifier) or |
|
* time multi-format specifier object. |
|
* @return {function(*):string} - The generated label formatter. |
|
*/ |
|
|
|
|
|
function tickFormat(scale, count, specifier, formatType, noSkip) { |
|
var type = scale.type, |
|
format = type === Time || formatType === Time ? timeFormat$1(specifier) : type === UTC || formatType === UTC ? utcFormat$1(specifier) : scale.tickFormat ? scale.tickFormat(count, specifier) : specifier ? format$1(specifier) : String; |
|
|
|
if (isLogarithmic(type)) { |
|
var logfmt = variablePrecision(specifier); |
|
format = noSkip || scale.bins ? logfmt : filter$1(format, logfmt); |
|
} |
|
|
|
return format; |
|
} |
|
|
|
function filter$1(sourceFormat, targetFormat) { |
|
return function (_) { |
|
return sourceFormat(_) ? targetFormat(_) : ''; |
|
}; |
|
} |
|
|
|
function variablePrecision(specifier) { |
|
var s = formatSpecifier(specifier || ','); |
|
|
|
if (s.precision == null) { |
|
s.precision = 12; |
|
|
|
switch (s.type) { |
|
case '%': |
|
s.precision -= 2; |
|
break; |
|
|
|
case 'e': |
|
s.precision -= 1; |
|
break; |
|
} |
|
|
|
return trimZeroes(format$1(s), // number format |
|
format$1('.1f')(1)[1] // decimal point character |
|
); |
|
} else { |
|
return format$1(s); |
|
} |
|
} |
|
|
|
function trimZeroes(format, decimalChar) { |
|
return function (x) { |
|
var str = format(x), |
|
dec = str.indexOf(decimalChar), |
|
idx, |
|
end; |
|
if (dec < 0) return str; |
|
idx = rightmostDigit(str, dec); |
|
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) { |
|
var 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 |
|
} |
|
} |
|
/** |
|
* Generates axis ticks for visualizing a spatial scale. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Scale} params.scale - The scale to generate ticks for. |
|
* @param {*} [params.count=10] - The approximate number of ticks, or |
|
* desired tick interval, to use. |
|
* @param {Array<*>} [params.values] - The exact tick values to use. |
|
* These must be legal domain values for the provided scale. |
|
* If provided, the count argument is ignored. |
|
* @param {function(*):string} [params.formatSpecifier] - A format specifier |
|
* to use in conjunction with scale.tickFormat. Legal values are |
|
* any valid d3 4.0 format specifier. |
|
* @param {function(*):string} [params.format] - The format function to use. |
|
* If provided, the formatSpecifier argument is ignored. |
|
*/ |
|
|
|
|
|
function AxisTicks(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$W = inherits(AxisTicks, Transform); |
|
|
|
prototype$W.transform = function (_, pulse) { |
|
if (this.value && !_.modified()) { |
|
return pulse.StopPropagation; |
|
} |
|
|
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS), |
|
ticks = this.value, |
|
scale = _.scale, |
|
tally = _.count == null ? _.values ? _.values.length : 10 : _.count, |
|
count = tickCount(scale, tally, _.minstep), |
|
format = _.format || tickFormat(scale, count, _.formatSpecifier, _.formatType, !!_.values), |
|
values = _.values ? validTicks(scale, _.values, count) : tickValues(scale, count); |
|
if (ticks) out.rem = ticks; |
|
ticks = values.map(function (value, i) { |
|
return ingest({ |
|
index: i / (values.length - 1 || 1), |
|
value: value, |
|
label: format(value) |
|
}); |
|
}); |
|
|
|
if (_.extra && ticks.length) { |
|
// add an extra tick pegged to the initial domain value |
|
// this is used to generate axes with 'binned' domains |
|
ticks.push(ingest({ |
|
index: -1, |
|
extra: { |
|
value: ticks[0].value |
|
}, |
|
label: '' |
|
})); |
|
} |
|
|
|
out.source = ticks; |
|
out.add = ticks; |
|
this.value = ticks; |
|
return out; |
|
}; |
|
/** |
|
* Joins a set of data elements against a set of visual items. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): object} [params.item] - An item generator function. |
|
* @param {function(object): *} [params.key] - The key field associating data and visual items. |
|
*/ |
|
|
|
|
|
function DataJoin(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$X = inherits(DataJoin, Transform); |
|
|
|
function defaultItemCreate() { |
|
return ingest({}); |
|
} |
|
|
|
function isExit(t) { |
|
return t.exit; |
|
} |
|
|
|
prototype$X.transform = function (_, pulse) { |
|
var df = pulse.dataflow, |
|
out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS), |
|
item = _.item || defaultItemCreate, |
|
key = _.key || tupleid, |
|
map = this.value; // prevent transient (e.g., hover) requests from |
|
// cascading across marks derived from marks |
|
|
|
if (isArray(out.encode)) { |
|
out.encode = null; |
|
} |
|
|
|
if (map && (_.modified('key') || pulse.modified(key))) { |
|
error('DataJoin does not support modified key function or fields.'); |
|
} |
|
|
|
if (!map) { |
|
pulse = pulse.addAll(); |
|
this.value = map = fastmap().test(isExit); |
|
|
|
map.lookup = function (t) { |
|
return map.get(key(t)); |
|
}; |
|
} |
|
|
|
pulse.visit(pulse.ADD, function (t) { |
|
var k = key(t), |
|
x = map.get(k); |
|
|
|
if (x) { |
|
if (x.exit) { |
|
map.empty--; |
|
out.add.push(x); |
|
} else { |
|
out.mod.push(x); |
|
} |
|
} else { |
|
map.set(k, x = item(t)); |
|
out.add.push(x); |
|
} |
|
|
|
x.datum = t; |
|
x.exit = false; |
|
}); |
|
pulse.visit(pulse.MOD, function (t) { |
|
var k = key(t), |
|
x = map.get(k); |
|
|
|
if (x) { |
|
x.datum = t; |
|
out.mod.push(x); |
|
} |
|
}); |
|
pulse.visit(pulse.REM, function (t) { |
|
var k = key(t), |
|
x = map.get(k); |
|
|
|
if (t === x.datum && !x.exit) { |
|
out.rem.push(x); |
|
x.exit = true; |
|
++map.empty; |
|
} |
|
}); |
|
if (pulse.changed(pulse.ADD_MOD)) out.modifies('datum'); |
|
if (_.clean && map.empty > df.cleanThreshold) df.runAfter(map.clean); |
|
return out; |
|
}; |
|
/** |
|
* Invokes encoding functions for visual items. |
|
* @constructor |
|
* @param {object} params - The parameters to the encoding functions. This |
|
* parameter object will be passed through to all invoked encoding functions. |
|
* @param {object} [params.mod=false] - Flag indicating if tuples in the input |
|
* mod set that are unmodified by encoders should be included in the output. |
|
* @param {object} param.encoders - The encoding functions |
|
* @param {function(object, object): boolean} [param.encoders.update] - Update encoding set |
|
* @param {function(object, object): boolean} [param.encoders.enter] - Enter encoding set |
|
* @param {function(object, object): boolean} [param.encoders.exit] - Exit encoding set |
|
*/ |
|
|
|
|
|
function Encode(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$Y = inherits(Encode, Transform); |
|
|
|
prototype$Y.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.ADD_REM), |
|
fmod = _.mod || false, |
|
encoders = _.encoders, |
|
encode = pulse.encode; // if an array, the encode directive includes additional sets |
|
// that must be defined in order for the primary set to be invoked |
|
// e.g., only run the update set if the hover set is defined |
|
|
|
if (isArray(encode)) { |
|
if (out.changed() || encode.every(function (e) { |
|
return encoders[e]; |
|
})) { |
|
encode = encode[0]; |
|
out.encode = null; // consume targeted encode directive |
|
} else { |
|
return pulse.StopPropagation; |
|
} |
|
} // marshall encoder functions |
|
|
|
|
|
var reenter = encode === 'enter', |
|
update = encoders.update || falsy, |
|
enter = encoders.enter || falsy, |
|
exit = encoders.exit || falsy, |
|
set = (encode && !reenter ? encoders[encode] : update) || falsy; |
|
|
|
if (pulse.changed(pulse.ADD)) { |
|
pulse.visit(pulse.ADD, function (t) { |
|
enter(t, _); |
|
update(t, _); |
|
}); |
|
out.modifies(enter.output); |
|
out.modifies(update.output); |
|
|
|
if (set !== falsy && set !== update) { |
|
pulse.visit(pulse.ADD, function (t) { |
|
set(t, _); |
|
}); |
|
out.modifies(set.output); |
|
} |
|
} |
|
|
|
if (pulse.changed(pulse.REM) && exit !== falsy) { |
|
pulse.visit(pulse.REM, function (t) { |
|
exit(t, _); |
|
}); |
|
out.modifies(exit.output); |
|
} |
|
|
|
if (reenter || set !== falsy) { |
|
var flag = pulse.MOD | (_.modified() ? pulse.REFLOW : 0); |
|
|
|
if (reenter) { |
|
pulse.visit(flag, function (t) { |
|
var mod = enter(t, _) || fmod; |
|
if (set(t, _) || mod) out.mod.push(t); |
|
}); |
|
if (out.mod.length) out.modifies(enter.output); |
|
} else { |
|
pulse.visit(flag, function (t) { |
|
if (set(t, _) || fmod) out.mod.push(t); |
|
}); |
|
} |
|
|
|
if (out.mod.length) out.modifies(set.output); |
|
} |
|
|
|
return out.changed() ? out : pulse.StopPropagation; |
|
}; |
|
|
|
var Symbols$1 = 'symbol'; |
|
var Discrete$1 = 'discrete'; |
|
var Gradient$1 = 'gradient'; |
|
var symbols$1 = (_symbols$ = {}, _defineProperty(_symbols$, Quantile$1, 'quantiles'), _defineProperty(_symbols$, Quantize, 'thresholds'), _defineProperty(_symbols$, Threshold, 'domain'), _symbols$); |
|
var formats$1 = (_formats$ = {}, _defineProperty(_formats$, Quantile$1, 'quantiles'), _defineProperty(_formats$, Quantize, 'domain'), _formats$); |
|
|
|
function labelValues(scale, count) { |
|
return scale.bins ? binValues(scale.bins) : scale.type === Log ? logValues(scale, count) : symbols$1[scale.type] ? thresholdValues(scale[symbols$1[scale.type]]()) : tickValues(scale, count); |
|
} |
|
|
|
function logValues(scale, count) { |
|
var ticks = tickValues(scale, count), |
|
base = scale.base(), |
|
logb = Math.log(base), |
|
k = Math.max(1, base * count / ticks.length); // apply d3-scale's log format filter criteria |
|
|
|
return ticks.filter(function (d) { |
|
var i = d / Math.pow(base, Math.round(Math.log(d) / logb)); |
|
if (i * base < base - 0.5) i *= base; |
|
return i <= k; |
|
}); |
|
} |
|
|
|
function thresholdFormat(scale, specifier) { |
|
var _ = scale[formats$1[scale.type]](), |
|
n = _.length, |
|
d = n > 1 ? _[1] - _[0] : _[0], |
|
i; |
|
|
|
for (i = 1; i < n; ++i) { |
|
d = Math.min(d, _[i] - _[i - 1]); |
|
} // 3 ticks times 10 for increased resolution |
|
|
|
|
|
return spanFormat(0, d, 3 * 10, specifier); |
|
} |
|
|
|
function thresholdValues(thresholds) { |
|
var values = [-Infinity].concat(thresholds); |
|
values.max = +Infinity; |
|
return values; |
|
} |
|
|
|
function binValues(bins) { |
|
var values = bins.slice(0, -1); |
|
values.max = peek(bins); |
|
return values; |
|
} |
|
|
|
function isDiscreteRange(scale) { |
|
return symbols$1[scale.type] || scale.bins; |
|
} |
|
|
|
function labelFormat(scale, count, type, specifier, formatType, noSkip) { |
|
var format = formats$1[scale.type] && formatType !== Time && formatType !== UTC ? thresholdFormat(scale, specifier) : tickFormat(scale, count, specifier, formatType, noSkip); |
|
return type === Symbols$1 && isDiscreteRange(scale) ? formatRange(format) : type === Discrete$1 ? formatDiscrete(format) : formatPoint(format); |
|
} |
|
|
|
function formatRange(format) { |
|
return function (value, index, array) { |
|
var limit = get$3(array[index + 1], get$3(array.max, +Infinity)), |
|
lo = formatValue(value, format), |
|
hi = formatValue(limit, format); |
|
return lo && hi ? lo + " \u2013 " + hi : hi ? '< ' + hi : "\u2265 " + lo; |
|
}; |
|
} |
|
|
|
function get$3(value, dflt) { |
|
return value != null ? value : dflt; |
|
} |
|
|
|
function formatDiscrete(format) { |
|
return function (value, index) { |
|
return index ? format(value) : null; |
|
}; |
|
} |
|
|
|
function formatPoint(format) { |
|
return function (value) { |
|
return format(value); |
|
}; |
|
} |
|
|
|
function formatValue(value, format) { |
|
return Number.isFinite(value) ? format(value) : null; |
|
} |
|
|
|
function labelFraction(scale) { |
|
var domain = scale.domain(), |
|
count = domain.length - 1, |
|
lo = +domain[0], |
|
hi = +peek(domain), |
|
span = hi - lo; |
|
|
|
if (scale.type === Threshold) { |
|
var adjust = count ? span / count : 0.1; |
|
lo -= adjust; |
|
hi += adjust; |
|
span = hi - lo; |
|
} |
|
|
|
return function (value) { |
|
return (value - lo) / span; |
|
}; |
|
} |
|
/** |
|
* Generates legend entries for visualizing a scale. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Scale} params.scale - The scale to generate items for. |
|
* @param {*} [params.count=5] - The approximate number of items, or |
|
* desired tick interval, to use. |
|
* @param {*} [params.limit] - The maximum number of entries to |
|
* include in a symbol legend. |
|
* @param {Array<*>} [params.values] - The exact tick values to use. |
|
* These must be legal domain values for the provided scale. |
|
* If provided, the count argument is ignored. |
|
* @param {string} [params.formatSpecifier] - A format specifier |
|
* to use in conjunction with scale.tickFormat. Legal values are |
|
* any valid D3 format specifier string. |
|
* @param {function(*):string} [params.format] - The format function to use. |
|
* If provided, the formatSpecifier argument is ignored. |
|
*/ |
|
|
|
|
|
function LegendEntries(params) { |
|
Transform.call(this, [], params); |
|
} |
|
|
|
var prototype$Z = inherits(LegendEntries, Transform); |
|
|
|
prototype$Z.transform = function (_, pulse) { |
|
if (this.value != null && !_.modified()) { |
|
return pulse.StopPropagation; |
|
} |
|
|
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS), |
|
items = this.value, |
|
type = _.type || Symbols$1, |
|
scale = _.scale, |
|
limit = +_.limit, |
|
count = tickCount(scale, _.count == null ? 5 : _.count, _.minstep), |
|
lskip = !!_.values || type === Symbols$1, |
|
format = _.format || labelFormat(scale, count, type, _.formatSpecifier, _.formatType, lskip), |
|
values = _.values || labelValues(scale, count), |
|
domain, |
|
fraction, |
|
size, |
|
offset, |
|
ellipsis; |
|
if (items) out.rem = items; |
|
|
|
if (type === Symbols$1) { |
|
if (limit && values.length > limit) { |
|
pulse.dataflow.warn('Symbol legend count exceeds limit, filtering items.'); |
|
items = values.slice(0, limit - 1); |
|
ellipsis = true; |
|
} else { |
|
items = values; |
|
} |
|
|
|
if (isFunction(size = _.size)) { |
|
// if first value maps to size zero, remove from list (vega#717) |
|
if (!_.values && scale(items[0]) === 0) { |
|
items = items.slice(1); |
|
} // compute size offset for legend entries |
|
|
|
|
|
offset = items.reduce(function (max, value) { |
|
return Math.max(max, size(value, _)); |
|
}, 0); |
|
} else { |
|
size = constant(offset = size || 8); |
|
} |
|
|
|
items = items.map(function (value, index) { |
|
return ingest({ |
|
index: index, |
|
label: format(value, index, items), |
|
value: value, |
|
offset: offset, |
|
size: size(value, _) |
|
}); |
|
}); |
|
|
|
if (ellipsis) { |
|
ellipsis = values[items.length]; |
|
items.push(ingest({ |
|
index: items.length, |
|
label: "\u2026".concat(values.length - items.length, " entries"), |
|
value: ellipsis, |
|
offset: offset, |
|
size: size(ellipsis, _) |
|
})); |
|
} |
|
} else if (type === Gradient$1) { |
|
domain = scale.domain(), fraction = scaleFraction(scale, domain[0], peek(domain)); // if automatic label generation produces 2 or fewer values, |
|
// use the domain end points instead (fixes vega/vega#1364) |
|
|
|
if (values.length < 3 && !_.values && domain[0] !== peek(domain)) { |
|
values = [domain[0], peek(domain)]; |
|
} |
|
|
|
items = values.map(function (value, index) { |
|
return ingest({ |
|
index: index, |
|
label: format(value, index, values), |
|
value: value, |
|
perc: fraction(value) |
|
}); |
|
}); |
|
} else { |
|
size = values.length - 1; |
|
fraction = labelFraction(scale); |
|
items = values.map(function (value, index) { |
|
return ingest({ |
|
index: index, |
|
label: format(value, index, values), |
|
value: value, |
|
perc: index ? fraction(value) : 0, |
|
perc2: index === size ? 1 : fraction(values[index + 1]) |
|
}); |
|
}); |
|
} |
|
|
|
out.source = items; |
|
out.add = items; |
|
this.value = items; |
|
return out; |
|
}; |
|
|
|
var Paths = fastmap({ |
|
'line': line$2, |
|
'line-radial': lineR, |
|
'arc': arc$2, |
|
'arc-radial': arcR, |
|
'curve': curve, |
|
'curve-radial': curveR, |
|
'orthogonal-horizontal': orthoX, |
|
'orthogonal-vertical': orthoY, |
|
'orthogonal-radial': orthoR, |
|
'diagonal-horizontal': diagonalX, |
|
'diagonal-vertical': diagonalY, |
|
'diagonal-radial': diagonalR |
|
}); |
|
|
|
function sourceX(t) { |
|
return t.source.x; |
|
} |
|
|
|
function sourceY(t) { |
|
return t.source.y; |
|
} |
|
|
|
function targetX(t) { |
|
return t.target.x; |
|
} |
|
|
|
function targetY(t) { |
|
return t.target.y; |
|
} |
|
/** |
|
* Layout paths linking source and target elements. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
*/ |
|
|
|
|
|
function LinkPath(params) { |
|
Transform.call(this, {}, params); |
|
} |
|
|
|
LinkPath.Definition = { |
|
"type": "LinkPath", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "sourceX", |
|
"type": "field", |
|
"default": "source.x" |
|
}, { |
|
"name": "sourceY", |
|
"type": "field", |
|
"default": "source.y" |
|
}, { |
|
"name": "targetX", |
|
"type": "field", |
|
"default": "target.x" |
|
}, { |
|
"name": "targetY", |
|
"type": "field", |
|
"default": "target.y" |
|
}, { |
|
"name": "orient", |
|
"type": "enum", |
|
"default": "vertical", |
|
"values": ["horizontal", "vertical", "radial"] |
|
}, { |
|
"name": "shape", |
|
"type": "enum", |
|
"default": "line", |
|
"values": ["line", "arc", "curve", "diagonal", "orthogonal"] |
|
}, { |
|
"name": "require", |
|
"type": "signal" |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"default": "path" |
|
}] |
|
}; |
|
var prototype$_ = inherits(LinkPath, Transform); |
|
|
|
prototype$_.transform = function (_, pulse) { |
|
var sx = _.sourceX || sourceX, |
|
sy = _.sourceY || sourceY, |
|
tx = _.targetX || targetX, |
|
ty = _.targetY || targetY, |
|
as = _.as || 'path', |
|
orient = _.orient || 'vertical', |
|
shape = _.shape || 'line', |
|
path = Paths.get(shape + '-' + orient) || Paths.get(shape); |
|
|
|
if (!path) { |
|
error('LinkPath unsupported type: ' + _.shape + (_.orient ? '-' + _.orient : '')); |
|
} |
|
|
|
pulse.visit(pulse.SOURCE, function (t) { |
|
t[as] = path(sx(t), sy(t), tx(t), ty(t)); |
|
}); |
|
return pulse.reflow(_.modified()).modifies(as); |
|
}; // -- Link Path Generation Methods ----- |
|
|
|
|
|
function line$2(sx, sy, tx, ty) { |
|
return 'M' + sx + ',' + sy + 'L' + tx + ',' + ty; |
|
} |
|
|
|
function lineR(sa, sr, ta, tr) { |
|
return line$2(sr * Math.cos(sa), sr * Math.sin(sa), tr * Math.cos(ta), tr * Math.sin(ta)); |
|
} |
|
|
|
function arc$2(sx, sy, tx, ty) { |
|
var dx = tx - sx, |
|
dy = ty - sy, |
|
rr = Math.sqrt(dx * dx + dy * dy) / 2, |
|
ra = 180 * Math.atan2(dy, dx) / Math.PI; |
|
return 'M' + sx + ',' + sy + 'A' + rr + ',' + rr + ' ' + ra + ' 0 1' + ' ' + tx + ',' + ty; |
|
} |
|
|
|
function arcR(sa, sr, ta, tr) { |
|
return arc$2(sr * Math.cos(sa), sr * Math.sin(sa), tr * Math.cos(ta), tr * Math.sin(ta)); |
|
} |
|
|
|
function curve(sx, sy, tx, ty) { |
|
var dx = tx - sx, |
|
dy = ty - sy, |
|
ix = 0.2 * (dx + dy), |
|
iy = 0.2 * (dy - dx); |
|
return 'M' + sx + ',' + sy + 'C' + (sx + ix) + ',' + (sy + iy) + ' ' + (tx + iy) + ',' + (ty - ix) + ' ' + tx + ',' + ty; |
|
} |
|
|
|
function curveR(sa, sr, ta, tr) { |
|
return curve(sr * Math.cos(sa), sr * Math.sin(sa), tr * Math.cos(ta), tr * Math.sin(ta)); |
|
} |
|
|
|
function orthoX(sx, sy, tx, ty) { |
|
return 'M' + sx + ',' + sy + 'V' + ty + 'H' + tx; |
|
} |
|
|
|
function orthoY(sx, sy, tx, ty) { |
|
return 'M' + sx + ',' + sy + 'H' + tx + 'V' + ty; |
|
} |
|
|
|
function orthoR(sa, sr, ta, tr) { |
|
var sc = Math.cos(sa), |
|
ss = Math.sin(sa), |
|
tc = Math.cos(ta), |
|
ts = Math.sin(ta), |
|
sf = Math.abs(ta - sa) > Math.PI ? ta <= sa : ta > sa; |
|
return 'M' + sr * sc + ',' + sr * ss + 'A' + sr + ',' + sr + ' 0 0,' + (sf ? 1 : 0) + ' ' + sr * tc + ',' + sr * ts + 'L' + tr * tc + ',' + tr * ts; |
|
} |
|
|
|
function diagonalX(sx, sy, tx, ty) { |
|
var m = (sx + tx) / 2; |
|
return 'M' + sx + ',' + sy + 'C' + m + ',' + sy + ' ' + m + ',' + ty + ' ' + tx + ',' + ty; |
|
} |
|
|
|
function diagonalY(sx, sy, tx, ty) { |
|
var m = (sy + ty) / 2; |
|
return 'M' + sx + ',' + sy + 'C' + sx + ',' + m + ' ' + tx + ',' + m + ' ' + tx + ',' + ty; |
|
} |
|
|
|
function diagonalR(sa, sr, ta, tr) { |
|
var sc = Math.cos(sa), |
|
ss = Math.sin(sa), |
|
tc = Math.cos(ta), |
|
ts = Math.sin(ta), |
|
mr = (sr + tr) / 2; |
|
return 'M' + sr * sc + ',' + sr * ss + 'C' + mr * sc + ',' + mr * ss + ' ' + mr * tc + ',' + mr * ts + ' ' + tr * tc + ',' + tr * ts; |
|
} |
|
/** |
|
* Pie and donut chart layout. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The value field to size pie segments. |
|
* @param {number} [params.startAngle=0] - The start angle (in radians) of the layout. |
|
* @param {number} [params.endAngle=2π] - The end angle (in radians) of the layout. |
|
* @param {boolean} [params.sort] - Boolean flag for sorting sectors by value. |
|
*/ |
|
|
|
|
|
function Pie(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Pie.Definition = { |
|
"type": "Pie", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field" |
|
}, { |
|
"name": "startAngle", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "endAngle", |
|
"type": "number", |
|
"default": 6.283185307179586 |
|
}, { |
|
"name": "sort", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": 2, |
|
"default": ["startAngle", "endAngle"] |
|
}] |
|
}; |
|
var prototype$$ = inherits(Pie, Transform); |
|
|
|
prototype$$.transform = function (_, pulse) { |
|
var as = _.as || ['startAngle', 'endAngle'], |
|
startAngle = as[0], |
|
endAngle = as[1], |
|
field = _.field || one, |
|
start = _.startAngle || 0, |
|
stop = _.endAngle != null ? _.endAngle : 2 * Math.PI, |
|
data = pulse.source, |
|
values = data.map(field), |
|
n = values.length, |
|
a = start, |
|
k = (stop - start) / sum(values), |
|
index = sequence(n), |
|
i, |
|
t, |
|
v; |
|
|
|
if (_.sort) { |
|
index.sort(function (a, b) { |
|
return values[a] - values[b]; |
|
}); |
|
} |
|
|
|
for (i = 0; i < n; ++i) { |
|
v = values[index[i]]; |
|
t = data[index[i]]; |
|
t[startAngle] = a; |
|
t[endAngle] = a += v * k; |
|
} |
|
|
|
this.value = values; |
|
return pulse.reflow(_.modified()).modifies(as); |
|
}; |
|
|
|
var DEFAULT_COUNT = 5; |
|
|
|
function includeZero(scale) { |
|
var type = scale.type; |
|
return !scale.bins && (type === Linear$1 || type === Pow || type === Sqrt); |
|
} |
|
|
|
function includePad(type) { |
|
return isContinuous(type) && type !== Sequential; |
|
} |
|
|
|
var SKIP$2 = toSet(['set', 'modified', 'clear', 'type', 'scheme', 'schemeExtent', 'schemeCount', 'domain', 'domainMin', 'domainMid', 'domainMax', 'domainRaw', 'domainImplicit', 'nice', 'zero', 'bins', 'range', 'rangeStep', 'round', 'reverse', 'interpolate', 'interpolateGamma']); |
|
/** |
|
* Maintains a scale function mapping data values to visual channels. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
*/ |
|
|
|
function Scale(params) { |
|
Transform.call(this, null, params); |
|
this.modified(true); // always treat as modified |
|
} |
|
|
|
var prototype$10 = inherits(Scale, Transform); |
|
|
|
prototype$10.transform = function (_, pulse) { |
|
var df = pulse.dataflow, |
|
scale = this.value, |
|
key = scaleKey(_); |
|
|
|
if (!scale || key !== scale.type) { |
|
this.value = scale = scale$2(key)(); |
|
} |
|
|
|
for (key in _) { |
|
if (!SKIP$2[key]) { |
|
// padding is a scale property for band/point but not others |
|
if (key === 'padding' && includePad(scale.type)) continue; // invoke scale property setter, raise warning if not found |
|
|
|
isFunction(scale[key]) ? scale[key](_[key]) : df.warn('Unsupported scale property: ' + key); |
|
} |
|
} |
|
|
|
configureRange(scale, _, configureBins(scale, _, configureDomain(scale, _, df))); |
|
return pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS); |
|
}; |
|
|
|
function scaleKey(_) { |
|
var t = _.type, |
|
d = '', |
|
n; // backwards compatibility pre Vega 5. |
|
|
|
if (t === Sequential) return Sequential + '-' + Linear$1; |
|
|
|
if (isContinuousColor(_)) { |
|
n = _.rawDomain ? _.rawDomain.length : _.domain ? _.domain.length + +(_.domainMid != null) : 0; |
|
d = n === 2 ? Sequential + '-' : n === 3 ? Diverging + '-' : ''; |
|
} |
|
|
|
return (d + t || Linear$1).toLowerCase(); |
|
} |
|
|
|
function isContinuousColor(_) { |
|
var t = _.type; |
|
return isContinuous(t) && t !== Time && t !== UTC && (_.scheme || _.range && _.range.length && _.range.every(isString)); |
|
} |
|
|
|
function configureDomain(scale, _, df) { |
|
// check raw domain, if provided use that and exit early |
|
var raw = rawDomain(scale, _.domainRaw, df); |
|
if (raw > -1) return raw; |
|
var domain = _.domain, |
|
type = scale.type, |
|
zero = _.zero || _.zero === undefined && includeZero(scale), |
|
n, |
|
mid; |
|
if (!domain) return 0; // adjust continuous domain for minimum pixel padding |
|
|
|
if (includePad(type) && _.padding && domain[0] !== peek(domain)) { |
|
domain = padDomain(type, domain, _.range, _.padding, _.exponent, _.constant); |
|
} // adjust domain based on zero, min, max settings |
|
|
|
|
|
if (zero || _.domainMin != null || _.domainMax != null || _.domainMid != null) { |
|
n = (domain = domain.slice()).length - 1 || 1; |
|
|
|
if (zero) { |
|
if (domain[0] > 0) domain[0] = 0; |
|
if (domain[n] < 0) domain[n] = 0; |
|
} |
|
|
|
if (_.domainMin != null) domain[0] = _.domainMin; |
|
if (_.domainMax != null) domain[n] = _.domainMax; |
|
|
|
if (_.domainMid != null) { |
|
mid = _.domainMid; |
|
|
|
if (mid < domain[0] || mid > domain[n]) { |
|
df.warn('Scale domainMid exceeds domain min or max.', mid); |
|
} |
|
|
|
domain.splice(n, 0, mid); |
|
} |
|
} // set the scale domain |
|
|
|
|
|
scale.domain(domainCheck(type, domain, df)); // if ordinal scale domain is defined, prevent implicit |
|
// domain construction as side-effect of scale lookup |
|
|
|
if (type === Ordinal) { |
|
scale.unknown(_.domainImplicit ? implicit : undefined); |
|
} // perform 'nice' adjustment as requested |
|
|
|
|
|
if (_.nice && scale.nice) { |
|
scale.nice(_.nice !== true && tickCount(scale, _.nice) || null); |
|
} // return the cardinality of the domain |
|
|
|
|
|
return domain.length; |
|
} |
|
|
|
function rawDomain(scale, raw, df) { |
|
if (raw) { |
|
scale.domain(domainCheck(scale.type, raw, df)); |
|
return raw.length; |
|
} else { |
|
return -1; |
|
} |
|
} |
|
|
|
function padDomain(type, domain, range, pad, exponent, constant) { |
|
var span = Math.abs(peek(range) - range[0]), |
|
frac = span / (span - 2 * pad), |
|
d = type === Log ? zoomLog(domain, null, frac) : type === Sqrt ? zoomPow(domain, null, frac, 0.5) : type === Pow ? zoomPow(domain, null, frac, exponent || 1) : type === Symlog ? zoomSymlog(domain, null, frac, constant || 1) : zoomLinear(domain, null, frac); |
|
domain = domain.slice(); |
|
domain[0] = d[0]; |
|
domain[domain.length - 1] = d[1]; |
|
return domain; |
|
} |
|
|
|
function domainCheck(type, domain, df) { |
|
if (isLogarithmic(type)) { |
|
// sum signs of domain values |
|
// if all pos or all neg, abs(sum) === domain.length |
|
var s = Math.abs(domain.reduce(function (s, v) { |
|
return s + (v < 0 ? -1 : v > 0 ? 1 : 0); |
|
}, 0)); |
|
|
|
if (s !== domain.length) { |
|
df.warn('Log scale domain includes zero: ' + $(domain)); |
|
} |
|
} |
|
|
|
return domain; |
|
} |
|
|
|
function configureBins(scale, _, count) { |
|
var bins = _.bins; |
|
|
|
if (bins && !isArray(bins)) { |
|
// generate bin boundary array |
|
var _domain2 = scale.domain(), |
|
lo = _domain2[0], |
|
hi = peek(_domain2), |
|
start = bins.start == null ? lo : bins.start, |
|
_stop = bins.stop == null ? hi : bins.stop, |
|
step = bins.step; |
|
|
|
if (!step) error('Scale bins parameter missing step property.'); |
|
if (start < lo) start = step * Math.ceil(lo / step); |
|
if (_stop > hi) _stop = step * Math.floor(hi / step); |
|
bins = sequence(start, _stop + step / 2, step); |
|
} |
|
|
|
if (bins) { |
|
// assign bin boundaries to scale instance |
|
scale.bins = bins; |
|
} else if (scale.bins) { |
|
// no current bins, remove bins if previously set |
|
delete scale.bins; |
|
} // special handling for bin-ordinal scales |
|
|
|
|
|
if (scale.type === BinOrdinal) { |
|
if (!bins) { |
|
// the domain specifies the bins |
|
scale.bins = scale.domain(); |
|
} else if (!_.domain && !_.domainRaw) { |
|
// the bins specify the domain |
|
scale.domain(bins); |
|
count = bins.length; |
|
} |
|
} // return domain cardinality |
|
|
|
|
|
return count; |
|
} |
|
|
|
function configureRange(scale, _, count) { |
|
var type = scale.type, |
|
round = _.round || false, |
|
range = _.range; // if range step specified, calculate full range extent |
|
|
|
if (_.rangeStep != null) { |
|
range = configureRangeStep(type, _, count); |
|
} // else if a range scheme is defined, use that |
|
else if (_.scheme) { |
|
range = configureScheme(type, _, count); |
|
|
|
if (isFunction(range)) { |
|
if (scale.interpolator) { |
|
return scale.interpolator(range); |
|
} else { |
|
error("Scale type ".concat(type, " does not support interpolating color schemes.")); |
|
} |
|
} |
|
} // given a range array for an interpolating scale, convert to interpolator |
|
|
|
|
|
if (range && isInterpolating(type)) { |
|
return scale.interpolator(interpolateColors(flip(range, _.reverse), _.interpolate, _.interpolateGamma)); |
|
} // configure rounding / interpolation |
|
|
|
|
|
if (range && _.interpolate && scale.interpolate) { |
|
scale.interpolate(interpolate$1(_.interpolate, _.interpolateGamma)); |
|
} else if (isFunction(scale.round)) { |
|
scale.round(round); |
|
} else if (isFunction(scale.rangeRound)) { |
|
scale.interpolate(round ? interpolateRound : interpolate); |
|
} |
|
|
|
if (range) scale.range(flip(range, _.reverse)); |
|
} |
|
|
|
function configureRangeStep(type, _, count) { |
|
if (type !== Band && type !== Point) { |
|
error('Only band and point scales support rangeStep.'); |
|
} // calculate full range based on requested step size and padding |
|
|
|
|
|
var outer = (_.paddingOuter != null ? _.paddingOuter : _.padding) || 0, |
|
inner = type === Point ? 1 : (_.paddingInner != null ? _.paddingInner : _.padding) || 0; |
|
return [0, _.rangeStep * bandSpace(count, inner, outer)]; |
|
} |
|
|
|
function configureScheme(type, _, count) { |
|
var extent = _.schemeExtent, |
|
name, |
|
scheme$1; |
|
|
|
if (isArray(_.scheme)) { |
|
scheme$1 = interpolateColors(_.scheme, _.interpolate, _.interpolateGamma); |
|
} else { |
|
name = _.scheme.toLowerCase(); |
|
scheme$1 = scheme(name); |
|
if (!scheme$1) error("Unrecognized scheme name: ".concat(_.scheme)); |
|
} // determine size for potential discrete range |
|
|
|
|
|
count = type === Threshold ? count + 1 : type === BinOrdinal ? count - 1 : type === Quantile$1 || type === Quantize ? +_.schemeCount || DEFAULT_COUNT : count; // adjust and/or quantize scheme as appropriate |
|
|
|
return isInterpolating(type) ? adjustScheme(scheme$1, extent, _.reverse) : isFunction(scheme$1) ? quantizeInterpolator(adjustScheme(scheme$1, extent), count) : type === Ordinal ? scheme$1 : scheme$1.slice(0, count); |
|
} |
|
|
|
function adjustScheme(scheme, extent, reverse) { |
|
return isFunction(scheme) && (extent || reverse) ? interpolateRange(scheme, flip(extent || [0, 1], reverse)) : scheme; |
|
} |
|
|
|
function flip(array, reverse) { |
|
return reverse ? array.slice().reverse() : array; |
|
} |
|
/** |
|
* Sorts scenegraph items in the pulse source array. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(*,*): number} [params.sort] - A comparator |
|
* function for sorting tuples. |
|
*/ |
|
|
|
|
|
function SortItems(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$11 = inherits(SortItems, Transform); |
|
|
|
prototype$11.transform = function (_, pulse) { |
|
var mod = _.modified('sort') || pulse.changed(pulse.ADD) || pulse.modified(_.sort.fields) || pulse.modified('datum'); |
|
if (mod) pulse.source.sort(stableCompare(_.sort)); |
|
this.modified(mod); |
|
return pulse; |
|
}; |
|
|
|
var Zero = 'zero', |
|
Center = 'center', |
|
Normalize = 'normalize', |
|
DefOutput = ['y0', 'y1']; |
|
/** |
|
* Stack layout for visualization elements. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The value field to stack. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby. |
|
* @param {function(object,object): number} [params.sort] - A comparator for stack sorting. |
|
* @param {string} [offset='zero'] - Stack baseline offset. One of 'zero', 'center', 'normalize'. |
|
*/ |
|
|
|
function Stack(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Stack.Definition = { |
|
"type": "Stack", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field" |
|
}, { |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "sort", |
|
"type": "compare" |
|
}, { |
|
"name": "offset", |
|
"type": "enum", |
|
"default": Zero, |
|
"values": [Zero, Center, Normalize] |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": 2, |
|
"default": DefOutput |
|
}] |
|
}; |
|
var prototype$12 = inherits(Stack, Transform); |
|
|
|
prototype$12.transform = function (_, pulse) { |
|
var as = _.as || DefOutput, |
|
y0 = as[0], |
|
y1 = as[1], |
|
sort = stableCompare(_.sort), |
|
field = _.field || one, |
|
stack = _.offset === Center ? stackCenter : _.offset === Normalize ? stackNormalize : stackZero, |
|
groups, |
|
i, |
|
n, |
|
max; // partition, sum, and sort the stack groups |
|
|
|
groups = partition$2(pulse.source, _.groupby, sort, field); // compute stack layouts per group |
|
|
|
for (i = 0, n = groups.length, max = groups.max; i < n; ++i) { |
|
stack(groups[i], max, field, y0, y1); |
|
} |
|
|
|
return pulse.reflow(_.modified()).modifies(as); |
|
}; |
|
|
|
function stackCenter(group, max, field, y0, y1) { |
|
var last = (max - group.sum) / 2, |
|
m = group.length, |
|
j = 0, |
|
t; |
|
|
|
for (; j < m; ++j) { |
|
t = group[j]; |
|
t[y0] = last; |
|
t[y1] = last += Math.abs(field(t)); |
|
} |
|
} |
|
|
|
function stackNormalize(group, max, field, y0, y1) { |
|
var scale = 1 / group.sum, |
|
last = 0, |
|
m = group.length, |
|
j = 0, |
|
v = 0, |
|
t; |
|
|
|
for (; j < m; ++j) { |
|
t = group[j]; |
|
t[y0] = last; |
|
t[y1] = last = scale * (v += Math.abs(field(t))); |
|
} |
|
} |
|
|
|
function stackZero(group, max, field, y0, y1) { |
|
var lastPos = 0, |
|
lastNeg = 0, |
|
m = group.length, |
|
j = 0, |
|
v, |
|
t; |
|
|
|
for (; j < m; ++j) { |
|
t = group[j]; |
|
v = +field(t); |
|
|
|
if (v < 0) { |
|
t[y0] = lastNeg; |
|
t[y1] = lastNeg += v; |
|
} else { |
|
t[y0] = lastPos; |
|
t[y1] = lastPos += v; |
|
} |
|
} |
|
} |
|
|
|
function partition$2(data, groupby, sort, field) { |
|
var groups = [], |
|
get = function get(f) { |
|
return f(t); |
|
}, |
|
map, |
|
i, |
|
n, |
|
m, |
|
t, |
|
k, |
|
g, |
|
s, |
|
max; // partition data points into stack groups |
|
|
|
|
|
if (groupby == null) { |
|
groups.push(data.slice()); |
|
} else { |
|
for (map = {}, i = 0, n = data.length; i < n; ++i) { |
|
t = data[i]; |
|
k = groupby.map(get); |
|
g = map[k]; |
|
|
|
if (!g) { |
|
map[k] = g = []; |
|
groups.push(g); |
|
} |
|
|
|
g.push(t); |
|
} |
|
} // compute sums of groups, sort groups as needed |
|
|
|
|
|
for (k = 0, max = 0, m = groups.length; k < m; ++k) { |
|
g = groups[k]; |
|
|
|
for (i = 0, s = 0, n = g.length; i < n; ++i) { |
|
s += Math.abs(field(g[i])); |
|
} |
|
|
|
g.sum = s; |
|
if (s > max) max = s; |
|
if (sort) g.sort(sort); |
|
} |
|
|
|
groups.max = max; |
|
return groups; |
|
} |
|
|
|
var encode = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
axisticks: AxisTicks, |
|
datajoin: DataJoin, |
|
encode: Encode, |
|
legendentries: LegendEntries, |
|
linkpath: LinkPath, |
|
pie: Pie, |
|
scale: Scale, |
|
sortitems: SortItems, |
|
stack: Stack, |
|
validTicks: validTicks |
|
}); |
|
|
|
function noop$2() {} |
|
|
|
var cases = [[], [[[1.0, 1.5], [0.5, 1.0]]], [[[1.5, 1.0], [1.0, 1.5]]], [[[1.5, 1.0], [0.5, 1.0]]], [[[1.0, 0.5], [1.5, 1.0]]], [[[1.0, 1.5], [0.5, 1.0]], [[1.0, 0.5], [1.5, 1.0]]], [[[1.0, 0.5], [1.0, 1.5]]], [[[1.0, 0.5], [0.5, 1.0]]], [[[0.5, 1.0], [1.0, 0.5]]], [[[1.0, 1.5], [1.0, 0.5]]], [[[0.5, 1.0], [1.0, 0.5]], [[1.5, 1.0], [1.0, 1.5]]], [[[1.5, 1.0], [1.0, 0.5]]], [[[0.5, 1.0], [1.5, 1.0]]], [[[1.0, 1.5], [1.5, 1.0]]], [[[0.5, 1.0], [1.0, 1.5]]], []]; // Implementation adapted from d3/d3-contour. Thanks! |
|
|
|
function contours() { |
|
var dx = 1, |
|
dy = 1, |
|
smooth = smoothLinear; |
|
|
|
function contours(values, tz) { |
|
return tz.map(function (value) { |
|
return contour(values, value); |
|
}); |
|
} // Accumulate, smooth contour rings, assign holes to exterior rings. |
|
// Based on https://github.com/mbostock/shapefile/blob/v0.6.2/shp/polygon.js |
|
|
|
|
|
function contour(values, value) { |
|
var polygons = [], |
|
holes = []; |
|
isorings(values, value, function (ring) { |
|
smooth(ring, values, value); |
|
if (area$2(ring) > 0) polygons.push([ring]);else holes.push(ring); |
|
}); |
|
holes.forEach(function (hole) { |
|
for (var i = 0, n = polygons.length, polygon; i < n; ++i) { |
|
if (contains((polygon = polygons[i])[0], hole) !== -1) { |
|
polygon.push(hole); |
|
return; |
|
} |
|
} |
|
}); |
|
return { |
|
type: 'MultiPolygon', |
|
value: value, |
|
coordinates: polygons |
|
}; |
|
} // Marching squares with isolines stitched into rings. |
|
// Based on https://github.com/topojson/topojson-client/blob/v3.0.0/src/stitch.js |
|
|
|
|
|
function isorings(values, value, callback) { |
|
var fragmentByStart = new Array(), |
|
fragmentByEnd = new Array(), |
|
x, |
|
y, |
|
t0, |
|
t1, |
|
t2, |
|
t3; // Special case for the first row (y = -1, t2 = t3 = 0). |
|
|
|
x = y = -1; |
|
t1 = values[0] >= value; |
|
cases[t1 << 1].forEach(stitch); |
|
|
|
while (++x < dx - 1) { |
|
t0 = t1, t1 = values[x + 1] >= value; |
|
cases[t0 | t1 << 1].forEach(stitch); |
|
} |
|
|
|
cases[t1 << 0].forEach(stitch); // General case for the intermediate rows. |
|
|
|
while (++y < dy - 1) { |
|
x = -1; |
|
t1 = values[y * dx + dx] >= value; |
|
t2 = values[y * dx] >= value; |
|
cases[t1 << 1 | t2 << 2].forEach(stitch); |
|
|
|
while (++x < dx - 1) { |
|
t0 = t1, t1 = values[y * dx + dx + x + 1] >= value; |
|
t3 = t2, t2 = values[y * dx + x + 1] >= value; |
|
cases[t0 | t1 << 1 | t2 << 2 | t3 << 3].forEach(stitch); |
|
} |
|
|
|
cases[t1 | t2 << 3].forEach(stitch); |
|
} // Special case for the last row (y = dy - 1, t0 = t1 = 0). |
|
|
|
|
|
x = -1; |
|
t2 = values[y * dx] >= value; |
|
cases[t2 << 2].forEach(stitch); |
|
|
|
while (++x < dx - 1) { |
|
t3 = t2, t2 = values[y * dx + x + 1] >= value; |
|
cases[t2 << 2 | t3 << 3].forEach(stitch); |
|
} |
|
|
|
cases[t2 << 3].forEach(stitch); |
|
|
|
function stitch(line) { |
|
var start = [line[0][0] + x, line[0][1] + y], |
|
end = [line[1][0] + x, line[1][1] + y], |
|
startIndex = index(start), |
|
endIndex = index(end), |
|
f, |
|
g; |
|
|
|
if (f = fragmentByEnd[startIndex]) { |
|
if (g = fragmentByStart[endIndex]) { |
|
delete fragmentByEnd[f.end]; |
|
delete fragmentByStart[g.start]; |
|
|
|
if (f === g) { |
|
f.ring.push(end); |
|
callback(f.ring); |
|
} else { |
|
fragmentByStart[f.start] = fragmentByEnd[g.end] = { |
|
start: f.start, |
|
end: g.end, |
|
ring: f.ring.concat(g.ring) |
|
}; |
|
} |
|
} else { |
|
delete fragmentByEnd[f.end]; |
|
f.ring.push(end); |
|
fragmentByEnd[f.end = endIndex] = f; |
|
} |
|
} else if (f = fragmentByStart[endIndex]) { |
|
if (g = fragmentByEnd[startIndex]) { |
|
delete fragmentByStart[f.start]; |
|
delete fragmentByEnd[g.end]; |
|
|
|
if (f === g) { |
|
f.ring.push(end); |
|
callback(f.ring); |
|
} else { |
|
fragmentByStart[g.start] = fragmentByEnd[f.end] = { |
|
start: g.start, |
|
end: f.end, |
|
ring: g.ring.concat(f.ring) |
|
}; |
|
} |
|
} else { |
|
delete fragmentByStart[f.start]; |
|
f.ring.unshift(start); |
|
fragmentByStart[f.start = startIndex] = f; |
|
} |
|
} else { |
|
fragmentByStart[startIndex] = fragmentByEnd[endIndex] = { |
|
start: startIndex, |
|
end: endIndex, |
|
ring: [start, end] |
|
}; |
|
} |
|
} |
|
} |
|
|
|
function index(point) { |
|
return point[0] * 2 + point[1] * (dx + 1) * 4; |
|
} |
|
|
|
function smoothLinear(ring, values, value) { |
|
ring.forEach(function (point) { |
|
var x = point[0], |
|
y = point[1], |
|
xt = x | 0, |
|
yt = y | 0, |
|
v0, |
|
v1 = values[yt * dx + xt]; |
|
|
|
if (x > 0 && x < dx && xt === x) { |
|
v0 = values[yt * dx + xt - 1]; |
|
point[0] = x + (value - v0) / (v1 - v0) - 0.5; |
|
} |
|
|
|
if (y > 0 && y < dy && yt === y) { |
|
v0 = values[(yt - 1) * dx + xt]; |
|
point[1] = y + (value - v0) / (v1 - v0) - 0.5; |
|
} |
|
}); |
|
} |
|
|
|
contours.contour = contour; |
|
|
|
contours.size = function (_) { |
|
if (!arguments.length) return [dx, dy]; |
|
|
|
var _0 = Math.ceil(_[0]), |
|
_1 = Math.ceil(_[1]); |
|
|
|
if (!(_0 > 0) || !(_1 > 0)) error('invalid size'); |
|
return dx = _0, dy = _1, contours; |
|
}; |
|
|
|
contours.smooth = function (_) { |
|
return arguments.length ? (smooth = _ ? smoothLinear : noop$2, contours) : smooth === smoothLinear; |
|
}; |
|
|
|
return contours; |
|
} |
|
|
|
function area$2(ring) { |
|
var i = 0, |
|
n = ring.length, |
|
area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1]; |
|
|
|
while (++i < n) { |
|
area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1]; |
|
} |
|
|
|
return area; |
|
} |
|
|
|
function contains(ring, hole) { |
|
var i = -1, |
|
n = hole.length, |
|
c; |
|
|
|
while (++i < n) { |
|
if (c = ringContains(ring, hole[i])) return c; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
function ringContains(ring, point) { |
|
var x = point[0], |
|
y = point[1], |
|
contains = -1; |
|
|
|
for (var i = 0, n = ring.length, j = n - 1; i < n; j = i++) { |
|
var pi = ring[i], |
|
xi = pi[0], |
|
yi = pi[1], |
|
pj = ring[j], |
|
xj = pj[0], |
|
yj = pj[1]; |
|
if (segmentContains(pi, pj, point)) return 0; |
|
if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) contains = -contains; |
|
} |
|
|
|
return contains; |
|
} |
|
|
|
function segmentContains(a, b, c) { |
|
var i; |
|
return collinear(a, b, c) && within(a[i = +(a[0] === b[0])], c[i], b[i]); |
|
} |
|
|
|
function collinear(a, b, c) { |
|
return (b[0] - a[0]) * (c[1] - a[1]) === (c[0] - a[0]) * (b[1] - a[1]); |
|
} |
|
|
|
function within(p, q, r) { |
|
return p <= q && q <= r || r <= q && q <= p; |
|
} |
|
|
|
function quantize$2(k, nice, zero) { |
|
return function (values) { |
|
var ex = extent(values), |
|
start = zero ? Math.min(ex[0], 0) : ex[0], |
|
stop = ex[1], |
|
span = stop - start, |
|
step = nice ? tickStep(start, stop, k) : span / (k + 1); |
|
return sequence(step, stop, step); |
|
}; |
|
} |
|
/** |
|
* Generate isocontours (level sets) based on input raster grid data. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} [params.field] - The field with raster grid |
|
* data. If unspecified, the tuple itself is interpreted as a raster grid. |
|
* @param {Array<number>} [params.thresholds] - Contour threshold array. If |
|
* specified, the levels, nice, resolve, and zero parameters are ignored. |
|
* @param {number} [params.levels] - The desired number of contour levels. |
|
* @param {boolean} [params.nice] - Boolean flag indicating if the contour |
|
* threshold values should be automatically aligned to "nice" |
|
* human-friendly values. Setting this flag may cause the number of |
|
* thresholds to deviate from the specified levels. |
|
* @param {string} [params.resolve] - The method for resolving thresholds |
|
* across multiple input grids. If 'independent' (the default), threshold |
|
* calculation will be performed separately for each grid. If 'shared', a |
|
* single set of threshold values will be used for all input grids. |
|
* @param {boolean} [params.zero] - Boolean flag indicating if the contour |
|
* threshold values should include zero. |
|
* @param {boolean} [params.smooth] - Boolean flag indicating if the contour |
|
* polygons should be smoothed using linear interpolation. The default is |
|
* true. The parameter is ignored when using density estimation. |
|
* @param {boolean} [params.scale] - Optional numerical value by which to |
|
* scale the output isocontour coordinates. This parameter can be useful |
|
* to scale the contours to match a desired output resolution. |
|
* @param {string} [params.as='contour'] - The output field in which to store |
|
* the generated isocontour data (default 'contour'). |
|
*/ |
|
|
|
|
|
function Isocontour(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Isocontour.Definition = { |
|
"type": "Isocontour", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field" |
|
}, { |
|
"name": "thresholds", |
|
"type": "number", |
|
"array": true |
|
}, { |
|
"name": "levels", |
|
"type": "number" |
|
}, { |
|
"name": "nice", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "resolve", |
|
"type": "enum", |
|
"values": ["shared", "independent"], |
|
"default": "independent" |
|
}, { |
|
"name": "zero", |
|
"type": "boolean", |
|
"default": true |
|
}, { |
|
"name": "smooth", |
|
"type": "boolean", |
|
"default": true |
|
}, { |
|
"name": "scale", |
|
"type": "number", |
|
"expr": true |
|
}, { |
|
"name": "translate", |
|
"type": "number", |
|
"array": true, |
|
"expr": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"null": true, |
|
"default": "contour" |
|
}] |
|
}; |
|
var prototype$13 = inherits(Isocontour, Transform); |
|
|
|
prototype$13.transform = function (_, pulse) { |
|
if (this.value && !pulse.changed() && !_.modified()) { |
|
return pulse.StopPropagation; |
|
} |
|
|
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS), |
|
source = pulse.materialize(pulse.SOURCE).source, |
|
field = _.field || identity, |
|
contour = contours().smooth(_.smooth !== false), |
|
tz = _.thresholds || levels(source, field, _), |
|
as = _.as === null ? null : _.as || 'contour', |
|
values = []; |
|
source.forEach(function (t) { |
|
var grid = field(t); // generate contour paths in GeoJSON format |
|
|
|
var paths = contour.size([grid.width, grid.height])(grid.values, isArray(tz) ? tz : tz(grid.values)); // adjust contour path coordinates as needed |
|
|
|
transformPaths(paths, grid, t, _); // ingest; copy source data properties to output |
|
|
|
paths.forEach(function (p) { |
|
values.push(rederive(t, ingest(as != null ? _defineProperty({}, as, p) : p))); |
|
}); |
|
}); |
|
if (this.value) out.rem = this.value; |
|
this.value = out.source = out.add = values; |
|
return out; |
|
}; |
|
|
|
function levels(values, f, _) { |
|
var q = quantize$2(_.levels || 10, _.nice, _.zero !== false); |
|
return _.resolve !== 'shared' ? q : q(values.map(function (t) { |
|
return max(f(t).values); |
|
})); |
|
} |
|
|
|
function transformPaths(paths, grid, datum, _) { |
|
var s = _.scale || grid.scale, |
|
t = _.translate || grid.translate; |
|
if (isFunction(s)) s = s(datum, _); |
|
if (isFunction(t)) t = t(datum, _); |
|
if ((s === 1 || s == null) && !t) return; |
|
var sx = (isNumber(s) ? s : s[0]) || 1, |
|
sy = (isNumber(s) ? s : s[1]) || 1, |
|
tx = t && t[0] || 0, |
|
ty = t && t[1] || 0; |
|
paths.forEach(transform$2(grid, sx, sy, tx, ty)); |
|
} |
|
|
|
function transform$2(grid, sx, sy, tx, ty) { |
|
var x1 = grid.x1 || 0, |
|
y1 = grid.y1 || 0, |
|
flip = sx * sy < 0; |
|
|
|
function transformPolygon(coordinates) { |
|
coordinates.forEach(transformRing); |
|
} |
|
|
|
function transformRing(coordinates) { |
|
if (flip) coordinates.reverse(); // maintain winding order |
|
|
|
coordinates.forEach(transformPoint); |
|
} |
|
|
|
function transformPoint(coordinates) { |
|
coordinates[0] = (coordinates[0] - x1) * sx + tx; |
|
coordinates[1] = (coordinates[1] - y1) * sy + ty; |
|
} |
|
|
|
return function (geometry) { |
|
geometry.coordinates.forEach(transformPolygon); |
|
return geometry; |
|
}; |
|
} |
|
|
|
function radius(bw, data, f) { |
|
var v = bw >= 0 ? bw : bandwidthNRD(data, f); |
|
return Math.round((Math.sqrt(4 * v * v + 1) - 1) / 2); |
|
} |
|
|
|
function number$4(_) { |
|
return isFunction(_) ? _ : constant(+_); |
|
} // Implementation adapted from d3/d3-contour. Thanks! |
|
|
|
|
|
function density2D() { |
|
var x = function x(d) { |
|
return d[0]; |
|
}, |
|
y = function y(d) { |
|
return d[1]; |
|
}, |
|
weight = one, |
|
bandwidth = [-1, -1], |
|
dx = 960, |
|
dy = 500, |
|
k = 2; // log2(cellSize) |
|
|
|
|
|
function density(data, counts) { |
|
var rx = radius(bandwidth[0], data, x) >> k, |
|
// blur x-radius |
|
ry = radius(bandwidth[1], data, y) >> k, |
|
// blur y-radius |
|
ox = rx ? rx + 2 : 0, |
|
// x-offset padding for blur |
|
oy = ry ? ry + 2 : 0, |
|
// y-offset padding for blur |
|
n = 2 * ox + (dx >> k), |
|
// grid width |
|
m = 2 * oy + (dy >> k), |
|
// grid height |
|
values0 = new Float32Array(n * m), |
|
values1 = new Float32Array(n * m); |
|
var values = values0; |
|
data.forEach(function (d) { |
|
var xi = ox + (+x(d) >> k), |
|
yi = oy + (+y(d) >> k); |
|
|
|
if (xi >= 0 && xi < n && yi >= 0 && yi < m) { |
|
values0[xi + yi * n] += +weight(d); |
|
} |
|
}); |
|
|
|
if (rx > 0 && ry > 0) { |
|
blurX(n, m, values0, values1, rx); |
|
blurY(n, m, values1, values0, ry); |
|
blurX(n, m, values0, values1, rx); |
|
blurY(n, m, values1, values0, ry); |
|
blurX(n, m, values0, values1, rx); |
|
blurY(n, m, values1, values0, ry); |
|
} else if (rx > 0) { |
|
blurX(n, m, values0, values1, rx); |
|
blurX(n, m, values1, values0, rx); |
|
blurX(n, m, values0, values1, rx); |
|
values = values1; |
|
} else if (ry > 0) { |
|
blurY(n, m, values0, values1, ry); |
|
blurY(n, m, values1, values0, ry); |
|
blurY(n, m, values0, values1, ry); |
|
values = values1; |
|
} // scale density estimates |
|
// density in points per square pixel or probability density |
|
|
|
|
|
var s = counts ? Math.pow(2, -2 * k) : 1 / sum(values); |
|
|
|
for (var i = 0, _sz = n * m; i < _sz; ++i) { |
|
values[i] *= s; |
|
} |
|
|
|
return { |
|
values: values, |
|
scale: 1 << k, |
|
width: n, |
|
height: m, |
|
x1: ox, |
|
y1: oy, |
|
x2: ox + (dx >> k), |
|
y2: oy + (dy >> k) |
|
}; |
|
} |
|
|
|
density.x = function (_) { |
|
return arguments.length ? (x = number$4(_), density) : x; |
|
}; |
|
|
|
density.y = function (_) { |
|
return arguments.length ? (y = number$4(_), density) : y; |
|
}; |
|
|
|
density.weight = function (_) { |
|
return arguments.length ? (weight = number$4(_), density) : weight; |
|
}; |
|
|
|
density.size = function (_) { |
|
if (!arguments.length) return [dx, dy]; |
|
|
|
var _0 = Math.ceil(_[0]), |
|
_1 = Math.ceil(_[1]); |
|
|
|
if (!(_0 >= 0) && !(_0 >= 0)) error('invalid size'); |
|
return dx = _0, dy = _1, density; |
|
}; |
|
|
|
density.cellSize = function (_) { |
|
if (!arguments.length) return 1 << k; |
|
if (!((_ = +_) >= 1)) error('invalid cell size'); |
|
k = Math.floor(Math.log(_) / Math.LN2); |
|
return density; |
|
}; |
|
|
|
density.bandwidth = function (_) { |
|
if (!arguments.length) return bandwidth; |
|
_ = array(_); |
|
if (_.length === 1) _ = [+_[0], +_[0]]; |
|
if (_.length !== 2) error('invalid bandwidth'); |
|
return bandwidth = _, density; |
|
}; |
|
|
|
return density; |
|
} |
|
|
|
function blurX(n, m, source, target, r) { |
|
var w = (r << 1) + 1; |
|
|
|
for (var j = 0; j < m; ++j) { |
|
for (var i = 0, sr = 0; i < n + r; ++i) { |
|
if (i < n) { |
|
sr += source[i + j * n]; |
|
} |
|
|
|
if (i >= r) { |
|
if (i >= w) { |
|
sr -= source[i - w + j * n]; |
|
} |
|
|
|
target[i - r + j * n] = sr / Math.min(i + 1, n - 1 + w - i, w); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function blurY(n, m, source, target, r) { |
|
var w = (r << 1) + 1; |
|
|
|
for (var i = 0; i < n; ++i) { |
|
for (var j = 0, sr = 0; j < m + r; ++j) { |
|
if (j < m) { |
|
sr += source[i + j * n]; |
|
} |
|
|
|
if (j >= r) { |
|
if (j >= w) { |
|
sr -= source[i + (j - w) * n]; |
|
} |
|
|
|
target[i + (j - r) * n] = sr / Math.min(j + 1, m - 1 + w - j, w); |
|
} |
|
} |
|
} |
|
} |
|
/** |
|
* Perform 2D kernel-density estimation of point data. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<number>} params.size - The [width, height] extent (in |
|
* units of input pixels) over which to perform density estimation. |
|
* @param {function(object): number} params.x - The x-coordinate accessor. |
|
* @param {function(object): number} params.y - The y-coordinate accessor. |
|
* @param {function(object): number} [params.weight] - The weight accessor. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors |
|
* to groupby. |
|
* @param {number} [params.cellSize] - Contour density calculation cell size. |
|
* This parameter determines the level of spatial approximation. For example, |
|
* the default value of 4 maps to 2x reductions in both x- and y- dimensions. |
|
* A value of 1 will result in an output raster grid whose dimensions exactly |
|
* matches the size parameter. |
|
* @param {Array<number>} [params.bandwidth] - The KDE kernel bandwidths, |
|
* in pixels. The input can be a two-element array specifying separate |
|
* x and y bandwidths, or a single-element array specifying both. If the |
|
* bandwidth is unspecified or less than zero, the bandwidth will be |
|
* automatically determined. |
|
* @param {boolean} [params.counts=false] - A boolean flag indicating if the |
|
* output values should be probability estimates (false, default) or |
|
* smoothed counts (true). |
|
* @param {string} [params.as='grid'] - The output field in which to store |
|
* the generated raster grid (default 'grid'). |
|
*/ |
|
|
|
|
|
function KDE2D(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
KDE2D.Definition = { |
|
"type": "KDE2D", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "size", |
|
"type": "number", |
|
"array": true, |
|
"length": 2, |
|
"required": true |
|
}, { |
|
"name": "x", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "y", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "weight", |
|
"type": "field" |
|
}, { |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "cellSize", |
|
"type": "number" |
|
}, { |
|
"name": "bandwidth", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "counts", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"default": "grid" |
|
}] |
|
}; |
|
var prototype$14 = inherits(KDE2D, Transform); |
|
var PARAMS = ['x', 'y', 'weight', 'size', 'cellSize', 'bandwidth']; |
|
|
|
function params(obj, _) { |
|
PARAMS.forEach(function (param) { |
|
return _[param] != null ? obj[param](_[param]) : 0; |
|
}); |
|
return obj; |
|
} |
|
|
|
prototype$14.transform = function (_, pulse) { |
|
if (this.value && !pulse.changed() && !_.modified()) return pulse.StopPropagation; |
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS), |
|
source = pulse.materialize(pulse.SOURCE).source, |
|
groups = partition$3(source, _.groupby), |
|
names = (_.groupby || []).map(accessorName), |
|
kde = params(density2D(), _), |
|
as = _.as || 'grid', |
|
values = []; |
|
|
|
function set(t, vals) { |
|
for (var i = 0; i < names.length; ++i) { |
|
t[names[i]] = vals[i]; |
|
} |
|
|
|
return t; |
|
} // generate density raster grids |
|
|
|
|
|
values = groups.map(function (g) { |
|
return ingest(set(_defineProperty({}, as, kde(g, _.counts)), g.dims)); |
|
}); |
|
if (this.value) out.rem = this.value; |
|
this.value = out.source = out.add = values; |
|
return out; |
|
}; |
|
|
|
function partition$3(data, groupby) { |
|
var groups = [], |
|
get = function get(f) { |
|
return f(t); |
|
}, |
|
map, |
|
i, |
|
n, |
|
t, |
|
k, |
|
g; // partition data points into groups |
|
|
|
|
|
if (groupby == null) { |
|
groups.push(data); |
|
} else { |
|
for (map = {}, i = 0, n = data.length; i < n; ++i) { |
|
t = data[i]; |
|
k = groupby.map(get); |
|
g = map[k]; |
|
|
|
if (!g) { |
|
map[k] = g = []; |
|
g.dims = k; |
|
groups.push(g); |
|
} |
|
|
|
g.push(t); |
|
} |
|
} |
|
|
|
return groups; |
|
} |
|
/** |
|
* Generate contours based on kernel-density estimation of point data. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<number>} params.size - The dimensions [width, height] over which to compute contours. |
|
* If the values parameter is provided, this must be the dimensions of the input data. |
|
* If density estimation is performed, this is the output view dimensions in pixels. |
|
* @param {Array<number>} [params.values] - An array of numeric values representing an |
|
* width x height grid of values over which to compute contours. If unspecified, this |
|
* transform will instead attempt to compute contours for the kernel density estimate |
|
* using values drawn from data tuples in the input pulse. |
|
* @param {function(object): number} [params.x] - The pixel x-coordinate accessor for density estimation. |
|
* @param {function(object): number} [params.y] - The pixel y-coordinate accessor for density estimation. |
|
* @param {function(object): number} [params.weight] - The data point weight accessor for density estimation. |
|
* @param {number} [params.cellSize] - Contour density calculation cell size. |
|
* @param {number} [params.bandwidth] - Kernel density estimation bandwidth. |
|
* @param {Array<number>} [params.thresholds] - Contour threshold array. If |
|
* this parameter is set, the count and nice parameters will be ignored. |
|
* @param {number} [params.count] - The desired number of contours. |
|
* @param {boolean} [params.nice] - Boolean flag indicating if the contour |
|
* threshold values should be automatically aligned to "nice" |
|
* human-friendly values. Setting this flag may cause the number of |
|
* thresholds to deviate from the specified count. |
|
* @param {boolean} [params.smooth] - Boolean flag indicating if the contour |
|
* polygons should be smoothed using linear interpolation. The default is |
|
* true. The parameter is ignored when using density estimation. |
|
*/ |
|
|
|
|
|
function Contour(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Contour.Definition = { |
|
"type": "Contour", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "size", |
|
"type": "number", |
|
"array": true, |
|
"length": 2, |
|
"required": true |
|
}, { |
|
"name": "values", |
|
"type": "number", |
|
"array": true |
|
}, { |
|
"name": "x", |
|
"type": "field" |
|
}, { |
|
"name": "y", |
|
"type": "field" |
|
}, { |
|
"name": "weight", |
|
"type": "field" |
|
}, { |
|
"name": "cellSize", |
|
"type": "number" |
|
}, { |
|
"name": "bandwidth", |
|
"type": "number" |
|
}, { |
|
"name": "count", |
|
"type": "number" |
|
}, { |
|
"name": "nice", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "thresholds", |
|
"type": "number", |
|
"array": true |
|
}, { |
|
"name": "smooth", |
|
"type": "boolean", |
|
"default": true |
|
}] |
|
}; |
|
var prototype$15 = inherits(Contour, Transform); |
|
|
|
prototype$15.transform = function (_, pulse) { |
|
if (this.value && !pulse.changed() && !_.modified()) { |
|
return pulse.StopPropagation; |
|
} |
|
|
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS), |
|
contour = contours().smooth(_.smooth !== false), |
|
values = _.values, |
|
thresh = _.thresholds || quantize$2(_.count || 10, _.nice, !!values), |
|
size = _.size, |
|
grid, |
|
post; |
|
|
|
if (!values) { |
|
values = pulse.materialize(pulse.SOURCE).source; |
|
grid = params(density2D(), _)(values, true); |
|
post = transform$2(grid, grid.scale || 1, grid.scale || 1, 0, 0); |
|
size = [grid.width, grid.height]; |
|
values = grid.values; |
|
} |
|
|
|
thresh = isArray(thresh) ? thresh : thresh(values); |
|
values = contour.size(size)(values, thresh); |
|
if (post) values.forEach(post); |
|
if (this.value) out.rem = this.value; |
|
this.value = out.source = out.add = (values || []).map(ingest); |
|
return out; |
|
}; |
|
|
|
var Feature = 'Feature'; |
|
var FeatureCollection = 'FeatureCollection'; |
|
var MultiPoint = 'MultiPoint'; |
|
/** |
|
* Consolidate an array of [longitude, latitude] points or GeoJSON features |
|
* into a combined GeoJSON object. This transform is particularly useful for |
|
* combining geo data for a Projection's fit argument. The resulting GeoJSON |
|
* data is available as this transform's value. Input pulses are unchanged. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<function(object): *>} [params.fields] - A two-element array |
|
* of field accessors for the longitude and latitude values. |
|
* @param {function(object): *} params.geojson - A field accessor for |
|
* retrieving GeoJSON feature data. |
|
*/ |
|
|
|
function GeoJSON(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
GeoJSON.Definition = { |
|
"type": "GeoJSON", |
|
"metadata": {}, |
|
"params": [{ |
|
"name": "fields", |
|
"type": "field", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "geojson", |
|
"type": "field" |
|
}] |
|
}; |
|
var prototype$16 = inherits(GeoJSON, Transform); |
|
|
|
prototype$16.transform = function (_, pulse) { |
|
var features = this._features, |
|
points = this._points, |
|
fields = _.fields, |
|
lon = fields && fields[0], |
|
lat = fields && fields[1], |
|
geojson = _.geojson || !fields && identity, |
|
flag = pulse.ADD, |
|
mod; |
|
mod = _.modified() || pulse.changed(pulse.REM) || pulse.modified(accessorFields(geojson)) || lon && pulse.modified(accessorFields(lon)) || lat && pulse.modified(accessorFields(lat)); |
|
|
|
if (!this.value || mod) { |
|
flag = pulse.SOURCE; |
|
this._features = features = []; |
|
this._points = points = []; |
|
} |
|
|
|
if (geojson) { |
|
pulse.visit(flag, function (t) { |
|
features.push(geojson(t)); |
|
}); |
|
} |
|
|
|
if (lon && lat) { |
|
pulse.visit(flag, function (t) { |
|
var x = lon(t), |
|
y = lat(t); |
|
|
|
if (x != null && y != null && (x = +x) === x && (y = +y) === y) { |
|
points.push([x, y]); |
|
} |
|
}); |
|
features = features.concat({ |
|
type: Feature, |
|
geometry: { |
|
type: MultiPoint, |
|
coordinates: points |
|
} |
|
}); |
|
} |
|
|
|
this.value = { |
|
type: FeatureCollection, |
|
features: features |
|
}; |
|
}; // Adds floating point numbers with twice the normal precision. |
|
// Reference: J. R. Shewchuk, Adaptive Precision Floating-Point Arithmetic and |
|
// Fast Robust Geometric Predicates, Discrete & Computational Geometry 18(3) |
|
// 305–363 (1997). |
|
// Code adapted from GeographicLib by Charles F. F. Karney, |
|
// http://geographiclib.sourceforge.net/ |
|
|
|
|
|
function adder() { |
|
return new Adder(); |
|
} |
|
|
|
function Adder() { |
|
this.reset(); |
|
} |
|
|
|
Adder.prototype = { |
|
constructor: Adder, |
|
reset: function reset() { |
|
this.s = // rounded value |
|
this.t = 0; // exact error |
|
}, |
|
add: function add(y) { |
|
add$2(temp$1, y, this.t); |
|
add$2(this, temp$1.s, this.s); |
|
if (this.s) this.t += temp$1.t;else this.s = temp$1.t; |
|
}, |
|
valueOf: function valueOf() { |
|
return this.s; |
|
} |
|
}; |
|
var temp$1 = new Adder(); |
|
|
|
function add$2(adder, a, b) { |
|
var x = adder.s = a + b, |
|
bv = x - a, |
|
av = x - bv; |
|
adder.t = a - av + (b - bv); |
|
} |
|
|
|
var epsilon$3 = 1e-6; |
|
var epsilon2$1 = 1e-12; |
|
var pi$2 = Math.PI; |
|
var halfPi$1 = pi$2 / 2; |
|
var quarterPi = pi$2 / 4; |
|
var tau$2 = pi$2 * 2; |
|
var degrees$1 = 180 / pi$2; |
|
var radians = pi$2 / 180; |
|
var abs$1 = Math.abs; |
|
var atan = Math.atan; |
|
var atan2$1 = Math.atan2; |
|
var cos$1 = Math.cos; |
|
var ceil = Math.ceil; |
|
var exp$1 = Math.exp; |
|
var log$3 = Math.log; |
|
var pow$2 = Math.pow; |
|
var sin$1 = Math.sin; |
|
|
|
var sign$1 = Math.sign || function (x) { |
|
return x > 0 ? 1 : x < 0 ? -1 : 0; |
|
}; |
|
|
|
var sqrt$2 = Math.sqrt; |
|
var tan = Math.tan; |
|
|
|
function acos$1(x) { |
|
return x > 1 ? 0 : x < -1 ? pi$2 : Math.acos(x); |
|
} |
|
|
|
function asin$1(x) { |
|
return x > 1 ? halfPi$1 : x < -1 ? -halfPi$1 : Math.asin(x); |
|
} |
|
|
|
function noop$3() {} |
|
|
|
function streamGeometry(geometry, stream) { |
|
if (geometry && streamGeometryType.hasOwnProperty(geometry.type)) { |
|
streamGeometryType[geometry.type](geometry, stream); |
|
} |
|
} |
|
|
|
var streamObjectType = { |
|
Feature: function Feature(object, stream) { |
|
streamGeometry(object.geometry, stream); |
|
}, |
|
FeatureCollection: function FeatureCollection(object, stream) { |
|
var features = object.features, |
|
i = -1, |
|
n = features.length; |
|
|
|
while (++i < n) { |
|
streamGeometry(features[i].geometry, stream); |
|
} |
|
} |
|
}; |
|
var streamGeometryType = { |
|
Sphere: function Sphere(object, stream) { |
|
stream.sphere(); |
|
}, |
|
Point: function Point(object, stream) { |
|
object = object.coordinates; |
|
stream.point(object[0], object[1], object[2]); |
|
}, |
|
MultiPoint: function MultiPoint(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 LineString(object, stream) { |
|
streamLine(object.coordinates, stream, 0); |
|
}, |
|
MultiLineString: function MultiLineString(object, stream) { |
|
var coordinates = object.coordinates, |
|
i = -1, |
|
n = coordinates.length; |
|
|
|
while (++i < n) { |
|
streamLine(coordinates[i], stream, 0); |
|
} |
|
}, |
|
Polygon: function Polygon(object, stream) { |
|
streamPolygon(object.coordinates, stream); |
|
}, |
|
MultiPolygon: function MultiPolygon(object, stream) { |
|
var coordinates = object.coordinates, |
|
i = -1, |
|
n = coordinates.length; |
|
|
|
while (++i < n) { |
|
streamPolygon(coordinates[i], stream); |
|
} |
|
}, |
|
GeometryCollection: function GeometryCollection(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 = adder(); |
|
var areaSum = adder(), |
|
lambda00, |
|
phi00, |
|
lambda0, |
|
cosPhi0, |
|
sinPhi0; |
|
var areaStream = { |
|
point: noop$3, |
|
lineStart: noop$3, |
|
lineEnd: noop$3, |
|
polygonStart: function polygonStart() { |
|
areaRingSum.reset(); |
|
areaStream.lineStart = areaRingStart; |
|
areaStream.lineEnd = areaRingEnd; |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
var areaRing = +areaRingSum; |
|
areaSum.add(areaRing < 0 ? tau$2 + areaRing : areaRing); |
|
this.lineStart = this.lineEnd = this.point = noop$3; |
|
}, |
|
sphere: function sphere() { |
|
areaSum.add(tau$2); |
|
} |
|
}; |
|
|
|
function areaRingStart() { |
|
areaStream.point = areaPointFirst; |
|
} |
|
|
|
function areaRingEnd() { |
|
areaPoint(lambda00, phi00); |
|
} |
|
|
|
function areaPointFirst(lambda, phi) { |
|
areaStream.point = areaPoint; |
|
lambda00 = lambda, phi00 = phi; |
|
lambda *= radians, phi *= radians; |
|
lambda0 = lambda, cosPhi0 = cos$1(phi = phi / 2 + quarterPi), sinPhi0 = sin$1(phi); |
|
} |
|
|
|
function areaPoint(lambda, phi) { |
|
lambda *= radians, phi *= radians; |
|
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, |
|
sdLambda = dLambda >= 0 ? 1 : -1, |
|
adLambda = sdLambda * dLambda, |
|
cosPhi = cos$1(phi), |
|
sinPhi = sin$1(phi), |
|
k = sinPhi0 * sinPhi, |
|
u = cosPhi0 * cosPhi + k * cos$1(adLambda), |
|
v = k * sdLambda * sin$1(adLambda); |
|
areaRingSum.add(atan2$1(v, u)); // Advance the previous points. |
|
|
|
lambda0 = lambda, cosPhi0 = cosPhi, sinPhi0 = sinPhi; |
|
} |
|
|
|
function area$3(object) { |
|
areaSum.reset(); |
|
geoStream(object, areaStream); |
|
return areaSum * 2; |
|
} |
|
|
|
function spherical(cartesian) { |
|
return [atan2$1(cartesian[1], cartesian[0]), asin$1(cartesian[2])]; |
|
} |
|
|
|
function cartesian(spherical) { |
|
var lambda = spherical[0], |
|
phi = spherical[1], |
|
cosPhi = cos$1(phi); |
|
return [cosPhi * cos$1(lambda), cosPhi * sin$1(lambda), sin$1(phi)]; |
|
} |
|
|
|
function cartesianDot(a, b) { |
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; |
|
} |
|
|
|
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 a |
|
|
|
|
|
function cartesianAddInPlace(a, b) { |
|
a[0] += b[0], a[1] += b[1], a[2] += b[2]; |
|
} |
|
|
|
function cartesianScale(vector, k) { |
|
return [vector[0] * k, vector[1] * k, vector[2] * k]; |
|
} // TODO return d |
|
|
|
|
|
function cartesianNormalizeInPlace(d) { |
|
var l = sqrt$2(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); |
|
d[0] /= l, d[1] /= l, d[2] /= l; |
|
} |
|
|
|
var lambda0$1, |
|
phi0, |
|
lambda1, |
|
phi1, |
|
// bounds |
|
lambda2, |
|
// previous lambda-coordinate |
|
lambda00$1, |
|
phi00$1, |
|
// first point |
|
p0, |
|
// previous 3D point |
|
deltaSum = adder(), |
|
ranges, |
|
range; |
|
var boundsStream = { |
|
point: boundsPoint, |
|
lineStart: boundsLineStart, |
|
lineEnd: boundsLineEnd, |
|
polygonStart: function polygonStart() { |
|
boundsStream.point = boundsRingPoint; |
|
boundsStream.lineStart = boundsRingStart; |
|
boundsStream.lineEnd = boundsRingEnd; |
|
deltaSum.reset(); |
|
areaStream.polygonStart(); |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
areaStream.polygonEnd(); |
|
boundsStream.point = boundsPoint; |
|
boundsStream.lineStart = boundsLineStart; |
|
boundsStream.lineEnd = boundsLineEnd; |
|
if (areaRingSum < 0) lambda0$1 = -(lambda1 = 180), phi0 = -(phi1 = 90);else if (deltaSum > epsilon$3) phi1 = 90;else if (deltaSum < -epsilon$3) phi0 = -90; |
|
range[0] = lambda0$1, range[1] = lambda1; |
|
}, |
|
sphere: function sphere() { |
|
lambda0$1 = -(lambda1 = 180), phi0 = -(phi1 = 90); |
|
} |
|
}; |
|
|
|
function boundsPoint(lambda, phi) { |
|
ranges.push(range = [lambda0$1 = lambda, lambda1 = lambda]); |
|
if (phi < phi0) phi0 = phi; |
|
if (phi > phi1) phi1 = phi; |
|
} |
|
|
|
function linePoint(lambda, phi) { |
|
var p = cartesian([lambda * radians, phi * radians]); |
|
|
|
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$1(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$1, lambda) > angle(lambda0$1, lambda1)) lambda1 = lambda; |
|
} else { |
|
if (angle(lambda, lambda1) > angle(lambda0$1, lambda1)) lambda0$1 = lambda; |
|
} |
|
} else { |
|
if (lambda1 >= lambda0$1) { |
|
if (lambda < lambda0$1) lambda0$1 = lambda; |
|
if (lambda > lambda1) lambda1 = lambda; |
|
} else { |
|
if (lambda > lambda2) { |
|
if (angle(lambda0$1, lambda) > angle(lambda0$1, lambda1)) lambda1 = lambda; |
|
} else { |
|
if (angle(lambda, lambda1) > angle(lambda0$1, lambda1)) lambda0$1 = lambda; |
|
} |
|
} |
|
} |
|
} else { |
|
ranges.push(range = [lambda0$1 = 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[0] = lambda0$1, range[1] = lambda1; |
|
boundsStream.point = boundsPoint; |
|
p0 = null; |
|
} |
|
|
|
function boundsRingPoint(lambda, phi) { |
|
if (p0) { |
|
var delta = lambda - lambda2; |
|
deltaSum.add(abs$1(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$1(deltaSum) > epsilon$3) lambda0$1 = -(lambda1 = 180); |
|
range[0] = lambda0$1, range[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$1(feature) { |
|
var i, n, a, b, merged, deltaMax, delta; |
|
phi1 = lambda1 = -(lambda0$1 = 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$1 = b[0], lambda1 = a[1]; |
|
} |
|
} |
|
|
|
ranges = range = null; |
|
return lambda0$1 === Infinity || phi0 === Infinity ? [[NaN, NaN], [NaN, NaN]] : [[lambda0$1, phi0], [lambda1, phi1]]; |
|
} |
|
|
|
var W0, W1, X0, Y0, Z0, X1, Y1, Z1, X2, Y2, Z2, lambda00$2, phi00$2, // first point |
|
x0, y0, z0; // previous point |
|
|
|
var centroidStream = { |
|
sphere: noop$3, |
|
point: centroidPoint, |
|
lineStart: centroidLineStart, |
|
lineEnd: centroidLineEnd, |
|
polygonStart: function polygonStart() { |
|
centroidStream.lineStart = centroidRingStart; |
|
centroidStream.lineEnd = centroidRingEnd; |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
centroidStream.lineStart = centroidLineStart; |
|
centroidStream.lineEnd = centroidLineEnd; |
|
} |
|
}; // Arithmetic mean of Cartesian vectors. |
|
|
|
function centroidPoint(lambda, phi) { |
|
lambda *= radians, phi *= radians; |
|
var cosPhi = cos$1(phi); |
|
centroidPointCartesian(cosPhi * cos$1(lambda), cosPhi * sin$1(lambda), sin$1(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, phi *= radians; |
|
var cosPhi = cos$1(phi); |
|
x0 = cosPhi * cos$1(lambda); |
|
y0 = cosPhi * sin$1(lambda); |
|
z0 = sin$1(phi); |
|
centroidStream.point = centroidLinePoint; |
|
centroidPointCartesian(x0, y0, z0); |
|
} |
|
|
|
function centroidLinePoint(lambda, phi) { |
|
lambda *= radians, phi *= radians; |
|
var cosPhi = cos$1(phi), |
|
x = cosPhi * cos$1(lambda), |
|
y = cosPhi * sin$1(lambda), |
|
z = sin$1(phi), |
|
w = atan2$1(sqrt$2((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$2, phi00$2); |
|
centroidStream.point = centroidPoint; |
|
} |
|
|
|
function centroidRingPointFirst(lambda, phi) { |
|
lambda00$2 = lambda, phi00$2 = phi; |
|
lambda *= radians, phi *= radians; |
|
centroidStream.point = centroidRingPoint; |
|
var cosPhi = cos$1(phi); |
|
x0 = cosPhi * cos$1(lambda); |
|
y0 = cosPhi * sin$1(lambda); |
|
z0 = sin$1(phi); |
|
centroidPointCartesian(x0, y0, z0); |
|
} |
|
|
|
function centroidRingPoint(lambda, phi) { |
|
lambda *= radians, phi *= radians; |
|
var cosPhi = cos$1(phi), |
|
x = cosPhi * cos$1(lambda), |
|
y = cosPhi * sin$1(lambda), |
|
z = sin$1(phi), |
|
cx = y0 * z - z0 * y, |
|
cy = z0 * x - x0 * z, |
|
cz = x0 * y - y0 * x, |
|
m = sqrt$2(cx * cx + cy * cy + cz * cz), |
|
w = asin$1(m), |
|
// line weight = angle |
|
v = m && -w / m; // area weight multiplier |
|
|
|
X2 += v * cx; |
|
Y2 += v * cy; |
|
Z2 += 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 = X2 = Y2 = Z2 = 0; |
|
geoStream(object, centroidStream); |
|
var x = X2, |
|
y = Y2, |
|
z = Z2, |
|
m = x * x + y * y + z * z; // If the area-weighted ccentroid is undefined, fall back to length-weighted ccentroid. |
|
|
|
if (m < epsilon2$1) { |
|
x = X1, y = Y1, z = Z1; // If the feature has zero length, fall back to arithmetic mean of point vectors. |
|
|
|
if (W1 < epsilon$3) x = X0, y = Y0, z = Z0; |
|
m = x * x + y * y + z * z; // If the feature still has an undefined ccentroid, then return. |
|
|
|
if (m < epsilon2$1) return [NaN, NaN]; |
|
} |
|
|
|
return [atan2$1(y, x) * degrees$1, asin$1(z / sqrt$2(m)) * degrees$1]; |
|
} |
|
|
|
function compose(a, b) { |
|
function compose(x, y) { |
|
return x = a(x, y), b(x[0], x[1]); |
|
} |
|
|
|
if (a.invert && b.invert) compose.invert = function (x, y) { |
|
return x = b.invert(x, y), x && a.invert(x[0], x[1]); |
|
}; |
|
return compose; |
|
} |
|
|
|
function rotationIdentity(lambda, phi) { |
|
return [abs$1(lambda) > pi$2 ? lambda + Math.round(-lambda / tau$2) * tau$2 : lambda, phi]; |
|
} |
|
|
|
rotationIdentity.invert = rotationIdentity; |
|
|
|
function rotateRadians(deltaLambda, deltaPhi, deltaGamma) { |
|
return (deltaLambda %= tau$2) ? deltaPhi || deltaGamma ? compose(rotationLambda(deltaLambda), rotationPhiGamma(deltaPhi, deltaGamma)) : rotationLambda(deltaLambda) : deltaPhi || deltaGamma ? rotationPhiGamma(deltaPhi, deltaGamma) : rotationIdentity; |
|
} |
|
|
|
function forwardRotationLambda(deltaLambda) { |
|
return function (lambda, phi) { |
|
return lambda += deltaLambda, [lambda > pi$2 ? lambda - tau$2 : lambda < -pi$2 ? lambda + tau$2 : lambda, phi]; |
|
}; |
|
} |
|
|
|
function rotationLambda(deltaLambda) { |
|
var rotation = forwardRotationLambda(deltaLambda); |
|
rotation.invert = forwardRotationLambda(-deltaLambda); |
|
return rotation; |
|
} |
|
|
|
function rotationPhiGamma(deltaPhi, deltaGamma) { |
|
var cosDeltaPhi = cos$1(deltaPhi), |
|
sinDeltaPhi = sin$1(deltaPhi), |
|
cosDeltaGamma = cos$1(deltaGamma), |
|
sinDeltaGamma = sin$1(deltaGamma); |
|
|
|
function rotation(lambda, phi) { |
|
var cosPhi = cos$1(phi), |
|
x = cos$1(lambda) * cosPhi, |
|
y = sin$1(lambda) * cosPhi, |
|
z = sin$1(phi), |
|
k = z * cosDeltaPhi + x * sinDeltaPhi; |
|
return [atan2$1(y * cosDeltaGamma - k * sinDeltaGamma, x * cosDeltaPhi - z * sinDeltaPhi), asin$1(k * cosDeltaGamma + y * sinDeltaGamma)]; |
|
} |
|
|
|
rotation.invert = function (lambda, phi) { |
|
var cosPhi = cos$1(phi), |
|
x = cos$1(lambda) * cosPhi, |
|
y = sin$1(lambda) * cosPhi, |
|
z = sin$1(phi), |
|
k = z * cosDeltaGamma - y * sinDeltaGamma; |
|
return [atan2$1(y * cosDeltaGamma + z * sinDeltaGamma, x * cosDeltaPhi + k * sinDeltaPhi), asin$1(k * cosDeltaPhi - x * sinDeltaPhi)]; |
|
}; |
|
|
|
return rotation; |
|
} |
|
|
|
function rotation(rotate) { |
|
rotate = rotateRadians(rotate[0] * radians, rotate[1] * radians, rotate.length > 2 ? rotate[2] * radians : 0); |
|
|
|
function forward(coordinates) { |
|
coordinates = rotate(coordinates[0] * radians, coordinates[1] * radians); |
|
return coordinates[0] *= degrees$1, coordinates[1] *= degrees$1, coordinates; |
|
} |
|
|
|
forward.invert = function (coordinates) { |
|
coordinates = rotate.invert(coordinates[0] * radians, coordinates[1] * radians); |
|
return coordinates[0] *= degrees$1, coordinates[1] *= degrees$1, coordinates; |
|
}; |
|
|
|
return forward; |
|
} // Generates a circle centered at [0°, 0°], with a given radius and precision. |
|
|
|
|
|
function circleStream(stream, radius, delta, direction, t0, t1) { |
|
if (!delta) return; |
|
var cosRadius = cos$1(radius), |
|
sinRadius = sin$1(radius), |
|
step = direction * delta; |
|
|
|
if (t0 == null) { |
|
t0 = radius + direction * tau$2; |
|
t1 = radius - step / 2; |
|
} else { |
|
t0 = circleRadius(cosRadius, t0); |
|
t1 = circleRadius(cosRadius, t1); |
|
if (direction > 0 ? t0 < t1 : t0 > t1) t0 += direction * tau$2; |
|
} |
|
|
|
for (var point, t = t0; direction > 0 ? t > t1 : t < t1; t -= step) { |
|
point = spherical([cosRadius, -sinRadius * cos$1(t), -sinRadius * sin$1(t)]); |
|
stream.point(point[0], point[1]); |
|
} |
|
} // Returns the signed angle of a cartesian point relative to [cosRadius, 0, 0]. |
|
|
|
|
|
function circleRadius(cosRadius, point) { |
|
point = cartesian(point), point[0] -= cosRadius; |
|
cartesianNormalizeInPlace(point); |
|
var radius = acos$1(-point[1]); |
|
return ((-point[2] < 0 ? -radius : radius) + tau$2 - epsilon$3) % tau$2; |
|
} |
|
|
|
function clipBuffer() { |
|
var lines = [], |
|
line; |
|
return { |
|
point: function point(x, y) { |
|
line.push([x, y]); |
|
}, |
|
lineStart: function lineStart() { |
|
lines.push(line = []); |
|
}, |
|
lineEnd: noop$3, |
|
rejoin: function rejoin() { |
|
if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); |
|
}, |
|
result: function result() { |
|
var result = lines; |
|
lines = []; |
|
line = null; |
|
return result; |
|
} |
|
}; |
|
} |
|
|
|
function pointEqual(a, b) { |
|
return abs$1(a[0] - b[0]) < epsilon$3 && abs$1(a[1] - b[1]) < epsilon$3; |
|
} |
|
|
|
function Intersection(point, points, other, entry) { |
|
this.x = point; |
|
this.z = points; |
|
this.o = other; // another intersection |
|
|
|
this.e = entry; // is an entry? |
|
|
|
this.v = false; // visited |
|
|
|
this.n = this.p = null; // next & previous |
|
} // A generalized polygon clipping algorithm: given a polygon that has been cut |
|
// into its visible line segments, and rejoins the segments by interpolating |
|
// along the clip edge. |
|
|
|
|
|
function clipRejoin(segments, compareIntersection, startInside, interpolate, stream) { |
|
var subject = [], |
|
clip = [], |
|
i, |
|
n; |
|
segments.forEach(function (segment) { |
|
if ((n = segment.length - 1) <= 0) return; |
|
var n, |
|
p0 = segment[0], |
|
p1 = segment[n], |
|
x; // If the first and last points of a segment are coincident, then treat as a |
|
// closed ring. TODO if all rings are closed, then the winding order of the |
|
// exterior ring should be checked. |
|
|
|
if (pointEqual(p0, p1)) { |
|
stream.lineStart(); |
|
|
|
for (i = 0; i < n; ++i) { |
|
stream.point((p0 = segment[i])[0], p0[1]); |
|
} |
|
|
|
stream.lineEnd(); |
|
return; |
|
} |
|
|
|
subject.push(x = new Intersection(p0, segment, null, true)); |
|
clip.push(x.o = new Intersection(p0, null, x, false)); |
|
subject.push(x = new Intersection(p1, segment, null, false)); |
|
clip.push(x.o = new Intersection(p1, null, x, true)); |
|
}); |
|
if (!subject.length) return; |
|
clip.sort(compareIntersection); |
|
link(subject); |
|
link(clip); |
|
|
|
for (i = 0, n = clip.length; i < n; ++i) { |
|
clip[i].e = startInside = !startInside; |
|
} |
|
|
|
var start = subject[0], |
|
points, |
|
point; |
|
|
|
while (1) { |
|
// Find first unvisited intersection. |
|
var current = start, |
|
isSubject = true; |
|
|
|
while (current.v) { |
|
if ((current = current.n) === start) return; |
|
} |
|
|
|
points = current.z; |
|
stream.lineStart(); |
|
|
|
do { |
|
current.v = current.o.v = true; |
|
|
|
if (current.e) { |
|
if (isSubject) { |
|
for (i = 0, n = points.length; i < n; ++i) { |
|
stream.point((point = points[i])[0], point[1]); |
|
} |
|
} else { |
|
interpolate(current.x, current.n.x, 1, stream); |
|
} |
|
|
|
current = current.n; |
|
} else { |
|
if (isSubject) { |
|
points = current.p.z; |
|
|
|
for (i = points.length - 1; i >= 0; --i) { |
|
stream.point((point = points[i])[0], point[1]); |
|
} |
|
} else { |
|
interpolate(current.x, current.p.x, -1, stream); |
|
} |
|
|
|
current = current.p; |
|
} |
|
|
|
current = current.o; |
|
points = current.z; |
|
isSubject = !isSubject; |
|
} while (!current.v); |
|
|
|
stream.lineEnd(); |
|
} |
|
} |
|
|
|
function link(array) { |
|
if (!(n = array.length)) return; |
|
var n, |
|
i = 0, |
|
a = array[0], |
|
b; |
|
|
|
while (++i < n) { |
|
a.n = b = array[i]; |
|
b.p = a; |
|
a = b; |
|
} |
|
|
|
a.n = b = array[0]; |
|
b.p = a; |
|
} |
|
|
|
var sum$1 = adder(); |
|
|
|
function longitude(point) { |
|
if (abs$1(point[0]) <= pi$2) return point[0];else return sign$1(point[0]) * ((abs$1(point[0]) + pi$2) % tau$2 - pi$2); |
|
} |
|
|
|
function polygonContains(polygon, point) { |
|
var lambda = longitude(point), |
|
phi = point[1], |
|
sinPhi = sin$1(phi), |
|
normal = [sin$1(lambda), -cos$1(lambda), 0], |
|
angle = 0, |
|
winding = 0; |
|
sum$1.reset(); |
|
if (sinPhi === 1) phi = halfPi$1 + epsilon$3;else if (sinPhi === -1) phi = -halfPi$1 - epsilon$3; |
|
|
|
for (var i = 0, n = polygon.length; i < n; ++i) { |
|
if (!(m = (ring = polygon[i]).length)) continue; |
|
var ring, |
|
m, |
|
point0 = ring[m - 1], |
|
lambda0 = longitude(point0), |
|
phi0 = point0[1] / 2 + quarterPi, |
|
sinPhi0 = sin$1(phi0), |
|
cosPhi0 = cos$1(phi0); |
|
|
|
for (var j = 0; j < m; ++j, lambda0 = lambda1, sinPhi0 = sinPhi1, cosPhi0 = cosPhi1, point0 = point1) { |
|
var point1 = ring[j], |
|
lambda1 = longitude(point1), |
|
phi1 = point1[1] / 2 + quarterPi, |
|
sinPhi1 = sin$1(phi1), |
|
cosPhi1 = cos$1(phi1), |
|
delta = lambda1 - lambda0, |
|
sign = delta >= 0 ? 1 : -1, |
|
absDelta = sign * delta, |
|
antimeridian = absDelta > pi$2, |
|
k = sinPhi0 * sinPhi1; |
|
sum$1.add(atan2$1(k * sign * sin$1(absDelta), cosPhi0 * cosPhi1 + k * cos$1(absDelta))); |
|
angle += antimeridian ? delta + sign * tau$2 : delta; // Are the longitudes either side of the point’s meridian (lambda), |
|
// and are the latitudes smaller than the parallel (phi)? |
|
|
|
if (antimeridian ^ lambda0 >= lambda ^ lambda1 >= lambda) { |
|
var arc = cartesianCross(cartesian(point0), cartesian(point1)); |
|
cartesianNormalizeInPlace(arc); |
|
var intersection = cartesianCross(normal, arc); |
|
cartesianNormalizeInPlace(intersection); |
|
var phiArc = (antimeridian ^ delta >= 0 ? -1 : 1) * asin$1(intersection[2]); |
|
|
|
if (phi > phiArc || phi === phiArc && (arc[0] || arc[1])) { |
|
winding += antimeridian ^ delta >= 0 ? 1 : -1; |
|
} |
|
} |
|
} |
|
} // First, determine whether the South pole is inside or outside: |
|
// |
|
// It is inside if: |
|
// * the polygon winds around it in a clockwise direction. |
|
// * the polygon does not (cumulatively) wind around it, but has a negative |
|
// (counter-clockwise) area. |
|
// |
|
// Second, count the (signed) number of times a segment crosses a lambda |
|
// from the point to the South pole. If it is zero, then the point is the |
|
// same side as the South pole. |
|
|
|
|
|
return (angle < -epsilon$3 || angle < epsilon$3 && sum$1 < -epsilon$3) ^ winding & 1; |
|
} |
|
|
|
function ascending$1(a, b) { |
|
return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; |
|
} |
|
|
|
function bisector$1(compare) { |
|
if (compare.length === 1) compare = ascendingComparator$1(compare); |
|
return { |
|
left: function left(a, x, lo, hi) { |
|
if (lo == null) lo = 0; |
|
if (hi == null) hi = a.length; |
|
|
|
while (lo < hi) { |
|
var mid = lo + hi >>> 1; |
|
if (compare(a[mid], x) < 0) lo = mid + 1;else hi = mid; |
|
} |
|
|
|
return lo; |
|
}, |
|
right: function right(a, x, lo, hi) { |
|
if (lo == null) lo = 0; |
|
if (hi == null) hi = a.length; |
|
|
|
while (lo < hi) { |
|
var mid = lo + hi >>> 1; |
|
if (compare(a[mid], x) > 0) hi = mid;else lo = mid + 1; |
|
} |
|
|
|
return lo; |
|
} |
|
}; |
|
} |
|
|
|
function ascendingComparator$1(f) { |
|
return function (d, x) { |
|
return ascending$1(f(d), x); |
|
}; |
|
} |
|
|
|
var ascendingBisect$1 = bisector$1(ascending$1); |
|
|
|
function range$1(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; |
|
} |
|
|
|
function merge$1(arrays) { |
|
var n = arrays.length, |
|
m, |
|
i = -1, |
|
j = 0, |
|
merged, |
|
array; |
|
|
|
while (++i < n) { |
|
j += arrays[i].length; |
|
} |
|
|
|
merged = new Array(j); |
|
|
|
while (--n >= 0) { |
|
array = arrays[n]; |
|
m = array.length; |
|
|
|
while (--m >= 0) { |
|
merged[--j] = array[m]; |
|
} |
|
} |
|
|
|
return merged; |
|
} |
|
|
|
function clip$2(pointVisible, clipLine, interpolate, start) { |
|
return function (sink) { |
|
var line = clipLine(sink), |
|
ringBuffer = clipBuffer(), |
|
ringSink = clipLine(ringBuffer), |
|
polygonStarted = false, |
|
polygon, |
|
segments, |
|
ring; |
|
var clip = { |
|
point: point, |
|
lineStart: lineStart, |
|
lineEnd: lineEnd, |
|
polygonStart: function polygonStart() { |
|
clip.point = pointRing; |
|
clip.lineStart = ringStart; |
|
clip.lineEnd = ringEnd; |
|
segments = []; |
|
polygon = []; |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
clip.point = point; |
|
clip.lineStart = lineStart; |
|
clip.lineEnd = lineEnd; |
|
segments = merge$1(segments); |
|
var startInside = polygonContains(polygon, start); |
|
|
|
if (segments.length) { |
|
if (!polygonStarted) sink.polygonStart(), polygonStarted = true; |
|
clipRejoin(segments, compareIntersection, startInside, interpolate, sink); |
|
} else if (startInside) { |
|
if (!polygonStarted) sink.polygonStart(), polygonStarted = true; |
|
sink.lineStart(); |
|
interpolate(null, null, 1, sink); |
|
sink.lineEnd(); |
|
} |
|
|
|
if (polygonStarted) sink.polygonEnd(), polygonStarted = false; |
|
segments = polygon = null; |
|
}, |
|
sphere: function sphere() { |
|
sink.polygonStart(); |
|
sink.lineStart(); |
|
interpolate(null, null, 1, sink); |
|
sink.lineEnd(); |
|
sink.polygonEnd(); |
|
} |
|
}; |
|
|
|
function point(lambda, phi) { |
|
if (pointVisible(lambda, phi)) sink.point(lambda, phi); |
|
} |
|
|
|
function pointLine(lambda, phi) { |
|
line.point(lambda, phi); |
|
} |
|
|
|
function lineStart() { |
|
clip.point = pointLine; |
|
line.lineStart(); |
|
} |
|
|
|
function lineEnd() { |
|
clip.point = point; |
|
line.lineEnd(); |
|
} |
|
|
|
function pointRing(lambda, phi) { |
|
ring.push([lambda, phi]); |
|
ringSink.point(lambda, phi); |
|
} |
|
|
|
function ringStart() { |
|
ringSink.lineStart(); |
|
ring = []; |
|
} |
|
|
|
function ringEnd() { |
|
pointRing(ring[0][0], ring[0][1]); |
|
ringSink.lineEnd(); |
|
var clean = ringSink.clean(), |
|
ringSegments = ringBuffer.result(), |
|
i, |
|
n = ringSegments.length, |
|
m, |
|
segment, |
|
point; |
|
ring.pop(); |
|
polygon.push(ring); |
|
ring = null; |
|
if (!n) return; // No intersections. |
|
|
|
if (clean & 1) { |
|
segment = ringSegments[0]; |
|
|
|
if ((m = segment.length - 1) > 0) { |
|
if (!polygonStarted) sink.polygonStart(), polygonStarted = true; |
|
sink.lineStart(); |
|
|
|
for (i = 0; i < m; ++i) { |
|
sink.point((point = segment[i])[0], point[1]); |
|
} |
|
|
|
sink.lineEnd(); |
|
} |
|
|
|
return; |
|
} // Rejoin connected segments. |
|
// TODO reuse ringBuffer.rejoin()? |
|
|
|
|
|
if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); |
|
segments.push(ringSegments.filter(validSegment)); |
|
} |
|
|
|
return clip; |
|
}; |
|
} |
|
|
|
function validSegment(segment) { |
|
return segment.length > 1; |
|
} // Intersections are sorted along the clip edge. For both antimeridian cutting |
|
// and circle clipping, the same comparison is used. |
|
|
|
|
|
function compareIntersection(a, b) { |
|
return ((a = a.x)[0] < 0 ? a[1] - halfPi$1 - epsilon$3 : halfPi$1 - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfPi$1 - epsilon$3 : halfPi$1 - b[1]); |
|
} |
|
|
|
var clipAntimeridian = clip$2(function () { |
|
return true; |
|
}, clipAntimeridianLine, clipAntimeridianInterpolate, [-pi$2, -halfPi$1]); // Takes a line and cuts into visible segments. Return values: 0 - there were |
|
// intersections or the line was empty; 1 - no intersections; 2 - there were |
|
// intersections, and the first and last segments should be rejoined. |
|
|
|
function clipAntimeridianLine(stream) { |
|
var lambda0 = NaN, |
|
phi0 = NaN, |
|
sign0 = NaN, |
|
_clean; // no intersections |
|
|
|
|
|
return { |
|
lineStart: function lineStart() { |
|
stream.lineStart(); |
|
_clean = 1; |
|
}, |
|
point: function point(lambda1, phi1) { |
|
var sign1 = lambda1 > 0 ? pi$2 : -pi$2, |
|
delta = abs$1(lambda1 - lambda0); |
|
|
|
if (abs$1(delta - pi$2) < epsilon$3) { |
|
// line crosses a pole |
|
stream.point(lambda0, phi0 = (phi0 + phi1) / 2 > 0 ? halfPi$1 : -halfPi$1); |
|
stream.point(sign0, phi0); |
|
stream.lineEnd(); |
|
stream.lineStart(); |
|
stream.point(sign1, phi0); |
|
stream.point(lambda1, phi0); |
|
_clean = 0; |
|
} else if (sign0 !== sign1 && delta >= pi$2) { |
|
// line crosses antimeridian |
|
if (abs$1(lambda0 - sign0) < epsilon$3) lambda0 -= sign0 * epsilon$3; // handle degeneracies |
|
|
|
if (abs$1(lambda1 - sign1) < epsilon$3) lambda1 -= sign1 * epsilon$3; |
|
phi0 = clipAntimeridianIntersect(lambda0, phi0, lambda1, phi1); |
|
stream.point(sign0, phi0); |
|
stream.lineEnd(); |
|
stream.lineStart(); |
|
stream.point(sign1, phi0); |
|
_clean = 0; |
|
} |
|
|
|
stream.point(lambda0 = lambda1, phi0 = phi1); |
|
sign0 = sign1; |
|
}, |
|
lineEnd: function lineEnd() { |
|
stream.lineEnd(); |
|
lambda0 = phi0 = NaN; |
|
}, |
|
clean: function clean() { |
|
return 2 - _clean; // if intersections, rejoin first and last segments |
|
} |
|
}; |
|
} |
|
|
|
function clipAntimeridianIntersect(lambda0, phi0, lambda1, phi1) { |
|
var cosPhi0, |
|
cosPhi1, |
|
sinLambda0Lambda1 = sin$1(lambda0 - lambda1); |
|
return abs$1(sinLambda0Lambda1) > epsilon$3 ? atan((sin$1(phi0) * (cosPhi1 = cos$1(phi1)) * sin$1(lambda1) - sin$1(phi1) * (cosPhi0 = cos$1(phi0)) * sin$1(lambda0)) / (cosPhi0 * cosPhi1 * sinLambda0Lambda1)) : (phi0 + phi1) / 2; |
|
} |
|
|
|
function clipAntimeridianInterpolate(from, to, direction, stream) { |
|
var phi; |
|
|
|
if (from == null) { |
|
phi = direction * halfPi$1; |
|
stream.point(-pi$2, phi); |
|
stream.point(0, phi); |
|
stream.point(pi$2, phi); |
|
stream.point(pi$2, 0); |
|
stream.point(pi$2, -phi); |
|
stream.point(0, -phi); |
|
stream.point(-pi$2, -phi); |
|
stream.point(-pi$2, 0); |
|
stream.point(-pi$2, phi); |
|
} else if (abs$1(from[0] - to[0]) > epsilon$3) { |
|
var lambda = from[0] < to[0] ? pi$2 : -pi$2; |
|
phi = direction * lambda / 2; |
|
stream.point(-lambda, phi); |
|
stream.point(0, phi); |
|
stream.point(lambda, phi); |
|
} else { |
|
stream.point(to[0], to[1]); |
|
} |
|
} |
|
|
|
function clipCircle(radius) { |
|
var cr = cos$1(radius), |
|
delta = 6 * radians, |
|
smallRadius = cr > 0, |
|
notHemisphere = abs$1(cr) > epsilon$3; // TODO optimise for this common case |
|
|
|
function interpolate(from, to, direction, stream) { |
|
circleStream(stream, radius, delta, direction, from, to); |
|
} |
|
|
|
function visible(lambda, phi) { |
|
return cos$1(lambda) * cos$1(phi) > cr; |
|
} // Takes a line and cuts into visible segments. Return values used for polygon |
|
// clipping: 0 - there were intersections or the line was empty; 1 - no |
|
// intersections 2 - there were intersections, and the first and last segments |
|
// should be rejoined. |
|
|
|
|
|
function clipLine(stream) { |
|
var point0, // previous point |
|
c0, // code for previous point |
|
v0, // visibility of previous point |
|
v00, // visibility of first point |
|
_clean2; // no intersections |
|
|
|
|
|
return { |
|
lineStart: function lineStart() { |
|
v00 = v0 = false; |
|
_clean2 = 1; |
|
}, |
|
point: function point(lambda, phi) { |
|
var point1 = [lambda, phi], |
|
point2, |
|
v = visible(lambda, phi), |
|
c = smallRadius ? v ? 0 : code(lambda, phi) : v ? code(lambda + (lambda < 0 ? pi$2 : -pi$2), phi) : 0; |
|
if (!point0 && (v00 = v0 = v)) stream.lineStart(); // Handle degeneracies. |
|
// TODO ignore if not clipping polygons. |
|
|
|
if (v !== v0) { |
|
point2 = intersect(point0, point1); |
|
|
|
if (!point2 || pointEqual(point0, point2) || pointEqual(point1, point2)) { |
|
point1[0] += epsilon$3; |
|
point1[1] += epsilon$3; |
|
v = visible(point1[0], point1[1]); |
|
} |
|
} |
|
|
|
if (v !== v0) { |
|
_clean2 = 0; |
|
|
|
if (v) { |
|
// outside going in |
|
stream.lineStart(); |
|
point2 = intersect(point1, point0); |
|
stream.point(point2[0], point2[1]); |
|
} else { |
|
// inside going out |
|
point2 = intersect(point0, point1); |
|
stream.point(point2[0], point2[1]); |
|
stream.lineEnd(); |
|
} |
|
|
|
point0 = point2; |
|
} else if (notHemisphere && point0 && smallRadius ^ v) { |
|
var t; // If the codes for two points are different, or are both zero, |
|
// and there this segment intersects with the small circle. |
|
|
|
if (!(c & c0) && (t = intersect(point1, point0, true))) { |
|
_clean2 = 0; |
|
|
|
if (smallRadius) { |
|
stream.lineStart(); |
|
stream.point(t[0][0], t[0][1]); |
|
stream.point(t[1][0], t[1][1]); |
|
stream.lineEnd(); |
|
} else { |
|
stream.point(t[1][0], t[1][1]); |
|
stream.lineEnd(); |
|
stream.lineStart(); |
|
stream.point(t[0][0], t[0][1]); |
|
} |
|
} |
|
} |
|
|
|
if (v && (!point0 || !pointEqual(point0, point1))) { |
|
stream.point(point1[0], point1[1]); |
|
} |
|
|
|
point0 = point1, v0 = v, c0 = c; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (v0) stream.lineEnd(); |
|
point0 = null; |
|
}, |
|
// Rejoin first and last segments if there were intersections and the first |
|
// and last points were visible. |
|
clean: function clean() { |
|
return _clean2 | (v00 && v0) << 1; |
|
} |
|
}; |
|
} // Intersects the great circle between a and b with the clip circle. |
|
|
|
|
|
function intersect(a, b, two) { |
|
var pa = cartesian(a), |
|
pb = cartesian(b); // We have two planes, n1.p = d1 and n2.p = d2. |
|
// Find intersection line p(t) = c1 n1 + c2 n2 + t (n1 ⨯ n2). |
|
|
|
var n1 = [1, 0, 0], |
|
// normal |
|
n2 = cartesianCross(pa, pb), |
|
n2n2 = cartesianDot(n2, n2), |
|
n1n2 = n2[0], |
|
// cartesianDot(n1, n2), |
|
determinant = n2n2 - n1n2 * n1n2; // Two polar points. |
|
|
|
if (!determinant) return !two && a; |
|
var c1 = cr * n2n2 / determinant, |
|
c2 = -cr * n1n2 / determinant, |
|
n1xn2 = cartesianCross(n1, n2), |
|
A = cartesianScale(n1, c1), |
|
B = cartesianScale(n2, c2); |
|
cartesianAddInPlace(A, B); // Solve |p(t)|^2 = 1. |
|
|
|
var u = n1xn2, |
|
w = cartesianDot(A, u), |
|
uu = cartesianDot(u, u), |
|
t2 = w * w - uu * (cartesianDot(A, A) - 1); |
|
if (t2 < 0) return; |
|
var t = sqrt$2(t2), |
|
q = cartesianScale(u, (-w - t) / uu); |
|
cartesianAddInPlace(q, A); |
|
q = spherical(q); |
|
if (!two) return q; // Two intersection points. |
|
|
|
var lambda0 = a[0], |
|
lambda1 = b[0], |
|
phi0 = a[1], |
|
phi1 = b[1], |
|
z; |
|
if (lambda1 < lambda0) z = lambda0, lambda0 = lambda1, lambda1 = z; |
|
var delta = lambda1 - lambda0, |
|
polar = abs$1(delta - pi$2) < epsilon$3, |
|
meridian = polar || delta < epsilon$3; |
|
if (!polar && phi1 < phi0) z = phi0, phi0 = phi1, phi1 = z; // Check that the first point is between a and b. |
|
|
|
if (meridian ? polar ? phi0 + phi1 > 0 ^ q[1] < (abs$1(q[0] - lambda0) < epsilon$3 ? phi0 : phi1) : phi0 <= q[1] && q[1] <= phi1 : delta > pi$2 ^ (lambda0 <= q[0] && q[0] <= lambda1)) { |
|
var q1 = cartesianScale(u, (-w + t) / uu); |
|
cartesianAddInPlace(q1, A); |
|
return [q, spherical(q1)]; |
|
} |
|
} // Generates a 4-bit vector representing the location of a point relative to |
|
// the small circle's bounding box. |
|
|
|
|
|
function code(lambda, phi) { |
|
var r = smallRadius ? radius : pi$2 - radius, |
|
code = 0; |
|
if (lambda < -r) code |= 1; // left |
|
else if (lambda > r) code |= 2; // right |
|
|
|
if (phi < -r) code |= 4; // below |
|
else if (phi > r) code |= 8; // above |
|
|
|
return code; |
|
} |
|
|
|
return clip$2(visible, clipLine, interpolate, smallRadius ? [0, -radius] : [-pi$2, radius - pi$2]); |
|
} |
|
|
|
function clipLine(a, b, x0, y0, x1, y1) { |
|
var ax = a[0], |
|
ay = a[1], |
|
bx = b[0], |
|
by = b[1], |
|
t0 = 0, |
|
t1 = 1, |
|
dx = bx - ax, |
|
dy = by - ay, |
|
r; |
|
r = x0 - ax; |
|
if (!dx && r > 0) return; |
|
r /= dx; |
|
|
|
if (dx < 0) { |
|
if (r < t0) return; |
|
if (r < t1) t1 = r; |
|
} else if (dx > 0) { |
|
if (r > t1) return; |
|
if (r > t0) t0 = r; |
|
} |
|
|
|
r = x1 - ax; |
|
if (!dx && r < 0) return; |
|
r /= dx; |
|
|
|
if (dx < 0) { |
|
if (r > t1) return; |
|
if (r > t0) t0 = r; |
|
} else if (dx > 0) { |
|
if (r < t0) return; |
|
if (r < t1) t1 = r; |
|
} |
|
|
|
r = y0 - ay; |
|
if (!dy && r > 0) return; |
|
r /= dy; |
|
|
|
if (dy < 0) { |
|
if (r < t0) return; |
|
if (r < t1) t1 = r; |
|
} else if (dy > 0) { |
|
if (r > t1) return; |
|
if (r > t0) t0 = r; |
|
} |
|
|
|
r = y1 - ay; |
|
if (!dy && r < 0) return; |
|
r /= dy; |
|
|
|
if (dy < 0) { |
|
if (r > t1) return; |
|
if (r > t0) t0 = r; |
|
} else if (dy > 0) { |
|
if (r < t0) return; |
|
if (r < t1) t1 = r; |
|
} |
|
|
|
if (t0 > 0) a[0] = ax + t0 * dx, a[1] = ay + t0 * dy; |
|
if (t1 < 1) b[0] = ax + t1 * dx, b[1] = ay + t1 * dy; |
|
return true; |
|
} |
|
|
|
var clipMax = 1e9, |
|
clipMin = -clipMax; // TODO Use d3-polygon’s polygonContains here for the ring check? |
|
// TODO Eliminate duplicate buffering in clipBuffer and polygon.push? |
|
|
|
function clipRectangle(x0, y0, x1, y1) { |
|
function visible(x, y) { |
|
return x0 <= x && x <= x1 && y0 <= y && y <= y1; |
|
} |
|
|
|
function interpolate(from, to, direction, stream) { |
|
var a = 0, |
|
a1 = 0; |
|
|
|
if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoint(from, to) < 0 ^ direction > 0) { |
|
do { |
|
stream.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); |
|
} while ((a = (a + direction + 4) % 4) !== a1); |
|
} else { |
|
stream.point(to[0], to[1]); |
|
} |
|
} |
|
|
|
function corner(p, direction) { |
|
return abs$1(p[0] - x0) < epsilon$3 ? direction > 0 ? 0 : 3 : abs$1(p[0] - x1) < epsilon$3 ? direction > 0 ? 2 : 1 : abs$1(p[1] - y0) < epsilon$3 ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; // abs(p[1] - y1) < epsilon |
|
} |
|
|
|
function compareIntersection(a, b) { |
|
return comparePoint(a.x, b.x); |
|
} |
|
|
|
function comparePoint(a, b) { |
|
var ca = corner(a, 1), |
|
cb = corner(b, 1); |
|
return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; |
|
} |
|
|
|
return function (stream) { |
|
var activeStream = stream, |
|
bufferStream = clipBuffer(), |
|
segments, |
|
polygon, |
|
ring, |
|
x__, |
|
y__, |
|
v__, |
|
// first point |
|
x_, |
|
y_, |
|
v_, |
|
// previous point |
|
first, |
|
clean; |
|
var clipStream = { |
|
point: point, |
|
lineStart: lineStart, |
|
lineEnd: lineEnd, |
|
polygonStart: polygonStart, |
|
polygonEnd: polygonEnd |
|
}; |
|
|
|
function point(x, y) { |
|
if (visible(x, y)) activeStream.point(x, y); |
|
} |
|
|
|
function polygonInside() { |
|
var winding = 0; |
|
|
|
for (var i = 0, n = polygon.length; i < n; ++i) { |
|
for (var ring = polygon[i], j = 1, m = ring.length, point = ring[0], a0, a1, b0 = point[0], b1 = point[1]; j < m; ++j) { |
|
a0 = b0, a1 = b1, point = ring[j], b0 = point[0], b1 = point[1]; |
|
|
|
if (a1 <= y1) { |
|
if (b1 > y1 && (b0 - a0) * (y1 - a1) > (b1 - a1) * (x0 - a0)) ++winding; |
|
} else { |
|
if (b1 <= y1 && (b0 - a0) * (y1 - a1) < (b1 - a1) * (x0 - a0)) --winding; |
|
} |
|
} |
|
} |
|
|
|
return winding; |
|
} // Buffer geometry within a polygon and then clip it en masse. |
|
|
|
|
|
function polygonStart() { |
|
activeStream = bufferStream, segments = [], polygon = [], clean = true; |
|
} |
|
|
|
function polygonEnd() { |
|
var startInside = polygonInside(), |
|
cleanInside = clean && startInside, |
|
visible = (segments = merge$1(segments)).length; |
|
|
|
if (cleanInside || visible) { |
|
stream.polygonStart(); |
|
|
|
if (cleanInside) { |
|
stream.lineStart(); |
|
interpolate(null, null, 1, stream); |
|
stream.lineEnd(); |
|
} |
|
|
|
if (visible) { |
|
clipRejoin(segments, compareIntersection, startInside, interpolate, stream); |
|
} |
|
|
|
stream.polygonEnd(); |
|
} |
|
|
|
activeStream = stream, segments = polygon = ring = null; |
|
} |
|
|
|
function lineStart() { |
|
clipStream.point = linePoint; |
|
if (polygon) polygon.push(ring = []); |
|
first = true; |
|
v_ = false; |
|
x_ = y_ = NaN; |
|
} // TODO rather than special-case polygons, simply handle them separately. |
|
// Ideally, coincident intersection points should be jittered to avoid |
|
// clipping issues. |
|
|
|
|
|
function lineEnd() { |
|
if (segments) { |
|
linePoint(x__, y__); |
|
if (v__ && v_) bufferStream.rejoin(); |
|
segments.push(bufferStream.result()); |
|
} |
|
|
|
clipStream.point = point; |
|
if (v_) activeStream.lineEnd(); |
|
} |
|
|
|
function linePoint(x, y) { |
|
var v = visible(x, y); |
|
if (polygon) ring.push([x, y]); |
|
|
|
if (first) { |
|
x__ = x, y__ = y, v__ = v; |
|
first = false; |
|
|
|
if (v) { |
|
activeStream.lineStart(); |
|
activeStream.point(x, y); |
|
} |
|
} else { |
|
if (v && v_) activeStream.point(x, y);else { |
|
var a = [x_ = Math.max(clipMin, Math.min(clipMax, x_)), y_ = Math.max(clipMin, Math.min(clipMax, y_))], |
|
b = [x = Math.max(clipMin, Math.min(clipMax, x)), y = Math.max(clipMin, Math.min(clipMax, y))]; |
|
|
|
if (clipLine(a, b, x0, y0, x1, y1)) { |
|
if (!v_) { |
|
activeStream.lineStart(); |
|
activeStream.point(a[0], a[1]); |
|
} |
|
|
|
activeStream.point(b[0], b[1]); |
|
if (!v) activeStream.lineEnd(); |
|
clean = false; |
|
} else if (v) { |
|
activeStream.lineStart(); |
|
activeStream.point(x, y); |
|
clean = false; |
|
} |
|
} |
|
} |
|
|
|
x_ = x, y_ = y, v_ = v; |
|
} |
|
|
|
return clipStream; |
|
}; |
|
} |
|
|
|
function graticuleX(y0, y1, dy) { |
|
var y = range$1(y0, y1 - epsilon$3, dy).concat(y1); |
|
return function (x) { |
|
return y.map(function (y) { |
|
return [x, y]; |
|
}); |
|
}; |
|
} |
|
|
|
function graticuleY(x0, x1, dx) { |
|
var x = range$1(x0, x1 - epsilon$3, dx).concat(x1); |
|
return function (y) { |
|
return x.map(function (x) { |
|
return [x, y]; |
|
}); |
|
}; |
|
} |
|
|
|
function graticule() { |
|
var x1, |
|
x0, |
|
X1, |
|
X0, |
|
y1, |
|
y0, |
|
Y1, |
|
Y0, |
|
dx = 10, |
|
dy = dx, |
|
DX = 90, |
|
DY = 360, |
|
x, |
|
y, |
|
X, |
|
Y, |
|
precision = 2.5; |
|
|
|
function graticule() { |
|
return { |
|
type: "MultiLineString", |
|
coordinates: lines() |
|
}; |
|
} |
|
|
|
function lines() { |
|
return range$1(ceil(X0 / DX) * DX, X1, DX).map(X).concat(range$1(ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(range$1(ceil(x0 / dx) * dx, x1, dx).filter(function (x) { |
|
return abs$1(x % DX) > epsilon$3; |
|
}).map(x)).concat(range$1(ceil(y0 / dy) * dy, y1, dy).filter(function (y) { |
|
return abs$1(y % DY) > epsilon$3; |
|
}).map(y)); |
|
} |
|
|
|
graticule.lines = function () { |
|
return lines().map(function (coordinates) { |
|
return { |
|
type: "LineString", |
|
coordinates: coordinates |
|
}; |
|
}); |
|
}; |
|
|
|
graticule.outline = function () { |
|
return { |
|
type: "Polygon", |
|
coordinates: [X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1))] |
|
}; |
|
}; |
|
|
|
graticule.extent = function (_) { |
|
if (!arguments.length) return graticule.extentMinor(); |
|
return graticule.extentMajor(_).extentMinor(_); |
|
}; |
|
|
|
graticule.extentMajor = function (_) { |
|
if (!arguments.length) return [[X0, Y0], [X1, Y1]]; |
|
X0 = +_[0][0], X1 = +_[1][0]; |
|
Y0 = +_[0][1], Y1 = +_[1][1]; |
|
if (X0 > X1) _ = X0, X0 = X1, X1 = _; |
|
if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; |
|
return graticule.precision(precision); |
|
}; |
|
|
|
graticule.extentMinor = function (_) { |
|
if (!arguments.length) return [[x0, y0], [x1, y1]]; |
|
x0 = +_[0][0], x1 = +_[1][0]; |
|
y0 = +_[0][1], y1 = +_[1][1]; |
|
if (x0 > x1) _ = x0, x0 = x1, x1 = _; |
|
if (y0 > y1) _ = y0, y0 = y1, y1 = _; |
|
return graticule.precision(precision); |
|
}; |
|
|
|
graticule.step = function (_) { |
|
if (!arguments.length) return graticule.stepMinor(); |
|
return graticule.stepMajor(_).stepMinor(_); |
|
}; |
|
|
|
graticule.stepMajor = function (_) { |
|
if (!arguments.length) return [DX, DY]; |
|
DX = +_[0], DY = +_[1]; |
|
return graticule; |
|
}; |
|
|
|
graticule.stepMinor = function (_) { |
|
if (!arguments.length) return [dx, dy]; |
|
dx = +_[0], dy = +_[1]; |
|
return graticule; |
|
}; |
|
|
|
graticule.precision = function (_) { |
|
if (!arguments.length) return precision; |
|
precision = +_; |
|
x = graticuleX(y0, y1, 90); |
|
y = graticuleY(x0, x1, precision); |
|
X = graticuleX(Y0, Y1, 90); |
|
Y = graticuleY(X0, X1, precision); |
|
return graticule; |
|
}; |
|
|
|
return graticule.extentMajor([[-180, -90 + epsilon$3], [180, 90 - epsilon$3]]).extentMinor([[-180, -80 - epsilon$3], [180, 80 + epsilon$3]]); |
|
} |
|
|
|
function identity$6(x) { |
|
return x; |
|
} |
|
|
|
var areaSum$1 = adder(), |
|
areaRingSum$1 = adder(), |
|
x00, |
|
y00, |
|
x0$1, |
|
y0$1; |
|
var areaStream$1 = { |
|
point: noop$3, |
|
lineStart: noop$3, |
|
lineEnd: noop$3, |
|
polygonStart: function polygonStart() { |
|
areaStream$1.lineStart = areaRingStart$1; |
|
areaStream$1.lineEnd = areaRingEnd$1; |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
areaStream$1.lineStart = areaStream$1.lineEnd = areaStream$1.point = noop$3; |
|
areaSum$1.add(abs$1(areaRingSum$1)); |
|
areaRingSum$1.reset(); |
|
}, |
|
result: function result() { |
|
var area = areaSum$1 / 2; |
|
areaSum$1.reset(); |
|
return area; |
|
} |
|
}; |
|
|
|
function areaRingStart$1() { |
|
areaStream$1.point = areaPointFirst$1; |
|
} |
|
|
|
function areaPointFirst$1(x, y) { |
|
areaStream$1.point = areaPoint$1; |
|
x00 = x0$1 = x, y00 = y0$1 = y; |
|
} |
|
|
|
function areaPoint$1(x, y) { |
|
areaRingSum$1.add(y0$1 * x - x0$1 * y); |
|
x0$1 = x, y0$1 = y; |
|
} |
|
|
|
function areaRingEnd$1() { |
|
areaPoint$1(x00, y00); |
|
} |
|
|
|
var x0$2 = Infinity, |
|
y0$2 = x0$2, |
|
x1 = -x0$2, |
|
y1 = x1; |
|
var boundsStream$1 = { |
|
point: boundsPoint$1, |
|
lineStart: noop$3, |
|
lineEnd: noop$3, |
|
polygonStart: noop$3, |
|
polygonEnd: noop$3, |
|
result: function result() { |
|
var bounds = [[x0$2, y0$2], [x1, y1]]; |
|
x1 = y1 = -(y0$2 = x0$2 = Infinity); |
|
return bounds; |
|
} |
|
}; |
|
|
|
function boundsPoint$1(x, y) { |
|
if (x < x0$2) x0$2 = x; |
|
if (x > x1) x1 = x; |
|
if (y < y0$2) y0$2 = y; |
|
if (y > y1) y1 = y; |
|
} // TODO Enforce positive area for exterior, negative area for interior? |
|
|
|
|
|
var X0$1 = 0, |
|
Y0$1 = 0, |
|
Z0$1 = 0, |
|
X1$1 = 0, |
|
Y1$1 = 0, |
|
Z1$1 = 0, |
|
X2$1 = 0, |
|
Y2$1 = 0, |
|
Z2$1 = 0, |
|
x00$1, |
|
y00$1, |
|
x0$3, |
|
y0$3; |
|
var centroidStream$1 = { |
|
point: centroidPoint$1, |
|
lineStart: centroidLineStart$1, |
|
lineEnd: centroidLineEnd$1, |
|
polygonStart: function polygonStart() { |
|
centroidStream$1.lineStart = centroidRingStart$1; |
|
centroidStream$1.lineEnd = centroidRingEnd$1; |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
centroidStream$1.point = centroidPoint$1; |
|
centroidStream$1.lineStart = centroidLineStart$1; |
|
centroidStream$1.lineEnd = centroidLineEnd$1; |
|
}, |
|
result: function result() { |
|
var centroid = Z2$1 ? [X2$1 / Z2$1, Y2$1 / Z2$1] : Z1$1 ? [X1$1 / Z1$1, Y1$1 / Z1$1] : Z0$1 ? [X0$1 / Z0$1, Y0$1 / Z0$1] : [NaN, NaN]; |
|
X0$1 = Y0$1 = Z0$1 = X1$1 = Y1$1 = Z1$1 = X2$1 = Y2$1 = Z2$1 = 0; |
|
return centroid; |
|
} |
|
}; |
|
|
|
function centroidPoint$1(x, y) { |
|
X0$1 += x; |
|
Y0$1 += y; |
|
++Z0$1; |
|
} |
|
|
|
function centroidLineStart$1() { |
|
centroidStream$1.point = centroidPointFirstLine; |
|
} |
|
|
|
function centroidPointFirstLine(x, y) { |
|
centroidStream$1.point = centroidPointLine; |
|
centroidPoint$1(x0$3 = x, y0$3 = y); |
|
} |
|
|
|
function centroidPointLine(x, y) { |
|
var dx = x - x0$3, |
|
dy = y - y0$3, |
|
z = sqrt$2(dx * dx + dy * dy); |
|
X1$1 += z * (x0$3 + x) / 2; |
|
Y1$1 += z * (y0$3 + y) / 2; |
|
Z1$1 += z; |
|
centroidPoint$1(x0$3 = x, y0$3 = y); |
|
} |
|
|
|
function centroidLineEnd$1() { |
|
centroidStream$1.point = centroidPoint$1; |
|
} |
|
|
|
function centroidRingStart$1() { |
|
centroidStream$1.point = centroidPointFirstRing; |
|
} |
|
|
|
function centroidRingEnd$1() { |
|
centroidPointRing(x00$1, y00$1); |
|
} |
|
|
|
function centroidPointFirstRing(x, y) { |
|
centroidStream$1.point = centroidPointRing; |
|
centroidPoint$1(x00$1 = x0$3 = x, y00$1 = y0$3 = y); |
|
} |
|
|
|
function centroidPointRing(x, y) { |
|
var dx = x - x0$3, |
|
dy = y - y0$3, |
|
z = sqrt$2(dx * dx + dy * dy); |
|
X1$1 += z * (x0$3 + x) / 2; |
|
Y1$1 += z * (y0$3 + y) / 2; |
|
Z1$1 += z; |
|
z = y0$3 * x - x0$3 * y; |
|
X2$1 += z * (x0$3 + x); |
|
Y2$1 += z * (y0$3 + y); |
|
Z2$1 += z * 3; |
|
centroidPoint$1(x0$3 = x, y0$3 = y); |
|
} |
|
|
|
function PathContext(context) { |
|
this._context = context; |
|
} |
|
|
|
PathContext.prototype = { |
|
_radius: 4.5, |
|
pointRadius: function pointRadius(_) { |
|
return this._radius = _, this; |
|
}, |
|
polygonStart: function polygonStart() { |
|
this._line = 0; |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (this._line === 0) this._context.closePath(); |
|
this._point = NaN; |
|
}, |
|
point: function point(x, y) { |
|
switch (this._point) { |
|
case 0: |
|
{ |
|
this._context.moveTo(x, y); |
|
|
|
this._point = 1; |
|
break; |
|
} |
|
|
|
case 1: |
|
{ |
|
this._context.lineTo(x, y); |
|
|
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
this._context.moveTo(x + this._radius, y); |
|
|
|
this._context.arc(x, y, this._radius, 0, tau$2); |
|
|
|
break; |
|
} |
|
} |
|
}, |
|
result: noop$3 |
|
}; |
|
var lengthSum = adder(), |
|
lengthRing, |
|
x00$2, |
|
y00$2, |
|
x0$4, |
|
y0$4; |
|
var lengthStream = { |
|
point: noop$3, |
|
lineStart: function lineStart() { |
|
lengthStream.point = lengthPointFirst; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (lengthRing) lengthPoint(x00$2, y00$2); |
|
lengthStream.point = noop$3; |
|
}, |
|
polygonStart: function polygonStart() { |
|
lengthRing = true; |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
lengthRing = null; |
|
}, |
|
result: function result() { |
|
var length = +lengthSum; |
|
lengthSum.reset(); |
|
return length; |
|
} |
|
}; |
|
|
|
function lengthPointFirst(x, y) { |
|
lengthStream.point = lengthPoint; |
|
x00$2 = x0$4 = x, y00$2 = y0$4 = y; |
|
} |
|
|
|
function lengthPoint(x, y) { |
|
x0$4 -= x, y0$4 -= y; |
|
lengthSum.add(sqrt$2(x0$4 * x0$4 + y0$4 * y0$4)); |
|
x0$4 = x, y0$4 = y; |
|
} |
|
|
|
function PathString() { |
|
this._string = []; |
|
} |
|
|
|
PathString.prototype = { |
|
_radius: 4.5, |
|
_circle: circle$1(4.5), |
|
pointRadius: function pointRadius(_) { |
|
if ((_ = +_) !== this._radius) this._radius = _, this._circle = null; |
|
return this; |
|
}, |
|
polygonStart: function polygonStart() { |
|
this._line = 0; |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
this._line = NaN; |
|
}, |
|
lineStart: function lineStart() { |
|
this._point = 0; |
|
}, |
|
lineEnd: function lineEnd() { |
|
if (this._line === 0) this._string.push("Z"); |
|
this._point = NaN; |
|
}, |
|
point: function point(x, y) { |
|
switch (this._point) { |
|
case 0: |
|
{ |
|
this._string.push("M", x, ",", y); |
|
|
|
this._point = 1; |
|
break; |
|
} |
|
|
|
case 1: |
|
{ |
|
this._string.push("L", x, ",", y); |
|
|
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
if (this._circle == null) this._circle = circle$1(this._radius); |
|
|
|
this._string.push("M", x, ",", y, this._circle); |
|
|
|
break; |
|
} |
|
} |
|
}, |
|
result: function result() { |
|
if (this._string.length) { |
|
var result = this._string.join(""); |
|
|
|
this._string = []; |
|
return result; |
|
} else { |
|
return null; |
|
} |
|
} |
|
}; |
|
|
|
function circle$1(radius) { |
|
return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z"; |
|
} |
|
|
|
function geoPath(projection, context) { |
|
var pointRadius = 4.5, |
|
projectionStream, |
|
contextStream; |
|
|
|
function path(object) { |
|
if (object) { |
|
if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments)); |
|
geoStream(object, projectionStream(contextStream)); |
|
} |
|
|
|
return contextStream.result(); |
|
} |
|
|
|
path.area = function (object) { |
|
geoStream(object, projectionStream(areaStream$1)); |
|
return areaStream$1.result(); |
|
}; |
|
|
|
path.measure = function (object) { |
|
geoStream(object, projectionStream(lengthStream)); |
|
return lengthStream.result(); |
|
}; |
|
|
|
path.bounds = function (object) { |
|
geoStream(object, projectionStream(boundsStream$1)); |
|
return boundsStream$1.result(); |
|
}; |
|
|
|
path.centroid = function (object) { |
|
geoStream(object, projectionStream(centroidStream$1)); |
|
return centroidStream$1.result(); |
|
}; |
|
|
|
path.projection = function (_) { |
|
return arguments.length ? (projectionStream = _ == null ? (projection = null, identity$6) : (projection = _).stream, path) : projection; |
|
}; |
|
|
|
path.context = function (_) { |
|
if (!arguments.length) return context; |
|
contextStream = _ == null ? (context = null, new PathString()) : new PathContext(context = _); |
|
if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius); |
|
return path; |
|
}; |
|
|
|
path.pointRadius = function (_) { |
|
if (!arguments.length) return pointRadius; |
|
pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_); |
|
return path; |
|
}; |
|
|
|
return path.projection(projection).context(context); |
|
} |
|
|
|
function transformer$3(methods) { |
|
return function (stream) { |
|
var s = new TransformStream(); |
|
|
|
for (var key in methods) { |
|
s[key] = methods[key]; |
|
} |
|
|
|
s.stream = stream; |
|
return s; |
|
}; |
|
} |
|
|
|
function TransformStream() {} |
|
|
|
TransformStream.prototype = { |
|
constructor: TransformStream, |
|
point: function point(x, y) { |
|
this.stream.point(x, y); |
|
}, |
|
sphere: function sphere() { |
|
this.stream.sphere(); |
|
}, |
|
lineStart: function lineStart() { |
|
this.stream.lineStart(); |
|
}, |
|
lineEnd: function lineEnd() { |
|
this.stream.lineEnd(); |
|
}, |
|
polygonStart: function polygonStart() { |
|
this.stream.polygonStart(); |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
this.stream.polygonEnd(); |
|
} |
|
}; |
|
|
|
function fit(projection, fitBounds, object) { |
|
var clip = projection.clipExtent && projection.clipExtent(); |
|
projection.scale(150).translate([0, 0]); |
|
if (clip != null) projection.clipExtent(null); |
|
geoStream(object, projection.stream(boundsStream$1)); |
|
fitBounds(boundsStream$1.result()); |
|
if (clip != null) projection.clipExtent(clip); |
|
return projection; |
|
} |
|
|
|
function _fitExtent(projection, extent, object) { |
|
return fit(projection, function (b) { |
|
var w = extent[1][0] - extent[0][0], |
|
h = extent[1][1] - extent[0][1], |
|
k = Math.min(w / (b[1][0] - b[0][0]), h / (b[1][1] - b[0][1])), |
|
x = +extent[0][0] + (w - k * (b[1][0] + b[0][0])) / 2, |
|
y = +extent[0][1] + (h - k * (b[1][1] + b[0][1])) / 2; |
|
projection.scale(150 * k).translate([x, y]); |
|
}, object); |
|
} |
|
|
|
function _fitSize(projection, size, object) { |
|
return _fitExtent(projection, [[0, 0], size], object); |
|
} |
|
|
|
function _fitWidth(projection, width, object) { |
|
return fit(projection, function (b) { |
|
var w = +width, |
|
k = w / (b[1][0] - b[0][0]), |
|
x = (w - k * (b[1][0] + b[0][0])) / 2, |
|
y = -k * b[0][1]; |
|
projection.scale(150 * k).translate([x, y]); |
|
}, object); |
|
} |
|
|
|
function _fitHeight(projection, height, object) { |
|
return fit(projection, function (b) { |
|
var h = +height, |
|
k = h / (b[1][1] - b[0][1]), |
|
x = -k * b[0][0], |
|
y = (h - k * (b[1][1] + b[0][1])) / 2; |
|
projection.scale(150 * k).translate([x, y]); |
|
}, object); |
|
} |
|
|
|
var maxDepth = 16, |
|
// maximum depth of subdivision |
|
cosMinDistance = cos$1(30 * radians); // cos(minimum angular distance) |
|
|
|
function resample(project, delta2) { |
|
return +delta2 ? resample$1(project, delta2) : resampleNone(project); |
|
} |
|
|
|
function resampleNone(project) { |
|
return transformer$3({ |
|
point: function point(x, y) { |
|
x = project(x, y); |
|
this.stream.point(x[0], x[1]); |
|
} |
|
}); |
|
} |
|
|
|
function resample$1(project, delta2) { |
|
function resampleLineTo(x0, y0, lambda0, a0, b0, c0, x1, y1, lambda1, a1, b1, c1, depth, stream) { |
|
var dx = x1 - x0, |
|
dy = y1 - y0, |
|
d2 = dx * dx + dy * dy; |
|
|
|
if (d2 > 4 * delta2 && depth--) { |
|
var a = a0 + a1, |
|
b = b0 + b1, |
|
c = c0 + c1, |
|
m = sqrt$2(a * a + b * b + c * c), |
|
phi2 = asin$1(c /= m), |
|
lambda2 = abs$1(abs$1(c) - 1) < epsilon$3 || abs$1(lambda0 - lambda1) < epsilon$3 ? (lambda0 + lambda1) / 2 : atan2$1(b, a), |
|
p = project(lambda2, phi2), |
|
x2 = p[0], |
|
y2 = p[1], |
|
dx2 = x2 - x0, |
|
dy2 = y2 - y0, |
|
dz = dy * dx2 - dx * dy2; |
|
|
|
if (dz * dz / d2 > delta2 // perpendicular projected distance |
|
|| abs$1((dx * dx2 + dy * dy2) / d2 - 0.5) > 0.3 // midpoint close to an end |
|
|| a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { |
|
// angular distance |
|
resampleLineTo(x0, y0, lambda0, a0, b0, c0, x2, y2, lambda2, a /= m, b /= m, c, depth, stream); |
|
stream.point(x2, y2); |
|
resampleLineTo(x2, y2, lambda2, a, b, c, x1, y1, lambda1, a1, b1, c1, depth, stream); |
|
} |
|
} |
|
} |
|
|
|
return function (stream) { |
|
var lambda00, x00, y00, a00, b00, c00, // first point |
|
lambda0, x0, y0, a0, b0, c0; // previous point |
|
|
|
var resampleStream = { |
|
point: point, |
|
lineStart: lineStart, |
|
lineEnd: lineEnd, |
|
polygonStart: function polygonStart() { |
|
stream.polygonStart(); |
|
resampleStream.lineStart = ringStart; |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
stream.polygonEnd(); |
|
resampleStream.lineStart = lineStart; |
|
} |
|
}; |
|
|
|
function point(x, y) { |
|
x = project(x, y); |
|
stream.point(x[0], x[1]); |
|
} |
|
|
|
function lineStart() { |
|
x0 = NaN; |
|
resampleStream.point = linePoint; |
|
stream.lineStart(); |
|
} |
|
|
|
function linePoint(lambda, phi) { |
|
var c = cartesian([lambda, phi]), |
|
p = project(lambda, phi); |
|
resampleLineTo(x0, y0, lambda0, a0, b0, c0, x0 = p[0], y0 = p[1], lambda0 = lambda, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); |
|
stream.point(x0, y0); |
|
} |
|
|
|
function lineEnd() { |
|
resampleStream.point = point; |
|
stream.lineEnd(); |
|
} |
|
|
|
function ringStart() { |
|
lineStart(); |
|
resampleStream.point = ringPoint; |
|
resampleStream.lineEnd = ringEnd; |
|
} |
|
|
|
function ringPoint(lambda, phi) { |
|
linePoint(lambda00 = lambda, phi), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; |
|
resampleStream.point = linePoint; |
|
} |
|
|
|
function ringEnd() { |
|
resampleLineTo(x0, y0, lambda0, a0, b0, c0, x00, y00, lambda00, a00, b00, c00, maxDepth, stream); |
|
resampleStream.lineEnd = lineEnd; |
|
lineEnd(); |
|
} |
|
|
|
return resampleStream; |
|
}; |
|
} |
|
|
|
var transformRadians = transformer$3({ |
|
point: function point(x, y) { |
|
this.stream.point(x * radians, y * radians); |
|
} |
|
}); |
|
|
|
function transformRotate(rotate) { |
|
return transformer$3({ |
|
point: function point(x, y) { |
|
var r = rotate(x, y); |
|
return this.stream.point(r[0], r[1]); |
|
} |
|
}); |
|
} |
|
|
|
function scaleTranslate(k, dx, dy) { |
|
function transform(x, y) { |
|
return [dx + k * x, dy - k * y]; |
|
} |
|
|
|
transform.invert = function (x, y) { |
|
return [(x - dx) / k, (dy - y) / k]; |
|
}; |
|
|
|
return transform; |
|
} |
|
|
|
function scaleTranslateRotate(k, dx, dy, alpha) { |
|
var cosAlpha = cos$1(alpha), |
|
sinAlpha = sin$1(alpha), |
|
a = cosAlpha * k, |
|
b = sinAlpha * k, |
|
ai = cosAlpha / k, |
|
bi = sinAlpha / k, |
|
ci = (sinAlpha * dy - cosAlpha * dx) / k, |
|
fi = (sinAlpha * dx + cosAlpha * dy) / k; |
|
|
|
function transform(x, y) { |
|
return [a * x - b * y + dx, dy - b * x - a * y]; |
|
} |
|
|
|
transform.invert = function (x, y) { |
|
return [ai * x - bi * y + ci, fi - bi * x - ai * y]; |
|
}; |
|
|
|
return transform; |
|
} |
|
|
|
function projection(project) { |
|
return projectionMutator(function () { |
|
return project; |
|
})(); |
|
} |
|
|
|
function projectionMutator(projectAt) { |
|
var project, |
|
k = 150, |
|
// scale |
|
x = 480, |
|
y = 250, |
|
// translate |
|
lambda = 0, |
|
phi = 0, |
|
// center |
|
deltaLambda = 0, |
|
deltaPhi = 0, |
|
deltaGamma = 0, |
|
rotate, |
|
// pre-rotate |
|
alpha = 0, |
|
// post-rotate |
|
theta = null, |
|
preclip = clipAntimeridian, |
|
// pre-clip angle |
|
x0 = null, |
|
y0, |
|
x1, |
|
y1, |
|
postclip = identity$6, |
|
// post-clip extent |
|
delta2 = 0.5, |
|
// precision |
|
projectResample, |
|
projectTransform, |
|
projectRotateTransform, |
|
cache, |
|
cacheStream; |
|
|
|
function projection(point) { |
|
return projectRotateTransform(point[0] * radians, point[1] * radians); |
|
} |
|
|
|
function invert(point) { |
|
point = projectRotateTransform.invert(point[0], point[1]); |
|
return point && [point[0] * degrees$1, point[1] * degrees$1]; |
|
} |
|
|
|
projection.stream = function (stream) { |
|
return cache && cacheStream === stream ? cache : cache = transformRadians(transformRotate(rotate)(preclip(projectResample(postclip(cacheStream = stream))))); |
|
}; |
|
|
|
projection.preclip = function (_) { |
|
return arguments.length ? (preclip = _, theta = undefined, reset()) : preclip; |
|
}; |
|
|
|
projection.postclip = function (_) { |
|
return arguments.length ? (postclip = _, x0 = y0 = x1 = y1 = null, reset()) : postclip; |
|
}; |
|
|
|
projection.clipAngle = function (_) { |
|
return arguments.length ? (preclip = +_ ? clipCircle(theta = _ * radians) : (theta = null, clipAntimeridian), reset()) : theta * degrees$1; |
|
}; |
|
|
|
projection.clipExtent = function (_) { |
|
return arguments.length ? (postclip = _ == null ? (x0 = y0 = x1 = y1 = null, identity$6) : clipRectangle(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]), reset()) : x0 == null ? null : [[x0, y0], [x1, y1]]; |
|
}; |
|
|
|
projection.scale = function (_) { |
|
return arguments.length ? (k = +_, recenter()) : k; |
|
}; |
|
|
|
projection.translate = function (_) { |
|
return arguments.length ? (x = +_[0], y = +_[1], recenter()) : [x, y]; |
|
}; |
|
|
|
projection.center = function (_) { |
|
return arguments.length ? (lambda = _[0] % 360 * radians, phi = _[1] % 360 * radians, recenter()) : [lambda * degrees$1, phi * degrees$1]; |
|
}; |
|
|
|
projection.rotate = function (_) { |
|
return arguments.length ? (deltaLambda = _[0] % 360 * radians, deltaPhi = _[1] % 360 * radians, deltaGamma = _.length > 2 ? _[2] % 360 * radians : 0, recenter()) : [deltaLambda * degrees$1, deltaPhi * degrees$1, deltaGamma * degrees$1]; |
|
}; |
|
|
|
projection.angle = function (_) { |
|
return arguments.length ? (alpha = _ % 360 * radians, recenter()) : alpha * degrees$1; |
|
}; |
|
|
|
projection.precision = function (_) { |
|
return arguments.length ? (projectResample = resample(projectTransform, delta2 = _ * _), reset()) : sqrt$2(delta2); |
|
}; |
|
|
|
projection.fitExtent = function (extent, object) { |
|
return _fitExtent(projection, extent, object); |
|
}; |
|
|
|
projection.fitSize = function (size, object) { |
|
return _fitSize(projection, size, object); |
|
}; |
|
|
|
projection.fitWidth = function (width, object) { |
|
return _fitWidth(projection, width, object); |
|
}; |
|
|
|
projection.fitHeight = function (height, object) { |
|
return _fitHeight(projection, height, object); |
|
}; |
|
|
|
function recenter() { |
|
var center = scaleTranslateRotate(k, 0, 0, alpha).apply(null, project(lambda, phi)), |
|
transform = (alpha ? scaleTranslateRotate : scaleTranslate)(k, x - center[0], y - center[1], alpha); |
|
rotate = rotateRadians(deltaLambda, deltaPhi, deltaGamma); |
|
projectTransform = compose(project, transform); |
|
projectRotateTransform = compose(rotate, projectTransform); |
|
projectResample = resample(projectTransform, delta2); |
|
return reset(); |
|
} |
|
|
|
function reset() { |
|
cache = cacheStream = null; |
|
return projection; |
|
} |
|
|
|
return function () { |
|
project = projectAt.apply(this, arguments); |
|
projection.invert = project.invert && invert; |
|
return recenter(); |
|
}; |
|
} |
|
|
|
function conicProjection(projectAt) { |
|
var phi0 = 0, |
|
phi1 = pi$2 / 3, |
|
m = projectionMutator(projectAt), |
|
p = m(phi0, phi1); |
|
|
|
p.parallels = function (_) { |
|
return arguments.length ? m(phi0 = _[0] * radians, phi1 = _[1] * radians) : [phi0 * degrees$1, phi1 * degrees$1]; |
|
}; |
|
|
|
return p; |
|
} |
|
|
|
function cylindricalEqualAreaRaw(phi0) { |
|
var cosPhi0 = cos$1(phi0); |
|
|
|
function forward(lambda, phi) { |
|
return [lambda * cosPhi0, sin$1(phi) / cosPhi0]; |
|
} |
|
|
|
forward.invert = function (x, y) { |
|
return [x / cosPhi0, asin$1(y * cosPhi0)]; |
|
}; |
|
|
|
return forward; |
|
} |
|
|
|
function conicEqualAreaRaw(y0, y1) { |
|
var sy0 = sin$1(y0), |
|
n = (sy0 + sin$1(y1)) / 2; // Are the parallels symmetrical around the Equator? |
|
|
|
if (abs$1(n) < epsilon$3) return cylindricalEqualAreaRaw(y0); |
|
var c = 1 + sy0 * (2 * n - sy0), |
|
r0 = sqrt$2(c) / n; |
|
|
|
function project(x, y) { |
|
var r = sqrt$2(c - 2 * n * sin$1(y)) / n; |
|
return [r * sin$1(x *= n), r0 - r * cos$1(x)]; |
|
} |
|
|
|
project.invert = function (x, y) { |
|
var r0y = r0 - y; |
|
return [atan2$1(x, abs$1(r0y)) / n * sign$1(r0y), asin$1((c - (x * x + r0y * r0y) * n * n) / (2 * n))]; |
|
}; |
|
|
|
return project; |
|
} |
|
|
|
function geoConicEqualArea() { |
|
return conicProjection(conicEqualAreaRaw).scale(155.424).center([0, 33.6442]); |
|
} |
|
|
|
function geoAlbers() { |
|
return geoConicEqualArea().parallels([29.5, 45.5]).scale(1070).translate([480, 250]).rotate([96, 0]).center([-0.6, 38.7]); |
|
} // The projections must have mutually exclusive clip regions on the sphere, |
|
// as this will avoid emitting interleaving lines and polygons. |
|
|
|
|
|
function multiplex(streams) { |
|
var n = streams.length; |
|
return { |
|
point: function point(x, y) { |
|
var i = -1; |
|
|
|
while (++i < n) { |
|
streams[i].point(x, y); |
|
} |
|
}, |
|
sphere: function sphere() { |
|
var i = -1; |
|
|
|
while (++i < n) { |
|
streams[i].sphere(); |
|
} |
|
}, |
|
lineStart: function lineStart() { |
|
var i = -1; |
|
|
|
while (++i < n) { |
|
streams[i].lineStart(); |
|
} |
|
}, |
|
lineEnd: function lineEnd() { |
|
var i = -1; |
|
|
|
while (++i < n) { |
|
streams[i].lineEnd(); |
|
} |
|
}, |
|
polygonStart: function polygonStart() { |
|
var i = -1; |
|
|
|
while (++i < n) { |
|
streams[i].polygonStart(); |
|
} |
|
}, |
|
polygonEnd: function polygonEnd() { |
|
var i = -1; |
|
|
|
while (++i < n) { |
|
streams[i].polygonEnd(); |
|
} |
|
} |
|
}; |
|
} // A composite projection for the United States, configured by default for |
|
// 960×500. The projection also works quite well at 960×600 if you change the |
|
// scale to 1285 and adjust the translate accordingly. The set of standard |
|
// parallels for each region comes from USGS, which is published here: |
|
// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers |
|
|
|
|
|
function geoAlbersUsa() { |
|
var cache, |
|
cacheStream, |
|
lower48 = geoAlbers(), |
|
lower48Point, |
|
alaska = geoConicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]), |
|
alaskaPoint, |
|
// EPSG:3338 |
|
hawaii = geoConicEqualArea().rotate([157, 0]).center([-3, 19.9]).parallels([8, 18]), |
|
hawaiiPoint, |
|
// ESRI:102007 |
|
_point2, |
|
pointStream = { |
|
point: function point(x, y) { |
|
_point2 = [x, y]; |
|
} |
|
}; |
|
|
|
function albersUsa(coordinates) { |
|
var x = coordinates[0], |
|
y = coordinates[1]; |
|
return _point2 = null, (lower48Point.point(x, y), _point2) || (alaskaPoint.point(x, y), _point2) || (hawaiiPoint.point(x, y), _point2); |
|
} |
|
|
|
albersUsa.invert = function (coordinates) { |
|
var k = lower48.scale(), |
|
t = lower48.translate(), |
|
x = (coordinates[0] - t[0]) / k, |
|
y = (coordinates[1] - t[1]) / k; |
|
return (y >= 0.120 && y < 0.234 && x >= -0.425 && x < -0.214 ? alaska : y >= 0.166 && y < 0.234 && x >= -0.214 && x < -0.115 ? hawaii : lower48).invert(coordinates); |
|
}; |
|
|
|
albersUsa.stream = function (stream) { |
|
return cache && cacheStream === stream ? cache : cache = multiplex([lower48.stream(cacheStream = stream), alaska.stream(stream), hawaii.stream(stream)]); |
|
}; |
|
|
|
albersUsa.precision = function (_) { |
|
if (!arguments.length) return lower48.precision(); |
|
lower48.precision(_), alaska.precision(_), hawaii.precision(_); |
|
return reset(); |
|
}; |
|
|
|
albersUsa.scale = function (_) { |
|
if (!arguments.length) return lower48.scale(); |
|
lower48.scale(_), alaska.scale(_ * 0.35), hawaii.scale(_); |
|
return albersUsa.translate(lower48.translate()); |
|
}; |
|
|
|
albersUsa.translate = function (_) { |
|
if (!arguments.length) return lower48.translate(); |
|
var k = lower48.scale(), |
|
x = +_[0], |
|
y = +_[1]; |
|
lower48Point = lower48.translate(_).clipExtent([[x - 0.455 * k, y - 0.238 * k], [x + 0.455 * k, y + 0.238 * k]]).stream(pointStream); |
|
alaskaPoint = alaska.translate([x - 0.307 * k, y + 0.201 * k]).clipExtent([[x - 0.425 * k + epsilon$3, y + 0.120 * k + epsilon$3], [x - 0.214 * k - epsilon$3, y + 0.234 * k - epsilon$3]]).stream(pointStream); |
|
hawaiiPoint = hawaii.translate([x - 0.205 * k, y + 0.212 * k]).clipExtent([[x - 0.214 * k + epsilon$3, y + 0.166 * k + epsilon$3], [x - 0.115 * k - epsilon$3, y + 0.234 * k - epsilon$3]]).stream(pointStream); |
|
return reset(); |
|
}; |
|
|
|
albersUsa.fitExtent = function (extent, object) { |
|
return _fitExtent(albersUsa, extent, object); |
|
}; |
|
|
|
albersUsa.fitSize = function (size, object) { |
|
return _fitSize(albersUsa, size, object); |
|
}; |
|
|
|
albersUsa.fitWidth = function (width, object) { |
|
return _fitWidth(albersUsa, width, object); |
|
}; |
|
|
|
albersUsa.fitHeight = function (height, object) { |
|
return _fitHeight(albersUsa, height, object); |
|
}; |
|
|
|
function reset() { |
|
cache = cacheStream = null; |
|
return albersUsa; |
|
} |
|
|
|
return albersUsa.scale(1070); |
|
} |
|
|
|
function azimuthalRaw(scale) { |
|
return function (x, y) { |
|
var cx = cos$1(x), |
|
cy = cos$1(y), |
|
k = scale(cx * cy); |
|
return [k * cy * sin$1(x), k * sin$1(y)]; |
|
}; |
|
} |
|
|
|
function azimuthalInvert(angle) { |
|
return function (x, y) { |
|
var z = sqrt$2(x * x + y * y), |
|
c = angle(z), |
|
sc = sin$1(c), |
|
cc = cos$1(c); |
|
return [atan2$1(x * sc, z * cc), asin$1(z && y * sc / z)]; |
|
}; |
|
} |
|
|
|
var azimuthalEqualAreaRaw = azimuthalRaw(function (cxcy) { |
|
return sqrt$2(2 / (1 + cxcy)); |
|
}); |
|
azimuthalEqualAreaRaw.invert = azimuthalInvert(function (z) { |
|
return 2 * asin$1(z / 2); |
|
}); |
|
|
|
function geoAzimuthalEqualArea() { |
|
return projection(azimuthalEqualAreaRaw).scale(124.75).clipAngle(180 - 1e-3); |
|
} |
|
|
|
var azimuthalEquidistantRaw = azimuthalRaw(function (c) { |
|
return (c = acos$1(c)) && c / sin$1(c); |
|
}); |
|
azimuthalEquidistantRaw.invert = azimuthalInvert(function (z) { |
|
return z; |
|
}); |
|
|
|
function geoAzimuthalEquidistant() { |
|
return projection(azimuthalEquidistantRaw).scale(79.4188).clipAngle(180 - 1e-3); |
|
} |
|
|
|
function mercatorRaw(lambda, phi) { |
|
return [lambda, log$3(tan((halfPi$1 + phi) / 2))]; |
|
} |
|
|
|
mercatorRaw.invert = function (x, y) { |
|
return [x, 2 * atan(exp$1(y)) - halfPi$1]; |
|
}; |
|
|
|
function geoMercator() { |
|
return mercatorProjection(mercatorRaw).scale(961 / tau$2); |
|
} |
|
|
|
function mercatorProjection(project) { |
|
var m = projection(project), |
|
center = m.center, |
|
scale = m.scale, |
|
translate = m.translate, |
|
clipExtent = m.clipExtent, |
|
x0 = null, |
|
y0, |
|
x1, |
|
y1; // clip extent |
|
|
|
m.scale = function (_) { |
|
return arguments.length ? (scale(_), reclip()) : scale(); |
|
}; |
|
|
|
m.translate = function (_) { |
|
return arguments.length ? (translate(_), reclip()) : translate(); |
|
}; |
|
|
|
m.center = function (_) { |
|
return arguments.length ? (center(_), reclip()) : center(); |
|
}; |
|
|
|
m.clipExtent = function (_) { |
|
return arguments.length ? (_ == null ? x0 = y0 = x1 = y1 = null : (x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]), reclip()) : x0 == null ? null : [[x0, y0], [x1, y1]]; |
|
}; |
|
|
|
function reclip() { |
|
var k = pi$2 * scale(), |
|
t = m(rotation(m.rotate()).invert([0, 0])); |
|
return clipExtent(x0 == null ? [[t[0] - k, t[1] - k], [t[0] + k, t[1] + k]] : project === mercatorRaw ? [[Math.max(t[0] - k, x0), y0], [Math.min(t[0] + k, x1), y1]] : [[x0, Math.max(t[1] - k, y0)], [x1, Math.min(t[1] + k, y1)]]); |
|
} |
|
|
|
return reclip(); |
|
} |
|
|
|
function tany(y) { |
|
return tan((halfPi$1 + y) / 2); |
|
} |
|
|
|
function conicConformalRaw(y0, y1) { |
|
var cy0 = cos$1(y0), |
|
n = y0 === y1 ? sin$1(y0) : log$3(cy0 / cos$1(y1)) / log$3(tany(y1) / tany(y0)), |
|
f = cy0 * pow$2(tany(y0), n) / n; |
|
if (!n) return mercatorRaw; |
|
|
|
function project(x, y) { |
|
if (f > 0) { |
|
if (y < -halfPi$1 + epsilon$3) y = -halfPi$1 + epsilon$3; |
|
} else { |
|
if (y > halfPi$1 - epsilon$3) y = halfPi$1 - epsilon$3; |
|
} |
|
|
|
var r = f / pow$2(tany(y), n); |
|
return [r * sin$1(n * x), f - r * cos$1(n * x)]; |
|
} |
|
|
|
project.invert = function (x, y) { |
|
var fy = f - y, |
|
r = sign$1(n) * sqrt$2(x * x + fy * fy); |
|
return [atan2$1(x, abs$1(fy)) / n * sign$1(fy), 2 * atan(pow$2(f / r, 1 / n)) - halfPi$1]; |
|
}; |
|
|
|
return project; |
|
} |
|
|
|
function geoConicConformal() { |
|
return conicProjection(conicConformalRaw).scale(109.5).parallels([30, 30]); |
|
} |
|
|
|
function equirectangularRaw(lambda, phi) { |
|
return [lambda, phi]; |
|
} |
|
|
|
equirectangularRaw.invert = equirectangularRaw; |
|
|
|
function geoEquirectangular() { |
|
return projection(equirectangularRaw).scale(152.63); |
|
} |
|
|
|
function conicEquidistantRaw(y0, y1) { |
|
var cy0 = cos$1(y0), |
|
n = y0 === y1 ? sin$1(y0) : (cy0 - cos$1(y1)) / (y1 - y0), |
|
g = cy0 / n + y0; |
|
if (abs$1(n) < epsilon$3) return equirectangularRaw; |
|
|
|
function project(x, y) { |
|
var gy = g - y, |
|
nx = n * x; |
|
return [gy * sin$1(nx), g - gy * cos$1(nx)]; |
|
} |
|
|
|
project.invert = function (x, y) { |
|
var gy = g - y; |
|
return [atan2$1(x, abs$1(gy)) / n * sign$1(gy), g - sign$1(n) * sqrt$2(x * x + gy * gy)]; |
|
}; |
|
|
|
return project; |
|
} |
|
|
|
function geoConicEquidistant() { |
|
return conicProjection(conicEquidistantRaw).scale(131.154).center([0, 13.9389]); |
|
} |
|
|
|
var A1 = 1.340264, |
|
A2 = -0.081106, |
|
A3 = 0.000893, |
|
A4 = 0.003796, |
|
M = sqrt$2(3) / 2, |
|
iterations = 12; |
|
|
|
function equalEarthRaw(lambda, phi) { |
|
var l = asin$1(M * sin$1(phi)), |
|
l2 = l * l, |
|
l6 = l2 * l2 * l2; |
|
return [lambda * cos$1(l) / (M * (A1 + 3 * A2 * l2 + l6 * (7 * A3 + 9 * A4 * l2))), l * (A1 + A2 * l2 + l6 * (A3 + A4 * l2))]; |
|
} |
|
|
|
equalEarthRaw.invert = function (x, y) { |
|
var l = y, |
|
l2 = l * l, |
|
l6 = l2 * l2 * l2; |
|
|
|
for (var i = 0, delta, fy, fpy; i < iterations; ++i) { |
|
fy = l * (A1 + A2 * l2 + l6 * (A3 + A4 * l2)) - y; |
|
fpy = A1 + 3 * A2 * l2 + l6 * (7 * A3 + 9 * A4 * l2); |
|
l -= delta = fy / fpy, l2 = l * l, l6 = l2 * l2 * l2; |
|
if (abs$1(delta) < epsilon2$1) break; |
|
} |
|
|
|
return [M * x * (A1 + 3 * A2 * l2 + l6 * (7 * A3 + 9 * A4 * l2)) / cos$1(l), asin$1(sin$1(l) / M)]; |
|
}; |
|
|
|
function geoEqualEarth() { |
|
return projection(equalEarthRaw).scale(177.158); |
|
} |
|
|
|
function gnomonicRaw(x, y) { |
|
var cy = cos$1(y), |
|
k = cos$1(x) * cy; |
|
return [cy * sin$1(x) / k, sin$1(y) / k]; |
|
} |
|
|
|
gnomonicRaw.invert = azimuthalInvert(atan); |
|
|
|
function geoGnomonic() { |
|
return projection(gnomonicRaw).scale(144.049).clipAngle(60); |
|
} |
|
|
|
function scaleTranslate$1(kx, ky, tx, ty) { |
|
return kx === 1 && ky === 1 && tx === 0 && ty === 0 ? identity$6 : transformer$3({ |
|
point: function point(x, y) { |
|
this.stream.point(x * kx + tx, y * ky + ty); |
|
} |
|
}); |
|
} |
|
|
|
function geoIdentity() { |
|
var k = 1, |
|
tx = 0, |
|
ty = 0, |
|
sx = 1, |
|
sy = 1, |
|
transform = identity$6, |
|
// scale, translate and reflect |
|
x0 = null, |
|
y0, |
|
x1, |
|
y1, |
|
// clip extent |
|
_postclip = identity$6, |
|
cache, |
|
cacheStream, |
|
projection; |
|
|
|
function reset() { |
|
cache = cacheStream = null; |
|
return projection; |
|
} |
|
|
|
return projection = { |
|
stream: function stream(_stream) { |
|
return cache && cacheStream === _stream ? cache : cache = transform(_postclip(cacheStream = _stream)); |
|
}, |
|
postclip: function postclip(_) { |
|
return arguments.length ? (_postclip = _, x0 = y0 = x1 = y1 = null, reset()) : _postclip; |
|
}, |
|
clipExtent: function clipExtent(_) { |
|
return arguments.length ? (_postclip = _ == null ? (x0 = y0 = x1 = y1 = null, identity$6) : clipRectangle(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]), reset()) : x0 == null ? null : [[x0, y0], [x1, y1]]; |
|
}, |
|
scale: function scale(_) { |
|
return arguments.length ? (transform = scaleTranslate$1((k = +_) * sx, k * sy, tx, ty), reset()) : k; |
|
}, |
|
translate: function translate(_) { |
|
return arguments.length ? (transform = scaleTranslate$1(k * sx, k * sy, tx = +_[0], ty = +_[1]), reset()) : [tx, ty]; |
|
}, |
|
reflectX: function reflectX(_) { |
|
return arguments.length ? (transform = scaleTranslate$1(k * (sx = _ ? -1 : 1), k * sy, tx, ty), reset()) : sx < 0; |
|
}, |
|
reflectY: function reflectY(_) { |
|
return arguments.length ? (transform = scaleTranslate$1(k * sx, k * (sy = _ ? -1 : 1), tx, ty), reset()) : sy < 0; |
|
}, |
|
fitExtent: function fitExtent(extent, object) { |
|
return _fitExtent(projection, extent, object); |
|
}, |
|
fitSize: function fitSize(size, object) { |
|
return _fitSize(projection, size, object); |
|
}, |
|
fitWidth: function fitWidth(width, object) { |
|
return _fitWidth(projection, width, object); |
|
}, |
|
fitHeight: function fitHeight(height, object) { |
|
return _fitHeight(projection, height, object); |
|
} |
|
}; |
|
} |
|
|
|
function naturalEarth1Raw(lambda, phi) { |
|
var phi2 = phi * phi, |
|
phi4 = phi2 * phi2; |
|
return [lambda * (0.8707 - 0.131979 * phi2 + phi4 * (-0.013791 + phi4 * (0.003971 * phi2 - 0.001529 * phi4))), phi * (1.007226 + phi2 * (0.015085 + phi4 * (-0.044475 + 0.028874 * phi2 - 0.005916 * phi4)))]; |
|
} |
|
|
|
naturalEarth1Raw.invert = function (x, y) { |
|
var phi = y, |
|
i = 25, |
|
delta; |
|
|
|
do { |
|
var phi2 = phi * phi, |
|
phi4 = phi2 * phi2; |
|
phi -= delta = (phi * (1.007226 + phi2 * (0.015085 + phi4 * (-0.044475 + 0.028874 * phi2 - 0.005916 * phi4))) - y) / (1.007226 + phi2 * (0.015085 * 3 + phi4 * (-0.044475 * 7 + 0.028874 * 9 * phi2 - 0.005916 * 11 * phi4))); |
|
} while (abs$1(delta) > epsilon$3 && --i > 0); |
|
|
|
return [x / (0.8707 + (phi2 = phi * phi) * (-0.131979 + phi2 * (-0.013791 + phi2 * phi2 * phi2 * (0.003971 - 0.001529 * phi2)))), phi]; |
|
}; |
|
|
|
function geoNaturalEarth1() { |
|
return projection(naturalEarth1Raw).scale(175.295); |
|
} |
|
|
|
function orthographicRaw(x, y) { |
|
return [cos$1(y) * sin$1(x), sin$1(y)]; |
|
} |
|
|
|
orthographicRaw.invert = azimuthalInvert(asin$1); |
|
|
|
function geoOrthographic() { |
|
return projection(orthographicRaw).scale(249.5).clipAngle(90 + epsilon$3); |
|
} |
|
|
|
function stereographicRaw(x, y) { |
|
var cy = cos$1(y), |
|
k = 1 + cos$1(x) * cy; |
|
return [cy * sin$1(x) / k, sin$1(y) / k]; |
|
} |
|
|
|
stereographicRaw.invert = azimuthalInvert(function (z) { |
|
return 2 * atan(z); |
|
}); |
|
|
|
function geoStereographic() { |
|
return projection(stereographicRaw).scale(250).clipAngle(142); |
|
} |
|
|
|
function transverseMercatorRaw(lambda, phi) { |
|
return [log$3(tan((halfPi$1 + phi) / 2)), -lambda]; |
|
} |
|
|
|
transverseMercatorRaw.invert = function (x, y) { |
|
return [-y, 2 * atan(exp$1(x)) - halfPi$1]; |
|
}; |
|
|
|
function geoTransverseMercator() { |
|
var m = mercatorProjection(transverseMercatorRaw), |
|
center = m.center, |
|
rotate = m.rotate; |
|
|
|
m.center = function (_) { |
|
return arguments.length ? center([-_[1], _[0]]) : (_ = center(), [_[1], -_[0]]); |
|
}; |
|
|
|
m.rotate = function (_) { |
|
return arguments.length ? rotate([_[0], _[1], _.length > 2 ? _[2] + 90 : 90]) : (_ = rotate(), [_[0], _[1], _[2] - 90]); |
|
}; |
|
|
|
return rotate([0, 0, 90]).scale(159.155); |
|
} |
|
|
|
var abs$2 = Math.abs; |
|
var cos$2 = Math.cos; |
|
var sin$2 = Math.sin; |
|
var epsilon$4 = 1e-6; |
|
var pi$3 = Math.PI; |
|
var halfPi$2 = pi$3 / 2; |
|
var sqrt2 = sqrt$3(2); |
|
|
|
function asin$2(x) { |
|
return x > 1 ? halfPi$2 : x < -1 ? -halfPi$2 : Math.asin(x); |
|
} |
|
|
|
function sqrt$3(x) { |
|
return x > 0 ? Math.sqrt(x) : 0; |
|
} |
|
|
|
function mollweideBromleyTheta(cp, phi) { |
|
var cpsinPhi = cp * sin$2(phi), |
|
i = 30, |
|
delta; |
|
|
|
do { |
|
phi -= delta = (phi + sin$2(phi) - cpsinPhi) / (1 + cos$2(phi)); |
|
} while (abs$2(delta) > epsilon$4 && --i > 0); |
|
|
|
return phi / 2; |
|
} |
|
|
|
function mollweideBromleyRaw(cx, cy, cp) { |
|
function forward(lambda, phi) { |
|
return [cx * lambda * cos$2(phi = mollweideBromleyTheta(cp, phi)), cy * sin$2(phi)]; |
|
} |
|
|
|
forward.invert = function (x, y) { |
|
return y = asin$2(y / cy), [x / (cx * cos$2(y)), asin$2((2 * y + sin$2(2 * y)) / cp)]; |
|
}; |
|
|
|
return forward; |
|
} |
|
|
|
var mollweideRaw = mollweideBromleyRaw(sqrt2 / halfPi$2, sqrt2, pi$3); |
|
|
|
function geoMollweide() { |
|
return projection(mollweideRaw).scale(169.529); |
|
} |
|
|
|
var defaultPath = geoPath(); |
|
var projectionProperties = [// standard properties in d3-geo |
|
'clipAngle', 'clipExtent', 'scale', 'translate', 'center', 'rotate', 'parallels', 'precision', 'reflectX', 'reflectY', // extended properties in d3-geo-projections |
|
'coefficient', 'distance', 'fraction', 'lobes', 'parallel', 'radius', 'ratio', 'spacing', 'tilt']; |
|
/** |
|
* Augment projections with their type and a copy method. |
|
*/ |
|
|
|
function create$1(type, constructor) { |
|
return function projection() { |
|
var p = constructor(); |
|
p.type = type; |
|
p.path = geoPath().projection(p); |
|
|
|
p.copy = p.copy || function () { |
|
var c = projection(); |
|
projectionProperties.forEach(function (prop) { |
|
if (p[prop]) c[prop](p[prop]()); |
|
}); |
|
c.path.pointRadius(p.path.pointRadius()); |
|
return c; |
|
}; |
|
|
|
return p; |
|
}; |
|
} |
|
|
|
function projection$1(type, proj) { |
|
if (!type || typeof type !== 'string') { |
|
throw new Error('Projection type must be a name string.'); |
|
} |
|
|
|
type = type.toLowerCase(); |
|
|
|
if (arguments.length > 1) { |
|
projections[type] = create$1(type, proj); |
|
return this; |
|
} else { |
|
return projections[type] || null; |
|
} |
|
} |
|
|
|
function getProjectionPath(proj) { |
|
return proj && proj.path || defaultPath; |
|
} |
|
|
|
var projections = { |
|
// base d3-geo projection types |
|
albers: geoAlbers, |
|
albersusa: geoAlbersUsa, |
|
azimuthalequalarea: geoAzimuthalEqualArea, |
|
azimuthalequidistant: geoAzimuthalEquidistant, |
|
conicconformal: geoConicConformal, |
|
conicequalarea: geoConicEqualArea, |
|
conicequidistant: geoConicEquidistant, |
|
equalEarth: geoEqualEarth, |
|
equirectangular: geoEquirectangular, |
|
gnomonic: geoGnomonic, |
|
identity: geoIdentity, |
|
mercator: geoMercator, |
|
mollweide: geoMollweide, |
|
naturalEarth1: geoNaturalEarth1, |
|
orthographic: geoOrthographic, |
|
stereographic: geoStereographic, |
|
transversemercator: geoTransverseMercator |
|
}; |
|
|
|
for (var key$1 in projections) { |
|
projection$1(key$1, projections[key$1]); |
|
} |
|
/** |
|
* Map GeoJSON data to an SVG path string. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(number, number): *} params.projection - The cartographic |
|
* projection to apply. |
|
* @param {function(object): *} [params.field] - The field with GeoJSON data, |
|
* or null if the tuple itself is a GeoJSON feature. |
|
* @param {string} [params.as='path'] - The output field in which to store |
|
* the generated path data (default 'path'). |
|
*/ |
|
|
|
|
|
function GeoPath(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
GeoPath.Definition = { |
|
"type": "GeoPath", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "projection", |
|
"type": "projection" |
|
}, { |
|
"name": "field", |
|
"type": "field" |
|
}, { |
|
"name": "pointRadius", |
|
"type": "number", |
|
"expr": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"default": "path" |
|
}] |
|
}; |
|
var prototype$17 = inherits(GeoPath, Transform); |
|
|
|
prototype$17.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.ALL), |
|
path = this.value, |
|
field = _.field || identity, |
|
as = _.as || 'path', |
|
flag = out.SOURCE; |
|
|
|
function set(t) { |
|
t[as] = path(field(t)); |
|
} |
|
|
|
if (!path || _.modified()) { |
|
// parameters updated, reset and reflow |
|
this.value = path = getProjectionPath(_.projection); |
|
out.materialize().reflow(); |
|
} else { |
|
flag = field === identity || pulse.modified(field.fields) ? out.ADD_MOD : out.ADD; |
|
} |
|
|
|
var prev = initPath(path, _.pointRadius); |
|
out.visit(flag, set); |
|
path.pointRadius(prev); |
|
return out.modifies(as); |
|
}; |
|
|
|
function initPath(path, pointRadius) { |
|
var prev = path.pointRadius(); |
|
path.context(null); |
|
|
|
if (pointRadius != null) { |
|
path.pointRadius(pointRadius); |
|
} |
|
|
|
return prev; |
|
} |
|
/** |
|
* Geo-code a longitude/latitude point to an x/y coordinate. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(number, number): *} params.projection - The cartographic |
|
* projection to apply. |
|
* @param {Array<function(object): *>} params.fields - A two-element array of |
|
* field accessors for the longitude and latitude values. |
|
* @param {Array<string>} [params.as] - A two-element array of field names |
|
* under which to store the result. Defaults to ['x','y']. |
|
*/ |
|
|
|
|
|
function GeoPoint(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
GeoPoint.Definition = { |
|
"type": "GeoPoint", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "projection", |
|
"type": "projection", |
|
"required": true |
|
}, { |
|
"name": "fields", |
|
"type": "field", |
|
"array": true, |
|
"required": true, |
|
"length": 2 |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": 2, |
|
"default": ["x", "y"] |
|
}] |
|
}; |
|
var prototype$18 = inherits(GeoPoint, Transform); |
|
|
|
prototype$18.transform = function (_, pulse) { |
|
var proj = _.projection, |
|
lon = _.fields[0], |
|
lat = _.fields[1], |
|
as = _.as || ['x', 'y'], |
|
x = as[0], |
|
y = as[1], |
|
mod; |
|
|
|
function set(t) { |
|
var xy = proj([lon(t), lat(t)]); |
|
|
|
if (xy) { |
|
t[x] = xy[0]; |
|
t[y] = xy[1]; |
|
} else { |
|
t[x] = undefined; |
|
t[y] = undefined; |
|
} |
|
} |
|
|
|
if (_.modified()) { |
|
// parameters updated, reflow |
|
pulse = pulse.materialize().reflow(true).visit(pulse.SOURCE, set); |
|
} else { |
|
mod = pulse.modified(lon.fields) || pulse.modified(lat.fields); |
|
pulse.visit(mod ? pulse.ADD_MOD : pulse.ADD, set); |
|
} |
|
|
|
return pulse.modifies(as); |
|
}; |
|
/** |
|
* Annotate items with a geopath shape generator. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(number, number): *} params.projection - The cartographic |
|
* projection to apply. |
|
* @param {function(object): *} [params.field] - The field with GeoJSON data, |
|
* or null if the tuple itself is a GeoJSON feature. |
|
* @param {string} [params.as='shape'] - The output field in which to store |
|
* the generated path data (default 'shape'). |
|
*/ |
|
|
|
|
|
function GeoShape(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
GeoShape.Definition = { |
|
"type": "GeoShape", |
|
"metadata": { |
|
"modifies": true, |
|
"nomod": true |
|
}, |
|
"params": [{ |
|
"name": "projection", |
|
"type": "projection" |
|
}, { |
|
"name": "field", |
|
"type": "field", |
|
"default": "datum" |
|
}, { |
|
"name": "pointRadius", |
|
"type": "number", |
|
"expr": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"default": "shape" |
|
}] |
|
}; |
|
var prototype$19 = inherits(GeoShape, Transform); |
|
|
|
prototype$19.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.ALL), |
|
shape = this.value, |
|
as = _.as || 'shape', |
|
flag = out.ADD; |
|
|
|
if (!shape || _.modified()) { |
|
// parameters updated, reset and reflow |
|
this.value = shape = shapeGenerator(getProjectionPath(_.projection), _.field || field('datum'), _.pointRadius); |
|
out.materialize().reflow(); |
|
flag = out.SOURCE; |
|
} |
|
|
|
out.visit(flag, function (t) { |
|
t[as] = shape; |
|
}); |
|
return out.modifies(as); |
|
}; |
|
|
|
function shapeGenerator(path, field, pointRadius) { |
|
var shape = pointRadius == null ? function (_) { |
|
return path(field(_)); |
|
} : function (_) { |
|
var prev = path.pointRadius(), |
|
value = path.pointRadius(pointRadius)(field(_)); |
|
path.pointRadius(prev); |
|
return value; |
|
}; |
|
|
|
shape.context = function (_) { |
|
path.context(_); |
|
return shape; |
|
}; |
|
|
|
return shape; |
|
} |
|
/** |
|
* GeoJSON feature generator for creating graticules. |
|
* @constructor |
|
*/ |
|
|
|
|
|
function Graticule(params) { |
|
Transform.call(this, [], params); |
|
this.generator = graticule(); |
|
} |
|
|
|
Graticule.Definition = { |
|
"type": "Graticule", |
|
"metadata": { |
|
"changes": true, |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "extent", |
|
"type": "array", |
|
"array": true, |
|
"length": 2, |
|
"content": { |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
} |
|
}, { |
|
"name": "extentMajor", |
|
"type": "array", |
|
"array": true, |
|
"length": 2, |
|
"content": { |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
} |
|
}, { |
|
"name": "extentMinor", |
|
"type": "array", |
|
"array": true, |
|
"length": 2, |
|
"content": { |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
} |
|
}, { |
|
"name": "step", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "stepMajor", |
|
"type": "number", |
|
"array": true, |
|
"length": 2, |
|
"default": [90, 360] |
|
}, { |
|
"name": "stepMinor", |
|
"type": "number", |
|
"array": true, |
|
"length": 2, |
|
"default": [10, 10] |
|
}, { |
|
"name": "precision", |
|
"type": "number", |
|
"default": 2.5 |
|
}] |
|
}; |
|
var prototype$1a = inherits(Graticule, Transform); |
|
|
|
prototype$1a.transform = function (_, pulse) { |
|
var src = this.value, |
|
gen = this.generator, |
|
t; |
|
|
|
if (!src.length || _.modified()) { |
|
for (var prop in _) { |
|
if (isFunction(gen[prop])) { |
|
gen[prop](_[prop]); |
|
} |
|
} |
|
} |
|
|
|
t = gen(); |
|
|
|
if (src.length) { |
|
pulse.mod.push(replace(src[0], t)); |
|
} else { |
|
pulse.add.push(ingest(t)); |
|
} |
|
|
|
src[0] = t; |
|
return pulse; |
|
}; |
|
/** |
|
* Render a heatmap image for input raster grid data. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} [params.field] - The field with raster grid |
|
* data. If unspecified, the tuple itself is interpreted as a raster grid. |
|
* @param {string} [params.color] - A constant color value or function for |
|
* individual pixel color. If a function, it will be invoked with an input |
|
* object that includes $x, $y, $value, and $max fields for the grid. |
|
* @param {number} [params.opacity] - A constant opacity value or function for |
|
* individual pixel opacity. If a function, it will be invoked with an input |
|
* object that includes $x, $y, $value, and $max fields for the grid. |
|
* @param {string} [params.resolve] - The method for resolving maximum values |
|
* across multiple input grids. If 'independent' (the default), maximum |
|
* calculation will be performed separately for each grid. If 'shared', |
|
* a single global maximum will be used for all input grids. |
|
* @param {string} [params.as='image'] - The output field in which to store |
|
* the generated bitmap canvas images (default 'image'). |
|
*/ |
|
|
|
|
|
function Heatmap(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Heatmap.Definition = { |
|
"type": "heatmap", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field" |
|
}, { |
|
"name": "color", |
|
"type": "string", |
|
"expr": true |
|
}, { |
|
"name": "opacity", |
|
"type": "number", |
|
"expr": true |
|
}, { |
|
"name": "resolve", |
|
"type": "enum", |
|
"values": ["shared", "independent"], |
|
"default": "independent" |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"default": "image" |
|
}] |
|
}; |
|
var prototype$1b = inherits(Heatmap, Transform); |
|
|
|
prototype$1b.transform = function (_, pulse) { |
|
if (!pulse.changed() && !_.modified()) { |
|
return pulse.StopPropagation; |
|
} |
|
|
|
var source = pulse.materialize(pulse.SOURCE).source, |
|
shared = _.resolve === 'shared', |
|
field = _.field || identity, |
|
opacity = opacity_(_.opacity, _), |
|
color = color_(_.color, _), |
|
as = _.as || 'image', |
|
obj = { |
|
$x: 0, |
|
$y: 0, |
|
$value: 0, |
|
$max: shared ? max(source.map(function (t) { |
|
return max(field(t).values); |
|
})) : 0 |
|
}; |
|
source.forEach(function (t) { |
|
var v = field(t); // build proxy data object |
|
|
|
var o = extend({}, t, obj); // set maximum value if not globally shared |
|
|
|
if (!shared) o.$max = max(v.values || []); // generate canvas image |
|
// optimize color/opacity if not pixel-dependent |
|
|
|
t[as] = toCanvas(v, o, color.dep ? color : constant(color(o)), opacity.dep ? opacity : constant(opacity(o))); |
|
}); |
|
return pulse.reflow(true).modifies(as); |
|
}; // get image color function |
|
|
|
|
|
function color_(color, _) { |
|
var f; |
|
|
|
if (isFunction(color)) { |
|
f = function f(obj) { |
|
return rgb(color(obj, _)); |
|
}; |
|
|
|
f.dep = dependency(color); |
|
} else { |
|
// default to mid-grey |
|
f = constant(rgb(color || '#888')); |
|
} |
|
|
|
return f; |
|
} // get image opacity function |
|
|
|
|
|
function opacity_(opacity, _) { |
|
var f; |
|
|
|
if (isFunction(opacity)) { |
|
f = function f(obj) { |
|
return opacity(obj, _); |
|
}; |
|
|
|
f.dep = dependency(opacity); |
|
} else if (opacity) { |
|
f = constant(opacity); |
|
} else { |
|
// default to [0, max] opacity gradient |
|
f = function f(obj) { |
|
return obj.$value / obj.$max || 0; |
|
}; |
|
|
|
f.dep = true; |
|
} |
|
|
|
return f; |
|
} // check if function depends on individual pixel data |
|
|
|
|
|
function dependency(f) { |
|
if (!isFunction(f)) return false; |
|
var set = toSet(accessorFields(f)); |
|
return set.$x || set.$y || set.$value || set.$max; |
|
} // render raster grid to canvas |
|
|
|
|
|
function toCanvas(grid, obj, color, opacity) { |
|
var n = grid.width, |
|
m = grid.height, |
|
x1 = grid.x1 || 0, |
|
y1 = grid.y1 || 0, |
|
x2 = grid.x2 || n, |
|
y2 = grid.y2 || m, |
|
val = grid.values, |
|
value = val ? function (i) { |
|
return val[i]; |
|
} : zero, |
|
can = domCanvas(x2 - x1, y2 - y1), |
|
ctx = can.getContext('2d'), |
|
img = ctx.getImageData(0, 0, x2 - x1, y2 - y1), |
|
pix = img.data; |
|
|
|
for (var j = y1, k = 0; j < y2; ++j) { |
|
obj.$y = j - y1; |
|
|
|
for (var i = x1, r = j * n; i < x2; ++i, k += 4) { |
|
obj.$x = i - x1; |
|
obj.$value = value(i + r); |
|
|
|
var _v3 = color(obj); |
|
|
|
pix[k + 0] = _v3.r; |
|
pix[k + 1] = _v3.g; |
|
pix[k + 2] = _v3.b; |
|
pix[k + 3] = ~~(255 * opacity(obj)); |
|
} |
|
} |
|
|
|
ctx.putImageData(img, 0, 0); |
|
return can; |
|
} |
|
/** |
|
* Maintains a cartographic projection. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
*/ |
|
|
|
|
|
function Projection(params) { |
|
Transform.call(this, null, params); |
|
this.modified(true); // always treat as modified |
|
} |
|
|
|
var prototype$1c = inherits(Projection, Transform); |
|
|
|
prototype$1c.transform = function (_, pulse) { |
|
var proj = this.value; |
|
|
|
if (!proj || _.modified('type')) { |
|
this.value = proj = create$2(_.type); |
|
projectionProperties.forEach(function (prop) { |
|
if (_[prop] != null) set$1(proj, prop, _[prop]); |
|
}); |
|
} else { |
|
projectionProperties.forEach(function (prop) { |
|
if (_.modified(prop)) set$1(proj, prop, _[prop]); |
|
}); |
|
} |
|
|
|
if (_.pointRadius != null) proj.path.pointRadius(_.pointRadius); |
|
if (_.fit) fit$1(proj, _); |
|
return pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS); |
|
}; |
|
|
|
function fit$1(proj, _) { |
|
var data = collectGeoJSON(_.fit); |
|
_.extent ? proj.fitExtent(_.extent, data) : _.size ? proj.fitSize(_.size, data) : 0; |
|
} |
|
|
|
function create$2(type) { |
|
var constructor = projection$1((type || 'mercator').toLowerCase()); |
|
if (!constructor) error('Unrecognized projection type: ' + type); |
|
return constructor(); |
|
} |
|
|
|
function set$1(proj, key, value) { |
|
if (isFunction(proj[key])) proj[key](value); |
|
} |
|
|
|
function collectGeoJSON(data) { |
|
data = array(data); |
|
return data.length === 1 ? data[0] : { |
|
type: FeatureCollection, |
|
features: data.reduce(function (a, f) { |
|
return a.concat(featurize(f)); |
|
}, []) |
|
}; |
|
} |
|
|
|
function featurize(f) { |
|
return f.type === FeatureCollection ? f.features : array(f).filter(function (d) { |
|
return d != null; |
|
}).map(function (d) { |
|
return d.type === Feature ? d : { |
|
type: Feature, |
|
geometry: d |
|
}; |
|
}); |
|
} |
|
|
|
var geo = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
contour: Contour, |
|
geojson: GeoJSON, |
|
geopath: GeoPath, |
|
geopoint: GeoPoint, |
|
geoshape: GeoShape, |
|
graticule: Graticule, |
|
heatmap: Heatmap, |
|
isocontour: Isocontour, |
|
kde2d: KDE2D, |
|
projection: Projection |
|
}); |
|
|
|
function forceCenter(x, y) { |
|
var nodes; |
|
if (x == null) x = 0; |
|
if (y == null) y = 0; |
|
|
|
function force() { |
|
var i, |
|
n = nodes.length, |
|
node, |
|
sx = 0, |
|
sy = 0; |
|
|
|
for (i = 0; i < n; ++i) { |
|
node = nodes[i], sx += node.x, sy += node.y; |
|
} |
|
|
|
for (sx = sx / n - x, sy = sy / n - y, i = 0; i < n; ++i) { |
|
node = nodes[i], node.x -= sx, node.y -= sy; |
|
} |
|
} |
|
|
|
force.initialize = function (_) { |
|
nodes = _; |
|
}; |
|
|
|
force.x = function (_) { |
|
return arguments.length ? (x = +_, force) : x; |
|
}; |
|
|
|
force.y = function (_) { |
|
return arguments.length ? (y = +_, force) : y; |
|
}; |
|
|
|
return force; |
|
} |
|
|
|
function tree_add(d) { |
|
var x = +this._x.call(null, d), |
|
y = +this._y.call(null, d); |
|
return add$3(this.cover(x, y), x, y, d); |
|
} |
|
|
|
function add$3(tree, x, y, d) { |
|
if (isNaN(x) || isNaN(y)) return tree; // ignore invalid points |
|
|
|
var parent, |
|
node = tree._root, |
|
leaf = { |
|
data: d |
|
}, |
|
x0 = tree._x0, |
|
y0 = tree._y0, |
|
x1 = tree._x1, |
|
y1 = tree._y1, |
|
xm, |
|
ym, |
|
xp, |
|
yp, |
|
right, |
|
bottom, |
|
i, |
|
j; // If the tree is empty, initialize the root as a leaf. |
|
|
|
if (!node) return tree._root = leaf, tree; // Find the existing leaf for the new point, or add it. |
|
|
|
while (node.length) { |
|
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm;else x1 = xm; |
|
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym;else y1 = ym; |
|
if (parent = node, !(node = node[i = bottom << 1 | right])) return parent[i] = leaf, tree; |
|
} // Is the new point is exactly coincident with the existing point? |
|
|
|
|
|
xp = +tree._x.call(null, node.data); |
|
yp = +tree._y.call(null, node.data); |
|
if (x === xp && y === yp) return leaf.next = node, parent ? parent[i] = leaf : tree._root = leaf, tree; // Otherwise, split the leaf node until the old and new point are separated. |
|
|
|
do { |
|
parent = parent ? parent[i] = new Array(4) : tree._root = new Array(4); |
|
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm;else x1 = xm; |
|
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym;else y1 = ym; |
|
} while ((i = bottom << 1 | right) === (j = (yp >= ym) << 1 | xp >= xm)); |
|
|
|
return parent[j] = node, parent[i] = leaf, tree; |
|
} |
|
|
|
function addAll(data) { |
|
var d, |
|
i, |
|
n = data.length, |
|
x, |
|
y, |
|
xz = new Array(n), |
|
yz = new Array(n), |
|
x0 = Infinity, |
|
y0 = Infinity, |
|
x1 = -Infinity, |
|
y1 = -Infinity; // Compute the points and their extent. |
|
|
|
for (i = 0; i < n; ++i) { |
|
if (isNaN(x = +this._x.call(null, d = data[i])) || isNaN(y = +this._y.call(null, d))) continue; |
|
xz[i] = x; |
|
yz[i] = y; |
|
if (x < x0) x0 = x; |
|
if (x > x1) x1 = x; |
|
if (y < y0) y0 = y; |
|
if (y > y1) y1 = y; |
|
} // If there were no (valid) points, abort. |
|
|
|
|
|
if (x0 > x1 || y0 > y1) return this; // Expand the tree to cover the new points. |
|
|
|
this.cover(x0, y0).cover(x1, y1); // Add the new points. |
|
|
|
for (i = 0; i < n; ++i) { |
|
add$3(this, xz[i], yz[i], data[i]); |
|
} |
|
|
|
return this; |
|
} |
|
|
|
function tree_cover(x, y) { |
|
if (isNaN(x = +x) || isNaN(y = +y)) return this; // ignore invalid points |
|
|
|
var x0 = this._x0, |
|
y0 = this._y0, |
|
x1 = this._x1, |
|
y1 = this._y1; // If the quadtree has no extent, initialize them. |
|
// Integer extent are necessary so that if we later double the extent, |
|
// the existing quadrant boundaries don’t change due to floating point error! |
|
|
|
if (isNaN(x0)) { |
|
x1 = (x0 = Math.floor(x)) + 1; |
|
y1 = (y0 = Math.floor(y)) + 1; |
|
} // Otherwise, double repeatedly to cover. |
|
else { |
|
var z = x1 - x0, |
|
node = this._root, |
|
parent, |
|
i; |
|
|
|
while (x0 > x || x >= x1 || y0 > y || y >= y1) { |
|
i = (y < y0) << 1 | x < x0; |
|
parent = new Array(4), parent[i] = node, node = parent, z *= 2; |
|
|
|
switch (i) { |
|
case 0: |
|
x1 = x0 + z, y1 = y0 + z; |
|
break; |
|
|
|
case 1: |
|
x0 = x1 - z, y1 = y0 + z; |
|
break; |
|
|
|
case 2: |
|
x1 = x0 + z, y0 = y1 - z; |
|
break; |
|
|
|
case 3: |
|
x0 = x1 - z, y0 = y1 - z; |
|
break; |
|
} |
|
} |
|
|
|
if (this._root && this._root.length) this._root = node; |
|
} |
|
|
|
this._x0 = x0; |
|
this._y0 = y0; |
|
this._x1 = x1; |
|
this._y1 = y1; |
|
return this; |
|
} |
|
|
|
function tree_data() { |
|
var data = []; |
|
this.visit(function (node) { |
|
if (!node.length) do { |
|
data.push(node.data); |
|
} while (node = node.next); |
|
}); |
|
return data; |
|
} |
|
|
|
function tree_extent(_) { |
|
return arguments.length ? this.cover(+_[0][0], +_[0][1]).cover(+_[1][0], +_[1][1]) : isNaN(this._x0) ? undefined : [[this._x0, this._y0], [this._x1, this._y1]]; |
|
} |
|
|
|
function Quad(node, x0, y0, x1, y1) { |
|
this.node = node; |
|
this.x0 = x0; |
|
this.y0 = y0; |
|
this.x1 = x1; |
|
this.y1 = y1; |
|
} |
|
|
|
function tree_find(x, y, radius) { |
|
var data, |
|
x0 = this._x0, |
|
y0 = this._y0, |
|
x1, |
|
y1, |
|
x2, |
|
y2, |
|
x3 = this._x1, |
|
y3 = this._y1, |
|
quads = [], |
|
node = this._root, |
|
q, |
|
i; |
|
if (node) quads.push(new Quad(node, x0, y0, x3, y3)); |
|
if (radius == null) radius = Infinity;else { |
|
x0 = x - radius, y0 = y - radius; |
|
x3 = x + radius, y3 = y + radius; |
|
radius *= radius; |
|
} |
|
|
|
while (q = quads.pop()) { |
|
// Stop searching if this quadrant can’t contain a closer node. |
|
if (!(node = q.node) || (x1 = q.x0) > x3 || (y1 = q.y0) > y3 || (x2 = q.x1) < x0 || (y2 = q.y1) < y0) continue; // Bisect the current quadrant. |
|
|
|
if (node.length) { |
|
var xm = (x1 + x2) / 2, |
|
ym = (y1 + y2) / 2; |
|
quads.push(new Quad(node[3], xm, ym, x2, y2), new Quad(node[2], x1, ym, xm, y2), new Quad(node[1], xm, y1, x2, ym), new Quad(node[0], x1, y1, xm, ym)); // Visit the closest quadrant first. |
|
|
|
if (i = (y >= ym) << 1 | x >= xm) { |
|
q = quads[quads.length - 1]; |
|
quads[quads.length - 1] = quads[quads.length - 1 - i]; |
|
quads[quads.length - 1 - i] = q; |
|
} |
|
} // Visit this point. (Visiting coincident points isn’t necessary!) |
|
else { |
|
var dx = x - +this._x.call(null, node.data), |
|
dy = y - +this._y.call(null, node.data), |
|
d2 = dx * dx + dy * dy; |
|
|
|
if (d2 < radius) { |
|
var d = Math.sqrt(radius = d2); |
|
x0 = x - d, y0 = y - d; |
|
x3 = x + d, y3 = y + d; |
|
data = node.data; |
|
} |
|
} |
|
} |
|
|
|
return data; |
|
} |
|
|
|
function tree_remove(d) { |
|
if (isNaN(x = +this._x.call(null, d)) || isNaN(y = +this._y.call(null, d))) return this; // ignore invalid points |
|
|
|
var parent, |
|
node = this._root, |
|
retainer, |
|
previous, |
|
next, |
|
x0 = this._x0, |
|
y0 = this._y0, |
|
x1 = this._x1, |
|
y1 = this._y1, |
|
x, |
|
y, |
|
xm, |
|
ym, |
|
right, |
|
bottom, |
|
i, |
|
j; // If the tree is empty, initialize the root as a leaf. |
|
|
|
if (!node) return this; // Find the leaf node for the point. |
|
// While descending, also retain the deepest parent with a non-removed sibling. |
|
|
|
if (node.length) while (true) { |
|
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm;else x1 = xm; |
|
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym;else y1 = ym; |
|
if (!(parent = node, node = node[i = bottom << 1 | right])) return this; |
|
if (!node.length) break; |
|
if (parent[i + 1 & 3] || parent[i + 2 & 3] || parent[i + 3 & 3]) retainer = parent, j = i; |
|
} // Find the point to remove. |
|
|
|
while (node.data !== d) { |
|
if (!(previous = node, node = node.next)) return this; |
|
} |
|
|
|
if (next = node.next) delete node.next; // If there are multiple coincident points, remove just the point. |
|
|
|
if (previous) return next ? previous.next = next : delete previous.next, this; // If this is the root point, remove it. |
|
|
|
if (!parent) return this._root = next, this; // Remove this leaf. |
|
|
|
next ? parent[i] = next : delete parent[i]; // If the parent now contains exactly one leaf, collapse superfluous parents. |
|
|
|
if ((node = parent[0] || parent[1] || parent[2] || parent[3]) && node === (parent[3] || parent[2] || parent[1] || parent[0]) && !node.length) { |
|
if (retainer) retainer[j] = node;else this._root = node; |
|
} |
|
|
|
return this; |
|
} |
|
|
|
function removeAll(data) { |
|
for (var i = 0, n = data.length; i < n; ++i) { |
|
this.remove(data[i]); |
|
} |
|
|
|
return this; |
|
} |
|
|
|
function tree_root() { |
|
return this._root; |
|
} |
|
|
|
function tree_size() { |
|
var size = 0; |
|
this.visit(function (node) { |
|
if (!node.length) do { |
|
++size; |
|
} while (node = node.next); |
|
}); |
|
return size; |
|
} |
|
|
|
function tree_visit(callback) { |
|
var quads = [], |
|
q, |
|
node = this._root, |
|
child, |
|
x0, |
|
y0, |
|
x1, |
|
y1; |
|
if (node) quads.push(new Quad(node, this._x0, this._y0, this._x1, this._y1)); |
|
|
|
while (q = quads.pop()) { |
|
if (!callback(node = q.node, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1) && node.length) { |
|
var xm = (x0 + x1) / 2, |
|
ym = (y0 + y1) / 2; |
|
if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1)); |
|
if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1)); |
|
if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym)); |
|
if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym)); |
|
} |
|
} |
|
|
|
return this; |
|
} |
|
|
|
function tree_visitAfter(callback) { |
|
var quads = [], |
|
next = [], |
|
q; |
|
if (this._root) quads.push(new Quad(this._root, this._x0, this._y0, this._x1, this._y1)); |
|
|
|
while (q = quads.pop()) { |
|
var node = q.node; |
|
|
|
if (node.length) { |
|
var child, |
|
x0 = q.x0, |
|
y0 = q.y0, |
|
x1 = q.x1, |
|
y1 = q.y1, |
|
xm = (x0 + x1) / 2, |
|
ym = (y0 + y1) / 2; |
|
if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym)); |
|
if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym)); |
|
if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1)); |
|
if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1)); |
|
} |
|
|
|
next.push(q); |
|
} |
|
|
|
while (q = next.pop()) { |
|
callback(q.node, q.x0, q.y0, q.x1, q.y1); |
|
} |
|
|
|
return this; |
|
} |
|
|
|
function defaultX(d) { |
|
return d[0]; |
|
} |
|
|
|
function tree_x(_) { |
|
return arguments.length ? (this._x = _, this) : this._x; |
|
} |
|
|
|
function defaultY(d) { |
|
return d[1]; |
|
} |
|
|
|
function tree_y(_) { |
|
return arguments.length ? (this._y = _, this) : this._y; |
|
} |
|
|
|
function quadtree(nodes, x, y) { |
|
var tree = new Quadtree(x == null ? defaultX : x, y == null ? defaultY : y, NaN, NaN, NaN, NaN); |
|
return nodes == null ? tree : tree.addAll(nodes); |
|
} |
|
|
|
function Quadtree(x, y, x0, y0, x1, y1) { |
|
this._x = x; |
|
this._y = y; |
|
this._x0 = x0; |
|
this._y0 = y0; |
|
this._x1 = x1; |
|
this._y1 = y1; |
|
this._root = undefined; |
|
} |
|
|
|
function leaf_copy(leaf) { |
|
var copy = { |
|
data: leaf.data |
|
}, |
|
next = copy; |
|
|
|
while (leaf = leaf.next) { |
|
next = next.next = { |
|
data: leaf.data |
|
}; |
|
} |
|
|
|
return copy; |
|
} |
|
|
|
var treeProto = quadtree.prototype = Quadtree.prototype; |
|
|
|
treeProto.copy = function () { |
|
var copy = new Quadtree(this._x, this._y, this._x0, this._y0, this._x1, this._y1), |
|
node = this._root, |
|
nodes, |
|
child; |
|
if (!node) return copy; |
|
if (!node.length) return copy._root = leaf_copy(node), copy; |
|
nodes = [{ |
|
source: node, |
|
target: copy._root = new Array(4) |
|
}]; |
|
|
|
while (node = nodes.pop()) { |
|
for (var i = 0; i < 4; ++i) { |
|
if (child = node.source[i]) { |
|
if (child.length) nodes.push({ |
|
source: child, |
|
target: node.target[i] = new Array(4) |
|
});else node.target[i] = leaf_copy(child); |
|
} |
|
} |
|
} |
|
|
|
return copy; |
|
}; |
|
|
|
treeProto.add = tree_add; |
|
treeProto.addAll = addAll; |
|
treeProto.cover = tree_cover; |
|
treeProto.data = tree_data; |
|
treeProto.extent = tree_extent; |
|
treeProto.find = tree_find; |
|
treeProto.remove = tree_remove; |
|
treeProto.removeAll = removeAll; |
|
treeProto.root = tree_root; |
|
treeProto.size = tree_size; |
|
treeProto.visit = tree_visit; |
|
treeProto.visitAfter = tree_visitAfter; |
|
treeProto.x = tree_x; |
|
treeProto.y = tree_y; |
|
|
|
function constant$4(x) { |
|
return function () { |
|
return x; |
|
}; |
|
} |
|
|
|
function jiggle() { |
|
return (Math.random() - 0.5) * 1e-6; |
|
} |
|
|
|
function x$2(d) { |
|
return d.x + d.vx; |
|
} |
|
|
|
function y$2(d) { |
|
return d.y + d.vy; |
|
} |
|
|
|
function forceCollide(radius) { |
|
var nodes, |
|
radii, |
|
strength = 1, |
|
iterations = 1; |
|
if (typeof radius !== "function") radius = constant$4(radius == null ? 1 : +radius); |
|
|
|
function force() { |
|
var i, |
|
n = nodes.length, |
|
tree, |
|
node, |
|
xi, |
|
yi, |
|
ri, |
|
ri2; |
|
|
|
for (var k = 0; k < iterations; ++k) { |
|
tree = quadtree(nodes, x$2, y$2).visitAfter(prepare); |
|
|
|
for (i = 0; i < n; ++i) { |
|
node = nodes[i]; |
|
ri = radii[node.index], ri2 = ri * ri; |
|
xi = node.x + node.vx; |
|
yi = node.y + node.vy; |
|
tree.visit(apply); |
|
} |
|
} |
|
|
|
function apply(quad, x0, y0, x1, y1) { |
|
var data = quad.data, |
|
rj = quad.r, |
|
r = ri + rj; |
|
|
|
if (data) { |
|
if (data.index > node.index) { |
|
var x = xi - data.x - data.vx, |
|
y = yi - data.y - data.vy, |
|
l = x * x + y * y; |
|
|
|
if (l < r * r) { |
|
if (x === 0) x = jiggle(), l += x * x; |
|
if (y === 0) y = jiggle(), l += y * y; |
|
l = (r - (l = Math.sqrt(l))) / l * strength; |
|
node.vx += (x *= l) * (r = (rj *= rj) / (ri2 + rj)); |
|
node.vy += (y *= l) * r; |
|
data.vx -= x * (r = 1 - r); |
|
data.vy -= y * r; |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
return x0 > xi + r || x1 < xi - r || y0 > yi + r || y1 < yi - r; |
|
} |
|
} |
|
|
|
function prepare(quad) { |
|
if (quad.data) return quad.r = radii[quad.data.index]; |
|
|
|
for (var i = quad.r = 0; i < 4; ++i) { |
|
if (quad[i] && quad[i].r > quad.r) { |
|
quad.r = quad[i].r; |
|
} |
|
} |
|
} |
|
|
|
function initialize() { |
|
if (!nodes) return; |
|
var i, |
|
n = nodes.length, |
|
node; |
|
radii = new Array(n); |
|
|
|
for (i = 0; i < n; ++i) { |
|
node = nodes[i], radii[node.index] = +radius(node, i, nodes); |
|
} |
|
} |
|
|
|
force.initialize = function (_) { |
|
nodes = _; |
|
initialize(); |
|
}; |
|
|
|
force.iterations = function (_) { |
|
return arguments.length ? (iterations = +_, force) : iterations; |
|
}; |
|
|
|
force.strength = function (_) { |
|
return arguments.length ? (strength = +_, force) : strength; |
|
}; |
|
|
|
force.radius = function (_) { |
|
return arguments.length ? (radius = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : radius; |
|
}; |
|
|
|
return force; |
|
} |
|
|
|
function index(d) { |
|
return d.index; |
|
} |
|
|
|
function find$1(nodeById, nodeId) { |
|
var node = nodeById.get(nodeId); |
|
if (!node) throw new Error("missing: " + nodeId); |
|
return node; |
|
} |
|
|
|
function forceLink(links) { |
|
var id = index, |
|
strength = defaultStrength, |
|
strengths, |
|
distance = constant$4(30), |
|
distances, |
|
nodes, |
|
count, |
|
bias, |
|
iterations = 1; |
|
if (links == null) links = []; |
|
|
|
function defaultStrength(link) { |
|
return 1 / Math.min(count[link.source.index], count[link.target.index]); |
|
} |
|
|
|
function force(alpha) { |
|
for (var k = 0, n = links.length; k < iterations; ++k) { |
|
for (var i = 0, link, source, target, x, y, l, b; i < n; ++i) { |
|
link = links[i], source = link.source, target = link.target; |
|
x = target.x + target.vx - source.x - source.vx || jiggle(); |
|
y = target.y + target.vy - source.y - source.vy || jiggle(); |
|
l = Math.sqrt(x * x + y * y); |
|
l = (l - distances[i]) / l * alpha * strengths[i]; |
|
x *= l, y *= l; |
|
target.vx -= x * (b = bias[i]); |
|
target.vy -= y * b; |
|
source.vx += x * (b = 1 - b); |
|
source.vy += y * b; |
|
} |
|
} |
|
} |
|
|
|
function initialize() { |
|
if (!nodes) return; |
|
var i, |
|
n = nodes.length, |
|
m = links.length, |
|
nodeById = new Map(nodes.map(function (d, i) { |
|
return [id(d, i, nodes), d]; |
|
})), |
|
link; |
|
|
|
for (i = 0, count = new Array(n); i < m; ++i) { |
|
link = links[i], link.index = i; |
|
if (_typeof(link.source) !== "object") link.source = find$1(nodeById, link.source); |
|
if (_typeof(link.target) !== "object") link.target = find$1(nodeById, link.target); |
|
count[link.source.index] = (count[link.source.index] || 0) + 1; |
|
count[link.target.index] = (count[link.target.index] || 0) + 1; |
|
} |
|
|
|
for (i = 0, bias = new Array(m); i < m; ++i) { |
|
link = links[i], bias[i] = count[link.source.index] / (count[link.source.index] + count[link.target.index]); |
|
} |
|
|
|
strengths = new Array(m), initializeStrength(); |
|
distances = new Array(m), initializeDistance(); |
|
} |
|
|
|
function initializeStrength() { |
|
if (!nodes) return; |
|
|
|
for (var i = 0, n = links.length; i < n; ++i) { |
|
strengths[i] = +strength(links[i], i, links); |
|
} |
|
} |
|
|
|
function initializeDistance() { |
|
if (!nodes) return; |
|
|
|
for (var i = 0, n = links.length; i < n; ++i) { |
|
distances[i] = +distance(links[i], i, links); |
|
} |
|
} |
|
|
|
force.initialize = function (_) { |
|
nodes = _; |
|
initialize(); |
|
}; |
|
|
|
force.links = function (_) { |
|
return arguments.length ? (links = _, initialize(), force) : links; |
|
}; |
|
|
|
force.id = function (_) { |
|
return arguments.length ? (id = _, force) : id; |
|
}; |
|
|
|
force.iterations = function (_) { |
|
return arguments.length ? (iterations = +_, force) : iterations; |
|
}; |
|
|
|
force.strength = function (_) { |
|
return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initializeStrength(), force) : strength; |
|
}; |
|
|
|
force.distance = function (_) { |
|
return arguments.length ? (distance = typeof _ === "function" ? _ : constant$4(+_), initializeDistance(), force) : distance; |
|
}; |
|
|
|
return force; |
|
} |
|
|
|
var noop$4 = { |
|
value: function value() {} |
|
}; |
|
|
|
function dispatch() { |
|
for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) { |
|
if (!(t = arguments[i] + "") || t in _ || /[\s.]/.test(t)) throw new Error("illegal type: " + t); |
|
_[t] = []; |
|
} |
|
|
|
return new Dispatch(_); |
|
} |
|
|
|
function Dispatch(_) { |
|
this._ = _; |
|
} |
|
|
|
function parseTypenames(typenames, types) { |
|
return typenames.trim().split(/^|\s+/).map(function (t) { |
|
var name = "", |
|
i = t.indexOf("."); |
|
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); |
|
if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t); |
|
return { |
|
type: t, |
|
name: name |
|
}; |
|
}); |
|
} |
|
|
|
Dispatch.prototype = dispatch.prototype = { |
|
constructor: Dispatch, |
|
on: function on(typename, callback) { |
|
var _ = this._, |
|
T = parseTypenames(typename + "", _), |
|
t, |
|
i = -1, |
|
n = T.length; // If no callback was specified, return the callback of the given type and name. |
|
|
|
if (arguments.length < 2) { |
|
while (++i < n) { |
|
if ((t = (typename = T[i]).type) && (t = get$4(_[t], typename.name))) return t; |
|
} |
|
|
|
return; |
|
} // If a type was specified, set the callback for the given type and name. |
|
// Otherwise, if a null callback was specified, remove callbacks of the given name. |
|
|
|
|
|
if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback); |
|
|
|
while (++i < n) { |
|
if (t = (typename = T[i]).type) _[t] = set$2(_[t], typename.name, callback);else if (callback == null) for (t in _) { |
|
_[t] = set$2(_[t], typename.name, null); |
|
} |
|
} |
|
|
|
return this; |
|
}, |
|
copy: function copy() { |
|
var copy = {}, |
|
_ = this._; |
|
|
|
for (var t in _) { |
|
copy[t] = _[t].slice(); |
|
} |
|
|
|
return new Dispatch(copy); |
|
}, |
|
call: function call(type, that) { |
|
if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) { |
|
args[i] = arguments[i + 2]; |
|
} |
|
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); |
|
|
|
for (t = this._[type], i = 0, n = t.length; i < n; ++i) { |
|
t[i].value.apply(that, args); |
|
} |
|
}, |
|
apply: function apply(type, that, args) { |
|
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); |
|
|
|
for (var t = this._[type], i = 0, n = t.length; i < n; ++i) { |
|
t[i].value.apply(that, args); |
|
} |
|
} |
|
}; |
|
|
|
function get$4(type, name) { |
|
for (var i = 0, n = type.length, c; i < n; ++i) { |
|
if ((c = type[i]).name === name) { |
|
return c.value; |
|
} |
|
} |
|
} |
|
|
|
function set$2(type, name, callback) { |
|
for (var i = 0, n = type.length; i < n; ++i) { |
|
if (type[i].name === name) { |
|
type[i] = noop$4, type = type.slice(0, i).concat(type.slice(i + 1)); |
|
break; |
|
} |
|
} |
|
|
|
if (callback != null) type.push({ |
|
name: name, |
|
value: callback |
|
}); |
|
return type; |
|
} |
|
|
|
var frame = 0, |
|
// is an animation frame pending? |
|
timeout = 0, |
|
// is a timeout pending? |
|
interval = 0, |
|
// are any timers active? |
|
pokeDelay = 1000, |
|
// how frequently we check for clock skew |
|
taskHead, |
|
taskTail, |
|
clockLast = 0, |
|
clockNow = 0, |
|
clockSkew = 0, |
|
clock = (typeof performance === "undefined" ? "undefined" : _typeof(performance)) === "object" && performance.now ? performance : Date, |
|
setFrame = (typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function (f) { |
|
setTimeout(f, 17); |
|
}; |
|
|
|
function now() { |
|
return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew); |
|
} |
|
|
|
function clearNow() { |
|
clockNow = 0; |
|
} |
|
|
|
function Timer() { |
|
this._call = this._time = this._next = null; |
|
} |
|
|
|
Timer.prototype = timer.prototype = { |
|
constructor: Timer, |
|
restart: function restart(callback, delay, time) { |
|
if (typeof callback !== "function") throw new TypeError("callback is not a function"); |
|
time = (time == null ? now() : +time) + (delay == null ? 0 : +delay); |
|
|
|
if (!this._next && taskTail !== this) { |
|
if (taskTail) taskTail._next = this;else taskHead = this; |
|
taskTail = this; |
|
} |
|
|
|
this._call = callback; |
|
this._time = time; |
|
sleep(); |
|
}, |
|
stop: function stop() { |
|
if (this._call) { |
|
this._call = null; |
|
this._time = Infinity; |
|
sleep(); |
|
} |
|
} |
|
}; |
|
|
|
function timer(callback, delay, time) { |
|
var t = new Timer(); |
|
t.restart(callback, delay, time); |
|
return t; |
|
} |
|
|
|
function timerFlush() { |
|
now(); // Get the current time, if not already set. |
|
|
|
++frame; // Pretend we’ve set an alarm, if we haven’t already. |
|
|
|
var t = taskHead, |
|
e; |
|
|
|
while (t) { |
|
if ((e = clockNow - t._time) >= 0) t._call.call(null, e); |
|
t = t._next; |
|
} |
|
|
|
--frame; |
|
} |
|
|
|
function wake() { |
|
clockNow = (clockLast = clock.now()) + clockSkew; |
|
frame = timeout = 0; |
|
|
|
try { |
|
timerFlush(); |
|
} finally { |
|
frame = 0; |
|
nap(); |
|
clockNow = 0; |
|
} |
|
} |
|
|
|
function poke() { |
|
var now = clock.now(), |
|
delay = now - clockLast; |
|
if (delay > pokeDelay) clockSkew -= delay, clockLast = now; |
|
} |
|
|
|
function nap() { |
|
var t0, |
|
t1 = taskHead, |
|
t2, |
|
time = Infinity; |
|
|
|
while (t1) { |
|
if (t1._call) { |
|
if (time > t1._time) time = t1._time; |
|
t0 = t1, t1 = t1._next; |
|
} else { |
|
t2 = t1._next, t1._next = null; |
|
t1 = t0 ? t0._next = t2 : taskHead = t2; |
|
} |
|
} |
|
|
|
taskTail = t0; |
|
sleep(time); |
|
} |
|
|
|
function sleep(time) { |
|
if (frame) return; // Soonest alarm already set, or will be. |
|
|
|
if (timeout) timeout = clearTimeout(timeout); |
|
var delay = time - clockNow; // Strictly less than if we recomputed clockNow. |
|
|
|
if (delay > 24) { |
|
if (time < Infinity) timeout = setTimeout(wake, time - clock.now() - clockSkew); |
|
if (interval) interval = clearInterval(interval); |
|
} else { |
|
if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay); |
|
frame = 1, setFrame(wake); |
|
} |
|
} |
|
|
|
function interval$1(callback, delay, time) { |
|
var t = new Timer(), |
|
total = delay; |
|
if (delay == null) return t.restart(callback, delay, time), t; |
|
delay = +delay, time = time == null ? now() : +time; |
|
t.restart(function tick(elapsed) { |
|
elapsed += total; |
|
t.restart(tick, total += delay, time); |
|
callback(elapsed); |
|
}, delay, time); |
|
return t; |
|
} |
|
|
|
function x$3(d) { |
|
return d.x; |
|
} |
|
|
|
function y$3(d) { |
|
return d.y; |
|
} |
|
|
|
var initialRadius = 10, |
|
initialAngle = Math.PI * (3 - Math.sqrt(5)); |
|
|
|
function forceSimulation(_nodes) { |
|
var simulation, |
|
_alpha = 1, |
|
_alphaMin = 0.001, |
|
_alphaDecay = 1 - Math.pow(_alphaMin, 1 / 300), |
|
_alphaTarget = 0, |
|
_velocityDecay = 0.6, |
|
forces = new Map(), |
|
stepper = timer(step), |
|
event = dispatch("tick", "end"); |
|
|
|
if (_nodes == null) _nodes = []; |
|
|
|
function step() { |
|
tick(); |
|
event.call("tick", simulation); |
|
|
|
if (_alpha < _alphaMin) { |
|
stepper.stop(); |
|
event.call("end", simulation); |
|
} |
|
} |
|
|
|
function tick(iterations) { |
|
var i, |
|
n = _nodes.length, |
|
node; |
|
if (iterations === undefined) iterations = 1; |
|
|
|
for (var k = 0; k < iterations; ++k) { |
|
_alpha += (_alphaTarget - _alpha) * _alphaDecay; |
|
forces.forEach(function (force) { |
|
force(_alpha); |
|
}); |
|
|
|
for (i = 0; i < n; ++i) { |
|
node = _nodes[i]; |
|
if (node.fx == null) node.x += node.vx *= _velocityDecay;else node.x = node.fx, node.vx = 0; |
|
if (node.fy == null) node.y += node.vy *= _velocityDecay;else node.y = node.fy, node.vy = 0; |
|
} |
|
} |
|
|
|
return simulation; |
|
} |
|
|
|
function initializeNodes() { |
|
for (var i = 0, n = _nodes.length, node; i < n; ++i) { |
|
node = _nodes[i], node.index = i; |
|
if (node.fx != null) node.x = node.fx; |
|
if (node.fy != null) node.y = node.fy; |
|
|
|
if (isNaN(node.x) || isNaN(node.y)) { |
|
var radius = initialRadius * Math.sqrt(i), |
|
angle = i * initialAngle; |
|
node.x = radius * Math.cos(angle); |
|
node.y = radius * Math.sin(angle); |
|
} |
|
|
|
if (isNaN(node.vx) || isNaN(node.vy)) { |
|
node.vx = node.vy = 0; |
|
} |
|
} |
|
} |
|
|
|
function initializeForce(force) { |
|
if (force.initialize) force.initialize(_nodes); |
|
return force; |
|
} |
|
|
|
initializeNodes(); |
|
return simulation = { |
|
tick: tick, |
|
restart: function restart() { |
|
return stepper.restart(step), simulation; |
|
}, |
|
stop: function stop() { |
|
return stepper.stop(), simulation; |
|
}, |
|
nodes: function nodes(_) { |
|
return arguments.length ? (_nodes = _, initializeNodes(), forces.forEach(initializeForce), simulation) : _nodes; |
|
}, |
|
alpha: function alpha(_) { |
|
return arguments.length ? (_alpha = +_, simulation) : _alpha; |
|
}, |
|
alphaMin: function alphaMin(_) { |
|
return arguments.length ? (_alphaMin = +_, simulation) : _alphaMin; |
|
}, |
|
alphaDecay: function alphaDecay(_) { |
|
return arguments.length ? (_alphaDecay = +_, simulation) : +_alphaDecay; |
|
}, |
|
alphaTarget: function alphaTarget(_) { |
|
return arguments.length ? (_alphaTarget = +_, simulation) : _alphaTarget; |
|
}, |
|
velocityDecay: function velocityDecay(_) { |
|
return arguments.length ? (_velocityDecay = 1 - _, simulation) : 1 - _velocityDecay; |
|
}, |
|
force: function force(name, _) { |
|
return arguments.length > 1 ? (_ == null ? forces.delete(name) : forces.set(name, initializeForce(_)), simulation) : forces.get(name); |
|
}, |
|
find: function find(x, y, radius) { |
|
var i = 0, |
|
n = _nodes.length, |
|
dx, |
|
dy, |
|
d2, |
|
node, |
|
closest; |
|
if (radius == null) radius = Infinity;else radius *= radius; |
|
|
|
for (i = 0; i < n; ++i) { |
|
node = _nodes[i]; |
|
dx = x - node.x; |
|
dy = y - node.y; |
|
d2 = dx * dx + dy * dy; |
|
if (d2 < radius) closest = node, radius = d2; |
|
} |
|
|
|
return closest; |
|
}, |
|
on: function on(name, _) { |
|
return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name); |
|
} |
|
}; |
|
} |
|
|
|
function forceManyBody() { |
|
var nodes, |
|
node, |
|
alpha, |
|
strength = constant$4(-30), |
|
strengths, |
|
distanceMin2 = 1, |
|
distanceMax2 = Infinity, |
|
theta2 = 0.81; |
|
|
|
function force(_) { |
|
var i, |
|
n = nodes.length, |
|
tree = quadtree(nodes, x$3, y$3).visitAfter(accumulate); |
|
|
|
for (alpha = _, i = 0; i < n; ++i) { |
|
node = nodes[i], tree.visit(apply); |
|
} |
|
} |
|
|
|
function initialize() { |
|
if (!nodes) return; |
|
var i, |
|
n = nodes.length, |
|
node; |
|
strengths = new Array(n); |
|
|
|
for (i = 0; i < n; ++i) { |
|
node = nodes[i], strengths[node.index] = +strength(node, i, nodes); |
|
} |
|
} |
|
|
|
function accumulate(quad) { |
|
var strength = 0, |
|
q, |
|
c, |
|
weight = 0, |
|
x, |
|
y, |
|
i; // For internal nodes, accumulate forces from child quadrants. |
|
|
|
if (quad.length) { |
|
for (x = y = i = 0; i < 4; ++i) { |
|
if ((q = quad[i]) && (c = Math.abs(q.value))) { |
|
strength += q.value, weight += c, x += c * q.x, y += c * q.y; |
|
} |
|
} |
|
|
|
quad.x = x / weight; |
|
quad.y = y / weight; |
|
} // For leaf nodes, accumulate forces from coincident quadrants. |
|
else { |
|
q = quad; |
|
q.x = q.data.x; |
|
q.y = q.data.y; |
|
|
|
do { |
|
strength += strengths[q.data.index]; |
|
} while (q = q.next); |
|
} |
|
|
|
quad.value = strength; |
|
} |
|
|
|
function apply(quad, x1, _, x2) { |
|
if (!quad.value) return true; |
|
var x = quad.x - node.x, |
|
y = quad.y - node.y, |
|
w = x2 - x1, |
|
l = x * x + y * y; // Apply the Barnes-Hut approximation if possible. |
|
// Limit forces for very close nodes; randomize direction if coincident. |
|
|
|
if (w * w / theta2 < l) { |
|
if (l < distanceMax2) { |
|
if (x === 0) x = jiggle(), l += x * x; |
|
if (y === 0) y = jiggle(), l += y * y; |
|
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); |
|
node.vx += x * quad.value * alpha / l; |
|
node.vy += y * quad.value * alpha / l; |
|
} |
|
|
|
return true; |
|
} // Otherwise, process points directly. |
|
else if (quad.length || l >= distanceMax2) return; // Limit forces for very close nodes; randomize direction if coincident. |
|
|
|
|
|
if (quad.data !== node || quad.next) { |
|
if (x === 0) x = jiggle(), l += x * x; |
|
if (y === 0) y = jiggle(), l += y * y; |
|
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); |
|
} |
|
|
|
do { |
|
if (quad.data !== node) { |
|
w = strengths[quad.data.index] * alpha / l; |
|
node.vx += x * w; |
|
node.vy += y * w; |
|
} |
|
} while (quad = quad.next); |
|
} |
|
|
|
force.initialize = function (_) { |
|
nodes = _; |
|
initialize(); |
|
}; |
|
|
|
force.strength = function (_) { |
|
return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; |
|
}; |
|
|
|
force.distanceMin = function (_) { |
|
return arguments.length ? (distanceMin2 = _ * _, force) : Math.sqrt(distanceMin2); |
|
}; |
|
|
|
force.distanceMax = function (_) { |
|
return arguments.length ? (distanceMax2 = _ * _, force) : Math.sqrt(distanceMax2); |
|
}; |
|
|
|
force.theta = function (_) { |
|
return arguments.length ? (theta2 = _ * _, force) : Math.sqrt(theta2); |
|
}; |
|
|
|
return force; |
|
} |
|
|
|
function forceX(x) { |
|
var strength = constant$4(0.1), |
|
nodes, |
|
strengths, |
|
xz; |
|
if (typeof x !== "function") x = constant$4(x == null ? 0 : +x); |
|
|
|
function force(alpha) { |
|
for (var i = 0, n = nodes.length, node; i < n; ++i) { |
|
node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha; |
|
} |
|
} |
|
|
|
function initialize() { |
|
if (!nodes) return; |
|
var i, |
|
n = nodes.length; |
|
strengths = new Array(n); |
|
xz = new Array(n); |
|
|
|
for (i = 0; i < n; ++i) { |
|
strengths[i] = isNaN(xz[i] = +x(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes); |
|
} |
|
} |
|
|
|
force.initialize = function (_) { |
|
nodes = _; |
|
initialize(); |
|
}; |
|
|
|
force.strength = function (_) { |
|
return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; |
|
}; |
|
|
|
force.x = function (_) { |
|
return arguments.length ? (x = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : x; |
|
}; |
|
|
|
return force; |
|
} |
|
|
|
function forceY(y) { |
|
var strength = constant$4(0.1), |
|
nodes, |
|
strengths, |
|
yz; |
|
if (typeof y !== "function") y = constant$4(y == null ? 0 : +y); |
|
|
|
function force(alpha) { |
|
for (var i = 0, n = nodes.length, node; i < n; ++i) { |
|
node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha; |
|
} |
|
} |
|
|
|
function initialize() { |
|
if (!nodes) return; |
|
var i, |
|
n = nodes.length; |
|
strengths = new Array(n); |
|
yz = new Array(n); |
|
|
|
for (i = 0; i < n; ++i) { |
|
strengths[i] = isNaN(yz[i] = +y(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes); |
|
} |
|
} |
|
|
|
force.initialize = function (_) { |
|
nodes = _; |
|
initialize(); |
|
}; |
|
|
|
force.strength = function (_) { |
|
return arguments.length ? (strength = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : strength; |
|
}; |
|
|
|
force.y = function (_) { |
|
return arguments.length ? (y = typeof _ === "function" ? _ : constant$4(+_), initialize(), force) : y; |
|
}; |
|
|
|
return force; |
|
} |
|
|
|
var ForceMap = { |
|
center: forceCenter, |
|
collide: forceCollide, |
|
nbody: forceManyBody, |
|
link: forceLink, |
|
x: forceX, |
|
y: forceY |
|
}; |
|
var Forces = 'forces', |
|
ForceParams = ['alpha', 'alphaMin', 'alphaTarget', 'velocityDecay', 'forces'], |
|
ForceConfig = ['static', 'iterations'], |
|
ForceOutput = ['x', 'y', 'vx', 'vy']; |
|
/** |
|
* Force simulation layout. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<object>} params.forces - The forces to apply. |
|
*/ |
|
|
|
function Force(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Force.Definition = { |
|
"type": "Force", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "static", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "restart", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "iterations", |
|
"type": "number", |
|
"default": 300 |
|
}, { |
|
"name": "alpha", |
|
"type": "number", |
|
"default": 1 |
|
}, { |
|
"name": "alphaMin", |
|
"type": "number", |
|
"default": 0.001 |
|
}, { |
|
"name": "alphaTarget", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "velocityDecay", |
|
"type": "number", |
|
"default": 0.4 |
|
}, { |
|
"name": "forces", |
|
"type": "param", |
|
"array": true, |
|
"params": [{ |
|
"key": { |
|
"force": "center" |
|
}, |
|
"params": [{ |
|
"name": "x", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "y", |
|
"type": "number", |
|
"default": 0 |
|
}] |
|
}, { |
|
"key": { |
|
"force": "collide" |
|
}, |
|
"params": [{ |
|
"name": "radius", |
|
"type": "number", |
|
"expr": true |
|
}, { |
|
"name": "strength", |
|
"type": "number", |
|
"default": 0.7 |
|
}, { |
|
"name": "iterations", |
|
"type": "number", |
|
"default": 1 |
|
}] |
|
}, { |
|
"key": { |
|
"force": "nbody" |
|
}, |
|
"params": [{ |
|
"name": "strength", |
|
"type": "number", |
|
"default": -30 |
|
}, { |
|
"name": "theta", |
|
"type": "number", |
|
"default": 0.9 |
|
}, { |
|
"name": "distanceMin", |
|
"type": "number", |
|
"default": 1 |
|
}, { |
|
"name": "distanceMax", |
|
"type": "number" |
|
}] |
|
}, { |
|
"key": { |
|
"force": "link" |
|
}, |
|
"params": [{ |
|
"name": "links", |
|
"type": "data" |
|
}, { |
|
"name": "id", |
|
"type": "field" |
|
}, { |
|
"name": "distance", |
|
"type": "number", |
|
"default": 30, |
|
"expr": true |
|
}, { |
|
"name": "strength", |
|
"type": "number", |
|
"expr": true |
|
}, { |
|
"name": "iterations", |
|
"type": "number", |
|
"default": 1 |
|
}] |
|
}, { |
|
"key": { |
|
"force": "x" |
|
}, |
|
"params": [{ |
|
"name": "strength", |
|
"type": "number", |
|
"default": 0.1 |
|
}, { |
|
"name": "x", |
|
"type": "field" |
|
}] |
|
}, { |
|
"key": { |
|
"force": "y" |
|
}, |
|
"params": [{ |
|
"name": "strength", |
|
"type": "number", |
|
"default": 0.1 |
|
}, { |
|
"name": "y", |
|
"type": "field" |
|
}] |
|
}] |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"modify": false, |
|
"default": ForceOutput |
|
}] |
|
}; |
|
var prototype$1d = inherits(Force, Transform); |
|
|
|
prototype$1d.transform = function (_, pulse) { |
|
var sim = this.value, |
|
change = pulse.changed(pulse.ADD_REM), |
|
params = _.modified(ForceParams), |
|
iters = _.iterations || 300; // configure simulation |
|
|
|
|
|
if (!sim) { |
|
this.value = sim = simulation(pulse.source, _); |
|
sim.on('tick', rerun(pulse.dataflow, this)); |
|
|
|
if (!_.static) { |
|
change = true; |
|
sim.tick(); // ensure we run on init |
|
} |
|
|
|
pulse.modifies('index'); |
|
} else { |
|
if (change) { |
|
pulse.modifies('index'); |
|
sim.nodes(pulse.source); |
|
} |
|
|
|
if (params || pulse.changed(pulse.MOD)) { |
|
setup(sim, _, 0, pulse); |
|
} |
|
} // run simulation |
|
|
|
|
|
if (params || change || _.modified(ForceConfig) || pulse.changed() && _.restart) { |
|
sim.alpha(Math.max(sim.alpha(), _.alpha || 1)).alphaDecay(1 - Math.pow(sim.alphaMin(), 1 / iters)); |
|
|
|
if (_.static) { |
|
for (sim.stop(); --iters >= 0;) { |
|
sim.tick(); |
|
} |
|
} else { |
|
if (sim.stopped()) sim.restart(); |
|
if (!change) return pulse.StopPropagation; // defer to sim ticks |
|
} |
|
} |
|
|
|
return this.finish(_, pulse); |
|
}; |
|
|
|
prototype$1d.finish = function (_, pulse) { |
|
var dataflow = pulse.dataflow; // inspect dependencies, touch link source data |
|
|
|
for (var args = this._argops, j = 0, m = args.length, arg; j < m; ++j) { |
|
arg = args[j]; |
|
|
|
if (arg.name !== Forces || arg.op._argval.force !== 'link') { |
|
continue; |
|
} |
|
|
|
for (var ops = arg.op._argops, i = 0, n = ops.length, op; i < n; ++i) { |
|
if (ops[i].name === 'links' && (op = ops[i].op.source)) { |
|
dataflow.pulse(op, dataflow.changeset().reflow()); |
|
break; |
|
} |
|
} |
|
} // reflow all nodes |
|
|
|
|
|
return pulse.reflow(_.modified()).modifies(ForceOutput); |
|
}; |
|
|
|
function rerun(df, op) { |
|
return function () { |
|
df.touch(op).run(); |
|
}; |
|
} |
|
|
|
function simulation(nodes, _) { |
|
var sim = forceSimulation(nodes), |
|
stopped = false, |
|
stop = sim.stop, |
|
restart = sim.restart; |
|
|
|
sim.stopped = function () { |
|
return stopped; |
|
}; |
|
|
|
sim.restart = function () { |
|
stopped = false; |
|
return restart(); |
|
}; |
|
|
|
sim.stop = function () { |
|
stopped = true; |
|
return stop(); |
|
}; |
|
|
|
return setup(sim, _, true).on('end', function () { |
|
stopped = true; |
|
}); |
|
} |
|
|
|
function setup(sim, _, init, pulse) { |
|
var f = array(_.forces), |
|
i, |
|
n, |
|
p, |
|
name; |
|
|
|
for (i = 0, n = ForceParams.length; i < n; ++i) { |
|
p = ForceParams[i]; |
|
if (p !== Forces && _.modified(p)) sim[p](_[p]); |
|
} |
|
|
|
for (i = 0, n = f.length; i < n; ++i) { |
|
name = Forces + i; |
|
p = init || _.modified(Forces, i) ? getForce(f[i]) : pulse && modified(f[i], pulse) ? sim.force(name) : null; |
|
if (p) sim.force(name, p); |
|
} |
|
|
|
for (n = sim.numForces || 0; i < n; ++i) { |
|
sim.force(Forces + i, null); // remove |
|
} |
|
|
|
sim.numForces = f.length; |
|
return sim; |
|
} |
|
|
|
function modified(f, pulse) { |
|
var k, v; |
|
|
|
for (k in f) { |
|
if (isFunction(v = f[k]) && pulse.modified(accessorFields(v))) return 1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
function getForce(_) { |
|
var f, p; |
|
|
|
if (!hasOwnProperty(ForceMap, _.force)) { |
|
error('Unrecognized force: ' + _.force); |
|
} |
|
|
|
f = ForceMap[_.force](); |
|
|
|
for (p in _) { |
|
if (isFunction(f[p])) setForceParam(f[p], _[p], _); |
|
} |
|
|
|
return f; |
|
} |
|
|
|
function setForceParam(f, v, _) { |
|
f(isFunction(v) ? function (d) { |
|
return v(d, _); |
|
} : v); |
|
} |
|
|
|
var force = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
force: Force |
|
}); // Build lookup table mapping tuple keys to tree node instances |
|
|
|
function lookup$3(tree, key, filter) { |
|
var map = {}; |
|
tree.each(function (node) { |
|
var t = node.data; |
|
if (filter(t)) map[key(t)] = node; |
|
}); |
|
tree.lookup = map; |
|
return tree; |
|
} |
|
|
|
function defaultSeparation(a, b) { |
|
return a.parent === b.parent ? 1 : 2; |
|
} |
|
|
|
function meanX(children) { |
|
return children.reduce(meanXReduce, 0) / children.length; |
|
} |
|
|
|
function meanXReduce(x, c) { |
|
return x + c.x; |
|
} |
|
|
|
function maxY(children) { |
|
return 1 + children.reduce(maxYReduce, 0); |
|
} |
|
|
|
function maxYReduce(y, c) { |
|
return Math.max(y, c.y); |
|
} |
|
|
|
function leafLeft(node) { |
|
var children; |
|
|
|
while (children = node.children) { |
|
node = children[0]; |
|
} |
|
|
|
return node; |
|
} |
|
|
|
function leafRight(node) { |
|
var children; |
|
|
|
while (children = node.children) { |
|
node = children[children.length - 1]; |
|
} |
|
|
|
return node; |
|
} |
|
|
|
function cluster() { |
|
var separation = defaultSeparation, |
|
dx = 1, |
|
dy = 1, |
|
nodeSize = false; |
|
|
|
function cluster(root) { |
|
var previousNode, |
|
x = 0; // First walk, computing the initial x & y values. |
|
|
|
root.eachAfter(function (node) { |
|
var children = node.children; |
|
|
|
if (children) { |
|
node.x = meanX(children); |
|
node.y = maxY(children); |
|
} else { |
|
node.x = previousNode ? x += separation(node, previousNode) : 0; |
|
node.y = 0; |
|
previousNode = node; |
|
} |
|
}); |
|
var left = leafLeft(root), |
|
right = leafRight(root), |
|
x0 = left.x - separation(left, right) / 2, |
|
x1 = right.x + separation(right, left) / 2; // Second walk, normalizing x & y to the desired size. |
|
|
|
return root.eachAfter(nodeSize ? function (node) { |
|
node.x = (node.x - root.x) * dx; |
|
node.y = (root.y - node.y) * dy; |
|
} : function (node) { |
|
node.x = (node.x - x0) / (x1 - x0) * dx; |
|
node.y = (1 - (root.y ? node.y / root.y : 1)) * dy; |
|
}); |
|
} |
|
|
|
cluster.separation = function (x) { |
|
return arguments.length ? (separation = x, cluster) : separation; |
|
}; |
|
|
|
cluster.size = function (x) { |
|
return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], cluster) : nodeSize ? null : [dx, dy]; |
|
}; |
|
|
|
cluster.nodeSize = function (x) { |
|
return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], cluster) : nodeSize ? [dx, dy] : null; |
|
}; |
|
|
|
return cluster; |
|
} |
|
|
|
function count(node) { |
|
var sum = 0, |
|
children = node.children, |
|
i = children && children.length; |
|
if (!i) sum = 1;else while (--i >= 0) { |
|
sum += children[i].value; |
|
} |
|
node.value = sum; |
|
} |
|
|
|
function node_count() { |
|
return this.eachAfter(count); |
|
} |
|
|
|
function node_each(callback) { |
|
var node = this, |
|
current, |
|
next = [node], |
|
children, |
|
i, |
|
n; |
|
|
|
do { |
|
current = next.reverse(), next = []; |
|
|
|
while (node = current.pop()) { |
|
callback(node), children = node.children; |
|
if (children) for (i = 0, n = children.length; i < n; ++i) { |
|
next.push(children[i]); |
|
} |
|
} |
|
} while (next.length); |
|
|
|
return this; |
|
} |
|
|
|
function node_eachBefore(callback) { |
|
var node = this, |
|
nodes = [node], |
|
children, |
|
i; |
|
|
|
while (node = nodes.pop()) { |
|
callback(node), children = node.children; |
|
if (children) for (i = children.length - 1; i >= 0; --i) { |
|
nodes.push(children[i]); |
|
} |
|
} |
|
|
|
return this; |
|
} |
|
|
|
function node_eachAfter(callback) { |
|
var node = this, |
|
nodes = [node], |
|
next = [], |
|
children, |
|
i, |
|
n; |
|
|
|
while (node = nodes.pop()) { |
|
next.push(node), children = node.children; |
|
if (children) for (i = 0, n = children.length; i < n; ++i) { |
|
nodes.push(children[i]); |
|
} |
|
} |
|
|
|
while (node = next.pop()) { |
|
callback(node); |
|
} |
|
|
|
return this; |
|
} |
|
|
|
function node_sum(value) { |
|
return this.eachAfter(function (node) { |
|
var sum = +value(node.data) || 0, |
|
children = node.children, |
|
i = children && children.length; |
|
|
|
while (--i >= 0) { |
|
sum += children[i].value; |
|
} |
|
|
|
node.value = sum; |
|
}); |
|
} |
|
|
|
function node_sort(compare) { |
|
return this.eachBefore(function (node) { |
|
if (node.children) { |
|
node.children.sort(compare); |
|
} |
|
}); |
|
} |
|
|
|
function node_path(end) { |
|
var start = this, |
|
ancestor = leastCommonAncestor(start, end), |
|
nodes = [start]; |
|
|
|
while (start !== ancestor) { |
|
start = start.parent; |
|
nodes.push(start); |
|
} |
|
|
|
var k = nodes.length; |
|
|
|
while (end !== ancestor) { |
|
nodes.splice(k, 0, end); |
|
end = end.parent; |
|
} |
|
|
|
return nodes; |
|
} |
|
|
|
function leastCommonAncestor(a, b) { |
|
if (a === b) return a; |
|
var aNodes = a.ancestors(), |
|
bNodes = b.ancestors(), |
|
c = null; |
|
a = aNodes.pop(); |
|
b = bNodes.pop(); |
|
|
|
while (a === b) { |
|
c = a; |
|
a = aNodes.pop(); |
|
b = bNodes.pop(); |
|
} |
|
|
|
return c; |
|
} |
|
|
|
function node_ancestors() { |
|
var node = this, |
|
nodes = [node]; |
|
|
|
while (node = node.parent) { |
|
nodes.push(node); |
|
} |
|
|
|
return nodes; |
|
} |
|
|
|
function node_descendants() { |
|
var nodes = []; |
|
this.each(function (node) { |
|
nodes.push(node); |
|
}); |
|
return nodes; |
|
} |
|
|
|
function node_leaves() { |
|
var leaves = []; |
|
this.eachBefore(function (node) { |
|
if (!node.children) { |
|
leaves.push(node); |
|
} |
|
}); |
|
return leaves; |
|
} |
|
|
|
function node_links() { |
|
var root = this, |
|
links = []; |
|
root.each(function (node) { |
|
if (node !== root) { |
|
// Don’t include the root’s parent, if any. |
|
links.push({ |
|
source: node.parent, |
|
target: node |
|
}); |
|
} |
|
}); |
|
return links; |
|
} |
|
|
|
function hierarchy(data, children) { |
|
var root = new Node(data), |
|
valued = +data.value && (root.value = data.value), |
|
node, |
|
nodes = [root], |
|
child, |
|
childs, |
|
i, |
|
n; |
|
if (children == null) children = defaultChildren; |
|
|
|
while (node = nodes.pop()) { |
|
if (valued) node.value = +node.data.value; |
|
|
|
if ((childs = children(node.data)) && (n = childs.length)) { |
|
node.children = new Array(n); |
|
|
|
for (i = n - 1; i >= 0; --i) { |
|
nodes.push(child = node.children[i] = new Node(childs[i])); |
|
child.parent = node; |
|
child.depth = node.depth + 1; |
|
} |
|
} |
|
} |
|
|
|
return root.eachBefore(computeHeight); |
|
} |
|
|
|
function node_copy() { |
|
return hierarchy(this).eachBefore(copyData); |
|
} |
|
|
|
function defaultChildren(d) { |
|
return d.children; |
|
} |
|
|
|
function copyData(node) { |
|
node.data = node.data.data; |
|
} |
|
|
|
function computeHeight(node) { |
|
var height = 0; |
|
|
|
do { |
|
node.height = height; |
|
} while ((node = node.parent) && node.height < ++height); |
|
} |
|
|
|
function Node(data) { |
|
this.data = data; |
|
this.depth = this.height = 0; |
|
this.parent = null; |
|
} |
|
|
|
Node.prototype = hierarchy.prototype = { |
|
constructor: Node, |
|
count: node_count, |
|
each: node_each, |
|
eachAfter: node_eachAfter, |
|
eachBefore: node_eachBefore, |
|
sum: node_sum, |
|
sort: node_sort, |
|
path: node_path, |
|
ancestors: node_ancestors, |
|
descendants: node_descendants, |
|
leaves: node_leaves, |
|
links: node_links, |
|
copy: node_copy |
|
}; |
|
var slice$1 = Array.prototype.slice; |
|
|
|
function shuffle(array) { |
|
var m = array.length, |
|
t, |
|
i; |
|
|
|
while (m) { |
|
i = Math.random() * m-- | 0; |
|
t = array[m]; |
|
array[m] = array[i]; |
|
array[i] = t; |
|
} |
|
|
|
return array; |
|
} |
|
|
|
function enclose(circles) { |
|
var i = 0, |
|
n = (circles = shuffle(slice$1.call(circles))).length, |
|
B = [], |
|
p, |
|
e; |
|
|
|
while (i < n) { |
|
p = circles[i]; |
|
if (e && enclosesWeak(e, p)) ++i;else e = encloseBasis(B = extendBasis(B, p)), i = 0; |
|
} |
|
|
|
return e; |
|
} |
|
|
|
function extendBasis(B, p) { |
|
var i, j; |
|
if (enclosesWeakAll(p, B)) return [p]; // If we get here then B must have at least one element. |
|
|
|
for (i = 0; i < B.length; ++i) { |
|
if (enclosesNot(p, B[i]) && enclosesWeakAll(encloseBasis2(B[i], p), B)) { |
|
return [B[i], p]; |
|
} |
|
} // If we get here then B must have at least two elements. |
|
|
|
|
|
for (i = 0; i < B.length - 1; ++i) { |
|
for (j = i + 1; j < B.length; ++j) { |
|
if (enclosesNot(encloseBasis2(B[i], B[j]), p) && enclosesNot(encloseBasis2(B[i], p), B[j]) && enclosesNot(encloseBasis2(B[j], p), B[i]) && enclosesWeakAll(encloseBasis3(B[i], B[j], p), B)) { |
|
return [B[i], B[j], p]; |
|
} |
|
} |
|
} // If we get here then something is very wrong. |
|
|
|
|
|
throw new Error(); |
|
} |
|
|
|
function enclosesNot(a, b) { |
|
var dr = a.r - b.r, |
|
dx = b.x - a.x, |
|
dy = b.y - a.y; |
|
return dr < 0 || dr * dr < dx * dx + dy * dy; |
|
} |
|
|
|
function enclosesWeak(a, b) { |
|
var dr = a.r - b.r + 1e-6, |
|
dx = b.x - a.x, |
|
dy = b.y - a.y; |
|
return dr > 0 && dr * dr > dx * dx + dy * dy; |
|
} |
|
|
|
function enclosesWeakAll(a, B) { |
|
for (var i = 0; i < B.length; ++i) { |
|
if (!enclosesWeak(a, B[i])) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function encloseBasis(B) { |
|
switch (B.length) { |
|
case 1: |
|
return encloseBasis1(B[0]); |
|
|
|
case 2: |
|
return encloseBasis2(B[0], B[1]); |
|
|
|
case 3: |
|
return encloseBasis3(B[0], B[1], B[2]); |
|
} |
|
} |
|
|
|
function encloseBasis1(a) { |
|
return { |
|
x: a.x, |
|
y: a.y, |
|
r: a.r |
|
}; |
|
} |
|
|
|
function encloseBasis2(a, b) { |
|
var x1 = a.x, |
|
y1 = a.y, |
|
r1 = a.r, |
|
x2 = b.x, |
|
y2 = b.y, |
|
r2 = b.r, |
|
x21 = x2 - x1, |
|
y21 = y2 - y1, |
|
r21 = r2 - r1, |
|
l = Math.sqrt(x21 * x21 + y21 * y21); |
|
return { |
|
x: (x1 + x2 + x21 / l * r21) / 2, |
|
y: (y1 + y2 + y21 / l * r21) / 2, |
|
r: (l + r1 + r2) / 2 |
|
}; |
|
} |
|
|
|
function encloseBasis3(a, b, c) { |
|
var x1 = a.x, |
|
y1 = a.y, |
|
r1 = a.r, |
|
x2 = b.x, |
|
y2 = b.y, |
|
r2 = b.r, |
|
x3 = c.x, |
|
y3 = c.y, |
|
r3 = c.r, |
|
a2 = x1 - x2, |
|
a3 = x1 - x3, |
|
b2 = y1 - y2, |
|
b3 = y1 - y3, |
|
c2 = r2 - r1, |
|
c3 = r3 - r1, |
|
d1 = x1 * x1 + y1 * y1 - r1 * r1, |
|
d2 = d1 - x2 * x2 - y2 * y2 + r2 * r2, |
|
d3 = d1 - x3 * x3 - y3 * y3 + r3 * r3, |
|
ab = a3 * b2 - a2 * b3, |
|
xa = (b2 * d3 - b3 * d2) / (ab * 2) - x1, |
|
xb = (b3 * c2 - b2 * c3) / ab, |
|
ya = (a3 * d2 - a2 * d3) / (ab * 2) - y1, |
|
yb = (a2 * c3 - a3 * c2) / ab, |
|
A = xb * xb + yb * yb - 1, |
|
B = 2 * (r1 + xa * xb + ya * yb), |
|
C = xa * xa + ya * ya - r1 * r1, |
|
r = -(A ? (B + Math.sqrt(B * B - 4 * A * C)) / (2 * A) : C / B); |
|
return { |
|
x: x1 + xa + xb * r, |
|
y: y1 + ya + yb * r, |
|
r: r |
|
}; |
|
} |
|
|
|
function place(b, a, c) { |
|
var dx = b.x - a.x, |
|
x, |
|
a2, |
|
dy = b.y - a.y, |
|
y, |
|
b2, |
|
d2 = dx * dx + dy * dy; |
|
|
|
if (d2) { |
|
a2 = a.r + c.r, a2 *= a2; |
|
b2 = b.r + c.r, b2 *= b2; |
|
|
|
if (a2 > b2) { |
|
x = (d2 + b2 - a2) / (2 * d2); |
|
y = Math.sqrt(Math.max(0, b2 / d2 - x * x)); |
|
c.x = b.x - x * dx - y * dy; |
|
c.y = b.y - x * dy + y * dx; |
|
} else { |
|
x = (d2 + a2 - b2) / (2 * d2); |
|
y = Math.sqrt(Math.max(0, a2 / d2 - x * x)); |
|
c.x = a.x + x * dx - y * dy; |
|
c.y = a.y + x * dy + y * dx; |
|
} |
|
} else { |
|
c.x = a.x + c.r; |
|
c.y = a.y; |
|
} |
|
} |
|
|
|
function intersects(a, b) { |
|
var dr = a.r + b.r - 1e-6, |
|
dx = b.x - a.x, |
|
dy = b.y - a.y; |
|
return dr > 0 && dr * dr > dx * dx + dy * dy; |
|
} |
|
|
|
function score(node) { |
|
var a = node._, |
|
b = node.next._, |
|
ab = a.r + b.r, |
|
dx = (a.x * b.r + b.x * a.r) / ab, |
|
dy = (a.y * b.r + b.y * a.r) / ab; |
|
return dx * dx + dy * dy; |
|
} |
|
|
|
function Node$1(circle) { |
|
this._ = circle; |
|
this.next = null; |
|
this.previous = null; |
|
} |
|
|
|
function packEnclose(circles) { |
|
if (!(n = circles.length)) return 0; |
|
var a, b, c, n, aa, ca, i, j, k, sj, sk; // Place the first circle. |
|
|
|
a = circles[0], a.x = 0, a.y = 0; |
|
if (!(n > 1)) return a.r; // Place the second circle. |
|
|
|
b = circles[1], a.x = -b.r, b.x = a.r, b.y = 0; |
|
if (!(n > 2)) return a.r + b.r; // Place the third circle. |
|
|
|
place(b, a, c = circles[2]); // Initialize the front-chain using the first three circles a, b and c. |
|
|
|
a = new Node$1(a), b = new Node$1(b), c = new Node$1(c); |
|
a.next = c.previous = b; |
|
b.next = a.previous = c; |
|
c.next = b.previous = a; // Attempt to place each remaining circle… |
|
|
|
pack: for (i = 3; i < n; ++i) { |
|
place(a._, b._, c = circles[i]), c = new Node$1(c); // Find the closest intersecting circle on the front-chain, if any. |
|
// “Closeness” is determined by linear distance along the front-chain. |
|
// “Ahead” or “behind” is likewise determined by linear distance. |
|
|
|
j = b.next, k = a.previous, sj = b._.r, sk = a._.r; |
|
|
|
do { |
|
if (sj <= sk) { |
|
if (intersects(j._, c._)) { |
|
b = j, a.next = b, b.previous = a, --i; |
|
continue pack; |
|
} |
|
|
|
sj += j._.r, j = j.next; |
|
} else { |
|
if (intersects(k._, c._)) { |
|
a = k, a.next = b, b.previous = a, --i; |
|
continue pack; |
|
} |
|
|
|
sk += k._.r, k = k.previous; |
|
} |
|
} while (j !== k.next); // Success! Insert the new circle c between a and b. |
|
|
|
|
|
c.previous = a, c.next = b, a.next = b.previous = b = c; // Compute the new closest circle pair to the centroid. |
|
|
|
aa = score(a); |
|
|
|
while ((c = c.next) !== b) { |
|
if ((ca = score(c)) < aa) { |
|
a = c, aa = ca; |
|
} |
|
} |
|
|
|
b = a.next; |
|
} // Compute the enclosing circle of the front chain. |
|
|
|
|
|
a = [b._], c = b; |
|
|
|
while ((c = c.next) !== b) { |
|
a.push(c._); |
|
} |
|
|
|
c = enclose(a); // Translate the circles to put the enclosing circle around the origin. |
|
|
|
for (i = 0; i < n; ++i) { |
|
a = circles[i], a.x -= c.x, a.y -= c.y; |
|
} |
|
|
|
return c.r; |
|
} |
|
|
|
function optional(f) { |
|
return f == null ? null : required(f); |
|
} |
|
|
|
function required(f) { |
|
if (typeof f !== "function") throw new Error(); |
|
return f; |
|
} |
|
|
|
function constantZero() { |
|
return 0; |
|
} |
|
|
|
function constant$5(x) { |
|
return function () { |
|
return x; |
|
}; |
|
} |
|
|
|
function defaultRadius(d) { |
|
return Math.sqrt(d.value); |
|
} |
|
|
|
function pack() { |
|
var radius = null, |
|
dx = 1, |
|
dy = 1, |
|
padding = constantZero; |
|
|
|
function pack(root) { |
|
root.x = dx / 2, root.y = dy / 2; |
|
|
|
if (radius) { |
|
root.eachBefore(radiusLeaf(radius)).eachAfter(packChildren(padding, 0.5)).eachBefore(translateChild(1)); |
|
} else { |
|
root.eachBefore(radiusLeaf(defaultRadius)).eachAfter(packChildren(constantZero, 1)).eachAfter(packChildren(padding, root.r / Math.min(dx, dy))).eachBefore(translateChild(Math.min(dx, dy) / (2 * root.r))); |
|
} |
|
|
|
return root; |
|
} |
|
|
|
pack.radius = function (x) { |
|
return arguments.length ? (radius = optional(x), pack) : radius; |
|
}; |
|
|
|
pack.size = function (x) { |
|
return arguments.length ? (dx = +x[0], dy = +x[1], pack) : [dx, dy]; |
|
}; |
|
|
|
pack.padding = function (x) { |
|
return arguments.length ? (padding = typeof x === "function" ? x : constant$5(+x), pack) : padding; |
|
}; |
|
|
|
return pack; |
|
} |
|
|
|
function radiusLeaf(radius) { |
|
return function (node) { |
|
if (!node.children) { |
|
node.r = Math.max(0, +radius(node) || 0); |
|
} |
|
}; |
|
} |
|
|
|
function packChildren(padding, k) { |
|
return function (node) { |
|
if (children = node.children) { |
|
var children, |
|
i, |
|
n = children.length, |
|
r = padding(node) * k || 0, |
|
e; |
|
if (r) for (i = 0; i < n; ++i) { |
|
children[i].r += r; |
|
} |
|
e = packEnclose(children); |
|
if (r) for (i = 0; i < n; ++i) { |
|
children[i].r -= r; |
|
} |
|
node.r = e + r; |
|
} |
|
}; |
|
} |
|
|
|
function translateChild(k) { |
|
return function (node) { |
|
var parent = node.parent; |
|
node.r *= k; |
|
|
|
if (parent) { |
|
node.x = parent.x + k * node.x; |
|
node.y = parent.y + k * node.y; |
|
} |
|
}; |
|
} |
|
|
|
function roundNode(node) { |
|
node.x0 = Math.round(node.x0); |
|
node.y0 = Math.round(node.y0); |
|
node.x1 = Math.round(node.x1); |
|
node.y1 = Math.round(node.y1); |
|
} |
|
|
|
function treemapDice(parent, x0, y0, x1, y1) { |
|
var nodes = parent.children, |
|
node, |
|
i = -1, |
|
n = nodes.length, |
|
k = parent.value && (x1 - x0) / parent.value; |
|
|
|
while (++i < n) { |
|
node = nodes[i], node.y0 = y0, node.y1 = y1; |
|
node.x0 = x0, node.x1 = x0 += node.value * k; |
|
} |
|
} |
|
|
|
function partition$4() { |
|
var dx = 1, |
|
dy = 1, |
|
padding = 0, |
|
round = false; |
|
|
|
function partition(root) { |
|
var n = root.height + 1; |
|
root.x0 = root.y0 = padding; |
|
root.x1 = dx; |
|
root.y1 = dy / n; |
|
root.eachBefore(positionNode(dy, n)); |
|
if (round) root.eachBefore(roundNode); |
|
return root; |
|
} |
|
|
|
function positionNode(dy, n) { |
|
return function (node) { |
|
if (node.children) { |
|
treemapDice(node, node.x0, dy * (node.depth + 1) / n, node.x1, dy * (node.depth + 2) / n); |
|
} |
|
|
|
var x0 = node.x0, |
|
y0 = node.y0, |
|
x1 = node.x1 - padding, |
|
y1 = node.y1 - padding; |
|
if (x1 < x0) x0 = x1 = (x0 + x1) / 2; |
|
if (y1 < y0) y0 = y1 = (y0 + y1) / 2; |
|
node.x0 = x0; |
|
node.y0 = y0; |
|
node.x1 = x1; |
|
node.y1 = y1; |
|
}; |
|
} |
|
|
|
partition.round = function (x) { |
|
return arguments.length ? (round = !!x, partition) : round; |
|
}; |
|
|
|
partition.size = function (x) { |
|
return arguments.length ? (dx = +x[0], dy = +x[1], partition) : [dx, dy]; |
|
}; |
|
|
|
partition.padding = function (x) { |
|
return arguments.length ? (padding = +x, partition) : padding; |
|
}; |
|
|
|
return partition; |
|
} |
|
|
|
var keyPrefix = "$", |
|
// Protect against keys like “__proto__”. |
|
preroot = { |
|
depth: -1 |
|
}, |
|
ambiguous = {}; |
|
|
|
function defaultId(d) { |
|
return d.id; |
|
} |
|
|
|
function defaultParentId(d) { |
|
return d.parentId; |
|
} |
|
|
|
function stratify() { |
|
var id = defaultId, |
|
parentId = defaultParentId; |
|
|
|
function stratify(data) { |
|
var d, |
|
i, |
|
n = data.length, |
|
root, |
|
parent, |
|
node, |
|
nodes = new Array(n), |
|
nodeId, |
|
nodeKey, |
|
nodeByKey = {}; |
|
|
|
for (i = 0; i < n; ++i) { |
|
d = data[i], node = nodes[i] = new Node(d); |
|
|
|
if ((nodeId = id(d, i, data)) != null && (nodeId += "")) { |
|
nodeKey = keyPrefix + (node.id = nodeId); |
|
nodeByKey[nodeKey] = nodeKey in nodeByKey ? ambiguous : node; |
|
} |
|
} |
|
|
|
for (i = 0; i < n; ++i) { |
|
node = nodes[i], nodeId = parentId(data[i], i, data); |
|
|
|
if (nodeId == null || !(nodeId += "")) { |
|
if (root) throw new Error("multiple roots"); |
|
root = node; |
|
} else { |
|
parent = nodeByKey[keyPrefix + nodeId]; |
|
if (!parent) throw new Error("missing: " + nodeId); |
|
if (parent === ambiguous) throw new Error("ambiguous: " + nodeId); |
|
if (parent.children) parent.children.push(node);else parent.children = [node]; |
|
node.parent = parent; |
|
} |
|
} |
|
|
|
if (!root) throw new Error("no root"); |
|
root.parent = preroot; |
|
root.eachBefore(function (node) { |
|
node.depth = node.parent.depth + 1; |
|
--n; |
|
}).eachBefore(computeHeight); |
|
root.parent = null; |
|
if (n > 0) throw new Error("cycle"); |
|
return root; |
|
} |
|
|
|
stratify.id = function (x) { |
|
return arguments.length ? (id = required(x), stratify) : id; |
|
}; |
|
|
|
stratify.parentId = function (x) { |
|
return arguments.length ? (parentId = required(x), stratify) : parentId; |
|
}; |
|
|
|
return stratify; |
|
} |
|
|
|
function defaultSeparation$1(a, b) { |
|
return a.parent === b.parent ? 1 : 2; |
|
} // function radialSeparation(a, b) { |
|
// return (a.parent === b.parent ? 1 : 2) / a.depth; |
|
// } |
|
// This function is used to traverse the left contour of a subtree (or |
|
// subforest). It returns the successor of v on this contour. This successor is |
|
// either given by the leftmost child of v or by the thread of v. The function |
|
// returns null if and only if v is on the highest level of its subtree. |
|
|
|
|
|
function nextLeft(v) { |
|
var children = v.children; |
|
return children ? children[0] : v.t; |
|
} // This function works analogously to nextLeft. |
|
|
|
|
|
function nextRight(v) { |
|
var children = v.children; |
|
return children ? children[children.length - 1] : v.t; |
|
} // Shifts the current subtree rooted at w+. This is done by increasing |
|
// prelim(w+) and mod(w+) by shift. |
|
|
|
|
|
function moveSubtree(wm, wp, shift) { |
|
var change = shift / (wp.i - wm.i); |
|
wp.c -= change; |
|
wp.s += shift; |
|
wm.c += change; |
|
wp.z += shift; |
|
wp.m += shift; |
|
} // All other shifts, applied to the smaller subtrees between w- and w+, are |
|
// performed by this function. To prepare the shifts, we have to adjust |
|
// change(w+), shift(w+), and change(w-). |
|
|
|
|
|
function executeShifts(v) { |
|
var shift = 0, |
|
change = 0, |
|
children = v.children, |
|
i = children.length, |
|
w; |
|
|
|
while (--i >= 0) { |
|
w = children[i]; |
|
w.z += shift; |
|
w.m += shift; |
|
shift += w.s + (change += w.c); |
|
} |
|
} // If vi-’s ancestor is a sibling of v, returns vi-’s ancestor. Otherwise, |
|
// returns the specified (default) ancestor. |
|
|
|
|
|
function nextAncestor(vim, v, ancestor) { |
|
return vim.a.parent === v.parent ? vim.a : ancestor; |
|
} |
|
|
|
function TreeNode(node, i) { |
|
this._ = node; |
|
this.parent = null; |
|
this.children = null; |
|
this.A = null; // default ancestor |
|
|
|
this.a = this; // ancestor |
|
|
|
this.z = 0; // prelim |
|
|
|
this.m = 0; // mod |
|
|
|
this.c = 0; // change |
|
|
|
this.s = 0; // shift |
|
|
|
this.t = null; // thread |
|
|
|
this.i = i; // number |
|
} |
|
|
|
TreeNode.prototype = Object.create(Node.prototype); |
|
|
|
function treeRoot(root) { |
|
var tree = new TreeNode(root, 0), |
|
node, |
|
nodes = [tree], |
|
child, |
|
children, |
|
i, |
|
n; |
|
|
|
while (node = nodes.pop()) { |
|
if (children = node._.children) { |
|
node.children = new Array(n = children.length); |
|
|
|
for (i = n - 1; i >= 0; --i) { |
|
nodes.push(child = node.children[i] = new TreeNode(children[i], i)); |
|
child.parent = node; |
|
} |
|
} |
|
} |
|
|
|
(tree.parent = new TreeNode(null, 0)).children = [tree]; |
|
return tree; |
|
} // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm |
|
|
|
|
|
function tree() { |
|
var separation = defaultSeparation$1, |
|
dx = 1, |
|
dy = 1, |
|
nodeSize = null; |
|
|
|
function tree(root) { |
|
var t = treeRoot(root); // Compute the layout using Buchheim et al.’s algorithm. |
|
|
|
t.eachAfter(firstWalk), t.parent.m = -t.z; |
|
t.eachBefore(secondWalk); // If a fixed node size is specified, scale x and y. |
|
|
|
if (nodeSize) root.eachBefore(sizeNode); // If a fixed tree size is specified, scale x and y based on the extent. |
|
// Compute the left-most, right-most, and depth-most nodes for extents. |
|
else { |
|
var left = root, |
|
right = root, |
|
bottom = root; |
|
root.eachBefore(function (node) { |
|
if (node.x < left.x) left = node; |
|
if (node.x > right.x) right = node; |
|
if (node.depth > bottom.depth) bottom = node; |
|
}); |
|
var s = left === right ? 1 : separation(left, right) / 2, |
|
tx = s - left.x, |
|
kx = dx / (right.x + s + tx), |
|
ky = dy / (bottom.depth || 1); |
|
root.eachBefore(function (node) { |
|
node.x = (node.x + tx) * kx; |
|
node.y = node.depth * ky; |
|
}); |
|
} |
|
return root; |
|
} // Computes a preliminary x-coordinate for v. Before that, FIRST WALK is |
|
// applied recursively to the children of v, as well as the function |
|
// APPORTION. After spacing out the children by calling EXECUTE SHIFTS, the |
|
// node v is placed to the midpoint of its outermost children. |
|
|
|
|
|
function firstWalk(v) { |
|
var children = v.children, |
|
siblings = v.parent.children, |
|
w = v.i ? siblings[v.i - 1] : null; |
|
|
|
if (children) { |
|
executeShifts(v); |
|
var midpoint = (children[0].z + children[children.length - 1].z) / 2; |
|
|
|
if (w) { |
|
v.z = w.z + separation(v._, w._); |
|
v.m = v.z - midpoint; |
|
} else { |
|
v.z = midpoint; |
|
} |
|
} else if (w) { |
|
v.z = w.z + separation(v._, w._); |
|
} |
|
|
|
v.parent.A = apportion(v, w, v.parent.A || siblings[0]); |
|
} // Computes all real x-coordinates by summing up the modifiers recursively. |
|
|
|
|
|
function secondWalk(v) { |
|
v._.x = v.z + v.parent.m; |
|
v.m += v.parent.m; |
|
} // The core of the algorithm. Here, a new subtree is combined with the |
|
// previous subtrees. Threads are used to traverse the inside and outside |
|
// contours of the left and right subtree up to the highest common level. The |
|
// vertices used for the traversals are vi+, vi-, vo-, and vo+, where the |
|
// superscript o means outside and i means inside, the subscript - means left |
|
// subtree and + means right subtree. For summing up the modifiers along the |
|
// contour, we use respective variables si+, si-, so-, and so+. Whenever two |
|
// nodes of the inside contours conflict, we compute the left one of the |
|
// greatest uncommon ancestors using the function ANCESTOR and call MOVE |
|
// SUBTREE to shift the subtree and prepare the shifts of smaller subtrees. |
|
// Finally, we add a new thread (if necessary). |
|
|
|
|
|
function apportion(v, w, ancestor) { |
|
if (w) { |
|
var vip = v, |
|
vop = v, |
|
vim = w, |
|
vom = vip.parent.children[0], |
|
sip = vip.m, |
|
sop = vop.m, |
|
sim = vim.m, |
|
som = vom.m, |
|
shift; |
|
|
|
while (vim = nextRight(vim), vip = nextLeft(vip), vim && vip) { |
|
vom = nextLeft(vom); |
|
vop = nextRight(vop); |
|
vop.a = v; |
|
shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); |
|
|
|
if (shift > 0) { |
|
moveSubtree(nextAncestor(vim, v, ancestor), v, shift); |
|
sip += shift; |
|
sop += shift; |
|
} |
|
|
|
sim += vim.m; |
|
sip += vip.m; |
|
som += vom.m; |
|
sop += vop.m; |
|
} |
|
|
|
if (vim && !nextRight(vop)) { |
|
vop.t = vim; |
|
vop.m += sim - sop; |
|
} |
|
|
|
if (vip && !nextLeft(vom)) { |
|
vom.t = vip; |
|
vom.m += sip - som; |
|
ancestor = v; |
|
} |
|
} |
|
|
|
return ancestor; |
|
} |
|
|
|
function sizeNode(node) { |
|
node.x *= dx; |
|
node.y = node.depth * dy; |
|
} |
|
|
|
tree.separation = function (x) { |
|
return arguments.length ? (separation = x, tree) : separation; |
|
}; |
|
|
|
tree.size = function (x) { |
|
return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], tree) : nodeSize ? null : [dx, dy]; |
|
}; |
|
|
|
tree.nodeSize = function (x) { |
|
return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], tree) : nodeSize ? [dx, dy] : null; |
|
}; |
|
|
|
return tree; |
|
} |
|
|
|
function treemapSlice(parent, x0, y0, x1, y1) { |
|
var nodes = parent.children, |
|
node, |
|
i = -1, |
|
n = nodes.length, |
|
k = parent.value && (y1 - y0) / parent.value; |
|
|
|
while (++i < n) { |
|
node = nodes[i], node.x0 = x0, node.x1 = x1; |
|
node.y0 = y0, node.y1 = y0 += node.value * k; |
|
} |
|
} |
|
|
|
var phi = (1 + Math.sqrt(5)) / 2; |
|
|
|
function squarifyRatio(ratio, parent, x0, y0, x1, y1) { |
|
var rows = [], |
|
nodes = parent.children, |
|
row, |
|
nodeValue, |
|
i0 = 0, |
|
i1 = 0, |
|
n = nodes.length, |
|
dx, |
|
dy, |
|
value = parent.value, |
|
sumValue, |
|
minValue, |
|
maxValue, |
|
newRatio, |
|
minRatio, |
|
alpha, |
|
beta; |
|
|
|
while (i0 < n) { |
|
dx = x1 - x0, dy = y1 - y0; // Find the next non-empty node. |
|
|
|
do { |
|
sumValue = nodes[i1++].value; |
|
} while (!sumValue && i1 < n); |
|
|
|
minValue = maxValue = sumValue; |
|
alpha = Math.max(dy / dx, dx / dy) / (value * ratio); |
|
beta = sumValue * sumValue * alpha; |
|
minRatio = Math.max(maxValue / beta, beta / minValue); // Keep adding nodes while the aspect ratio maintains or improves. |
|
|
|
for (; i1 < n; ++i1) { |
|
sumValue += nodeValue = nodes[i1].value; |
|
if (nodeValue < minValue) minValue = nodeValue; |
|
if (nodeValue > maxValue) maxValue = nodeValue; |
|
beta = sumValue * sumValue * alpha; |
|
newRatio = Math.max(maxValue / beta, beta / minValue); |
|
|
|
if (newRatio > minRatio) { |
|
sumValue -= nodeValue; |
|
break; |
|
} |
|
|
|
minRatio = newRatio; |
|
} // Position and record the row orientation. |
|
|
|
|
|
rows.push(row = { |
|
value: sumValue, |
|
dice: dx < dy, |
|
children: nodes.slice(i0, i1) |
|
}); |
|
if (row.dice) treemapDice(row, x0, y0, x1, value ? y0 += dy * sumValue / value : y1);else treemapSlice(row, x0, y0, value ? x0 += dx * sumValue / value : x1, y1); |
|
value -= sumValue, i0 = i1; |
|
} |
|
|
|
return rows; |
|
} |
|
|
|
var treemapSquarify = function custom(ratio) { |
|
function squarify(parent, x0, y0, x1, y1) { |
|
squarifyRatio(ratio, parent, x0, y0, x1, y1); |
|
} |
|
|
|
squarify.ratio = function (x) { |
|
return custom((x = +x) > 1 ? x : 1); |
|
}; |
|
|
|
return squarify; |
|
}(phi); |
|
|
|
function treemap() { |
|
var tile = treemapSquarify, |
|
round = false, |
|
dx = 1, |
|
dy = 1, |
|
paddingStack = [0], |
|
paddingInner = constantZero, |
|
paddingTop = constantZero, |
|
paddingRight = constantZero, |
|
paddingBottom = constantZero, |
|
paddingLeft = constantZero; |
|
|
|
function treemap(root) { |
|
root.x0 = root.y0 = 0; |
|
root.x1 = dx; |
|
root.y1 = dy; |
|
root.eachBefore(positionNode); |
|
paddingStack = [0]; |
|
if (round) root.eachBefore(roundNode); |
|
return root; |
|
} |
|
|
|
function positionNode(node) { |
|
var p = paddingStack[node.depth], |
|
x0 = node.x0 + p, |
|
y0 = node.y0 + p, |
|
x1 = node.x1 - p, |
|
y1 = node.y1 - p; |
|
if (x1 < x0) x0 = x1 = (x0 + x1) / 2; |
|
if (y1 < y0) y0 = y1 = (y0 + y1) / 2; |
|
node.x0 = x0; |
|
node.y0 = y0; |
|
node.x1 = x1; |
|
node.y1 = y1; |
|
|
|
if (node.children) { |
|
p = paddingStack[node.depth + 1] = paddingInner(node) / 2; |
|
x0 += paddingLeft(node) - p; |
|
y0 += paddingTop(node) - p; |
|
x1 -= paddingRight(node) - p; |
|
y1 -= paddingBottom(node) - p; |
|
if (x1 < x0) x0 = x1 = (x0 + x1) / 2; |
|
if (y1 < y0) y0 = y1 = (y0 + y1) / 2; |
|
tile(node, x0, y0, x1, y1); |
|
} |
|
} |
|
|
|
treemap.round = function (x) { |
|
return arguments.length ? (round = !!x, treemap) : round; |
|
}; |
|
|
|
treemap.size = function (x) { |
|
return arguments.length ? (dx = +x[0], dy = +x[1], treemap) : [dx, dy]; |
|
}; |
|
|
|
treemap.tile = function (x) { |
|
return arguments.length ? (tile = required(x), treemap) : tile; |
|
}; |
|
|
|
treemap.padding = function (x) { |
|
return arguments.length ? treemap.paddingInner(x).paddingOuter(x) : treemap.paddingInner(); |
|
}; |
|
|
|
treemap.paddingInner = function (x) { |
|
return arguments.length ? (paddingInner = typeof x === "function" ? x : constant$5(+x), treemap) : paddingInner; |
|
}; |
|
|
|
treemap.paddingOuter = function (x) { |
|
return arguments.length ? treemap.paddingTop(x).paddingRight(x).paddingBottom(x).paddingLeft(x) : treemap.paddingTop(); |
|
}; |
|
|
|
treemap.paddingTop = function (x) { |
|
return arguments.length ? (paddingTop = typeof x === "function" ? x : constant$5(+x), treemap) : paddingTop; |
|
}; |
|
|
|
treemap.paddingRight = function (x) { |
|
return arguments.length ? (paddingRight = typeof x === "function" ? x : constant$5(+x), treemap) : paddingRight; |
|
}; |
|
|
|
treemap.paddingBottom = function (x) { |
|
return arguments.length ? (paddingBottom = typeof x === "function" ? x : constant$5(+x), treemap) : paddingBottom; |
|
}; |
|
|
|
treemap.paddingLeft = function (x) { |
|
return arguments.length ? (paddingLeft = typeof x === "function" ? x : constant$5(+x), treemap) : paddingLeft; |
|
}; |
|
|
|
return treemap; |
|
} |
|
|
|
function treemapBinary(parent, x0, y0, x1, y1) { |
|
var nodes = parent.children, |
|
i, |
|
n = nodes.length, |
|
sum, |
|
sums = new Array(n + 1); |
|
|
|
for (sums[0] = sum = i = 0; i < n; ++i) { |
|
sums[i + 1] = sum += nodes[i].value; |
|
} |
|
|
|
partition(0, n, parent.value, x0, y0, x1, y1); |
|
|
|
function partition(i, j, value, x0, y0, x1, y1) { |
|
if (i >= j - 1) { |
|
var node = nodes[i]; |
|
node.x0 = x0, node.y0 = y0; |
|
node.x1 = x1, node.y1 = y1; |
|
return; |
|
} |
|
|
|
var valueOffset = sums[i], |
|
valueTarget = value / 2 + valueOffset, |
|
k = i + 1, |
|
hi = j - 1; |
|
|
|
while (k < hi) { |
|
var mid = k + hi >>> 1; |
|
if (sums[mid] < valueTarget) k = mid + 1;else hi = mid; |
|
} |
|
|
|
if (valueTarget - sums[k - 1] < sums[k] - valueTarget && i + 1 < k) --k; |
|
var valueLeft = sums[k] - valueOffset, |
|
valueRight = value - valueLeft; |
|
|
|
if (x1 - x0 > y1 - y0) { |
|
var xk = (x0 * valueRight + x1 * valueLeft) / value; |
|
partition(i, k, valueLeft, x0, y0, xk, y1); |
|
partition(k, j, valueRight, xk, y0, x1, y1); |
|
} else { |
|
var yk = (y0 * valueRight + y1 * valueLeft) / value; |
|
partition(i, k, valueLeft, x0, y0, x1, yk); |
|
partition(k, j, valueRight, x0, yk, x1, y1); |
|
} |
|
} |
|
} |
|
|
|
function treemapSliceDice(parent, x0, y0, x1, y1) { |
|
(parent.depth & 1 ? treemapSlice : treemapDice)(parent, x0, y0, x1, y1); |
|
} |
|
|
|
var treemapResquarify = function custom(ratio) { |
|
function resquarify(parent, x0, y0, x1, y1) { |
|
if ((rows = parent._squarify) && rows.ratio === ratio) { |
|
var rows, |
|
row, |
|
nodes, |
|
i, |
|
j = -1, |
|
n, |
|
m = rows.length, |
|
value = parent.value; |
|
|
|
while (++j < m) { |
|
row = rows[j], nodes = row.children; |
|
|
|
for (i = row.value = 0, n = nodes.length; i < n; ++i) { |
|
row.value += nodes[i].value; |
|
} |
|
|
|
if (row.dice) treemapDice(row, x0, y0, x1, y0 += (y1 - y0) * row.value / value);else treemapSlice(row, x0, y0, x0 += (x1 - x0) * row.value / value, y1); |
|
value -= row.value; |
|
} |
|
} else { |
|
parent._squarify = rows = squarifyRatio(ratio, parent, x0, y0, x1, y1); |
|
rows.ratio = ratio; |
|
} |
|
} |
|
|
|
resquarify.ratio = function (x) { |
|
return custom((x = +x) > 1 ? x : 1); |
|
}; |
|
|
|
return resquarify; |
|
}(phi); |
|
/** |
|
* Nest tuples into a tree structure, grouped by key values. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<function(object): *>} params.keys - The key fields to nest by, in order. |
|
* @param {boolean} [params.generate=false] - A boolean flag indicating if |
|
* non-leaf nodes generated by this transform should be included in the |
|
* output. The default (false) includes only the input data (leaf nodes) |
|
* in the data stream. |
|
*/ |
|
|
|
|
|
function Nest(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Nest.Definition = { |
|
"type": "Nest", |
|
"metadata": { |
|
"treesource": true, |
|
"changes": true |
|
}, |
|
"params": [{ |
|
"name": "keys", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "generate", |
|
"type": "boolean" |
|
}] |
|
}; |
|
var prototype$1e = inherits(Nest, Transform); |
|
|
|
function children(n) { |
|
return n.values; |
|
} |
|
|
|
prototype$1e.transform = function (_, pulse) { |
|
if (!pulse.source) { |
|
error('Nest transform requires an upstream data source.'); |
|
} |
|
|
|
var gen = _.generate, |
|
mod = _.modified(), |
|
out = pulse.clone(), |
|
tree = this.value; |
|
|
|
if (!tree || mod || pulse.changed()) { |
|
// collect nodes to remove |
|
if (tree) { |
|
tree.each(function (node) { |
|
if (node.children && isTuple(node.data)) { |
|
out.rem.push(node.data); |
|
} |
|
}); |
|
} // generate new tree structure |
|
|
|
|
|
this.value = tree = hierarchy({ |
|
values: array(_.keys).reduce(function (n, k) { |
|
n.key(k); |
|
return n; |
|
}, nest()).entries(out.source) |
|
}, children); // collect nodes to add |
|
|
|
if (gen) { |
|
tree.each(function (node) { |
|
if (node.children) { |
|
node = ingest(node.data); |
|
out.add.push(node); |
|
out.source.push(node); |
|
} |
|
}); |
|
} // build lookup table |
|
|
|
|
|
lookup$3(tree, tupleid, tupleid); |
|
} |
|
|
|
out.source.root = tree; |
|
return out; |
|
}; |
|
|
|
function nest() { |
|
var keys = [], |
|
nest; |
|
|
|
function apply(array, depth) { |
|
if (depth >= keys.length) { |
|
return array; |
|
} |
|
|
|
var i = -1, |
|
n = array.length, |
|
key = keys[depth++], |
|
keyValue, |
|
value, |
|
valuesByKey = {}, |
|
values, |
|
result = {}; |
|
|
|
while (++i < n) { |
|
keyValue = key(value = array[i]) + ''; |
|
|
|
if (values = valuesByKey[keyValue]) { |
|
values.push(value); |
|
} else { |
|
valuesByKey[keyValue] = [value]; |
|
} |
|
} |
|
|
|
for (keyValue in valuesByKey) { |
|
result[keyValue] = apply(valuesByKey[keyValue], depth); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
function _entries(map, depth) { |
|
if (++depth > keys.length) return map; |
|
var array = [], |
|
k; |
|
|
|
for (k in map) { |
|
array.push({ |
|
key: k, |
|
values: _entries(map[k], depth) |
|
}); |
|
} |
|
|
|
return array; |
|
} |
|
|
|
return nest = { |
|
entries: function entries(array) { |
|
return _entries(apply(array, 0), 0); |
|
}, |
|
key: function key(d) { |
|
keys.push(d); |
|
return nest; |
|
} |
|
}; |
|
} |
|
/** |
|
* Abstract class for tree layout. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
*/ |
|
|
|
|
|
function HierarchyLayout(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
var prototype$1f = inherits(HierarchyLayout, Transform); |
|
|
|
prototype$1f.transform = function (_, pulse) { |
|
if (!pulse.source || !pulse.source.root) { |
|
error(this.constructor.name + ' transform requires a backing tree data source.'); |
|
} |
|
|
|
var layout = this.layout(_.method), |
|
fields = this.fields, |
|
root = pulse.source.root, |
|
as = _.as || fields; |
|
if (_.field) root.sum(_.field);else root.count(); |
|
if (_.sort) root.sort(stableCompare(_.sort, function (d) { |
|
return d.data; |
|
})); |
|
setParams(layout, this.params, _); |
|
|
|
if (layout.separation) { |
|
layout.separation(_.separation !== false ? defaultSeparation$2 : one); |
|
} |
|
|
|
try { |
|
this.value = layout(root); |
|
} catch (err) { |
|
error(err); |
|
} |
|
|
|
root.each(function (node) { |
|
setFields(node, fields, as); |
|
}); |
|
return pulse.reflow(_.modified()).modifies(as).modifies('leaf'); |
|
}; |
|
|
|
function setParams(layout, params, _) { |
|
for (var p, i = 0, n = params.length; i < n; ++i) { |
|
p = params[i]; |
|
if (p in _) layout[p](_[p]); |
|
} |
|
} |
|
|
|
function setFields(node, fields, as) { |
|
var t = node.data; |
|
|
|
for (var i = 0, n = fields.length - 1; i < n; ++i) { |
|
t[as[i]] = node[fields[i]]; |
|
} |
|
|
|
t[as[n]] = node.children ? node.children.length : 0; |
|
} |
|
|
|
function defaultSeparation$2(a, b) { |
|
return a.parent === b.parent ? 1 : 2; |
|
} |
|
|
|
var Output$1 = ['x', 'y', 'r', 'depth', 'children']; |
|
/** |
|
* Packed circle tree layout. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The value field to size nodes. |
|
*/ |
|
|
|
function Pack(params) { |
|
HierarchyLayout.call(this, params); |
|
} |
|
|
|
Pack.Definition = { |
|
"type": "Pack", |
|
"metadata": { |
|
"tree": true, |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field" |
|
}, { |
|
"name": "sort", |
|
"type": "compare" |
|
}, { |
|
"name": "padding", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "radius", |
|
"type": "field", |
|
"default": null |
|
}, { |
|
"name": "size", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": Output$1.length, |
|
"default": Output$1 |
|
}] |
|
}; |
|
var prototype$1g = inherits(Pack, HierarchyLayout); |
|
prototype$1g.layout = pack; |
|
prototype$1g.params = ['radius', 'size', 'padding']; |
|
prototype$1g.fields = Output$1; |
|
var Output$2 = ['x0', 'y0', 'x1', 'y1', 'depth', 'children']; |
|
/** |
|
* Partition tree layout. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The value field to size nodes. |
|
*/ |
|
|
|
function Partition(params) { |
|
HierarchyLayout.call(this, params); |
|
} |
|
|
|
Partition.Definition = { |
|
"type": "Partition", |
|
"metadata": { |
|
"tree": true, |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field" |
|
}, { |
|
"name": "sort", |
|
"type": "compare" |
|
}, { |
|
"name": "padding", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "round", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "size", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": Output$2.length, |
|
"default": Output$2 |
|
}] |
|
}; |
|
var prototype$1h = inherits(Partition, HierarchyLayout); |
|
prototype$1h.layout = partition$4; |
|
prototype$1h.params = ['size', 'round', 'padding']; |
|
prototype$1h.fields = Output$2; |
|
/** |
|
* Stratify a collection of tuples into a tree structure based on |
|
* id and parent id fields. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.key - Unique key field for each tuple. |
|
* @param {function(object): *} params.parentKey - Field with key for parent tuple. |
|
*/ |
|
|
|
function Stratify(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Stratify.Definition = { |
|
"type": "Stratify", |
|
"metadata": { |
|
"treesource": true |
|
}, |
|
"params": [{ |
|
"name": "key", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "parentKey", |
|
"type": "field", |
|
"required": true |
|
}] |
|
}; |
|
var prototype$1i = inherits(Stratify, Transform); |
|
|
|
prototype$1i.transform = function (_, pulse) { |
|
if (!pulse.source) { |
|
error('Stratify transform requires an upstream data source.'); |
|
} |
|
|
|
var tree = this.value, |
|
mod = _.modified(), |
|
out = pulse.fork(pulse.ALL).materialize(pulse.SOURCE), |
|
run = !this.value || mod || pulse.changed(pulse.ADD_REM) || pulse.modified(_.key.fields) || pulse.modified(_.parentKey.fields); // prevent upstream source pollution |
|
|
|
|
|
out.source = out.source.slice(); |
|
|
|
if (run) { |
|
if (out.source.length) { |
|
tree = lookup$3(stratify().id(_.key).parentId(_.parentKey)(out.source), _.key, truthy); |
|
} else { |
|
tree = lookup$3(stratify()([{}]), _.key, _.key); |
|
} |
|
} |
|
|
|
out.source.root = this.value = tree; |
|
return out; |
|
}; |
|
|
|
var Layouts = { |
|
tidy: tree, |
|
cluster: cluster |
|
}; |
|
var Output$3 = ['x', 'y', 'depth', 'children']; |
|
/** |
|
* Tree layout. Depending on the method parameter, performs either |
|
* Reingold-Tilford 'tidy' layout or dendrogram 'cluster' layout. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
*/ |
|
|
|
function Tree(params) { |
|
HierarchyLayout.call(this, params); |
|
} |
|
|
|
Tree.Definition = { |
|
"type": "Tree", |
|
"metadata": { |
|
"tree": true, |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field" |
|
}, { |
|
"name": "sort", |
|
"type": "compare" |
|
}, { |
|
"name": "method", |
|
"type": "enum", |
|
"default": "tidy", |
|
"values": ["tidy", "cluster"] |
|
}, { |
|
"name": "size", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "nodeSize", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "separation", |
|
"type": "boolean", |
|
"default": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": Output$3.length, |
|
"default": Output$3 |
|
}] |
|
}; |
|
var prototype$1j = inherits(Tree, HierarchyLayout); |
|
/** |
|
* Tree layout generator. Supports both 'tidy' and 'cluster' layouts. |
|
*/ |
|
|
|
prototype$1j.layout = function (method) { |
|
var m = method || 'tidy'; |
|
if (hasOwnProperty(Layouts, m)) return Layouts[m]();else error('Unrecognized Tree layout method: ' + m); |
|
}; |
|
|
|
prototype$1j.params = ['size', 'nodeSize']; |
|
prototype$1j.fields = Output$3; |
|
/** |
|
* Generate tuples representing links between tree nodes. |
|
* The resulting tuples will contain 'source' and 'target' fields, |
|
* which point to parent and child node tuples, respectively. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
*/ |
|
|
|
function TreeLinks(params) { |
|
Transform.call(this, [], params); |
|
} |
|
|
|
TreeLinks.Definition = { |
|
"type": "TreeLinks", |
|
"metadata": { |
|
"tree": true, |
|
"generates": true, |
|
"changes": true |
|
}, |
|
"params": [] |
|
}; |
|
var prototype$1k = inherits(TreeLinks, Transform); |
|
|
|
prototype$1k.transform = function (_, pulse) { |
|
var links = this.value, |
|
tree = pulse.source && pulse.source.root, |
|
out = pulse.fork(pulse.NO_SOURCE), |
|
lut = {}; |
|
if (!tree) error('TreeLinks transform requires a tree data source.'); |
|
|
|
if (pulse.changed(pulse.ADD_REM)) { |
|
// remove previous links |
|
out.rem = links; // build lookup table of valid tuples |
|
|
|
pulse.visit(pulse.SOURCE, function (t) { |
|
lut[tupleid(t)] = 1; |
|
}); // generate links for all edges incident on valid tuples |
|
|
|
tree.each(function (node) { |
|
var t = node.data, |
|
p = node.parent && node.parent.data; |
|
|
|
if (p && lut[tupleid(t)] && lut[tupleid(p)]) { |
|
out.add.push(ingest({ |
|
source: p, |
|
target: t |
|
})); |
|
} |
|
}); |
|
this.value = out.add; |
|
} else if (pulse.changed(pulse.MOD)) { |
|
// build lookup table of modified tuples |
|
pulse.visit(pulse.MOD, function (t) { |
|
lut[tupleid(t)] = 1; |
|
}); // gather links incident on modified tuples |
|
|
|
links.forEach(function (link) { |
|
if (lut[tupleid(link.source)] || lut[tupleid(link.target)]) { |
|
out.mod.push(link); |
|
} |
|
}); |
|
} |
|
|
|
return out; |
|
}; |
|
|
|
var Tiles = { |
|
binary: treemapBinary, |
|
dice: treemapDice, |
|
slice: treemapSlice, |
|
slicedice: treemapSliceDice, |
|
squarify: treemapSquarify, |
|
resquarify: treemapResquarify |
|
}; |
|
var Output$4 = ['x0', 'y0', 'x1', 'y1', 'depth', 'children']; |
|
/** |
|
* Treemap layout. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.field - The value field to size nodes. |
|
*/ |
|
|
|
function Treemap(params) { |
|
HierarchyLayout.call(this, params); |
|
} |
|
|
|
Treemap.Definition = { |
|
"type": "Treemap", |
|
"metadata": { |
|
"tree": true, |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "field", |
|
"type": "field" |
|
}, { |
|
"name": "sort", |
|
"type": "compare" |
|
}, { |
|
"name": "method", |
|
"type": "enum", |
|
"default": "squarify", |
|
"values": ["squarify", "resquarify", "binary", "dice", "slice", "slicedice"] |
|
}, { |
|
"name": "padding", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "paddingInner", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "paddingOuter", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "paddingTop", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "paddingRight", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "paddingBottom", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "paddingLeft", |
|
"type": "number", |
|
"default": 0 |
|
}, { |
|
"name": "ratio", |
|
"type": "number", |
|
"default": 1.618033988749895 |
|
}, { |
|
"name": "round", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "size", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": Output$4.length, |
|
"default": Output$4 |
|
}] |
|
}; |
|
var prototype$1l = inherits(Treemap, HierarchyLayout); |
|
/** |
|
* Treemap layout generator. Adds 'method' and 'ratio' parameters |
|
* to configure the underlying tile method. |
|
*/ |
|
|
|
prototype$1l.layout = function () { |
|
var x = treemap(); |
|
|
|
x.ratio = function (_) { |
|
var t = x.tile(); |
|
if (t.ratio) x.tile(t.ratio(_)); |
|
}; |
|
|
|
x.method = function (_) { |
|
if (hasOwnProperty(Tiles, _)) x.tile(Tiles[_]);else error('Unrecognized Treemap layout method: ' + _); |
|
}; |
|
|
|
return x; |
|
}; |
|
|
|
prototype$1l.params = ['method', 'ratio', 'size', 'round', 'padding', 'paddingInner', 'paddingOuter', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft']; |
|
prototype$1l.fields = Output$4; |
|
var tree$1 = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
nest: Nest, |
|
pack: Pack, |
|
partition: Partition, |
|
stratify: Stratify, |
|
tree: Tree, |
|
treelinks: TreeLinks, |
|
treemap: Treemap |
|
}); |
|
|
|
function partition$5(data, groupby) { |
|
var groups = [], |
|
get = function get(f) { |
|
return f(t); |
|
}, |
|
map, |
|
i, |
|
n, |
|
t, |
|
k, |
|
g; // partition data points into stack groups |
|
|
|
|
|
if (groupby == null) { |
|
groups.push(data); |
|
} else { |
|
for (map = {}, i = 0, n = data.length; i < n; ++i) { |
|
t = data[i]; |
|
k = groupby.map(get); |
|
g = map[k]; |
|
|
|
if (!g) { |
|
map[k] = g = []; |
|
g.dims = k; |
|
groups.push(g); |
|
} |
|
|
|
g.push(t); |
|
} |
|
} |
|
|
|
return groups; |
|
} |
|
/** |
|
* Compute locally-weighted regression fits for one or more data groups. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.x - An accessor for the predictor data field. |
|
* @param {function(object): *} params.y - An accessor for the predicted data field. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby. |
|
* @param {number} [params.bandwidth=0.3] - The loess bandwidth. |
|
*/ |
|
|
|
|
|
function Loess(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Loess.Definition = { |
|
"type": "Loess", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "x", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "y", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "bandwidth", |
|
"type": "number", |
|
"default": 0.3 |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true |
|
}] |
|
}; |
|
var prototype$1m = inherits(Loess, Transform); |
|
|
|
prototype$1m.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS); |
|
|
|
if (!this.value || pulse.changed() || _.modified()) { |
|
var _source2 = pulse.materialize(pulse.SOURCE).source, |
|
groups = partition$5(_source2, _.groupby), |
|
names = (_.groupby || []).map(accessorName), |
|
m = names.length, |
|
as = _.as || [accessorName(_.x), accessorName(_.y)], |
|
_values2 = []; |
|
groups.forEach(function (g) { |
|
regressionLoess(g, _.x, _.y, _.bandwidth || 0.3).forEach(function (p) { |
|
var t = {}; |
|
|
|
for (var i = 0; i < m; ++i) { |
|
t[names[i]] = g.dims[i]; |
|
} |
|
|
|
t[as[0]] = p[0]; |
|
t[as[1]] = p[1]; |
|
|
|
_values2.push(ingest(t)); |
|
}); |
|
}); |
|
if (this.value) out.rem = this.value; |
|
this.value = out.add = out.source = _values2; |
|
} |
|
|
|
return out; |
|
}; |
|
|
|
var Methods$1 = { |
|
linear: regressionLinear, |
|
log: regressionLog, |
|
exp: regressionExp, |
|
pow: regressionPow, |
|
quad: regressionQuad, |
|
poly: regressionPoly |
|
}; |
|
|
|
function degreesOfFreedom(method, order) { |
|
return method === 'poly' ? order : method === 'quad' ? 2 : 1; |
|
} |
|
/** |
|
* Compute regression fits for one or more data groups. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {function(object): *} params.x - An accessor for the predictor data field. |
|
* @param {function(object): *} params.y - An accessor for the predicted data field. |
|
* @param {string} [params.method='linear'] - The regression method to apply. |
|
* @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby. |
|
* @param {Array<number>} [params.extent] - The domain extent over which to plot the regression line. |
|
* @param {number} [params.order=3] - The polynomial order. Only applies to the 'poly' method. |
|
*/ |
|
|
|
|
|
function Regression(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Regression.Definition = { |
|
"type": "Regression", |
|
"metadata": { |
|
"generates": true |
|
}, |
|
"params": [{ |
|
"name": "x", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "y", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "groupby", |
|
"type": "field", |
|
"array": true |
|
}, { |
|
"name": "method", |
|
"type": "string", |
|
"default": "linear", |
|
"values": Object.keys(Methods$1) |
|
}, { |
|
"name": "order", |
|
"type": "number", |
|
"default": 3 |
|
}, { |
|
"name": "extent", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "params", |
|
"type": "boolean", |
|
"default": false |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true |
|
}] |
|
}; |
|
var prototype$1n = inherits(Regression, Transform); |
|
|
|
prototype$1n.transform = function (_, pulse) { |
|
var out = pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS); |
|
|
|
if (!this.value || pulse.changed() || _.modified()) { |
|
var _source3 = pulse.materialize(pulse.SOURCE).source, |
|
groups = partition$5(_source3, _.groupby), |
|
names = (_.groupby || []).map(accessorName), |
|
_method2 = _.method || 'linear', |
|
order = _.order || 3, |
|
dof = degreesOfFreedom(_method2, order), |
|
as = _.as || [accessorName(_.x), accessorName(_.y)], |
|
_fit = Methods$1[_method2], |
|
_values3 = []; |
|
|
|
var _domain3 = _.extent; |
|
|
|
if (!hasOwnProperty(Methods$1, _method2)) { |
|
error('Invalid regression method: ' + _method2); |
|
} |
|
|
|
if (_domain3 != null) { |
|
if (_method2 === 'log' && _domain3[0] <= 0) { |
|
pulse.dataflow.warn('Ignoring extent with values <= 0 for log regression.'); |
|
_domain3 = null; |
|
} |
|
} |
|
|
|
groups.forEach(function (g) { |
|
var n = g.length; |
|
|
|
if (n <= dof) { |
|
pulse.dataflow.warn('Skipping regression with more parameters than data points.'); |
|
return; |
|
} |
|
|
|
var model = _fit(g, _.x, _.y, order); |
|
|
|
if (_.params) { |
|
// if parameter vectors requested return those |
|
_values3.push(ingest({ |
|
keys: g.dims, |
|
coef: model.coef, |
|
rSquared: model.rSquared |
|
})); |
|
|
|
return; |
|
} |
|
|
|
var dom = _domain3 || extent(g, _.x), |
|
add = function add(p) { |
|
var t = {}; |
|
|
|
for (var i = 0; i < names.length; ++i) { |
|
t[names[i]] = g.dims[i]; |
|
} |
|
|
|
t[as[0]] = p[0]; |
|
t[as[1]] = p[1]; |
|
|
|
_values3.push(ingest(t)); |
|
}; |
|
|
|
if (_method2 === 'linear') { |
|
// for linear regression we only need the end points |
|
dom.forEach(function (x) { |
|
return add([x, model.predict(x)]); |
|
}); |
|
} else { |
|
// otherwise return trend line sample points |
|
sampleCurve(model.predict, dom, 25, 200).forEach(add); |
|
} |
|
}); |
|
if (this.value) out.rem = this.value; |
|
this.value = out.add = out.source = _values3; |
|
} |
|
|
|
return out; |
|
}; |
|
|
|
var reg = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
loess: Loess, |
|
regression: Regression |
|
}); |
|
var EPSILON$2 = Math.pow(2, -52); |
|
var EDGE_STACK = new Uint32Array(512); |
|
|
|
var Delaunator = |
|
/*#__PURE__*/ |
|
function () { |
|
_createClass(Delaunator, null, [{ |
|
key: "from", |
|
value: function from(points) { |
|
var getX = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultGetX; |
|
var getY = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : defaultGetY; |
|
var n = points.length; |
|
var coords = new Float64Array(n * 2); |
|
|
|
for (var i = 0; i < n; i++) { |
|
var p = points[i]; |
|
coords[2 * i] = getX(p); |
|
coords[2 * i + 1] = getY(p); |
|
} |
|
|
|
return new Delaunator(coords); |
|
} |
|
}]); |
|
|
|
function Delaunator(coords) { |
|
_classCallCheck(this, Delaunator); |
|
|
|
var n = coords.length >> 1; |
|
if (n > 0 && typeof coords[0] !== 'number') throw new Error('Expected coords to contain numbers.'); |
|
this.coords = coords; // arrays that will store the triangulation graph |
|
|
|
var maxTriangles = Math.max(2 * n - 5, 0); |
|
this._triangles = new Uint32Array(maxTriangles * 3); |
|
this._halfedges = new Int32Array(maxTriangles * 3); // temporary arrays for tracking the edges of the advancing convex hull |
|
|
|
this._hashSize = Math.ceil(Math.sqrt(n)); |
|
this._hullPrev = new Uint32Array(n); // edge to prev edge |
|
|
|
this._hullNext = new Uint32Array(n); // edge to next edge |
|
|
|
this._hullTri = new Uint32Array(n); // edge to adjacent triangle |
|
|
|
this._hullHash = new Int32Array(this._hashSize).fill(-1); // angular edge hash |
|
// temporary arrays for sorting points |
|
|
|
this._ids = new Uint32Array(n); |
|
this._dists = new Float64Array(n); |
|
this.update(); |
|
} |
|
|
|
_createClass(Delaunator, [{ |
|
key: "update", |
|
value: function update() { |
|
var coords = this.coords, |
|
hullPrev = this._hullPrev, |
|
hullNext = this._hullNext, |
|
hullTri = this._hullTri, |
|
hullHash = this._hullHash; |
|
var n = coords.length >> 1; // populate an array of point indices; calculate input data bbox |
|
|
|
var minX = Infinity; |
|
var minY = Infinity; |
|
var maxX = -Infinity; |
|
var maxY = -Infinity; |
|
|
|
for (var i = 0; i < n; i++) { |
|
var _x23 = coords[2 * i]; |
|
var _y3 = coords[2 * i + 1]; |
|
if (_x23 < minX) minX = _x23; |
|
if (_y3 < minY) minY = _y3; |
|
if (_x23 > maxX) maxX = _x23; |
|
if (_y3 > maxY) maxY = _y3; |
|
this._ids[i] = i; |
|
} |
|
|
|
var cx = (minX + maxX) / 2; |
|
var cy = (minY + maxY) / 2; |
|
var minDist = Infinity; |
|
var i0, i1, i2; // pick a seed point close to the center |
|
|
|
for (var _i8 = 0; _i8 < n; _i8++) { |
|
var d = dist(cx, cy, coords[2 * _i8], coords[2 * _i8 + 1]); |
|
|
|
if (d < minDist) { |
|
i0 = _i8; |
|
minDist = d; |
|
} |
|
} |
|
|
|
var i0x = coords[2 * i0]; |
|
var i0y = coords[2 * i0 + 1]; |
|
minDist = Infinity; // find the point closest to the seed |
|
|
|
for (var _i9 = 0; _i9 < n; _i9++) { |
|
if (_i9 === i0) continue; |
|
|
|
var _d2 = dist(i0x, i0y, coords[2 * _i9], coords[2 * _i9 + 1]); |
|
|
|
if (_d2 < minDist && _d2 > 0) { |
|
i1 = _i9; |
|
minDist = _d2; |
|
} |
|
} |
|
|
|
var i1x = coords[2 * i1]; |
|
var i1y = coords[2 * i1 + 1]; |
|
var minRadius = Infinity; // find the third point which forms the smallest circumcircle with the first two |
|
|
|
for (var _i10 = 0; _i10 < n; _i10++) { |
|
if (_i10 === i0 || _i10 === i1) continue; |
|
var r = circumradius(i0x, i0y, i1x, i1y, coords[2 * _i10], coords[2 * _i10 + 1]); |
|
|
|
if (r < minRadius) { |
|
i2 = _i10; |
|
minRadius = r; |
|
} |
|
} |
|
|
|
var i2x = coords[2 * i2]; |
|
var i2y = coords[2 * i2 + 1]; |
|
|
|
if (minRadius === Infinity) { |
|
// order collinear points by dx (or dy if all x are identical) |
|
// and return the list as a hull |
|
for (var _i11 = 0; _i11 < n; _i11++) { |
|
this._dists[_i11] = coords[2 * _i11] - coords[0] || coords[2 * _i11 + 1] - coords[1]; |
|
} |
|
|
|
quicksort(this._ids, this._dists, 0, n - 1); |
|
var hull = new Uint32Array(n); |
|
var j = 0; |
|
|
|
for (var _i12 = 0, d0 = -Infinity; _i12 < n; _i12++) { |
|
var _id = this._ids[_i12]; |
|
|
|
if (this._dists[_id] > d0) { |
|
hull[j++] = _id; |
|
d0 = this._dists[_id]; |
|
} |
|
} |
|
|
|
this.hull = hull.subarray(0, j); |
|
this.triangles = new Uint32Array(0); |
|
this.halfedges = new Uint32Array(0); |
|
return; |
|
} // swap the order of the seed points for counter-clockwise orientation |
|
|
|
|
|
if (orient(i0x, i0y, i1x, i1y, i2x, i2y)) { |
|
var _i13 = i1; |
|
var _x24 = i1x; |
|
var _y4 = i1y; |
|
i1 = i2; |
|
i1x = i2x; |
|
i1y = i2y; |
|
i2 = _i13; |
|
i2x = _x24; |
|
i2y = _y4; |
|
} |
|
|
|
var center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y); |
|
this._cx = center.x; |
|
this._cy = center.y; |
|
|
|
for (var _i14 = 0; _i14 < n; _i14++) { |
|
this._dists[_i14] = dist(coords[2 * _i14], coords[2 * _i14 + 1], center.x, center.y); |
|
} // sort the points by distance from the seed triangle circumcenter |
|
|
|
|
|
quicksort(this._ids, this._dists, 0, n - 1); // set up the seed triangle as the starting hull |
|
|
|
this._hullStart = i0; |
|
var hullSize = 3; |
|
hullNext[i0] = hullPrev[i2] = i1; |
|
hullNext[i1] = hullPrev[i0] = i2; |
|
hullNext[i2] = hullPrev[i1] = i0; |
|
hullTri[i0] = 0; |
|
hullTri[i1] = 1; |
|
hullTri[i2] = 2; |
|
hullHash.fill(-1); |
|
hullHash[this._hashKey(i0x, i0y)] = i0; |
|
hullHash[this._hashKey(i1x, i1y)] = i1; |
|
hullHash[this._hashKey(i2x, i2y)] = i2; |
|
this.trianglesLen = 0; |
|
|
|
this._addTriangle(i0, i1, i2, -1, -1, -1); |
|
|
|
for (var k = 0, xp, yp; k < this._ids.length; k++) { |
|
var _i15 = this._ids[k]; |
|
var _x25 = coords[2 * _i15]; |
|
var _y5 = coords[2 * _i15 + 1]; // skip near-duplicate points |
|
|
|
if (k > 0 && Math.abs(_x25 - xp) <= EPSILON$2 && Math.abs(_y5 - yp) <= EPSILON$2) continue; |
|
xp = _x25; |
|
yp = _y5; // skip seed triangle points |
|
|
|
if (_i15 === i0 || _i15 === i1 || _i15 === i2) continue; // find a visible edge on the convex hull using edge hash |
|
|
|
var start = 0; |
|
|
|
for (var _j = 0, _key3 = this._hashKey(_x25, _y5); _j < this._hashSize; _j++) { |
|
start = hullHash[(_key3 + _j) % this._hashSize]; |
|
if (start !== -1 && start !== hullNext[start]) break; |
|
} |
|
|
|
start = hullPrev[start]; |
|
var e = start, |
|
q = void 0; |
|
|
|
while (q = hullNext[e], !orient(_x25, _y5, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1])) { |
|
e = q; |
|
|
|
if (e === start) { |
|
e = -1; |
|
break; |
|
} |
|
} |
|
|
|
if (e === -1) continue; // likely a near-duplicate point; skip it |
|
// add the first triangle from the point |
|
|
|
var t = this._addTriangle(e, _i15, hullNext[e], -1, -1, hullTri[e]); // recursively flip triangles from the point until they satisfy the Delaunay condition |
|
|
|
|
|
hullTri[_i15] = this._legalize(t + 2); |
|
hullTri[e] = t; // keep track of boundary triangles on the hull |
|
|
|
hullSize++; // walk forward through the hull, adding more triangles and flipping recursively |
|
|
|
var _n2 = hullNext[e]; |
|
|
|
while (q = hullNext[_n2], orient(_x25, _y5, coords[2 * _n2], coords[2 * _n2 + 1], coords[2 * q], coords[2 * q + 1])) { |
|
t = this._addTriangle(_n2, _i15, q, hullTri[_i15], -1, hullTri[_n2]); |
|
hullTri[_i15] = this._legalize(t + 2); |
|
hullNext[_n2] = _n2; // mark as removed |
|
|
|
hullSize--; |
|
_n2 = q; |
|
} // walk backward from the other side, adding more triangles and flipping |
|
|
|
|
|
if (e === start) { |
|
while (q = hullPrev[e], orient(_x25, _y5, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1])) { |
|
t = this._addTriangle(q, _i15, e, -1, hullTri[e], hullTri[q]); |
|
|
|
this._legalize(t + 2); |
|
|
|
hullTri[q] = t; |
|
hullNext[e] = e; // mark as removed |
|
|
|
hullSize--; |
|
e = q; |
|
} |
|
} // update the hull indices |
|
|
|
|
|
this._hullStart = hullPrev[_i15] = e; |
|
hullNext[e] = hullPrev[_n2] = _i15; |
|
hullNext[_i15] = _n2; // save the two new edges in the hash table |
|
|
|
hullHash[this._hashKey(_x25, _y5)] = _i15; |
|
hullHash[this._hashKey(coords[2 * e], coords[2 * e + 1])] = e; |
|
} |
|
|
|
this.hull = new Uint32Array(hullSize); |
|
|
|
for (var _i16 = 0, _e2 = this._hullStart; _i16 < hullSize; _i16++) { |
|
this.hull[_i16] = _e2; |
|
_e2 = hullNext[_e2]; |
|
} // trim typed triangle mesh arrays |
|
|
|
|
|
this.triangles = this._triangles.subarray(0, this.trianglesLen); |
|
this.halfedges = this._halfedges.subarray(0, this.trianglesLen); |
|
} |
|
}, { |
|
key: "_hashKey", |
|
value: function _hashKey(x, y) { |
|
return Math.floor(pseudoAngle(x - this._cx, y - this._cy) * this._hashSize) % this._hashSize; |
|
} |
|
}, { |
|
key: "_legalize", |
|
value: function _legalize(a) { |
|
var triangles = this._triangles, |
|
halfedges = this._halfedges, |
|
coords = this.coords; |
|
var i = 0; |
|
var ar = 0; // recursion eliminated with a fixed-size stack |
|
|
|
while (true) { |
|
var _b2 = halfedges[a]; |
|
/* if the pair of triangles doesn't satisfy the Delaunay condition |
|
* (p1 is inside the circumcircle of [p0, pl, pr]), flip them, |
|
* then do the same check/flip recursively for the new pair of triangles |
|
* |
|
* pl pl |
|
* /||\ / \ |
|
* al/ || \bl al/ \a |
|
* / || \ / \ |
|
* / a||b \ flip /___ar___\ |
|
* p0\ || /p1 => p0\---bl---/p1 |
|
* \ || / \ / |
|
* ar\ || /br b\ /br |
|
* \||/ \ / |
|
* pr pr |
|
*/ |
|
|
|
var a0 = a - a % 3; |
|
ar = a0 + (a + 2) % 3; |
|
|
|
if (_b2 === -1) { |
|
// convex hull edge |
|
if (i === 0) break; |
|
a = EDGE_STACK[--i]; |
|
continue; |
|
} |
|
|
|
var b0 = _b2 - _b2 % 3; |
|
var al = a0 + (a + 1) % 3; |
|
|
|
var _bl = b0 + (_b2 + 2) % 3; |
|
|
|
var _p2 = triangles[ar]; |
|
var pr = triangles[a]; |
|
var pl = triangles[al]; |
|
var p1 = triangles[_bl]; |
|
var illegal = inCircle(coords[2 * _p2], coords[2 * _p2 + 1], coords[2 * pr], coords[2 * pr + 1], coords[2 * pl], coords[2 * pl + 1], coords[2 * p1], coords[2 * p1 + 1]); |
|
|
|
if (illegal) { |
|
triangles[a] = p1; |
|
triangles[_b2] = _p2; |
|
var hbl = halfedges[_bl]; // edge swapped on the other side of the hull (rare); fix the halfedge reference |
|
|
|
if (hbl === -1) { |
|
var e = this._hullStart; |
|
|
|
do { |
|
if (this._hullTri[e] === _bl) { |
|
this._hullTri[e] = a; |
|
break; |
|
} |
|
|
|
e = this._hullPrev[e]; |
|
} while (e !== this._hullStart); |
|
} |
|
|
|
this._link(a, hbl); |
|
|
|
this._link(_b2, halfedges[ar]); |
|
|
|
this._link(ar, _bl); |
|
|
|
var _br = b0 + (_b2 + 1) % 3; // don't worry about hitting the cap: it can only happen on extremely degenerate input |
|
|
|
|
|
if (i < EDGE_STACK.length) { |
|
EDGE_STACK[i++] = _br; |
|
} |
|
} else { |
|
if (i === 0) break; |
|
a = EDGE_STACK[--i]; |
|
} |
|
} |
|
|
|
return ar; |
|
} |
|
}, { |
|
key: "_link", |
|
value: function _link(a, b) { |
|
this._halfedges[a] = b; |
|
if (b !== -1) this._halfedges[b] = a; |
|
} // add a new triangle given vertex indices and adjacent half-edge ids |
|
|
|
}, { |
|
key: "_addTriangle", |
|
value: function _addTriangle(i0, i1, i2, a, b, c) { |
|
var t = this.trianglesLen; |
|
this._triangles[t] = i0; |
|
this._triangles[t + 1] = i1; |
|
this._triangles[t + 2] = i2; |
|
|
|
this._link(t, a); |
|
|
|
this._link(t + 1, b); |
|
|
|
this._link(t + 2, c); |
|
|
|
this.trianglesLen += 3; |
|
return t; |
|
} |
|
}]); |
|
|
|
return Delaunator; |
|
}(); // monotonically increases with real angle, but doesn't need expensive trigonometry |
|
|
|
|
|
function pseudoAngle(dx, dy) { |
|
var p = dx / (Math.abs(dx) + Math.abs(dy)); |
|
return (dy > 0 ? 3 - p : 1 + p) / 4; // [0..1] |
|
} |
|
|
|
function dist(ax, ay, bx, by) { |
|
var dx = ax - bx; |
|
var dy = ay - by; |
|
return dx * dx + dy * dy; |
|
} // return 2d orientation sign if we're confident in it through J. Shewchuk's error bound check |
|
|
|
|
|
function orientIfSure(px, py, rx, ry, qx, qy) { |
|
var l = (ry - py) * (qx - px); |
|
var r = (rx - px) * (qy - py); |
|
return Math.abs(l - r) >= 3.3306690738754716e-16 * Math.abs(l + r) ? l - r : 0; |
|
} // a more robust orientation test that's stable in a given triangle (to fix robustness issues) |
|
|
|
|
|
function orient(rx, ry, qx, qy, px, py) { |
|
var sign = orientIfSure(px, py, rx, ry, qx, qy) || orientIfSure(rx, ry, qx, qy, px, py) || orientIfSure(qx, qy, px, py, rx, ry); |
|
return sign < 0; |
|
} |
|
|
|
function inCircle(ax, ay, bx, by, cx, cy, px, py) { |
|
var dx = ax - px; |
|
var dy = ay - py; |
|
var ex = bx - px; |
|
var ey = by - py; |
|
var fx = cx - px; |
|
var fy = cy - py; |
|
var ap = dx * dx + dy * dy; |
|
var bp = ex * ex + ey * ey; |
|
var cp = fx * fx + fy * fy; |
|
return dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) + ap * (ex * fy - ey * fx) < 0; |
|
} |
|
|
|
function circumradius(ax, ay, bx, by, cx, cy) { |
|
var dx = bx - ax; |
|
var dy = by - ay; |
|
var ex = cx - ax; |
|
var ey = cy - ay; |
|
var bl = dx * dx + dy * dy; |
|
var cl = ex * ex + ey * ey; |
|
var d = 0.5 / (dx * ey - dy * ex); |
|
var x = (ey * bl - dy * cl) * d; |
|
var y = (dx * cl - ex * bl) * d; |
|
return x * x + y * y; |
|
} |
|
|
|
function circumcenter(ax, ay, bx, by, cx, cy) { |
|
var dx = bx - ax; |
|
var dy = by - ay; |
|
var ex = cx - ax; |
|
var ey = cy - ay; |
|
var bl = dx * dx + dy * dy; |
|
var cl = ex * ex + ey * ey; |
|
var d = 0.5 / (dx * ey - dy * ex); |
|
var x = ax + (ey * bl - dy * cl) * d; |
|
var y = ay + (dx * cl - ex * bl) * d; |
|
return { |
|
x: x, |
|
y: y |
|
}; |
|
} |
|
|
|
function quicksort(ids, dists, left, right) { |
|
if (right - left <= 20) { |
|
for (var i = left + 1; i <= right; i++) { |
|
var _temp = ids[i]; |
|
var tempDist = dists[_temp]; |
|
var j = i - 1; |
|
|
|
while (j >= left && dists[ids[j]] > tempDist) { |
|
ids[j + 1] = ids[j--]; |
|
} |
|
|
|
ids[j + 1] = _temp; |
|
} |
|
} else { |
|
var _median = left + right >> 1; |
|
|
|
var _i17 = left + 1; |
|
|
|
var _j2 = right; |
|
swap$1(ids, _median, _i17); |
|
if (dists[ids[left]] > dists[ids[right]]) swap$1(ids, left, right); |
|
if (dists[ids[_i17]] > dists[ids[right]]) swap$1(ids, _i17, right); |
|
if (dists[ids[left]] > dists[ids[_i17]]) swap$1(ids, left, _i17); |
|
var _temp2 = ids[_i17]; |
|
var _tempDist = dists[_temp2]; |
|
|
|
while (true) { |
|
do { |
|
_i17++; |
|
} while (dists[ids[_i17]] < _tempDist); |
|
|
|
do { |
|
_j2--; |
|
} while (dists[ids[_j2]] > _tempDist); |
|
|
|
if (_j2 < _i17) break; |
|
swap$1(ids, _i17, _j2); |
|
} |
|
|
|
ids[left + 1] = ids[_j2]; |
|
ids[_j2] = _temp2; |
|
|
|
if (right - _i17 + 1 >= _j2 - left) { |
|
quicksort(ids, dists, _i17, right); |
|
quicksort(ids, dists, left, _j2 - 1); |
|
} else { |
|
quicksort(ids, dists, left, _j2 - 1); |
|
quicksort(ids, dists, _i17, right); |
|
} |
|
} |
|
} |
|
|
|
function swap$1(arr, i, j) { |
|
var tmp = arr[i]; |
|
arr[i] = arr[j]; |
|
arr[j] = tmp; |
|
} |
|
|
|
function defaultGetX(p) { |
|
return p[0]; |
|
} |
|
|
|
function defaultGetY(p) { |
|
return p[1]; |
|
} |
|
|
|
var epsilon$5 = 1e-6; |
|
|
|
var Path$1 = |
|
/*#__PURE__*/ |
|
function () { |
|
function Path$1() { |
|
_classCallCheck(this, Path$1); |
|
|
|
this._x0 = this._y0 = // start of current subpath |
|
this._x1 = this._y1 = null; // end of current subpath |
|
|
|
this._ = ""; |
|
} |
|
|
|
_createClass(Path$1, [{ |
|
key: "moveTo", |
|
value: function moveTo(x, y) { |
|
this._ += "M".concat(this._x0 = this._x1 = +x, ",").concat(this._y0 = this._y1 = +y); |
|
} |
|
}, { |
|
key: "closePath", |
|
value: function closePath() { |
|
if (this._x1 !== null) { |
|
this._x1 = this._x0, this._y1 = this._y0; |
|
this._ += "Z"; |
|
} |
|
} |
|
}, { |
|
key: "lineTo", |
|
value: function lineTo(x, y) { |
|
this._ += "L".concat(this._x1 = +x, ",").concat(this._y1 = +y); |
|
} |
|
}, { |
|
key: "arc", |
|
value: function arc(x, y, r) { |
|
x = +x, y = +y, r = +r; |
|
var x0 = x + r; |
|
var y0 = y; |
|
if (r < 0) throw new Error("negative radius"); |
|
if (this._x1 === null) this._ += "M".concat(x0, ",").concat(y0);else if (Math.abs(this._x1 - x0) > epsilon$5 || Math.abs(this._y1 - y0) > epsilon$5) this._ += "L" + x0 + "," + y0; |
|
if (!r) return; |
|
this._ += "A".concat(r, ",").concat(r, ",0,1,1,").concat(x - r, ",").concat(y, "A").concat(r, ",").concat(r, ",0,1,1,").concat(this._x1 = x0, ",").concat(this._y1 = y0); |
|
} |
|
}, { |
|
key: "rect", |
|
value: function rect(x, y, w, h) { |
|
this._ += "M".concat(this._x0 = this._x1 = +x, ",").concat(this._y0 = this._y1 = +y, "h").concat(+w, "v").concat(+h, "h").concat(-w, "Z"); |
|
} |
|
}, { |
|
key: "value", |
|
value: function value() { |
|
return this._ || null; |
|
} |
|
}]); |
|
|
|
return Path$1; |
|
}(); |
|
|
|
var Polygon = |
|
/*#__PURE__*/ |
|
function () { |
|
function Polygon() { |
|
_classCallCheck(this, Polygon); |
|
|
|
this._ = []; |
|
} |
|
|
|
_createClass(Polygon, [{ |
|
key: "moveTo", |
|
value: function moveTo(x, y) { |
|
this._.push([x, y]); |
|
} |
|
}, { |
|
key: "closePath", |
|
value: function closePath() { |
|
this._.push(this._[0].slice()); |
|
} |
|
}, { |
|
key: "lineTo", |
|
value: function lineTo(x, y) { |
|
this._.push([x, y]); |
|
} |
|
}, { |
|
key: "value", |
|
value: function value() { |
|
return this._.length ? this._ : null; |
|
} |
|
}]); |
|
|
|
return Polygon; |
|
}(); |
|
|
|
var Voronoi = |
|
/*#__PURE__*/ |
|
function () { |
|
function Voronoi(delaunay) { |
|
var _ref14 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [0, 0, 960, 500], |
|
_ref15 = _slicedToArray(_ref14, 4), |
|
xmin = _ref15[0], |
|
ymin = _ref15[1], |
|
xmax = _ref15[2], |
|
ymax = _ref15[3]; |
|
|
|
_classCallCheck(this, Voronoi); |
|
|
|
if (!((xmax = +xmax) >= (xmin = +xmin)) || !((ymax = +ymax) >= (ymin = +ymin))) throw new Error("invalid bounds"); |
|
this.delaunay = delaunay; |
|
this._circumcenters = new Float64Array(delaunay.points.length * 2); |
|
this.vectors = new Float64Array(delaunay.points.length * 2); |
|
this.xmax = xmax, this.xmin = xmin; |
|
this.ymax = ymax, this.ymin = ymin; |
|
|
|
this._init(); |
|
} |
|
|
|
_createClass(Voronoi, [{ |
|
key: "update", |
|
value: function update() { |
|
this.delaunay.update(); |
|
|
|
this._init(); |
|
|
|
return this; |
|
} |
|
}, { |
|
key: "_init", |
|
value: function _init() { |
|
var _this$delaunay = this.delaunay, |
|
points = _this$delaunay.points, |
|
hull = _this$delaunay.hull, |
|
triangles = _this$delaunay.triangles, |
|
vectors = this.vectors; // Compute circumcenters. |
|
|
|
var circumcenters = this.circumcenters = this._circumcenters.subarray(0, triangles.length / 3 * 2); |
|
|
|
for (var i = 0, j = 0, n = triangles.length, _x26, _y6; i < n; i += 3, j += 2) { |
|
var _t = triangles[i] * 2; |
|
|
|
var _t2 = triangles[i + 1] * 2; |
|
|
|
var _t3 = triangles[i + 2] * 2; |
|
|
|
var _x27 = points[_t]; |
|
var _y7 = points[_t + 1]; |
|
var x2 = points[_t2]; |
|
var y2 = points[_t2 + 1]; |
|
var x3 = points[_t3]; |
|
var y3 = points[_t3 + 1]; |
|
var dx = x2 - _x27; |
|
var dy = y2 - _y7; |
|
var ex = x3 - _x27; |
|
var ey = y3 - _y7; |
|
|
|
var _bl2 = dx * dx + dy * dy; |
|
|
|
var cl = ex * ex + ey * ey; |
|
var ab = (dx * ey - dy * ex) * 2; |
|
|
|
if (!ab) { |
|
// degenerate case (collinear diagram) |
|
_x26 = (_x27 + x3) / 2 - 1e8 * ey; |
|
_y6 = (_y7 + y3) / 2 + 1e8 * ex; |
|
} else if (Math.abs(ab) < 1e-8) { |
|
// almost equal points (degenerate triangle) |
|
_x26 = (_x27 + x3) / 2; |
|
_y6 = (_y7 + y3) / 2; |
|
} else { |
|
var d = 1 / ab; |
|
_x26 = _x27 + (ey * _bl2 - dy * cl) * d; |
|
_y6 = _y7 + (dx * cl - ex * _bl2) * d; |
|
} |
|
|
|
circumcenters[j] = _x26; |
|
circumcenters[j + 1] = _y6; |
|
} // Compute exterior cell rays. |
|
|
|
|
|
var h = hull[hull.length - 1]; |
|
var p0, |
|
p1 = h * 4; |
|
var x0, |
|
x1 = points[2 * h]; |
|
var y0, |
|
y1 = points[2 * h + 1]; |
|
vectors.fill(0); |
|
|
|
for (var _i18 = 0; _i18 < hull.length; ++_i18) { |
|
h = hull[_i18]; |
|
p0 = p1, x0 = x1, y0 = y1; |
|
p1 = h * 4, x1 = points[2 * h], y1 = points[2 * h + 1]; |
|
vectors[p0 + 2] = vectors[p1] = y0 - y1; |
|
vectors[p0 + 3] = vectors[p1 + 1] = x1 - x0; |
|
} |
|
} |
|
}, { |
|
key: "render", |
|
value: function render(context) { |
|
var buffer = context == null ? context = new Path$1() : undefined; |
|
var _this$delaunay2 = this.delaunay, |
|
halfedges = _this$delaunay2.halfedges, |
|
inedges = _this$delaunay2.inedges, |
|
hull = _this$delaunay2.hull, |
|
circumcenters = this.circumcenters, |
|
vectors = this.vectors; |
|
if (hull.length <= 1) return null; |
|
|
|
for (var i = 0, n = halfedges.length; i < n; ++i) { |
|
var j = halfedges[i]; |
|
if (j < i) continue; |
|
var ti = Math.floor(i / 3) * 2; |
|
var tj = Math.floor(j / 3) * 2; |
|
var xi = circumcenters[ti]; |
|
var yi = circumcenters[ti + 1]; |
|
var xj = circumcenters[tj]; |
|
var yj = circumcenters[tj + 1]; |
|
|
|
this._renderSegment(xi, yi, xj, yj, context); |
|
} |
|
|
|
var h0, |
|
h1 = hull[hull.length - 1]; |
|
|
|
for (var _i19 = 0; _i19 < hull.length; ++_i19) { |
|
h0 = h1, h1 = hull[_i19]; |
|
var t = Math.floor(inedges[h1] / 3) * 2; |
|
var _x28 = circumcenters[t]; |
|
var _y8 = circumcenters[t + 1]; |
|
|
|
var _v4 = h0 * 4; |
|
|
|
var p = this._project(_x28, _y8, vectors[_v4 + 2], vectors[_v4 + 3]); |
|
|
|
if (p) this._renderSegment(_x28, _y8, p[0], p[1], context); |
|
} |
|
|
|
return buffer && buffer.value(); |
|
} |
|
}, { |
|
key: "renderBounds", |
|
value: function renderBounds(context) { |
|
var buffer = context == null ? context = new Path$1() : undefined; |
|
context.rect(this.xmin, this.ymin, this.xmax - this.xmin, this.ymax - this.ymin); |
|
return buffer && buffer.value(); |
|
} |
|
}, { |
|
key: "renderCell", |
|
value: function renderCell(i, context) { |
|
var buffer = context == null ? context = new Path$1() : undefined; |
|
|
|
var points = this._clip(i); |
|
|
|
if (points === null) return; |
|
context.moveTo(points[0], points[1]); |
|
var n = points.length; |
|
|
|
while (points[0] === points[n - 2] && points[1] === points[n - 1] && n > 1) { |
|
n -= 2; |
|
} |
|
|
|
for (var _i20 = 2; _i20 < n; _i20 += 2) { |
|
if (points[_i20] !== points[_i20 - 2] || points[_i20 + 1] !== points[_i20 - 1]) context.lineTo(points[_i20], points[_i20 + 1]); |
|
} |
|
|
|
context.closePath(); |
|
return buffer && buffer.value(); |
|
} |
|
}, { |
|
key: "cellPolygons", |
|
value: |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function cellPolygons() { |
|
var points, i, n, _cell2; |
|
|
|
return regeneratorRuntime.wrap(function cellPolygons$(_context4) { |
|
while (1) { |
|
switch (_context4.prev = _context4.next) { |
|
case 0: |
|
points = this.delaunay.points; |
|
i = 0, n = points.length / 2; |
|
|
|
case 2: |
|
if (!(i < n)) { |
|
_context4.next = 10; |
|
break; |
|
} |
|
|
|
_cell2 = this.cellPolygon(i); |
|
|
|
if (!_cell2) { |
|
_context4.next = 7; |
|
break; |
|
} |
|
|
|
_context4.next = 7; |
|
return _cell2; |
|
|
|
case 7: |
|
++i; |
|
_context4.next = 2; |
|
break; |
|
|
|
case 10: |
|
case "end": |
|
return _context4.stop(); |
|
} |
|
} |
|
}, cellPolygons, this); |
|
}) |
|
}, { |
|
key: "cellPolygon", |
|
value: function cellPolygon(i) { |
|
var polygon = new Polygon(); |
|
this.renderCell(i, polygon); |
|
return polygon.value(); |
|
} |
|
}, { |
|
key: "_renderSegment", |
|
value: function _renderSegment(x0, y0, x1, y1, context) { |
|
var S; |
|
|
|
var c0 = this._regioncode(x0, y0); |
|
|
|
var c1 = this._regioncode(x1, y1); |
|
|
|
if (c0 === 0 && c1 === 0) { |
|
context.moveTo(x0, y0); |
|
context.lineTo(x1, y1); |
|
} else if (S = this._clipSegment(x0, y0, x1, y1, c0, c1)) { |
|
context.moveTo(S[0], S[1]); |
|
context.lineTo(S[2], S[3]); |
|
} |
|
} |
|
}, { |
|
key: "contains", |
|
value: function contains(i, x, y) { |
|
if ((x = +x, x !== x) || (y = +y, y !== y)) return false; |
|
return this.delaunay._step(i, x, y) === i; |
|
} |
|
}, { |
|
key: "_cell", |
|
value: function _cell(i) { |
|
var circumcenters = this.circumcenters, |
|
_this$delaunay3 = this.delaunay, |
|
inedges = _this$delaunay3.inedges, |
|
halfedges = _this$delaunay3.halfedges, |
|
triangles = _this$delaunay3.triangles; |
|
var e0 = inedges[i]; |
|
if (e0 === -1) return null; // coincident point |
|
|
|
var points = []; |
|
var e = e0; |
|
|
|
do { |
|
var t = Math.floor(e / 3); |
|
points.push(circumcenters[t * 2], circumcenters[t * 2 + 1]); |
|
e = e % 3 === 2 ? e - 2 : e + 1; |
|
if (triangles[e] !== i) break; // bad triangulation |
|
|
|
e = halfedges[e]; |
|
} while (e !== e0 && e !== -1); |
|
|
|
return points; |
|
} |
|
}, { |
|
key: "_clip", |
|
value: function _clip(i) { |
|
// degenerate case (1 valid point: return the box) |
|
if (i === 0 && this.delaunay.hull.length === 1) { |
|
return [this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax, this.xmin, this.ymin]; |
|
} |
|
|
|
var points = this._cell(i); |
|
|
|
if (points === null) return null; |
|
var V = this.vectors; |
|
var v = i * 4; |
|
return V[v] || V[v + 1] ? this._clipInfinite(i, points, V[v], V[v + 1], V[v + 2], V[v + 3]) : this._clipFinite(i, points); |
|
} |
|
}, { |
|
key: "_clipFinite", |
|
value: function _clipFinite(i, points) { |
|
var n = points.length; |
|
var P = null; |
|
var x0, |
|
y0, |
|
x1 = points[n - 2], |
|
y1 = points[n - 1]; |
|
|
|
var c0, |
|
c1 = this._regioncode(x1, y1); |
|
|
|
var e0, e1; |
|
|
|
for (var j = 0; j < n; j += 2) { |
|
x0 = x1, y0 = y1, x1 = points[j], y1 = points[j + 1]; |
|
c0 = c1, c1 = this._regioncode(x1, y1); |
|
|
|
if (c0 === 0 && c1 === 0) { |
|
e0 = e1, e1 = 0; |
|
if (P) P.push(x1, y1);else P = [x1, y1]; |
|
} else { |
|
var S = void 0, |
|
sx0 = void 0, |
|
sy0 = void 0, |
|
sx1 = void 0, |
|
sy1 = void 0; |
|
|
|
if (c0 === 0) { |
|
if ((S = this._clipSegment(x0, y0, x1, y1, c0, c1)) === null) continue; |
|
var _S = S; |
|
|
|
var _S2 = _slicedToArray(_S, 4); |
|
|
|
sx0 = _S2[0]; |
|
sy0 = _S2[1]; |
|
sx1 = _S2[2]; |
|
sy1 = _S2[3]; |
|
} else { |
|
if ((S = this._clipSegment(x1, y1, x0, y0, c1, c0)) === null) continue; |
|
var _S3 = S; |
|
|
|
var _S4 = _slicedToArray(_S3, 4); |
|
|
|
sx1 = _S4[0]; |
|
sy1 = _S4[1]; |
|
sx0 = _S4[2]; |
|
sy0 = _S4[3]; |
|
e0 = e1, e1 = this._edgecode(sx0, sy0); |
|
if (e0 && e1) this._edge(i, e0, e1, P, P.length); |
|
if (P) P.push(sx0, sy0);else P = [sx0, sy0]; |
|
} |
|
|
|
e0 = e1, e1 = this._edgecode(sx1, sy1); |
|
if (e0 && e1) this._edge(i, e0, e1, P, P.length); |
|
if (P) P.push(sx1, sy1);else P = [sx1, sy1]; |
|
} |
|
} |
|
|
|
if (P) { |
|
e0 = e1, e1 = this._edgecode(P[0], P[1]); |
|
if (e0 && e1) this._edge(i, e0, e1, P, P.length); |
|
} else if (this.contains(i, (this.xmin + this.xmax) / 2, (this.ymin + this.ymax) / 2)) { |
|
return [this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax, this.xmin, this.ymin]; |
|
} |
|
|
|
return P; |
|
} |
|
}, { |
|
key: "_clipSegment", |
|
value: function _clipSegment(x0, y0, x1, y1, c0, c1) { |
|
while (true) { |
|
if (c0 === 0 && c1 === 0) return [x0, y0, x1, y1]; |
|
if (c0 & c1) return null; |
|
|
|
var _x29 = void 0, |
|
_y9 = void 0, |
|
c = c0 || c1; |
|
|
|
if (c & 8) _x29 = x0 + (x1 - x0) * (this.ymax - y0) / (y1 - y0), _y9 = this.ymax;else if (c & 4) _x29 = x0 + (x1 - x0) * (this.ymin - y0) / (y1 - y0), _y9 = this.ymin;else if (c & 2) _y9 = y0 + (y1 - y0) * (this.xmax - x0) / (x1 - x0), _x29 = this.xmax;else _y9 = y0 + (y1 - y0) * (this.xmin - x0) / (x1 - x0), _x29 = this.xmin; |
|
if (c0) x0 = _x29, y0 = _y9, c0 = this._regioncode(x0, y0);else x1 = _x29, y1 = _y9, c1 = this._regioncode(x1, y1); |
|
} |
|
} |
|
}, { |
|
key: "_clipInfinite", |
|
value: function _clipInfinite(i, points, vx0, vy0, vxn, vyn) { |
|
var P = Array.from(points), |
|
p; |
|
if (p = this._project(P[0], P[1], vx0, vy0)) P.unshift(p[0], p[1]); |
|
if (p = this._project(P[P.length - 2], P[P.length - 1], vxn, vyn)) P.push(p[0], p[1]); |
|
|
|
if (P = this._clipFinite(i, P)) { |
|
for (var j = 0, n = P.length, c0, c1 = this._edgecode(P[n - 2], P[n - 1]); j < n; j += 2) { |
|
c0 = c1, c1 = this._edgecode(P[j], P[j + 1]); |
|
if (c0 && c1) j = this._edge(i, c0, c1, P, j), n = P.length; |
|
} |
|
} else if (this.contains(i, (this.xmin + this.xmax) / 2, (this.ymin + this.ymax) / 2)) { |
|
P = [this.xmin, this.ymin, this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax]; |
|
} |
|
|
|
return P; |
|
} |
|
}, { |
|
key: "_edge", |
|
value: function _edge(i, e0, e1, P, j) { |
|
while (e0 !== e1) { |
|
var _x30 = void 0, |
|
_y10 = void 0; |
|
|
|
switch (e0) { |
|
case 5: |
|
e0 = 4; |
|
continue; |
|
// top-left |
|
|
|
case 4: |
|
e0 = 6, _x30 = this.xmax, _y10 = this.ymin; |
|
break; |
|
// top |
|
|
|
case 6: |
|
e0 = 2; |
|
continue; |
|
// top-right |
|
|
|
case 2: |
|
e0 = 10, _x30 = this.xmax, _y10 = this.ymax; |
|
break; |
|
// right |
|
|
|
case 10: |
|
e0 = 8; |
|
continue; |
|
// bottom-right |
|
|
|
case 8: |
|
e0 = 9, _x30 = this.xmin, _y10 = this.ymax; |
|
break; |
|
// bottom |
|
|
|
case 9: |
|
e0 = 1; |
|
continue; |
|
// bottom-left |
|
|
|
case 1: |
|
e0 = 5, _x30 = this.xmin, _y10 = this.ymin; |
|
break; |
|
// left |
|
} |
|
|
|
if ((P[j] !== _x30 || P[j + 1] !== _y10) && this.contains(i, _x30, _y10)) { |
|
P.splice(j, 0, _x30, _y10), j += 2; |
|
} |
|
} |
|
|
|
if (P.length > 4) { |
|
for (var _i21 = 0; _i21 < P.length; _i21 += 2) { |
|
var _j3 = (_i21 + 2) % P.length, |
|
k = (_i21 + 4) % P.length; |
|
|
|
if (P[_i21] === P[_j3] && P[_j3] === P[k] || P[_i21 + 1] === P[_j3 + 1] && P[_j3 + 1] === P[k + 1]) P.splice(_j3, 2), _i21 -= 2; |
|
} |
|
} |
|
|
|
return j; |
|
} |
|
}, { |
|
key: "_project", |
|
value: function _project(x0, y0, vx, vy) { |
|
var t = Infinity, |
|
c, |
|
x, |
|
y; |
|
|
|
if (vy < 0) { |
|
// top |
|
if (y0 <= this.ymin) return null; |
|
if ((c = (this.ymin - y0) / vy) < t) y = this.ymin, x = x0 + (t = c) * vx; |
|
} else if (vy > 0) { |
|
// bottom |
|
if (y0 >= this.ymax) return null; |
|
if ((c = (this.ymax - y0) / vy) < t) y = this.ymax, x = x0 + (t = c) * vx; |
|
} |
|
|
|
if (vx > 0) { |
|
// right |
|
if (x0 >= this.xmax) return null; |
|
if ((c = (this.xmax - x0) / vx) < t) x = this.xmax, y = y0 + (t = c) * vy; |
|
} else if (vx < 0) { |
|
// left |
|
if (x0 <= this.xmin) return null; |
|
if ((c = (this.xmin - x0) / vx) < t) x = this.xmin, y = y0 + (t = c) * vy; |
|
} |
|
|
|
return [x, y]; |
|
} |
|
}, { |
|
key: "_edgecode", |
|
value: function _edgecode(x, y) { |
|
return (x === this.xmin ? 1 : x === this.xmax ? 2 : 0) | (y === this.ymin ? 4 : y === this.ymax ? 8 : 0); |
|
} |
|
}, { |
|
key: "_regioncode", |
|
value: function _regioncode(x, y) { |
|
return (x < this.xmin ? 1 : x > this.xmax ? 2 : 0) | (y < this.ymin ? 4 : y > this.ymax ? 8 : 0); |
|
} |
|
}]); |
|
|
|
return Voronoi; |
|
}(); |
|
|
|
var tau$3 = 2 * Math.PI; |
|
|
|
function pointX(p) { |
|
return p[0]; |
|
} |
|
|
|
function pointY(p) { |
|
return p[1]; |
|
} // A triangulation is collinear if all its triangles have a non-null area |
|
|
|
|
|
function collinear$1(d) { |
|
var triangles = d.triangles, |
|
coords = d.coords; |
|
|
|
for (var i = 0; i < triangles.length; i += 3) { |
|
var a = 2 * triangles[i], |
|
_b3 = 2 * triangles[i + 1], |
|
c = 2 * triangles[i + 2], |
|
_cross = (coords[c] - coords[a]) * (coords[_b3 + 1] - coords[a + 1]) - (coords[_b3] - coords[a]) * (coords[c + 1] - coords[a + 1]); |
|
|
|
if (_cross > 1e-10) return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function jitter(x, y, r) { |
|
return [x + Math.sin(x + y) * r, y + Math.cos(x - y) * r]; |
|
} |
|
|
|
var Delaunay = |
|
/*#__PURE__*/ |
|
function () { |
|
function Delaunay(points) { |
|
_classCallCheck(this, Delaunay); |
|
|
|
this._delaunator = new Delaunator(points); |
|
this.inedges = new Int32Array(points.length / 2); |
|
this._hullIndex = new Int32Array(points.length / 2); |
|
this.points = this._delaunator.coords; |
|
|
|
this._init(); |
|
} |
|
|
|
_createClass(Delaunay, [{ |
|
key: "update", |
|
value: function update() { |
|
this._delaunator.update(); |
|
|
|
this._init(); |
|
|
|
return this; |
|
} |
|
}, { |
|
key: "_init", |
|
value: function _init() { |
|
var d = this._delaunator, |
|
points = this.points; // check for collinear |
|
|
|
if (d.hull && d.hull.length > 2 && collinear$1(d)) { |
|
this.collinear = Int32Array.from({ |
|
length: points.length / 2 |
|
}, function (_, i) { |
|
return i; |
|
}).sort(function (i, j) { |
|
return points[2 * i] - points[2 * j] || points[2 * i + 1] - points[2 * j + 1]; |
|
}); // for exact neighbors |
|
|
|
var e = this.collinear[0], |
|
f = this.collinear[this.collinear.length - 1], |
|
_bounds = [points[2 * e], points[2 * e + 1], points[2 * f], points[2 * f + 1]], |
|
r = 1e-8 * Math.sqrt(Math.pow(_bounds[3] - _bounds[1], 2) + Math.pow(_bounds[2] - _bounds[0], 2)); |
|
|
|
for (var i = 0, n = points.length / 2; i < n; ++i) { |
|
var p = jitter(points[2 * i], points[2 * i + 1], r); |
|
points[2 * i] = p[0]; |
|
points[2 * i + 1] = p[1]; |
|
} |
|
|
|
this._delaunator = new Delaunator(points); |
|
} else { |
|
delete this.collinear; |
|
} |
|
|
|
var halfedges = this.halfedges = this._delaunator.halfedges; |
|
var hull = this.hull = this._delaunator.hull; |
|
var triangles = this.triangles = this._delaunator.triangles; |
|
var inedges = this.inedges.fill(-1); |
|
|
|
var hullIndex = this._hullIndex.fill(-1); // Compute an index from each point to an (arbitrary) incoming halfedge |
|
// Used to give the first neighbor of each point; for this reason, |
|
// on the hull we give priority to exterior halfedges |
|
|
|
|
|
for (var _e3 = 0, _n3 = halfedges.length; _e3 < _n3; ++_e3) { |
|
var _p3 = triangles[_e3 % 3 === 2 ? _e3 - 2 : _e3 + 1]; |
|
if (halfedges[_e3] === -1 || inedges[_p3] === -1) inedges[_p3] = _e3; |
|
} |
|
|
|
for (var _i22 = 0, _n4 = hull.length; _i22 < _n4; ++_i22) { |
|
hullIndex[hull[_i22]] = _i22; |
|
} // degenerate case: 1 or 2 (distinct) points |
|
|
|
|
|
if (hull.length <= 2 && hull.length > 0) { |
|
this.triangles = new Int32Array(3).fill(-1); |
|
this.halfedges = new Int32Array(3).fill(-1); |
|
this.triangles[0] = hull[0]; |
|
this.triangles[1] = hull[1]; |
|
this.triangles[2] = hull[1]; |
|
inedges[hull[0]] = 1; |
|
if (hull.length === 2) inedges[hull[1]] = 0; |
|
} |
|
} |
|
}, { |
|
key: "voronoi", |
|
value: function voronoi(bounds) { |
|
return new Voronoi(this, bounds); |
|
} |
|
}, { |
|
key: "neighbors", |
|
value: |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function neighbors(i) { |
|
var inedges, hull, _hullIndex, halfedges, triangles, l, e0, e, p0, p; |
|
|
|
return regeneratorRuntime.wrap(function neighbors$(_context5) { |
|
while (1) { |
|
switch (_context5.prev = _context5.next) { |
|
case 0: |
|
inedges = this.inedges, hull = this.hull, _hullIndex = this._hullIndex, halfedges = this.halfedges, triangles = this.triangles; // degenerate case with several collinear points |
|
|
|
if (!this.collinear) { |
|
_context5.next = 10; |
|
break; |
|
} |
|
|
|
l = this.collinear.indexOf(i); |
|
|
|
if (!(l > 0)) { |
|
_context5.next = 6; |
|
break; |
|
} |
|
|
|
_context5.next = 6; |
|
return this.collinear[l - 1]; |
|
|
|
case 6: |
|
if (!(l < this.collinear.length - 1)) { |
|
_context5.next = 9; |
|
break; |
|
} |
|
|
|
_context5.next = 9; |
|
return this.collinear[l + 1]; |
|
|
|
case 9: |
|
return _context5.abrupt("return"); |
|
|
|
case 10: |
|
e0 = inedges[i]; |
|
|
|
if (!(e0 === -1)) { |
|
_context5.next = 13; |
|
break; |
|
} |
|
|
|
return _context5.abrupt("return"); |
|
|
|
case 13: |
|
// coincident point |
|
e = e0, p0 = -1; |
|
|
|
case 14: |
|
_context5.next = 16; |
|
return p0 = triangles[e]; |
|
|
|
case 16: |
|
e = e % 3 === 2 ? e - 2 : e + 1; |
|
|
|
if (!(triangles[e] !== i)) { |
|
_context5.next = 19; |
|
break; |
|
} |
|
|
|
return _context5.abrupt("return"); |
|
|
|
case 19: |
|
// bad triangulation |
|
e = halfedges[e]; |
|
|
|
if (!(e === -1)) { |
|
_context5.next = 26; |
|
break; |
|
} |
|
|
|
p = hull[(_hullIndex[i] + 1) % hull.length]; |
|
|
|
if (!(p !== p0)) { |
|
_context5.next = 25; |
|
break; |
|
} |
|
|
|
_context5.next = 25; |
|
return p; |
|
|
|
case 25: |
|
return _context5.abrupt("return"); |
|
|
|
case 26: |
|
if (e !== e0) { |
|
_context5.next = 14; |
|
break; |
|
} |
|
|
|
case 27: |
|
case "end": |
|
return _context5.stop(); |
|
} |
|
} |
|
}, neighbors, this); |
|
}) |
|
}, { |
|
key: "find", |
|
value: function find(x, y) { |
|
var i = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; |
|
if ((x = +x, x !== x) || (y = +y, y !== y)) return -1; |
|
var i0 = i; |
|
var c; |
|
|
|
while ((c = this._step(i, x, y)) >= 0 && c !== i && c !== i0) { |
|
i = c; |
|
} |
|
|
|
return c; |
|
} |
|
}, { |
|
key: "_step", |
|
value: function _step(i, x, y) { |
|
var inedges = this.inedges, |
|
hull = this.hull, |
|
_hullIndex = this._hullIndex, |
|
halfedges = this.halfedges, |
|
triangles = this.triangles, |
|
points = this.points; |
|
if (inedges[i] === -1 || !points.length) return (i + 1) % (points.length >> 1); |
|
var c = i; |
|
var dc = Math.pow(x - points[i * 2], 2) + Math.pow(y - points[i * 2 + 1], 2); |
|
var e0 = inedges[i]; |
|
var e = e0; |
|
|
|
do { |
|
var t = triangles[e]; |
|
var dt = Math.pow(x - points[t * 2], 2) + Math.pow(y - points[t * 2 + 1], 2); |
|
if (dt < dc) dc = dt, c = t; |
|
e = e % 3 === 2 ? e - 2 : e + 1; |
|
if (triangles[e] !== i) break; // bad triangulation |
|
|
|
e = halfedges[e]; |
|
|
|
if (e === -1) { |
|
e = hull[(_hullIndex[i] + 1) % hull.length]; |
|
|
|
if (e !== t) { |
|
if (Math.pow(x - points[e * 2], 2) + Math.pow(y - points[e * 2 + 1], 2) < dc) return e; |
|
} |
|
|
|
break; |
|
} |
|
} while (e !== e0); |
|
|
|
return c; |
|
} |
|
}, { |
|
key: "render", |
|
value: function render(context) { |
|
var buffer = context == null ? context = new Path$1() : undefined; |
|
var points = this.points, |
|
halfedges = this.halfedges, |
|
triangles = this.triangles; |
|
|
|
for (var i = 0, n = halfedges.length; i < n; ++i) { |
|
var j = halfedges[i]; |
|
if (j < i) continue; |
|
var ti = triangles[i] * 2; |
|
var tj = triangles[j] * 2; |
|
context.moveTo(points[ti], points[ti + 1]); |
|
context.lineTo(points[tj], points[tj + 1]); |
|
} |
|
|
|
this.renderHull(context); |
|
return buffer && buffer.value(); |
|
} |
|
}, { |
|
key: "renderPoints", |
|
value: function renderPoints(context) { |
|
var r = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2; |
|
var buffer = context == null ? context = new Path$1() : undefined; |
|
var points = this.points; |
|
|
|
for (var i = 0, n = points.length; i < n; i += 2) { |
|
var _x31 = points[i], |
|
_y11 = points[i + 1]; |
|
context.moveTo(_x31 + r, _y11); |
|
context.arc(_x31, _y11, r, 0, tau$3); |
|
} |
|
|
|
return buffer && buffer.value(); |
|
} |
|
}, { |
|
key: "renderHull", |
|
value: function renderHull(context) { |
|
var buffer = context == null ? context = new Path$1() : undefined; |
|
var hull = this.hull, |
|
points = this.points; |
|
var h = hull[0] * 2, |
|
n = hull.length; |
|
context.moveTo(points[h], points[h + 1]); |
|
|
|
for (var i = 1; i < n; ++i) { |
|
var _h = 2 * hull[i]; |
|
|
|
context.lineTo(points[_h], points[_h + 1]); |
|
} |
|
|
|
context.closePath(); |
|
return buffer && buffer.value(); |
|
} |
|
}, { |
|
key: "hullPolygon", |
|
value: function hullPolygon() { |
|
var polygon = new Polygon(); |
|
this.renderHull(polygon); |
|
return polygon.value(); |
|
} |
|
}, { |
|
key: "renderTriangle", |
|
value: function renderTriangle(i, context) { |
|
var buffer = context == null ? context = new Path$1() : undefined; |
|
var points = this.points, |
|
triangles = this.triangles; |
|
var t0 = triangles[i *= 3] * 2; |
|
var t1 = triangles[i + 1] * 2; |
|
var t2 = triangles[i + 2] * 2; |
|
context.moveTo(points[t0], points[t0 + 1]); |
|
context.lineTo(points[t1], points[t1 + 1]); |
|
context.lineTo(points[t2], points[t2 + 1]); |
|
context.closePath(); |
|
return buffer && buffer.value(); |
|
} |
|
}, { |
|
key: "trianglePolygons", |
|
value: |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function trianglePolygons() { |
|
var triangles, i, n; |
|
return regeneratorRuntime.wrap(function trianglePolygons$(_context6) { |
|
while (1) { |
|
switch (_context6.prev = _context6.next) { |
|
case 0: |
|
triangles = this.triangles; |
|
i = 0, n = triangles.length / 3; |
|
|
|
case 2: |
|
if (!(i < n)) { |
|
_context6.next = 8; |
|
break; |
|
} |
|
|
|
_context6.next = 5; |
|
return this.trianglePolygon(i); |
|
|
|
case 5: |
|
++i; |
|
_context6.next = 2; |
|
break; |
|
|
|
case 8: |
|
case "end": |
|
return _context6.stop(); |
|
} |
|
} |
|
}, trianglePolygons, this); |
|
}) |
|
}, { |
|
key: "trianglePolygon", |
|
value: function trianglePolygon(i) { |
|
var polygon = new Polygon(); |
|
this.renderTriangle(i, polygon); |
|
return polygon.value(); |
|
} |
|
}]); |
|
|
|
return Delaunay; |
|
}(); |
|
|
|
Delaunay.from = function (points) { |
|
var fx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : pointX; |
|
var fy = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : pointY; |
|
var that = arguments.length > 3 ? arguments[3] : undefined; |
|
return new Delaunay("length" in points ? flatArray(points, fx, fy, that) : Float64Array.from(flatIterable(points, fx, fy, that))); |
|
}; |
|
|
|
function flatArray(points, fx, fy, that) { |
|
var n = points.length; |
|
var array = new Float64Array(n * 2); |
|
|
|
for (var i = 0; i < n; ++i) { |
|
var p = points[i]; |
|
array[i * 2] = fx.call(that, p, i, points); |
|
array[i * 2 + 1] = fy.call(that, p, i, points); |
|
} |
|
|
|
return array; |
|
} |
|
|
|
function flatIterable(points, fx, fy, that) { |
|
var i, _iteratorNormalCompletion20, _didIteratorError20, _iteratorError20, _iterator20, _step20, p; |
|
|
|
return regeneratorRuntime.wrap(function flatIterable$(_context7) { |
|
while (1) { |
|
switch (_context7.prev = _context7.next) { |
|
case 0: |
|
i = 0; |
|
_iteratorNormalCompletion20 = true; |
|
_didIteratorError20 = false; |
|
_iteratorError20 = undefined; |
|
_context7.prev = 4; |
|
_iterator20 = points[Symbol.iterator](); |
|
|
|
case 6: |
|
if (_iteratorNormalCompletion20 = (_step20 = _iterator20.next()).done) { |
|
_context7.next = 16; |
|
break; |
|
} |
|
|
|
p = _step20.value; |
|
_context7.next = 10; |
|
return fx.call(that, p, i, points); |
|
|
|
case 10: |
|
_context7.next = 12; |
|
return fy.call(that, p, i, points); |
|
|
|
case 12: |
|
++i; |
|
|
|
case 13: |
|
_iteratorNormalCompletion20 = true; |
|
_context7.next = 6; |
|
break; |
|
|
|
case 16: |
|
_context7.next = 22; |
|
break; |
|
|
|
case 18: |
|
_context7.prev = 18; |
|
_context7.t0 = _context7["catch"](4); |
|
_didIteratorError20 = true; |
|
_iteratorError20 = _context7.t0; |
|
|
|
case 22: |
|
_context7.prev = 22; |
|
_context7.prev = 23; |
|
|
|
if (!_iteratorNormalCompletion20 && _iterator20.return != null) { |
|
_iterator20.return(); |
|
} |
|
|
|
case 25: |
|
_context7.prev = 25; |
|
|
|
if (!_didIteratorError20) { |
|
_context7.next = 28; |
|
break; |
|
} |
|
|
|
throw _iteratorError20; |
|
|
|
case 28: |
|
return _context7.finish(25); |
|
|
|
case 29: |
|
return _context7.finish(22); |
|
|
|
case 30: |
|
case "end": |
|
return _context7.stop(); |
|
} |
|
} |
|
}, _marked3, null, [[4, 18, 22, 30], [23,, 25, 29]]); |
|
} |
|
|
|
function Voronoi$1(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
Voronoi$1.Definition = { |
|
"type": "Voronoi", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "x", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "y", |
|
"type": "field", |
|
"required": true |
|
}, { |
|
"name": "size", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "extent", |
|
"type": "array", |
|
"array": true, |
|
"length": 2, |
|
"default": [[-1e5, -1e5], [1e5, 1e5]], |
|
"content": { |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
} |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"default": "path" |
|
}] |
|
}; |
|
var prototype$1o = inherits(Voronoi$1, Transform); |
|
var defaultExtent = [-1e5, -1e5, 1e5, 1e5]; |
|
|
|
prototype$1o.transform = function (_, pulse) { |
|
var as = _.as || 'path', |
|
data = pulse.source; // nothing to do if no data |
|
|
|
if (!data || !data.length) return pulse; // configure and construct voronoi diagram |
|
|
|
var s = _.size; |
|
s = s ? [0, 0, s[0], s[1]] : (s = _.extent) ? [s[0][0], s[0][1], s[1][0], s[1][1]] : defaultExtent; |
|
var voronoi = this.value = Delaunay.from(data, _.x, _.y).voronoi(s); // map polygons to paths |
|
|
|
for (var i = 0, n = data.length; i < n; ++i) { |
|
var polygon = voronoi.cellPolygon(i); |
|
data[i][as] = polygon ? toPathString(polygon) : null; |
|
} |
|
|
|
return pulse.reflow(_.modified()).modifies(as); |
|
}; // suppress duplicated end point vertices |
|
|
|
|
|
function toPathString(p) { |
|
var x = p[0][0], |
|
y = p[0][1]; |
|
var n = p.length - 1; |
|
|
|
for (; p[n][0] === x && p[n][1] === y; --n) { |
|
; |
|
} |
|
|
|
return 'M' + p.slice(0, n + 1).join('L') + 'Z'; |
|
} |
|
|
|
var voronoi = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
voronoi: Voronoi$1 |
|
}); |
|
/* |
|
Copyright (c) 2013, Jason Davies. |
|
All rights reserved. |
|
Redistribution and use in source and binary forms, with or without |
|
modification, are permitted provided that the following conditions are met: |
|
* Redistributions of source code must retain the above copyright notice, this |
|
list of conditions and the following disclaimer. |
|
* Redistributions in binary form must reproduce the above copyright notice, |
|
this list of conditions and the following disclaimer in the documentation |
|
and/or other materials provided with the distribution. |
|
* The name Jason Davies may not be used to endorse or promote products |
|
derived from this software without specific prior written permission. |
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
DISCLAIMED. IN NO EVENT SHALL JASON DAVIES BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
// Word cloud layout by Jason Davies, https://www.jasondavies.com/wordcloud/ |
|
// Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf |
|
|
|
var cloudRadians = Math.PI / 180, |
|
cw = 1 << 11 >> 5, |
|
ch = 1 << 11; |
|
|
|
function cloud() { |
|
var size = [256, 256], |
|
text, |
|
font, |
|
fontSize, |
|
fontStyle, |
|
fontWeight, |
|
rotate, |
|
padding, |
|
spiral = archimedeanSpiral, |
|
words = [], |
|
random = Math.random, |
|
cloud = {}; |
|
|
|
cloud.layout = function () { |
|
var contextAndRatio = getContext(domCanvas()), |
|
board = zeroArray((size[0] >> 5) * size[1]), |
|
bounds = null, |
|
n = words.length, |
|
i = -1, |
|
tags = [], |
|
data = words.map(function (d) { |
|
return { |
|
text: text(d), |
|
font: font(d), |
|
style: fontStyle(d), |
|
weight: fontWeight(d), |
|
rotate: rotate(d), |
|
size: ~~(fontSize(d) + 1e-14), |
|
padding: padding(d), |
|
xoff: 0, |
|
yoff: 0, |
|
x1: 0, |
|
y1: 0, |
|
x0: 0, |
|
y0: 0, |
|
hasText: false, |
|
sprite: null, |
|
datum: d |
|
}; |
|
}).sort(function (a, b) { |
|
return b.size - a.size; |
|
}); |
|
|
|
while (++i < n) { |
|
var d = data[i]; |
|
d.x = size[0] * (random() + .5) >> 1; |
|
d.y = size[1] * (random() + .5) >> 1; |
|
cloudSprite(contextAndRatio, d, data, i); |
|
|
|
if (d.hasText && place(board, d, bounds)) { |
|
tags.push(d); |
|
if (bounds) cloudBounds(bounds, d);else bounds = [{ |
|
x: d.x + d.x0, |
|
y: d.y + d.y0 |
|
}, { |
|
x: d.x + d.x1, |
|
y: d.y + d.y1 |
|
}]; // Temporary hack |
|
|
|
d.x -= size[0] >> 1; |
|
d.y -= size[1] >> 1; |
|
} |
|
} |
|
|
|
return tags; |
|
}; |
|
|
|
function getContext(canvas) { |
|
canvas.width = canvas.height = 1; |
|
var ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2); |
|
canvas.width = (cw << 5) / ratio; |
|
canvas.height = ch / ratio; |
|
var context = canvas.getContext("2d"); |
|
context.fillStyle = context.strokeStyle = "red"; |
|
context.textAlign = "center"; |
|
return { |
|
context: context, |
|
ratio: ratio |
|
}; |
|
} |
|
|
|
function place(board, tag, bounds) { |
|
var startX = tag.x, |
|
startY = tag.y, |
|
maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]), |
|
s = spiral(size), |
|
dt = random() < .5 ? 1 : -1, |
|
t = -dt, |
|
dxdy, |
|
dx, |
|
dy; |
|
|
|
while (dxdy = s(t += dt)) { |
|
dx = ~~dxdy[0]; |
|
dy = ~~dxdy[1]; |
|
if (Math.min(Math.abs(dx), Math.abs(dy)) >= maxDelta) break; |
|
tag.x = startX + dx; |
|
tag.y = startY + dy; |
|
if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 || tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue; // TODO only check for collisions within current bounds. |
|
|
|
if (!bounds || !cloudCollide(tag, board, size[0])) { |
|
if (!bounds || collideRects(tag, bounds)) { |
|
var sprite = tag.sprite, |
|
w = tag.width >> 5, |
|
sw = size[0] >> 5, |
|
lx = tag.x - (w << 4), |
|
sx = lx & 0x7f, |
|
msx = 32 - sx, |
|
h = tag.y1 - tag.y0, |
|
x = (tag.y + tag.y0) * sw + (lx >> 5), |
|
last; |
|
|
|
for (var j = 0; j < h; j++) { |
|
last = 0; |
|
|
|
for (var i = 0; i <= w; i++) { |
|
board[x + i] |= last << msx | (i < w ? (last = sprite[j * w + i]) >>> sx : 0); |
|
} |
|
|
|
x += sw; |
|
} |
|
|
|
tag.sprite = null; |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
cloud.words = function (_) { |
|
if (arguments.length) { |
|
words = _; |
|
return cloud; |
|
} else { |
|
return words; |
|
} |
|
}; |
|
|
|
cloud.size = function (_) { |
|
if (arguments.length) { |
|
size = [+_[0], +_[1]]; |
|
return cloud; |
|
} else { |
|
return size; |
|
} |
|
}; |
|
|
|
cloud.font = function (_) { |
|
if (arguments.length) { |
|
font = functor(_); |
|
return cloud; |
|
} else { |
|
return font; |
|
} |
|
}; |
|
|
|
cloud.fontStyle = function (_) { |
|
if (arguments.length) { |
|
fontStyle = functor(_); |
|
return cloud; |
|
} else { |
|
return fontStyle; |
|
} |
|
}; |
|
|
|
cloud.fontWeight = function (_) { |
|
if (arguments.length) { |
|
fontWeight = functor(_); |
|
return cloud; |
|
} else { |
|
return fontWeight; |
|
} |
|
}; |
|
|
|
cloud.rotate = function (_) { |
|
if (arguments.length) { |
|
rotate = functor(_); |
|
return cloud; |
|
} else { |
|
return rotate; |
|
} |
|
}; |
|
|
|
cloud.text = function (_) { |
|
if (arguments.length) { |
|
text = functor(_); |
|
return cloud; |
|
} else { |
|
return text; |
|
} |
|
}; |
|
|
|
cloud.spiral = function (_) { |
|
if (arguments.length) { |
|
spiral = spirals[_] || _; |
|
return cloud; |
|
} else { |
|
return spiral; |
|
} |
|
}; |
|
|
|
cloud.fontSize = function (_) { |
|
if (arguments.length) { |
|
fontSize = functor(_); |
|
return cloud; |
|
} else { |
|
return fontSize; |
|
} |
|
}; |
|
|
|
cloud.padding = function (_) { |
|
if (arguments.length) { |
|
padding = functor(_); |
|
return cloud; |
|
} else { |
|
return padding; |
|
} |
|
}; |
|
|
|
cloud.random = function (_) { |
|
if (arguments.length) { |
|
random = _; |
|
return cloud; |
|
} else { |
|
return random; |
|
} |
|
}; |
|
|
|
return cloud; |
|
} // Fetches a monochrome sprite bitmap for the specified text. |
|
// Load in batches for speed. |
|
|
|
|
|
function cloudSprite(contextAndRatio, d, data, di) { |
|
if (d.sprite) return; |
|
var c = contextAndRatio.context, |
|
ratio = contextAndRatio.ratio; |
|
c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio); |
|
var x = 0, |
|
y = 0, |
|
maxh = 0, |
|
n = data.length, |
|
w, |
|
w32, |
|
h, |
|
i, |
|
j; |
|
--di; |
|
|
|
while (++di < n) { |
|
d = data[di]; |
|
c.save(); |
|
c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font; |
|
w = c.measureText(d.text + "m").width * ratio; |
|
h = d.size << 1; |
|
|
|
if (d.rotate) { |
|
var sr = Math.sin(d.rotate * cloudRadians), |
|
cr = Math.cos(d.rotate * cloudRadians), |
|
wcr = w * cr, |
|
wsr = w * sr, |
|
hcr = h * cr, |
|
hsr = h * sr; |
|
w = Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f >> 5 << 5; |
|
h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr)); |
|
} else { |
|
w = w + 0x1f >> 5 << 5; |
|
} |
|
|
|
if (h > maxh) maxh = h; |
|
|
|
if (x + w >= cw << 5) { |
|
x = 0; |
|
y += maxh; |
|
maxh = 0; |
|
} |
|
|
|
if (y + h >= ch) break; |
|
c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio); |
|
if (d.rotate) c.rotate(d.rotate * cloudRadians); |
|
c.fillText(d.text, 0, 0); |
|
|
|
if (d.padding) { |
|
c.lineWidth = 2 * d.padding; |
|
c.strokeText(d.text, 0, 0); |
|
} |
|
|
|
c.restore(); |
|
d.width = w; |
|
d.height = h; |
|
d.xoff = x; |
|
d.yoff = y; |
|
d.x1 = w >> 1; |
|
d.y1 = h >> 1; |
|
d.x0 = -d.x1; |
|
d.y0 = -d.y1; |
|
d.hasText = true; |
|
x += w; |
|
} |
|
|
|
var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data, |
|
sprite = []; |
|
|
|
while (--di >= 0) { |
|
d = data[di]; |
|
if (!d.hasText) continue; |
|
w = d.width; |
|
w32 = w >> 5; |
|
h = d.y1 - d.y0; // Zero the buffer |
|
|
|
for (i = 0; i < h * w32; i++) { |
|
sprite[i] = 0; |
|
} |
|
|
|
x = d.xoff; |
|
if (x == null) return; |
|
y = d.yoff; |
|
var seen = 0, |
|
seenRow = -1; |
|
|
|
for (j = 0; j < h; j++) { |
|
for (i = 0; i < w; i++) { |
|
var k = w32 * j + (i >> 5), |
|
m = pixels[(y + j) * (cw << 5) + (x + i) << 2] ? 1 << 31 - i % 32 : 0; |
|
sprite[k] |= m; |
|
seen |= m; |
|
} |
|
|
|
if (seen) seenRow = j;else { |
|
d.y0++; |
|
h--; |
|
j--; |
|
y++; |
|
} |
|
} |
|
|
|
d.y1 = d.y0 + seenRow; |
|
d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32); |
|
} |
|
} // Use mask-based collision detection. |
|
|
|
|
|
function cloudCollide(tag, board, sw) { |
|
sw >>= 5; |
|
var sprite = tag.sprite, |
|
w = tag.width >> 5, |
|
lx = tag.x - (w << 4), |
|
sx = lx & 0x7f, |
|
msx = 32 - sx, |
|
h = tag.y1 - tag.y0, |
|
x = (tag.y + tag.y0) * sw + (lx >> 5), |
|
last; |
|
|
|
for (var j = 0; j < h; j++) { |
|
last = 0; |
|
|
|
for (var i = 0; i <= w; i++) { |
|
if ((last << msx | (i < w ? (last = sprite[j * w + i]) >>> sx : 0)) & board[x + i]) return true; |
|
} |
|
|
|
x += sw; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function cloudBounds(bounds, d) { |
|
var b0 = bounds[0], |
|
b1 = bounds[1]; |
|
if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0; |
|
if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0; |
|
if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1; |
|
if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1; |
|
} |
|
|
|
function collideRects(a, b) { |
|
return a.x + a.x1 > b[0].x && a.x + a.x0 < b[1].x && a.y + a.y1 > b[0].y && a.y + a.y0 < b[1].y; |
|
} |
|
|
|
function archimedeanSpiral(size) { |
|
var e = size[0] / size[1]; |
|
return function (t) { |
|
return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)]; |
|
}; |
|
} |
|
|
|
function rectangularSpiral(size) { |
|
var dy = 4, |
|
dx = dy * size[0] / size[1], |
|
x = 0, |
|
y = 0; |
|
return function (t) { |
|
var sign = t < 0 ? -1 : 1; // See triangular numbers: T_n = n * (n + 1) / 2. |
|
|
|
switch (Math.sqrt(1 + 4 * sign * t) - sign & 3) { |
|
case 0: |
|
x += dx; |
|
break; |
|
|
|
case 1: |
|
y += dy; |
|
break; |
|
|
|
case 2: |
|
x -= dx; |
|
break; |
|
|
|
default: |
|
y -= dy; |
|
break; |
|
} |
|
|
|
return [x, y]; |
|
}; |
|
} // TODO reuse arrays? |
|
|
|
|
|
function zeroArray(n) { |
|
var a = [], |
|
i = -1; |
|
|
|
while (++i < n) { |
|
a[i] = 0; |
|
} |
|
|
|
return a; |
|
} |
|
|
|
function functor(d) { |
|
return typeof d === "function" ? d : function () { |
|
return d; |
|
}; |
|
} |
|
|
|
var spirals = { |
|
archimedean: archimedeanSpiral, |
|
rectangular: rectangularSpiral |
|
}; |
|
var Output$5 = ['x', 'y', 'font', 'fontSize', 'fontStyle', 'fontWeight', 'angle']; |
|
var Params$1 = ['text', 'font', 'rotate', 'fontSize', 'fontStyle', 'fontWeight']; |
|
|
|
function Wordcloud(params) { |
|
Transform.call(this, cloud(), params); |
|
} |
|
|
|
Wordcloud.Definition = { |
|
"type": "Wordcloud", |
|
"metadata": { |
|
"modifies": true |
|
}, |
|
"params": [{ |
|
"name": "size", |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
}, { |
|
"name": "font", |
|
"type": "string", |
|
"expr": true, |
|
"default": "sans-serif" |
|
}, { |
|
"name": "fontStyle", |
|
"type": "string", |
|
"expr": true, |
|
"default": "normal" |
|
}, { |
|
"name": "fontWeight", |
|
"type": "string", |
|
"expr": true, |
|
"default": "normal" |
|
}, { |
|
"name": "fontSize", |
|
"type": "number", |
|
"expr": true, |
|
"default": 14 |
|
}, { |
|
"name": "fontSizeRange", |
|
"type": "number", |
|
"array": "nullable", |
|
"default": [10, 50] |
|
}, { |
|
"name": "rotate", |
|
"type": "number", |
|
"expr": true, |
|
"default": 0 |
|
}, { |
|
"name": "text", |
|
"type": "field" |
|
}, { |
|
"name": "spiral", |
|
"type": "string", |
|
"values": ["archimedean", "rectangular"] |
|
}, { |
|
"name": "padding", |
|
"type": "number", |
|
"expr": true |
|
}, { |
|
"name": "as", |
|
"type": "string", |
|
"array": true, |
|
"length": 7, |
|
"default": Output$5 |
|
}] |
|
}; |
|
var prototype$1p = inherits(Wordcloud, Transform); |
|
|
|
prototype$1p.transform = function (_, pulse) { |
|
if (_.size && !(_.size[0] && _.size[1])) { |
|
error('Wordcloud size dimensions must be non-zero.'); |
|
} |
|
|
|
function modp(param) { |
|
var p = _[param]; |
|
return isFunction(p) && pulse.modified(p.fields); |
|
} |
|
|
|
var mod = _.modified(); |
|
|
|
if (!(mod || pulse.changed(pulse.ADD_REM) || Params$1.some(modp))) return; |
|
var data = pulse.materialize(pulse.SOURCE).source, |
|
layout = this.value, |
|
as = _.as || Output$5, |
|
fontSize = _.fontSize || 14, |
|
range; |
|
isFunction(fontSize) ? range = _.fontSizeRange : fontSize = constant(fontSize); // create font size scaling function as needed |
|
|
|
if (range) { |
|
var fsize = fontSize, |
|
sizeScale = scale$2('sqrt')().domain(extent(data, fsize)).range(range); |
|
|
|
fontSize = function fontSize(x) { |
|
return sizeScale(fsize(x)); |
|
}; |
|
} |
|
|
|
data.forEach(function (t) { |
|
t[as[0]] = NaN; |
|
t[as[1]] = NaN; |
|
t[as[3]] = 0; |
|
}); // configure layout |
|
|
|
var words = layout.words(data).text(_.text).size(_.size || [500, 500]).padding(_.padding || 1).spiral(_.spiral || 'archimedean').rotate(_.rotate || 0).font(_.font || 'sans-serif').fontStyle(_.fontStyle || 'normal').fontWeight(_.fontWeight || 'normal').fontSize(fontSize).random(exports.random).layout(); |
|
var size = layout.size(), |
|
dx = size[0] >> 1, |
|
dy = size[1] >> 1, |
|
i = 0, |
|
n = words.length, |
|
w, |
|
t; |
|
|
|
for (; i < n; ++i) { |
|
w = words[i]; |
|
t = w.datum; |
|
t[as[0]] = w.x + dx; |
|
t[as[1]] = w.y + dy; |
|
t[as[2]] = w.font; |
|
t[as[3]] = w.size; |
|
t[as[4]] = w.style; |
|
t[as[5]] = w.weight; |
|
t[as[6]] = w.rotate; |
|
} |
|
|
|
return pulse.reflow(mod).modifies(as); |
|
}; |
|
|
|
var wordcloud = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
wordcloud: Wordcloud |
|
}); |
|
|
|
function array8(n) { |
|
return new Uint8Array(n); |
|
} |
|
|
|
function array16(n) { |
|
return new Uint16Array(n); |
|
} |
|
|
|
function array32(n) { |
|
return new Uint32Array(n); |
|
} |
|
/** |
|
* Maintains CrossFilter state. |
|
*/ |
|
|
|
|
|
function Bitmaps() { |
|
var width = 8, |
|
_data2 = [], |
|
_seen = array32(0), |
|
_curr = array$2(0, width), |
|
_prev = array$2(0, width); |
|
|
|
return { |
|
data: function data() { |
|
return _data2; |
|
}, |
|
seen: function seen() { |
|
return _seen = lengthen(_seen, _data2.length); |
|
}, |
|
add: function add(array) { |
|
for (var i = 0, j = _data2.length, n = array.length, t; i < n; ++i) { |
|
t = array[i]; |
|
t._index = j++; |
|
|
|
_data2.push(t); |
|
} |
|
}, |
|
remove: function remove(num, map) { |
|
// map: index -> boolean (true => remove) |
|
var n = _data2.length, |
|
copy = Array(n - num), |
|
reindex = _data2, |
|
// reuse old data array for index map |
|
t, |
|
i, |
|
j; // seek forward to first removal |
|
|
|
for (i = 0; !map[i] && i < n; ++i) { |
|
copy[i] = _data2[i]; |
|
reindex[i] = i; |
|
} // condense arrays |
|
|
|
|
|
for (j = i; i < n; ++i) { |
|
t = _data2[i]; |
|
|
|
if (!map[i]) { |
|
reindex[i] = j; |
|
_curr[j] = _curr[i]; |
|
_prev[j] = _prev[i]; |
|
copy[j] = t; |
|
t._index = j++; |
|
} else { |
|
reindex[i] = -1; |
|
} |
|
|
|
_curr[i] = 0; // clear unused bits |
|
} |
|
|
|
_data2 = copy; |
|
return reindex; |
|
}, |
|
size: function size() { |
|
return _data2.length; |
|
}, |
|
curr: function curr() { |
|
return _curr; |
|
}, |
|
prev: function prev() { |
|
return _prev; |
|
}, |
|
reset: function reset(k) { |
|
_prev[k] = _curr[k]; |
|
}, |
|
all: function all() { |
|
return width < 0x101 ? 0xff : width < 0x10001 ? 0xffff : 0xffffffff; |
|
}, |
|
set: function set(k, one) { |
|
_curr[k] |= one; |
|
}, |
|
clear: function clear(k, one) { |
|
_curr[k] &= ~one; |
|
}, |
|
resize: function resize(n, m) { |
|
var k = _curr.length; |
|
|
|
if (n > k || m > width) { |
|
width = Math.max(m, width); |
|
_curr = array$2(n, width, _curr); |
|
_prev = array$2(n, width); |
|
} |
|
} |
|
}; |
|
} |
|
|
|
function lengthen(array, length, copy) { |
|
if (array.length >= length) return array; |
|
copy = copy || new array.constructor(length); |
|
copy.set(array); |
|
return copy; |
|
} |
|
|
|
function array$2(n, m, array) { |
|
var copy = (m < 0x101 ? array8 : m < 0x10001 ? array16 : array32)(n); |
|
if (array) copy.set(array); |
|
return copy; |
|
} |
|
|
|
function Dimension(index, i, query) { |
|
var bit = 1 << i; |
|
return { |
|
one: bit, |
|
zero: ~bit, |
|
range: query.slice(), |
|
bisect: index.bisect, |
|
index: index.index, |
|
size: index.size, |
|
onAdd: function onAdd(added, curr) { |
|
var dim = this, |
|
range = dim.bisect(dim.range, added.value), |
|
idx = added.index, |
|
lo = range[0], |
|
hi = range[1], |
|
n1 = idx.length, |
|
i; |
|
|
|
for (i = 0; i < lo; ++i) { |
|
curr[idx[i]] |= bit; |
|
} |
|
|
|
for (i = hi; i < n1; ++i) { |
|
curr[idx[i]] |= bit; |
|
} |
|
|
|
return dim; |
|
} |
|
}; |
|
} |
|
/** |
|
* Maintains a list of values, sorted by key. |
|
*/ |
|
|
|
|
|
function SortedIndex() { |
|
var _index8 = array32(0), |
|
value = [], |
|
_size = 0; |
|
|
|
function insert(key, data, base) { |
|
if (!data.length) return []; |
|
var n0 = _size, |
|
n1 = data.length, |
|
addv = Array(n1), |
|
addi = array32(n1), |
|
oldv, |
|
oldi, |
|
i; |
|
|
|
for (i = 0; i < n1; ++i) { |
|
addv[i] = key(data[i]); |
|
addi[i] = i; |
|
} |
|
|
|
addv = sort(addv, addi); |
|
|
|
if (n0) { |
|
oldv = value; |
|
oldi = _index8; |
|
value = Array(n0 + n1); |
|
_index8 = array32(n0 + n1); |
|
merge$2(base, oldv, oldi, n0, addv, addi, n1, value, _index8); |
|
} else { |
|
if (base > 0) for (i = 0; i < n1; ++i) { |
|
addi[i] += base; |
|
} |
|
value = addv; |
|
_index8 = addi; |
|
} |
|
|
|
_size = n0 + n1; |
|
return { |
|
index: addi, |
|
value: addv |
|
}; |
|
} |
|
|
|
function remove(num, map) { |
|
// map: index -> remove |
|
var n = _size, |
|
idx, |
|
i, |
|
j; // seek forward to first removal |
|
|
|
for (i = 0; !map[_index8[i]] && i < n; ++i) { |
|
; |
|
} // condense index and value arrays |
|
|
|
|
|
for (j = i; i < n; ++i) { |
|
if (!map[idx = _index8[i]]) { |
|
_index8[j] = idx; |
|
value[j] = value[i]; |
|
++j; |
|
} |
|
} |
|
|
|
_size = n - num; |
|
} |
|
|
|
function reindex(map) { |
|
for (var i = 0, n = _size; i < n; ++i) { |
|
_index8[i] = map[_index8[i]]; |
|
} |
|
} |
|
|
|
function bisect(range, array) { |
|
var n; |
|
|
|
if (array) { |
|
n = array.length; |
|
} else { |
|
array = value; |
|
n = _size; |
|
} |
|
|
|
return [bisectLeft(array, range[0], 0, n), bisectRight(array, range[1], 0, n)]; |
|
} |
|
|
|
return { |
|
insert: insert, |
|
remove: remove, |
|
bisect: bisect, |
|
reindex: reindex, |
|
index: function index() { |
|
return _index8; |
|
}, |
|
size: function size() { |
|
return _size; |
|
} |
|
}; |
|
} |
|
|
|
function sort(values, index) { |
|
values.sort.call(index, function (a, b) { |
|
var x = values[a], |
|
y = values[b]; |
|
return x < y ? -1 : x > y ? 1 : 0; |
|
}); |
|
return permute(values, index); |
|
} |
|
|
|
function merge$2(base, value0, index0, n0, value1, index1, n1, value, index) { |
|
var i0 = 0, |
|
i1 = 0, |
|
i; |
|
|
|
for (i = 0; i0 < n0 && i1 < n1; ++i) { |
|
if (value0[i0] < value1[i1]) { |
|
value[i] = value0[i0]; |
|
index[i] = index0[i0++]; |
|
} else { |
|
value[i] = value1[i1]; |
|
index[i] = index1[i1++] + base; |
|
} |
|
} |
|
|
|
for (; i0 < n0; ++i0, ++i) { |
|
value[i] = value0[i0]; |
|
index[i] = index0[i0]; |
|
} |
|
|
|
for (; i1 < n1; ++i1, ++i) { |
|
value[i] = value1[i1]; |
|
index[i] = index1[i1] + base; |
|
} |
|
} |
|
/** |
|
* An indexed multi-dimensional filter. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {Array<function(object): *>} params.fields - An array of dimension accessors to filter. |
|
* @param {Array} params.query - An array of per-dimension range queries. |
|
*/ |
|
|
|
|
|
function CrossFilter(params) { |
|
Transform.call(this, Bitmaps(), params); |
|
this._indices = null; |
|
this._dims = null; |
|
} |
|
|
|
CrossFilter.Definition = { |
|
"type": "CrossFilter", |
|
"metadata": {}, |
|
"params": [{ |
|
"name": "fields", |
|
"type": "field", |
|
"array": true, |
|
"required": true |
|
}, { |
|
"name": "query", |
|
"type": "array", |
|
"array": true, |
|
"required": true, |
|
"content": { |
|
"type": "number", |
|
"array": true, |
|
"length": 2 |
|
} |
|
}] |
|
}; |
|
var prototype$1q = inherits(CrossFilter, Transform); |
|
|
|
prototype$1q.transform = function (_, pulse) { |
|
if (!this._dims) { |
|
return this.init(_, pulse); |
|
} else { |
|
var init = _.modified('fields') || _.fields.some(function (f) { |
|
return pulse.modified(f.fields); |
|
}); |
|
|
|
return init ? this.reinit(_, pulse) : this.eval(_, pulse); |
|
} |
|
}; |
|
|
|
prototype$1q.init = function (_, pulse) { |
|
var fields = _.fields, |
|
query = _.query, |
|
indices = this._indices = {}, |
|
dims = this._dims = [], |
|
m = query.length, |
|
i = 0, |
|
key, |
|
index; // instantiate indices and dimensions |
|
|
|
for (; i < m; ++i) { |
|
key = fields[i].fname; |
|
index = indices[key] || (indices[key] = SortedIndex()); |
|
dims.push(Dimension(index, i, query[i])); |
|
} |
|
|
|
return this.eval(_, pulse); |
|
}; |
|
|
|
prototype$1q.reinit = function (_, pulse) { |
|
var output = pulse.materialize().fork(), |
|
fields = _.fields, |
|
query = _.query, |
|
indices = this._indices, |
|
dims = this._dims, |
|
bits = this.value, |
|
curr = bits.curr(), |
|
prev = bits.prev(), |
|
all = bits.all(), |
|
out = output.rem = output.add, |
|
mod = output.mod, |
|
m = query.length, |
|
adds = {}, |
|
add, |
|
index, |
|
key, |
|
mods, |
|
remMap, |
|
modMap, |
|
i, |
|
n, |
|
f; // set prev to current state |
|
|
|
prev.set(curr); // if pulse has remove tuples, process them first |
|
|
|
if (pulse.rem.length) { |
|
remMap = this.remove(_, pulse, output); |
|
} // if pulse has added tuples, add them to state |
|
|
|
|
|
if (pulse.add.length) { |
|
bits.add(pulse.add); |
|
} // if pulse has modified tuples, create an index map |
|
|
|
|
|
if (pulse.mod.length) { |
|
modMap = {}; |
|
|
|
for (mods = pulse.mod, i = 0, n = mods.length; i < n; ++i) { |
|
modMap[mods[i]._index] = 1; |
|
} |
|
} // re-initialize indices as needed, update curr bitmap |
|
|
|
|
|
for (i = 0; i < m; ++i) { |
|
f = fields[i]; |
|
|
|
if (!dims[i] || _.modified('fields', i) || pulse.modified(f.fields)) { |
|
key = f.fname; |
|
|
|
if (!(add = adds[key])) { |
|
indices[key] = index = SortedIndex(); |
|
adds[key] = add = index.insert(f, pulse.source, 0); |
|
} |
|
|
|
dims[i] = Dimension(index, i, query[i]).onAdd(add, curr); |
|
} |
|
} // visit each tuple |
|
// if filter state changed, push index to add/rem |
|
// else if in mod and passes a filter, push index to mod |
|
|
|
|
|
for (i = 0, n = bits.data().length; i < n; ++i) { |
|
if (remMap[i]) { |
|
// skip if removed tuple |
|
continue; |
|
} else if (prev[i] !== curr[i]) { |
|
// add if state changed |
|
out.push(i); |
|
} else if (modMap[i] && curr[i] !== all) { |
|
// otherwise, pass mods through |
|
mod.push(i); |
|
} |
|
} |
|
|
|
bits.mask = (1 << m) - 1; |
|
return output; |
|
}; |
|
|
|
prototype$1q.eval = function (_, pulse) { |
|
var output = pulse.materialize().fork(), |
|
m = this._dims.length, |
|
mask = 0; |
|
|
|
if (pulse.rem.length) { |
|
this.remove(_, pulse, output); |
|
mask |= (1 << m) - 1; |
|
} |
|
|
|
if (_.modified('query') && !_.modified('fields')) { |
|
mask |= this.update(_, pulse, output); |
|
} |
|
|
|
if (pulse.add.length) { |
|
this.insert(_, pulse, output); |
|
mask |= (1 << m) - 1; |
|
} |
|
|
|
if (pulse.mod.length) { |
|
this.modify(pulse, output); |
|
mask |= (1 << m) - 1; |
|
} |
|
|
|
this.value.mask = mask; |
|
return output; |
|
}; |
|
|
|
prototype$1q.insert = function (_, pulse, output) { |
|
var tuples = pulse.add, |
|
bits = this.value, |
|
dims = this._dims, |
|
indices = this._indices, |
|
fields = _.fields, |
|
adds = {}, |
|
out = output.add, |
|
k = bits.size(), |
|
n = k + tuples.length, |
|
m = dims.length, |
|
j, |
|
key, |
|
add; // resize bitmaps and add tuples as needed |
|
|
|
bits.resize(n, m); |
|
bits.add(tuples); |
|
var curr = bits.curr(), |
|
prev = bits.prev(), |
|
all = bits.all(); // add to dimensional indices |
|
|
|
for (j = 0; j < m; ++j) { |
|
key = fields[j].fname; |
|
add = adds[key] || (adds[key] = indices[key].insert(fields[j], tuples, k)); |
|
dims[j].onAdd(add, curr); |
|
} // set previous filters, output if passes at least one filter |
|
|
|
|
|
for (; k < n; ++k) { |
|
prev[k] = all; |
|
if (curr[k] !== all) out.push(k); |
|
} |
|
}; |
|
|
|
prototype$1q.modify = function (pulse, output) { |
|
var out = output.mod, |
|
bits = this.value, |
|
curr = bits.curr(), |
|
all = bits.all(), |
|
tuples = pulse.mod, |
|
i, |
|
n, |
|
k; |
|
|
|
for (i = 0, n = tuples.length; i < n; ++i) { |
|
k = tuples[i]._index; |
|
if (curr[k] !== all) out.push(k); |
|
} |
|
}; |
|
|
|
prototype$1q.remove = function (_, pulse, output) { |
|
var indices = this._indices, |
|
bits = this.value, |
|
curr = bits.curr(), |
|
prev = bits.prev(), |
|
all = bits.all(), |
|
map = {}, |
|
out = output.rem, |
|
tuples = pulse.rem, |
|
i, |
|
n, |
|
k, |
|
f; // process tuples, output if passes at least one filter |
|
|
|
for (i = 0, n = tuples.length; i < n; ++i) { |
|
k = tuples[i]._index; |
|
map[k] = 1; // build index map |
|
|
|
prev[k] = f = curr[k]; |
|
curr[k] = all; |
|
if (f !== all) out.push(k); |
|
} // remove from dimensional indices |
|
|
|
|
|
for (k in indices) { |
|
indices[k].remove(n, map); |
|
} |
|
|
|
this.reindex(pulse, n, map); |
|
return map; |
|
}; // reindex filters and indices after propagation completes |
|
|
|
|
|
prototype$1q.reindex = function (pulse, num, map) { |
|
var indices = this._indices, |
|
bits = this.value; |
|
pulse.runAfter(function () { |
|
var indexMap = bits.remove(num, map); |
|
|
|
for (var key in indices) { |
|
indices[key].reindex(indexMap); |
|
} |
|
}); |
|
}; |
|
|
|
prototype$1q.update = function (_, pulse, output) { |
|
var dims = this._dims, |
|
query = _.query, |
|
stamp = pulse.stamp, |
|
m = dims.length, |
|
mask = 0, |
|
i, |
|
q; // survey how many queries have changed |
|
|
|
output.filters = 0; |
|
|
|
for (q = 0; q < m; ++q) { |
|
if (_.modified('query', q)) { |
|
i = q; |
|
++mask; |
|
} |
|
} |
|
|
|
if (mask === 1) { |
|
// only one query changed, use more efficient update |
|
mask = dims[i].one; |
|
this.incrementOne(dims[i], query[i], output.add, output.rem); |
|
} else { |
|
// multiple queries changed, perform full record keeping |
|
for (q = 0, mask = 0; q < m; ++q) { |
|
if (!_.modified('query', q)) continue; |
|
mask |= dims[q].one; |
|
this.incrementAll(dims[q], query[q], stamp, output.add); |
|
output.rem = output.add; // duplicate add/rem for downstream resolve |
|
} |
|
} |
|
|
|
return mask; |
|
}; |
|
|
|
prototype$1q.incrementAll = function (dim, query, stamp, out) { |
|
var bits = this.value, |
|
seen = bits.seen(), |
|
curr = bits.curr(), |
|
prev = bits.prev(), |
|
index = dim.index(), |
|
old = dim.bisect(dim.range), |
|
range = dim.bisect(query), |
|
lo1 = range[0], |
|
hi1 = range[1], |
|
lo0 = old[0], |
|
hi0 = old[1], |
|
one = dim.one, |
|
i, |
|
j, |
|
k; // Fast incremental update based on previous lo index. |
|
|
|
if (lo1 < lo0) { |
|
for (i = lo1, j = Math.min(lo0, hi1); i < j; ++i) { |
|
k = index[i]; |
|
|
|
if (seen[k] !== stamp) { |
|
prev[k] = curr[k]; |
|
seen[k] = stamp; |
|
out.push(k); |
|
} |
|
|
|
curr[k] ^= one; |
|
} |
|
} else if (lo1 > lo0) { |
|
for (i = lo0, j = Math.min(lo1, hi0); i < j; ++i) { |
|
k = index[i]; |
|
|
|
if (seen[k] !== stamp) { |
|
prev[k] = curr[k]; |
|
seen[k] = stamp; |
|
out.push(k); |
|
} |
|
|
|
curr[k] ^= one; |
|
} |
|
} // Fast incremental update based on previous hi index. |
|
|
|
|
|
if (hi1 > hi0) { |
|
for (i = Math.max(lo1, hi0), j = hi1; i < j; ++i) { |
|
k = index[i]; |
|
|
|
if (seen[k] !== stamp) { |
|
prev[k] = curr[k]; |
|
seen[k] = stamp; |
|
out.push(k); |
|
} |
|
|
|
curr[k] ^= one; |
|
} |
|
} else if (hi1 < hi0) { |
|
for (i = Math.max(lo0, hi1), j = hi0; i < j; ++i) { |
|
k = index[i]; |
|
|
|
if (seen[k] !== stamp) { |
|
prev[k] = curr[k]; |
|
seen[k] = stamp; |
|
out.push(k); |
|
} |
|
|
|
curr[k] ^= one; |
|
} |
|
} |
|
|
|
dim.range = query.slice(); |
|
}; |
|
|
|
prototype$1q.incrementOne = function (dim, query, add, rem) { |
|
var bits = this.value, |
|
curr = bits.curr(), |
|
index = dim.index(), |
|
old = dim.bisect(dim.range), |
|
range = dim.bisect(query), |
|
lo1 = range[0], |
|
hi1 = range[1], |
|
lo0 = old[0], |
|
hi0 = old[1], |
|
one = dim.one, |
|
i, |
|
j, |
|
k; // Fast incremental update based on previous lo index. |
|
|
|
if (lo1 < lo0) { |
|
for (i = lo1, j = Math.min(lo0, hi1); i < j; ++i) { |
|
k = index[i]; |
|
curr[k] ^= one; |
|
add.push(k); |
|
} |
|
} else if (lo1 > lo0) { |
|
for (i = lo0, j = Math.min(lo1, hi0); i < j; ++i) { |
|
k = index[i]; |
|
curr[k] ^= one; |
|
rem.push(k); |
|
} |
|
} // Fast incremental update based on previous hi index. |
|
|
|
|
|
if (hi1 > hi0) { |
|
for (i = Math.max(lo1, hi0), j = hi1; i < j; ++i) { |
|
k = index[i]; |
|
curr[k] ^= one; |
|
add.push(k); |
|
} |
|
} else if (hi1 < hi0) { |
|
for (i = Math.max(lo0, hi1), j = hi0; i < j; ++i) { |
|
k = index[i]; |
|
curr[k] ^= one; |
|
rem.push(k); |
|
} |
|
} |
|
|
|
dim.range = query.slice(); |
|
}; |
|
/** |
|
* Selectively filters tuples by resolving against a filter bitmap. |
|
* Useful for processing the output of a cross-filter transform. |
|
* @constructor |
|
* @param {object} params - The parameters for this operator. |
|
* @param {object} params.ignore - A bit mask indicating which filters to ignore. |
|
* @param {object} params.filter - The per-tuple filter bitmaps. Typically this |
|
* parameter value is a reference to a {@link CrossFilter} transform. |
|
*/ |
|
|
|
|
|
function ResolveFilter(params) { |
|
Transform.call(this, null, params); |
|
} |
|
|
|
ResolveFilter.Definition = { |
|
"type": "ResolveFilter", |
|
"metadata": {}, |
|
"params": [{ |
|
"name": "ignore", |
|
"type": "number", |
|
"required": true, |
|
"description": "A bit mask indicating which filters to ignore." |
|
}, { |
|
"name": "filter", |
|
"type": "object", |
|
"required": true, |
|
"description": "Per-tuple filter bitmaps from a CrossFilter transform." |
|
}] |
|
}; |
|
var prototype$1r = inherits(ResolveFilter, Transform); |
|
|
|
prototype$1r.transform = function (_, pulse) { |
|
var ignore = ~(_.ignore || 0), |
|
// bit mask where zeros -> dims to ignore |
|
bitmap = _.filter, |
|
mask = bitmap.mask; // exit early if no relevant filter changes |
|
|
|
if ((mask & ignore) === 0) return pulse.StopPropagation; |
|
|
|
var output = pulse.fork(pulse.ALL), |
|
data = bitmap.data(), |
|
curr = bitmap.curr(), |
|
prev = bitmap.prev(), |
|
pass = function pass(k) { |
|
return !(curr[k] & ignore) ? data[k] : null; |
|
}; // propagate all mod tuples that pass the filter |
|
|
|
|
|
output.filter(output.MOD, pass); // determine add & rem tuples via filter functions |
|
// for efficiency, we do *not* populate new arrays, |
|
// instead we add filter functions applied downstream |
|
|
|
if (!(mask & mask - 1)) { |
|
// only one filter changed |
|
output.filter(output.ADD, pass); |
|
output.filter(output.REM, function (k) { |
|
return (curr[k] & ignore) === mask ? data[k] : null; |
|
}); |
|
} else { |
|
// multiple filters changed |
|
output.filter(output.ADD, function (k) { |
|
var c = curr[k] & ignore, |
|
f = !c && c ^ prev[k] & ignore; |
|
return f ? data[k] : null; |
|
}); |
|
output.filter(output.REM, function (k) { |
|
var c = curr[k] & ignore, |
|
f = c && !(c ^ (c ^ prev[k] & ignore)); |
|
return f ? data[k] : null; |
|
}); |
|
} // add filter to source data in case of reflow... |
|
|
|
|
|
return output.filter(output.SOURCE, function (t) { |
|
return pass(t._index); |
|
}); |
|
}; |
|
|
|
var xf = |
|
/*#__PURE__*/ |
|
Object.freeze({ |
|
__proto__: null, |
|
crossfilter: CrossFilter, |
|
resolvefilter: ResolveFilter |
|
}); |
|
var version = "5.9.1"; |
|
var Default = 'default'; |
|
|
|
function cursor(view) { |
|
var cursor = view._signals.cursor; // add cursor signal to dataflow, if needed |
|
|
|
if (!cursor) { |
|
view._signals.cursor = cursor = view.add({ |
|
user: Default, |
|
item: null |
|
}); |
|
} // evaluate cursor on each mousemove event |
|
|
|
|
|
view.on(view.events('view', 'mousemove'), cursor, function (_, event) { |
|
var value = cursor.value, |
|
user = value ? isString(value) ? value : value.user : Default, |
|
item = event.item && event.item.cursor || null; |
|
return value && user === value.user && item == value.item ? value : { |
|
user: user, |
|
item: item |
|
}; |
|
}); // when cursor signal updates, set visible cursor |
|
|
|
view.add(null, function (_) { |
|
var user = _.cursor, |
|
item = this.value; |
|
|
|
if (!isString(user)) { |
|
item = user.item; |
|
user = user.user; |
|
} |
|
|
|
setCursor(user && user !== Default ? user : item || user); |
|
return item; |
|
}, { |
|
cursor: cursor |
|
}); |
|
} |
|
|
|
function setCursor(cursor) { |
|
// set cursor on document body |
|
// this ensures cursor applies even if dragging out of view |
|
if (typeof document !== 'undefined' && document.body) { |
|
document.body.style.cursor = cursor; |
|
} |
|
} |
|
|
|
function dataref(view, name) { |
|
var data = view._runtime.data; |
|
|
|
if (!hasOwnProperty(data, name)) { |
|
error('Unrecognized data set: ' + name); |
|
} |
|
|
|
return data[name]; |
|
} |
|
|
|
function data(name, values) { |
|
return arguments.length < 2 ? dataref(this, name).values.value : change.call(this, name, changeset().remove(truthy).insert(values)); |
|
} |
|
|
|
function change(name, changes) { |
|
if (!isChangeSet(changes)) { |
|
error('Second argument to changes must be a changeset.'); |
|
} |
|
|
|
var dataset = dataref(this, name); |
|
dataset.modified = true; |
|
return this.pulse(dataset.input, changes); |
|
} |
|
|
|
function insert(name, _) { |
|
return change.call(this, name, changeset().insert(_)); |
|
} |
|
|
|
function remove(name, _) { |
|
return change.call(this, name, changeset().remove(_)); |
|
} |
|
|
|
function width(view) { |
|
var padding = view.padding(); |
|
return Math.max(0, view._viewWidth + padding.left + padding.right); |
|
} |
|
|
|
function height(view) { |
|
var padding = view.padding(); |
|
return Math.max(0, view._viewHeight + padding.top + padding.bottom); |
|
} |
|
|
|
function offset$3(view) { |
|
var padding = view.padding(), |
|
origin = view._origin; |
|
return [padding.left + origin[0], padding.top + origin[1]]; |
|
} |
|
|
|
function resizeRenderer(view) { |
|
var origin = offset$3(view), |
|
w = width(view), |
|
h = height(view); |
|
|
|
view._renderer.background(view._background); |
|
|
|
view._renderer.resize(w, h, origin); |
|
|
|
view._handler.origin(origin); |
|
|
|
view._resizeListeners.forEach(function (handler) { |
|
try { |
|
handler(w, h); |
|
} catch (error) { |
|
view.error(error); |
|
} |
|
}); |
|
} |
|
/** |
|
* Extend an event with additional view-specific methods. |
|
* Adds a new property ('vega') to an event that provides a number |
|
* of methods for querying information about the current interaction. |
|
* The vega object provides the following methods: |
|
* view - Returns the backing View instance. |
|
* item - Returns the currently active scenegraph item (if any). |
|
* group - Returns the currently active scenegraph group (if any). |
|
* This method accepts a single string-typed argument indicating the name |
|
* of the desired parent group. The scenegraph will be traversed from |
|
* the item up towards the root to search for a matching group. If no |
|
* argument is provided the enclosing group for the active item is |
|
* returned, unless the item it itself a group, in which case it is |
|
* returned directly. |
|
* xy - Returns a two-element array containing the x and y coordinates for |
|
* mouse or touch events. For touch events, this is based on the first |
|
* elements in the changedTouches array. This method accepts a single |
|
* argument: either an item instance or mark name that should serve as |
|
* the reference coordinate system. If no argument is provided the |
|
* top-level view coordinate system is assumed. |
|
* x - Returns the current x-coordinate, accepts the same arguments as xy. |
|
* y - Returns the current y-coordinate, accepts the same arguments as xy. |
|
* @param {Event} event - The input event to extend. |
|
* @param {Item} item - The currently active scenegraph item (if any). |
|
* @return {Event} - The extended input event. |
|
*/ |
|
|
|
|
|
function eventExtend(view, event, item) { |
|
var r = view._renderer, |
|
el = r && r.canvas(), |
|
p, |
|
e, |
|
translate; |
|
|
|
if (el) { |
|
translate = offset$3(view); |
|
e = event.changedTouches ? event.changedTouches[0] : event; |
|
p = point$4(e, el); |
|
p[0] -= translate[0]; |
|
p[1] -= translate[1]; |
|
} |
|
|
|
event.dataflow = view; |
|
event.item = item; |
|
event.vega = extension(view, item, p); |
|
return event; |
|
} |
|
|
|
function extension(view, item, point) { |
|
var itemGroup = item ? item.mark.marktype === 'group' ? item : item.mark.group : null; |
|
|
|
function group(name) { |
|
var g = itemGroup, |
|
i; |
|
if (name) for (i = item; i; i = i.mark.group) { |
|
if (i.mark.name === name) { |
|
g = i; |
|
break; |
|
} |
|
} |
|
return g && g.mark && g.mark.interactive ? g : {}; |
|
} |
|
|
|
function xy(item) { |
|
if (!item) return point; |
|
if (isString(item)) item = group(item); |
|
var p = point.slice(); |
|
|
|
while (item) { |
|
p[0] -= item.x || 0; |
|
p[1] -= item.y || 0; |
|
item = item.mark && item.mark.group; |
|
} |
|
|
|
return p; |
|
} |
|
|
|
return { |
|
view: constant(view), |
|
item: constant(item || {}), |
|
group: group, |
|
xy: xy, |
|
x: function x(item) { |
|
return xy(item)[0]; |
|
}, |
|
y: function y(item) { |
|
return xy(item)[1]; |
|
} |
|
}; |
|
} |
|
|
|
var VIEW = 'view', |
|
TIMER = 'timer', |
|
WINDOW = 'window', |
|
NO_TRAP = { |
|
trap: false |
|
}; |
|
/** |
|
* Initialize event handling configuration. |
|
* @param {object} config - The configuration settings. |
|
* @return {object} |
|
*/ |
|
|
|
function initializeEventConfig(config) { |
|
var events = extend({ |
|
defaults: {} |
|
}, config); |
|
|
|
var unpack = function unpack(obj, keys) { |
|
keys.forEach(function (k) { |
|
if (isArray(obj[k])) obj[k] = toSet(obj[k]); |
|
}); |
|
}; |
|
|
|
unpack(events.defaults, ['prevent', 'allow']); |
|
unpack(events, ['view', 'window', 'selector']); |
|
return events; |
|
} |
|
|
|
function prevent(view, type) { |
|
var def = view._eventConfig.defaults, |
|
prevent = def.prevent, |
|
allow = def.allow; |
|
return prevent === false || allow === true ? false : prevent === true || allow === false ? true : prevent ? prevent[type] : allow ? !allow[type] : view.preventDefault(); |
|
} |
|
|
|
function permit(view, key, type) { |
|
var rule = view._eventConfig && view._eventConfig[key]; |
|
|
|
if (rule === false || isObject(rule) && !rule[type]) { |
|
view.warn("Blocked ".concat(key, " ").concat(type, " event listener.")); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
/** |
|
* Create a new event stream from an event source. |
|
* @param {object} source - The event source to monitor. |
|
* @param {string} type - The event type. |
|
* @param {function(object): boolean} [filter] - Event filter function. |
|
* @return {EventStream} |
|
*/ |
|
|
|
|
|
function events$1(source, type, filter) { |
|
var view = this, |
|
s = new EventStream(filter), |
|
send = function send(e, item) { |
|
view.runAsync(null, function () { |
|
if (source === VIEW && prevent(view, type)) { |
|
e.preventDefault(); |
|
} |
|
|
|
s.receive(eventExtend(view, e, item)); |
|
}); |
|
}, |
|
sources; |
|
|
|
if (source === TIMER) { |
|
if (permit(view, 'timer', type)) { |
|
view.timer(send, type); |
|
} |
|
} else if (source === VIEW) { |
|
if (permit(view, 'view', type)) { |
|
// send traps errors, so use {trap: false} option |
|
view.addEventListener(type, send, NO_TRAP); |
|
} |
|
} else { |
|
if (source === WINDOW) { |
|
if (permit(view, 'window', type) && typeof window !== 'undefined') { |
|
sources = [window]; |
|
} |
|
} else if (typeof document !== 'undefined') { |
|
if (permit(view, 'selector', type)) { |
|
sources = document.querySelectorAll(source); |
|
} |
|
} |
|
|
|
if (!sources) { |
|
view.warn('Can not resolve event source: ' + source); |
|
} else { |
|
for (var i = 0, n = sources.length; i < n; ++i) { |
|
sources[i].addEventListener(type, send); |
|
} |
|
|
|
view._eventListeners.push({ |
|
type: type, |
|
sources: sources, |
|
handler: send |
|
}); |
|
} |
|
} |
|
|
|
return s; |
|
} |
|
|
|
function itemFilter(event) { |
|
return event.item; |
|
} |
|
|
|
function markTarget(event) { |
|
// grab upstream collector feeding the mark operator |
|
return event.item.mark.source; |
|
} |
|
|
|
function invoke(name) { |
|
return function (_, event) { |
|
return event.vega.view().changeset().encode(event.item, name); |
|
}; |
|
} |
|
|
|
function hover(hoverSet, leaveSet) { |
|
hoverSet = [hoverSet || 'hover']; |
|
leaveSet = [leaveSet || 'update', hoverSet[0]]; // invoke hover set upon mouseover |
|
|
|
this.on(this.events('view', 'mouseover', itemFilter), markTarget, invoke(hoverSet)); // invoke leave set upon mouseout |
|
|
|
this.on(this.events('view', 'mouseout', itemFilter), markTarget, invoke(leaveSet)); |
|
return this; |
|
} |
|
/** |
|
* Finalize a View instance that is being removed. |
|
* Cancel any running timers. |
|
* Remove all external event listeners. |
|
* Remove any currently displayed tooltip. |
|
*/ |
|
|
|
|
|
function finalize() { |
|
var tooltip = this._tooltip, |
|
timers = this._timers, |
|
listeners = this._eventListeners, |
|
n, |
|
m, |
|
e; |
|
n = timers.length; |
|
|
|
while (--n >= 0) { |
|
timers[n].stop(); |
|
} |
|
|
|
n = listeners.length; |
|
|
|
while (--n >= 0) { |
|
e = listeners[n]; |
|
m = e.sources.length; |
|
|
|
while (--m >= 0) { |
|
e.sources[m].removeEventListener(e.type, e.handler); |
|
} |
|
} |
|
|
|
if (tooltip) { |
|
tooltip.call(this, this._handler, null, null, null); |
|
} |
|
|
|
return this; |
|
} |
|
|
|
function element$1(tag, attr, text) { |
|
var el = document.createElement(tag); |
|
|
|
for (var key in attr) { |
|
el.setAttribute(key, attr[key]); |
|
} |
|
|
|
if (text != null) el.textContent = text; |
|
return el; |
|
} |
|
|
|
var BindClass = 'vega-bind', |
|
NameClass = 'vega-bind-name', |
|
RadioClass = 'vega-bind-radio', |
|
OptionClass = 'vega-option-'; |
|
/** |
|
* Bind a signal to an external HTML input element. The resulting two-way |
|
* binding will propagate input changes to signals, and propagate signal |
|
* changes to the input element state. If this view instance has no parent |
|
* element, we assume the view is headless and no bindings are created. |
|
* @param {Element|string} el - The parent DOM element to which the input |
|
* element should be appended as a child. If string-valued, this argument |
|
* will be treated as a CSS selector. If null or undefined, the parent |
|
* element of this view will be used as the element. |
|
* @param {object} param - The binding parameters which specify the signal |
|
* to bind to, the input element type, and type-specific configuration. |
|
* @return {View} - This view instance. |
|
*/ |
|
|
|
function bind$1(view, el, binding) { |
|
if (!el) return; |
|
var param = binding.param, |
|
bind = binding.state; |
|
|
|
if (!bind) { |
|
bind = binding.state = { |
|
elements: null, |
|
active: false, |
|
set: null, |
|
update: function update(value) { |
|
if (value !== view.signal(param.signal)) { |
|
view.runAsync(null, function () { |
|
bind.source = true; |
|
view.signal(param.signal, value); |
|
}); |
|
} |
|
} |
|
}; |
|
|
|
if (param.debounce) { |
|
bind.update = debounce(param.debounce, bind.update); |
|
} |
|
} |
|
|
|
generate(bind, el, param, view.signal(param.signal)); |
|
|
|
if (!bind.active) { |
|
view.on(view._signals[param.signal], null, function () { |
|
bind.source ? bind.source = false : bind.set(view.signal(param.signal)); |
|
}); |
|
bind.active = true; |
|
} |
|
|
|
return bind; |
|
} |
|
/** |
|
* Generate an HTML input form element and bind it to a signal. |
|
*/ |
|
|
|
|
|
function generate(bind, el, param, value) { |
|
var div = element$1('div', { |
|
'class': BindClass |
|
}); |
|
div.appendChild(element$1('span', { |
|
'class': NameClass |
|
}, param.name || param.signal)); |
|
el.appendChild(div); |
|
var input = form; |
|
|
|
switch (param.input) { |
|
case 'checkbox': |
|
input = checkbox; |
|
break; |
|
|
|
case 'select': |
|
input = select; |
|
break; |
|
|
|
case 'radio': |
|
input = radio; |
|
break; |
|
|
|
case 'range': |
|
input = range$2; |
|
break; |
|
} |
|
|
|
input(bind, div, param, value); |
|
} |
|
/** |
|
* Generates an arbitrary input form element. |
|
* The input type is controlled via user-provided parameters. |
|
*/ |
|
|
|
|
|
function form(bind, el, param, value) { |
|
var node = element$1('input'); |
|
|
|
for (var key in param) { |
|
if (key !== 'signal' && key !== 'element') { |
|
node.setAttribute(key === 'input' ? 'type' : key, param[key]); |
|
} |
|
} |
|
|
|
node.setAttribute('name', param.signal); |
|
node.value = value; |
|
el.appendChild(node); |
|
node.addEventListener('input', function () { |
|
bind.update(node.value); |
|
}); |
|
bind.elements = [node]; |
|
|
|
bind.set = function (value) { |
|
node.value = value; |
|
}; |
|
} |
|
/** |
|
* Generates a checkbox input element. |
|
*/ |
|
|
|
|
|
function checkbox(bind, el, param, value) { |
|
var attr = { |
|
type: 'checkbox', |
|
name: param.signal |
|
}; |
|
if (value) attr.checked = true; |
|
var node = element$1('input', attr); |
|
el.appendChild(node); |
|
node.addEventListener('change', function () { |
|
bind.update(node.checked); |
|
}); |
|
bind.elements = [node]; |
|
|
|
bind.set = function (value) { |
|
node.checked = !!value || null; |
|
}; |
|
} |
|
/** |
|
* Generates a selection list input element. |
|
*/ |
|
|
|
|
|
function select(bind, el, param, value) { |
|
var node = element$1('select', { |
|
name: param.signal |
|
}), |
|
label = param.labels || []; |
|
param.options.forEach(function (option, i) { |
|
var attr = { |
|
value: option |
|
}; |
|
if (valuesEqual(option, value)) attr.selected = true; |
|
node.appendChild(element$1('option', attr, (label[i] || option) + '')); |
|
}); |
|
el.appendChild(node); |
|
node.addEventListener('change', function () { |
|
bind.update(param.options[node.selectedIndex]); |
|
}); |
|
bind.elements = [node]; |
|
|
|
bind.set = function (value) { |
|
for (var i = 0, n = param.options.length; i < n; ++i) { |
|
if (valuesEqual(param.options[i], value)) { |
|
node.selectedIndex = i; |
|
return; |
|
} |
|
} |
|
}; |
|
} |
|
/** |
|
* Generates a radio button group. |
|
*/ |
|
|
|
|
|
function radio(bind, el, param, value) { |
|
var group = element$1('span', { |
|
'class': RadioClass |
|
}), |
|
label = param.labels || []; |
|
el.appendChild(group); |
|
bind.elements = param.options.map(function (option, i) { |
|
var id = OptionClass + param.signal + '-' + option; |
|
var attr = { |
|
id: id, |
|
type: 'radio', |
|
name: param.signal, |
|
value: option |
|
}; |
|
if (valuesEqual(option, value)) attr.checked = true; |
|
var input = element$1('input', attr); |
|
input.addEventListener('change', function () { |
|
bind.update(option); |
|
}); |
|
group.appendChild(input); |
|
group.appendChild(element$1('label', { |
|
'for': id |
|
}, (label[i] || option) + '')); |
|
return input; |
|
}); |
|
|
|
bind.set = function (value) { |
|
var nodes = bind.elements, |
|
i = 0, |
|
n = nodes.length; |
|
|
|
for (; i < n; ++i) { |
|
if (valuesEqual(nodes[i].value, value)) nodes[i].checked = true; |
|
} |
|
}; |
|
} |
|
/** |
|
* Generates a slider input element. |
|
*/ |
|
|
|
|
|
function range$2(bind, el, param, value) { |
|
value = value !== undefined ? value : (+param.max + +param.min) / 2; |
|
var max = param.max != null ? param.max : Math.max(100, +value) || 100, |
|
min = param.min || Math.min(0, max, +value) || 0, |
|
step = param.step || tickStep(min, max, 100); |
|
var node = element$1('input', { |
|
type: 'range', |
|
name: param.signal, |
|
min: min, |
|
max: max, |
|
step: step |
|
}); |
|
node.value = value; |
|
var label = element$1('label', {}, +value); |
|
el.appendChild(node); |
|
el.appendChild(label); |
|
|
|
function update() { |
|
label.textContent = node.value; |
|
bind.update(+node.value); |
|
} // subscribe to both input and change |
|
|
|
|
|
node.addEventListener('input', update); |
|
node.addEventListener('change', update); |
|
bind.elements = [node]; |
|
|
|
bind.set = function (value) { |
|
node.value = value; |
|
label.textContent = value; |
|
}; |
|
} |
|
|
|
function valuesEqual(a, b) { |
|
return a === b || a + '' === b + ''; |
|
} |
|
|
|
function initializeRenderer(view, r, el, constructor, scaleFactor, opt) { |
|
r = r || new constructor(view.loader()); |
|
return r.initialize(el, width(view), height(view), offset$3(view), scaleFactor, opt).background(view._background); |
|
} |
|
|
|
function trap(view, fn) { |
|
return !fn ? null : function () { |
|
try { |
|
fn.apply(this, arguments); |
|
} catch (error) { |
|
view.error(error); |
|
} |
|
}; |
|
} |
|
|
|
function initializeHandler(view, prevHandler, el, constructor) { |
|
// instantiate scenegraph handler |
|
var handler = new constructor(view.loader(), trap(view, view.tooltip())).scene(view.scenegraph().root).initialize(el, offset$3(view), view); // transfer event handlers |
|
|
|
if (prevHandler) { |
|
prevHandler.handlers().forEach(function (h) { |
|
handler.on(h.type, h.handler); |
|
}); |
|
} |
|
|
|
return handler; |
|
} |
|
|
|
function initialize$1(el, elBind) { |
|
var view = this, |
|
type = view._renderType, |
|
config = view._eventConfig.bind, |
|
module = renderModule(type), |
|
Handler, |
|
Renderer; // containing dom element |
|
|
|
el = view._el = el ? lookup$4(view, el) : null; // select appropriate renderer & handler |
|
|
|
if (!module) view.error('Unrecognized renderer type: ' + type); |
|
Handler = module.handler || CanvasHandler; |
|
Renderer = el ? module.renderer : module.headless; // initialize renderer and input handler |
|
|
|
view._renderer = !Renderer ? null : initializeRenderer(view, view._renderer, el, Renderer); |
|
view._handler = initializeHandler(view, view._handler, el, Handler); |
|
view._redraw = true; // initialize signal bindings |
|
|
|
if (el && config !== 'none') { |
|
elBind = elBind ? view._elBind = lookup$4(view, elBind) : el.appendChild(element$1('div', { |
|
'class': 'vega-bindings' |
|
})); |
|
|
|
view._bind.forEach(function (_) { |
|
if (_.param.element && config !== 'container') { |
|
_.element = lookup$4(view, _.param.element); |
|
} |
|
}); |
|
|
|
view._bind.forEach(function (_) { |
|
bind$1(view, _.element || elBind, _); |
|
}); |
|
} |
|
|
|
return view; |
|
} |
|
|
|
function lookup$4(view, el) { |
|
if (typeof el === 'string') { |
|
if (typeof document !== 'undefined') { |
|
el = document.querySelector(el); |
|
|
|
if (!el) { |
|
view.error('Signal bind element not found: ' + el); |
|
return null; |
|
} |
|
} else { |
|
view.error('DOM document instance not found.'); |
|
return null; |
|
} |
|
} |
|
|
|
if (el) { |
|
try { |
|
el.innerHTML = ''; |
|
} catch (e) { |
|
el = null; |
|
view.error(e); |
|
} |
|
} |
|
|
|
return el; |
|
} |
|
/** |
|
* Render the current scene in a headless fashion. |
|
* This method is asynchronous, returning a Promise instance. |
|
* @return {Promise} - A Promise that resolves to a renderer. |
|
*/ |
|
|
|
|
|
function renderHeadless(_x32, _x33, _x34, _x35) { |
|
return _renderHeadless.apply(this, arguments); |
|
} |
|
/** |
|
* Produce an image URL for the visualization. Depending on the type |
|
* parameter, the generated URL contains data for either a PNG or SVG image. |
|
* The URL can be used (for example) to download images of the visualization. |
|
* This method is asynchronous, returning a Promise instance. |
|
* @param {string} type - The image type. One of 'svg', 'png' or 'canvas'. |
|
* The 'canvas' and 'png' types are synonyms for a PNG image. |
|
* @return {Promise} - A promise that resolves to an image URL. |
|
*/ |
|
|
|
|
|
function _renderHeadless() { |
|
_renderHeadless = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee12(view, type, scaleFactor, opt) { |
|
var module, ctr; |
|
return regeneratorRuntime.wrap(function _callee12$(_context18) { |
|
while (1) { |
|
switch (_context18.prev = _context18.next) { |
|
case 0: |
|
module = renderModule(type), ctr = module && module.headless; |
|
if (!ctr) error('Unrecognized renderer type: ' + type); |
|
_context18.next = 4; |
|
return view.runAsync(); |
|
|
|
case 4: |
|
return _context18.abrupt("return", initializeRenderer(view, null, null, ctr, scaleFactor, opt).renderAsync(view._scenegraph.root)); |
|
|
|
case 5: |
|
case "end": |
|
return _context18.stop(); |
|
} |
|
} |
|
}, _callee12); |
|
})); |
|
return _renderHeadless.apply(this, arguments); |
|
} |
|
|
|
function renderToImageURL(_x36, _x37) { |
|
return _renderToImageURL.apply(this, arguments); |
|
} |
|
|
|
function _renderToImageURL() { |
|
_renderToImageURL = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee13(type, scaleFactor) { |
|
var r; |
|
return regeneratorRuntime.wrap(function _callee13$(_context19) { |
|
while (1) { |
|
switch (_context19.prev = _context19.next) { |
|
case 0: |
|
if (type !== RenderType.Canvas && type !== RenderType.SVG && type !== RenderType.PNG) { |
|
error('Unrecognized image type: ' + type); |
|
} |
|
|
|
_context19.next = 3; |
|
return renderHeadless(this, type, scaleFactor); |
|
|
|
case 3: |
|
r = _context19.sent; |
|
return _context19.abrupt("return", type === RenderType.SVG ? toBlobURL(r.svg(), 'image/svg+xml') : r.canvas().toDataURL('image/png')); |
|
|
|
case 5: |
|
case "end": |
|
return _context19.stop(); |
|
} |
|
} |
|
}, _callee13, this); |
|
})); |
|
return _renderToImageURL.apply(this, arguments); |
|
} |
|
|
|
function toBlobURL(data, mime) { |
|
var blob = new Blob([data], { |
|
type: mime |
|
}); |
|
return window.URL.createObjectURL(blob); |
|
} |
|
/** |
|
* Produce a Canvas instance containing a rendered visualization. |
|
* This method is asynchronous, returning a Promise instance. |
|
* @return {Promise} - A promise that resolves to a Canvas instance. |
|
*/ |
|
|
|
|
|
function renderToCanvas(_x38, _x39) { |
|
return _renderToCanvas.apply(this, arguments); |
|
} |
|
/** |
|
* Produce a rendered SVG string of the visualization. |
|
* This method is asynchronous, returning a Promise instance. |
|
* @return {Promise} - A promise that resolves to an SVG string. |
|
*/ |
|
|
|
|
|
function _renderToCanvas() { |
|
_renderToCanvas = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee14(scaleFactor, opt) { |
|
var r; |
|
return regeneratorRuntime.wrap(function _callee14$(_context20) { |
|
while (1) { |
|
switch (_context20.prev = _context20.next) { |
|
case 0: |
|
_context20.next = 2; |
|
return renderHeadless(this, RenderType.Canvas, scaleFactor, opt); |
|
|
|
case 2: |
|
r = _context20.sent; |
|
return _context20.abrupt("return", r.canvas()); |
|
|
|
case 4: |
|
case "end": |
|
return _context20.stop(); |
|
} |
|
} |
|
}, _callee14, this); |
|
})); |
|
return _renderToCanvas.apply(this, arguments); |
|
} |
|
|
|
function renderToSVG(_x40) { |
|
return _renderToSVG.apply(this, arguments); |
|
} |
|
|
|
function _renderToSVG() { |
|
_renderToSVG = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee15(scaleFactor) { |
|
var r; |
|
return regeneratorRuntime.wrap(function _callee15$(_context21) { |
|
while (1) { |
|
switch (_context21.prev = _context21.next) { |
|
case 0: |
|
_context21.next = 2; |
|
return renderHeadless(this, RenderType.SVG, scaleFactor); |
|
|
|
case 2: |
|
r = _context21.sent; |
|
return _context21.abrupt("return", r.svg()); |
|
|
|
case 4: |
|
case "end": |
|
return _context21.stop(); |
|
} |
|
} |
|
}, _callee15, this); |
|
})); |
|
return _renderToSVG.apply(this, arguments); |
|
} |
|
|
|
var RawCode = 'RawCode'; |
|
var Literal = 'Literal'; |
|
var Property = 'Property'; |
|
var Identifier$1 = 'Identifier'; |
|
var ArrayExpression = 'ArrayExpression'; |
|
var BinaryExpression = 'BinaryExpression'; |
|
var CallExpression = 'CallExpression'; |
|
var ConditionalExpression = 'ConditionalExpression'; |
|
var LogicalExpression = 'LogicalExpression'; |
|
var MemberExpression = 'MemberExpression'; |
|
var ObjectExpression = 'ObjectExpression'; |
|
var UnaryExpression = 'UnaryExpression'; |
|
|
|
function ASTNode(type) { |
|
this.type = type; |
|
} |
|
|
|
ASTNode.prototype.visit = function (visitor) { |
|
var node = this, |
|
c, |
|
i, |
|
n; |
|
if (visitor(node)) return 1; |
|
|
|
for (c = children$1(node), i = 0, n = c.length; i < n; ++i) { |
|
if (c[i].visit(visitor)) return 1; |
|
} |
|
}; |
|
|
|
function children$1(node) { |
|
switch (node.type) { |
|
case ArrayExpression: |
|
return node.elements; |
|
|
|
case BinaryExpression: |
|
case LogicalExpression: |
|
return [node.left, node.right]; |
|
|
|
case CallExpression: |
|
var args = node.arguments.slice(); |
|
args.unshift(node.callee); |
|
return args; |
|
|
|
case ConditionalExpression: |
|
return [node.test, node.consequent, node.alternate]; |
|
|
|
case MemberExpression: |
|
return [node.object, node.property]; |
|
|
|
case ObjectExpression: |
|
return node.properties; |
|
|
|
case Property: |
|
return [node.key, node.value]; |
|
|
|
case UnaryExpression: |
|
return [node.argument]; |
|
|
|
case Identifier$1: |
|
case Literal: |
|
case RawCode: |
|
default: |
|
return []; |
|
} |
|
} |
|
/* |
|
The following expression parser is based on Esprima (http://esprima.org/). |
|
Original header comment and license for Esprima is included here: |
|
Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com> |
|
Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com> |
|
Copyright (C) 2013 Mathias Bynens <mathias@qiwi.be> |
|
Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> |
|
Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be> |
|
Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl> |
|
Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com> |
|
Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com> |
|
Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com> |
|
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com> |
|
Redistribution and use in source and binary forms, with or without |
|
modification, are permitted provided that the following conditions are met: |
|
* Redistributions of source code must retain the above copyright |
|
notice, this list of conditions and the following disclaimer. |
|
* Redistributions in binary form must reproduce the above copyright |
|
notice, this list of conditions and the following disclaimer in the |
|
documentation and/or other materials provided with the distribution. |
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
|
|
|
|
var TokenName, source$1, index$1, length, lookahead; |
|
var TokenBooleanLiteral = 1, |
|
TokenEOF = 2, |
|
TokenIdentifier = 3, |
|
TokenKeyword = 4, |
|
TokenNullLiteral = 5, |
|
TokenNumericLiteral = 6, |
|
TokenPunctuator = 7, |
|
TokenStringLiteral = 8, |
|
TokenRegularExpression = 9; |
|
TokenName = {}; |
|
TokenName[TokenBooleanLiteral] = 'Boolean'; |
|
TokenName[TokenEOF] = '<end>'; |
|
TokenName[TokenIdentifier] = 'Identifier'; |
|
TokenName[TokenKeyword] = 'Keyword'; |
|
TokenName[TokenNullLiteral] = 'Null'; |
|
TokenName[TokenNumericLiteral] = 'Numeric'; |
|
TokenName[TokenPunctuator] = 'Punctuator'; |
|
TokenName[TokenStringLiteral] = 'String'; |
|
TokenName[TokenRegularExpression] = 'RegularExpression'; |
|
var SyntaxArrayExpression = 'ArrayExpression', |
|
SyntaxBinaryExpression = 'BinaryExpression', |
|
SyntaxCallExpression = 'CallExpression', |
|
SyntaxConditionalExpression = 'ConditionalExpression', |
|
SyntaxIdentifier = 'Identifier', |
|
SyntaxLiteral = 'Literal', |
|
SyntaxLogicalExpression = 'LogicalExpression', |
|
SyntaxMemberExpression = 'MemberExpression', |
|
SyntaxObjectExpression = 'ObjectExpression', |
|
SyntaxProperty = 'Property', |
|
SyntaxUnaryExpression = 'UnaryExpression'; // Error messages should be identical to V8. |
|
|
|
var MessageUnexpectedToken = 'Unexpected token %0', |
|
MessageUnexpectedNumber = 'Unexpected number', |
|
MessageUnexpectedString = 'Unexpected string', |
|
MessageUnexpectedIdentifier = 'Unexpected identifier', |
|
MessageUnexpectedReserved = 'Unexpected reserved word', |
|
MessageUnexpectedEOS = 'Unexpected end of input', |
|
MessageInvalidRegExp = 'Invalid regular expression', |
|
MessageUnterminatedRegExp = 'Invalid regular expression: missing /', |
|
MessageStrictOctalLiteral = 'Octal literals are not allowed in strict mode.', |
|
MessageStrictDuplicateProperty = 'Duplicate data property in object literal not allowed in strict mode'; |
|
var ILLEGAL = 'ILLEGAL', |
|
DISABLED = 'Disabled.'; // See also tools/generate-unicode-regex.py. |
|
|
|
var RegexNonAsciiIdentifierStart = new RegExp("[\\xAA\\xB5\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0-\\u08B2\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F8\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA7AD\\uA7B0\\uA7B1\\uA7F7-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB5F\\uAB64\\uAB65\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"), |
|
// eslint-disable-next-line no-misleading-character-class |
|
RegexNonAsciiIdentifierPart = new RegExp("[\\xAA\\xB5\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0300-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u0483-\\u0487\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0610-\\u061A\\u0620-\\u0669\\u066E-\\u06D3\\u06D5-\\u06DC\\u06DF-\\u06E8\\u06EA-\\u06FC\\u06FF\\u0710-\\u074A\\u074D-\\u07B1\\u07C0-\\u07F5\\u07FA\\u0800-\\u082D\\u0840-\\u085B\\u08A0-\\u08B2\\u08E4-\\u0963\\u0966-\\u096F\\u0971-\\u0983\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BC-\\u09C4\\u09C7\\u09C8\\u09CB-\\u09CE\\u09D7\\u09DC\\u09DD\\u09DF-\\u09E3\\u09E6-\\u09F1\\u0A01-\\u0A03\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A3C\\u0A3E-\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A59-\\u0A5C\\u0A5E\\u0A66-\\u0A75\\u0A81-\\u0A83\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABC-\\u0AC5\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0AD0\\u0AE0-\\u0AE3\\u0AE6-\\u0AEF\\u0B01-\\u0B03\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3C-\\u0B44\\u0B47\\u0B48\\u0B4B-\\u0B4D\\u0B56\\u0B57\\u0B5C\\u0B5D\\u0B5F-\\u0B63\\u0B66-\\u0B6F\\u0B71\\u0B82\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0BD0\\u0BD7\\u0BE6-\\u0BEF\\u0C00-\\u0C03\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C58\\u0C59\\u0C60-\\u0C63\\u0C66-\\u0C6F\\u0C81-\\u0C83\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBC-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5\\u0CD6\\u0CDE\\u0CE0-\\u0CE3\\u0CE6-\\u0CEF\\u0CF1\\u0CF2\\u0D01-\\u0D03\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D-\\u0D44\\u0D46-\\u0D48\\u0D4A-\\u0D4E\\u0D57\\u0D60-\\u0D63\\u0D66-\\u0D6F\\u0D7A-\\u0D7F\\u0D82\\u0D83\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0DCA\\u0DCF-\\u0DD4\\u0DD6\\u0DD8-\\u0DDF\\u0DE6-\\u0DEF\\u0DF2\\u0DF3\\u0E01-\\u0E3A\\u0E40-\\u0E4E\\u0E50-\\u0E59\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB9\\u0EBB-\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EC8-\\u0ECD\\u0ED0-\\u0ED9\\u0EDC-\\u0EDF\\u0F00\\u0F18\\u0F19\\u0F20-\\u0F29\\u0F35\\u0F37\\u0F39\\u0F3E-\\u0F47\\u0F49-\\u0F6C\\u0F71-\\u0F84\\u0F86-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u1000-\\u1049\\u1050-\\u109D\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u135D-\\u135F\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F8\\u1700-\\u170C\\u170E-\\u1714\\u1720-\\u1734\\u1740-\\u1753\\u1760-\\u176C\\u176E-\\u1770\\u1772\\u1773\\u1780-\\u17D3\\u17D7\\u17DC\\u17DD\\u17E0-\\u17E9\\u180B-\\u180D\\u1810-\\u1819\\u1820-\\u1877\\u1880-\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1920-\\u192B\\u1930-\\u193B\\u1946-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u19D0-\\u19D9\\u1A00-\\u1A1B\\u1A20-\\u1A5E\\u1A60-\\u1A7C\\u1A7F-\\u1A89\\u1A90-\\u1A99\\u1AA7\\u1AB0-\\u1ABD\\u1B00-\\u1B4B\\u1B50-\\u1B59\\u1B6B-\\u1B73\\u1B80-\\u1BF3\\u1C00-\\u1C37\\u1C40-\\u1C49\\u1C4D-\\u1C7D\\u1CD0-\\u1CD2\\u1CD4-\\u1CF6\\u1CF8\\u1CF9\\u1D00-\\u1DF5\\u1DFC-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u200C\\u200D\\u203F\\u2040\\u2054\\u2071\\u207F\\u2090-\\u209C\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D7F-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2DE0-\\u2DFF\\u2E2F\\u3005-\\u3007\\u3021-\\u302F\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u3099\\u309A\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA62B\\uA640-\\uA66F\\uA674-\\uA67D\\uA67F-\\uA69D\\uA69F-\\uA6F1\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA7AD\\uA7B0\\uA7B1\\uA7F7-\\uA827\\uA840-\\uA873\\uA880-\\uA8C4\\uA8D0-\\uA8D9\\uA8E0-\\uA8F7\\uA8FB\\uA900-\\uA92D\\uA930-\\uA953\\uA960-\\uA97C\\uA980-\\uA9C0\\uA9CF-\\uA9D9\\uA9E0-\\uA9FE\\uAA00-\\uAA36\\uAA40-\\uAA4D\\uAA50-\\uAA59\\uAA60-\\uAA76\\uAA7A-\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEF\\uAAF2-\\uAAF6\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB5F\\uAB64\\uAB65\\uABC0-\\uABEA\\uABEC\\uABED\\uABF0-\\uABF9\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE00-\\uFE0F\\uFE20-\\uFE2D\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF10-\\uFF19\\uFF21-\\uFF3A\\uFF3F\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"); // Ensure the condition is true, otherwise throw an error. |
|
// This is only to have a better contract semantic, i.e. another safety net |
|
// to catch a logic error. The condition shall be fulfilled in normal case. |
|
// Do NOT use this to enforce a certain condition on any user input. |
|
|
|
function assert(condition, message) { |
|
/* istanbul ignore next */ |
|
if (!condition) { |
|
throw new Error('ASSERT: ' + message); |
|
} |
|
} |
|
|
|
function isDecimalDigit(ch) { |
|
return ch >= 0x30 && ch <= 0x39; // 0..9 |
|
} |
|
|
|
function isHexDigit(ch) { |
|
return '0123456789abcdefABCDEF'.indexOf(ch) >= 0; |
|
} |
|
|
|
function isOctalDigit(ch) { |
|
return '01234567'.indexOf(ch) >= 0; |
|
} // 7.2 White Space |
|
|
|
|
|
function isWhiteSpace(ch) { |
|
return ch === 0x20 || ch === 0x09 || ch === 0x0B || ch === 0x0C || ch === 0xA0 || ch >= 0x1680 && [0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF].indexOf(ch) >= 0; |
|
} // 7.3 Line Terminators |
|
|
|
|
|
function isLineTerminator(ch) { |
|
return ch === 0x0A || ch === 0x0D || ch === 0x2028 || ch === 0x2029; |
|
} // 7.6 Identifier Names and Identifiers |
|
|
|
|
|
function isIdentifierStart(ch) { |
|
return ch === 0x24 || ch === 0x5F || // $ (dollar) and _ (underscore) |
|
ch >= 0x41 && ch <= 0x5A || // A..Z |
|
ch >= 0x61 && ch <= 0x7A || // a..z |
|
ch === 0x5C || // \ (backslash) |
|
ch >= 0x80 && RegexNonAsciiIdentifierStart.test(String.fromCharCode(ch)); |
|
} |
|
|
|
function isIdentifierPart(ch) { |
|
return ch === 0x24 || ch === 0x5F || // $ (dollar) and _ (underscore) |
|
ch >= 0x41 && ch <= 0x5A || // A..Z |
|
ch >= 0x61 && ch <= 0x7A || // a..z |
|
ch >= 0x30 && ch <= 0x39 || // 0..9 |
|
ch === 0x5C || // \ (backslash) |
|
ch >= 0x80 && RegexNonAsciiIdentifierPart.test(String.fromCharCode(ch)); |
|
} // 7.6.1.1 Keywords |
|
|
|
|
|
var keywords = { |
|
'if': 1, |
|
'in': 1, |
|
'do': 1, |
|
'var': 1, |
|
'for': 1, |
|
'new': 1, |
|
'try': 1, |
|
'let': 1, |
|
'this': 1, |
|
'else': 1, |
|
'case': 1, |
|
'void': 1, |
|
'with': 1, |
|
'enum': 1, |
|
'while': 1, |
|
'break': 1, |
|
'catch': 1, |
|
'throw': 1, |
|
'const': 1, |
|
'yield': 1, |
|
'class': 1, |
|
'super': 1, |
|
'return': 1, |
|
'typeof': 1, |
|
'delete': 1, |
|
'switch': 1, |
|
'export': 1, |
|
'import': 1, |
|
'public': 1, |
|
'static': 1, |
|
'default': 1, |
|
'finally': 1, |
|
'extends': 1, |
|
'package': 1, |
|
'private': 1, |
|
'function': 1, |
|
'continue': 1, |
|
'debugger': 1, |
|
'interface': 1, |
|
'protected': 1, |
|
'instanceof': 1, |
|
'implements': 1 |
|
}; |
|
|
|
function skipComment() { |
|
var ch; |
|
|
|
while (index$1 < length) { |
|
ch = source$1.charCodeAt(index$1); |
|
|
|
if (isWhiteSpace(ch) || isLineTerminator(ch)) { |
|
++index$1; |
|
} else { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
function scanHexEscape(prefix) { |
|
var i, |
|
len, |
|
ch, |
|
code = 0; |
|
len = prefix === 'u' ? 4 : 2; |
|
|
|
for (i = 0; i < len; ++i) { |
|
if (index$1 < length && isHexDigit(source$1[index$1])) { |
|
ch = source$1[index$1++]; |
|
code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase()); |
|
} else { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
} |
|
|
|
return String.fromCharCode(code); |
|
} |
|
|
|
function scanUnicodeCodePointEscape() { |
|
var ch, code, cu1, cu2; |
|
ch = source$1[index$1]; |
|
code = 0; // At least, one hex digit is required. |
|
|
|
if (ch === '}') { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
while (index$1 < length) { |
|
ch = source$1[index$1++]; |
|
|
|
if (!isHexDigit(ch)) { |
|
break; |
|
} |
|
|
|
code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase()); |
|
} |
|
|
|
if (code > 0x10FFFF || ch !== '}') { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} // UTF-16 Encoding |
|
|
|
|
|
if (code <= 0xFFFF) { |
|
return String.fromCharCode(code); |
|
} |
|
|
|
cu1 = (code - 0x10000 >> 10) + 0xD800; |
|
cu2 = (code - 0x10000 & 1023) + 0xDC00; |
|
return String.fromCharCode(cu1, cu2); |
|
} |
|
|
|
function getEscapedIdentifier() { |
|
var ch, id; |
|
ch = source$1.charCodeAt(index$1++); |
|
id = String.fromCharCode(ch); // '\u' (U+005C, U+0075) denotes an escaped character. |
|
|
|
if (ch === 0x5C) { |
|
if (source$1.charCodeAt(index$1) !== 0x75) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
++index$1; |
|
ch = scanHexEscape('u'); |
|
|
|
if (!ch || ch === '\\' || !isIdentifierStart(ch.charCodeAt(0))) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
id = ch; |
|
} |
|
|
|
while (index$1 < length) { |
|
ch = source$1.charCodeAt(index$1); |
|
|
|
if (!isIdentifierPart(ch)) { |
|
break; |
|
} |
|
|
|
++index$1; |
|
id += String.fromCharCode(ch); // '\u' (U+005C, U+0075) denotes an escaped character. |
|
|
|
if (ch === 0x5C) { |
|
id = id.substr(0, id.length - 1); |
|
|
|
if (source$1.charCodeAt(index$1) !== 0x75) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
++index$1; |
|
ch = scanHexEscape('u'); |
|
|
|
if (!ch || ch === '\\' || !isIdentifierPart(ch.charCodeAt(0))) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
id += ch; |
|
} |
|
} |
|
|
|
return id; |
|
} |
|
|
|
function getIdentifier() { |
|
var start, ch; |
|
start = index$1++; |
|
|
|
while (index$1 < length) { |
|
ch = source$1.charCodeAt(index$1); |
|
|
|
if (ch === 0x5C) { |
|
// Blackslash (U+005C) marks Unicode escape sequence. |
|
index$1 = start; |
|
return getEscapedIdentifier(); |
|
} |
|
|
|
if (isIdentifierPart(ch)) { |
|
++index$1; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
return source$1.slice(start, index$1); |
|
} |
|
|
|
function scanIdentifier() { |
|
var start, id, type; |
|
start = index$1; // Backslash (U+005C) starts an escaped character. |
|
|
|
id = source$1.charCodeAt(index$1) === 0x5C ? getEscapedIdentifier() : getIdentifier(); // There is no keyword or literal with only one character. |
|
// Thus, it must be an identifier. |
|
|
|
if (id.length === 1) { |
|
type = TokenIdentifier; |
|
} else if (keywords.hasOwnProperty(id)) { |
|
// eslint-disable-line no-prototype-builtins |
|
type = TokenKeyword; |
|
} else if (id === 'null') { |
|
type = TokenNullLiteral; |
|
} else if (id === 'true' || id === 'false') { |
|
type = TokenBooleanLiteral; |
|
} else { |
|
type = TokenIdentifier; |
|
} |
|
|
|
return { |
|
type: type, |
|
value: id, |
|
start: start, |
|
end: index$1 |
|
}; |
|
} // 7.7 Punctuators |
|
|
|
|
|
function scanPunctuator() { |
|
var start = index$1, |
|
code = source$1.charCodeAt(index$1), |
|
code2, |
|
ch1 = source$1[index$1], |
|
ch2, |
|
ch3, |
|
ch4; |
|
|
|
switch (code) { |
|
// Check for most common single-character punctuators. |
|
case 0x2E: // . dot |
|
|
|
case 0x28: // ( open bracket |
|
|
|
case 0x29: // ) close bracket |
|
|
|
case 0x3B: // ; semicolon |
|
|
|
case 0x2C: // , comma |
|
|
|
case 0x7B: // { open curly brace |
|
|
|
case 0x7D: // } close curly brace |
|
|
|
case 0x5B: // [ |
|
|
|
case 0x5D: // ] |
|
|
|
case 0x3A: // : |
|
|
|
case 0x3F: // ? |
|
|
|
case 0x7E: |
|
// ~ |
|
++index$1; |
|
return { |
|
type: TokenPunctuator, |
|
value: String.fromCharCode(code), |
|
start: start, |
|
end: index$1 |
|
}; |
|
|
|
default: |
|
code2 = source$1.charCodeAt(index$1 + 1); // '=' (U+003D) marks an assignment or comparison operator. |
|
|
|
if (code2 === 0x3D) { |
|
switch (code) { |
|
case 0x2B: // + |
|
|
|
case 0x2D: // - |
|
|
|
case 0x2F: // / |
|
|
|
case 0x3C: // < |
|
|
|
case 0x3E: // > |
|
|
|
case 0x5E: // ^ |
|
|
|
case 0x7C: // | |
|
|
|
case 0x25: // % |
|
|
|
case 0x26: // & |
|
|
|
case 0x2A: |
|
// * |
|
index$1 += 2; |
|
return { |
|
type: TokenPunctuator, |
|
value: String.fromCharCode(code) + String.fromCharCode(code2), |
|
start: start, |
|
end: index$1 |
|
}; |
|
|
|
case 0x21: // ! |
|
|
|
case 0x3D: |
|
// = |
|
index$1 += 2; // !== and === |
|
|
|
if (source$1.charCodeAt(index$1) === 0x3D) { |
|
++index$1; |
|
} |
|
|
|
return { |
|
type: TokenPunctuator, |
|
value: source$1.slice(start, index$1), |
|
start: start, |
|
end: index$1 |
|
}; |
|
} |
|
} |
|
|
|
} // 4-character punctuator: >>>= |
|
|
|
|
|
ch4 = source$1.substr(index$1, 4); |
|
|
|
if (ch4 === '>>>=') { |
|
index$1 += 4; |
|
return { |
|
type: TokenPunctuator, |
|
value: ch4, |
|
start: start, |
|
end: index$1 |
|
}; |
|
} // 3-character punctuators: === !== >>> <<= >>= |
|
|
|
|
|
ch3 = ch4.substr(0, 3); |
|
|
|
if (ch3 === '>>>' || ch3 === '<<=' || ch3 === '>>=') { |
|
index$1 += 3; |
|
return { |
|
type: TokenPunctuator, |
|
value: ch3, |
|
start: start, |
|
end: index$1 |
|
}; |
|
} // Other 2-character punctuators: ++ -- << >> && || |
|
|
|
|
|
ch2 = ch3.substr(0, 2); |
|
|
|
if (ch1 === ch2[1] && '+-<>&|'.indexOf(ch1) >= 0 || ch2 === '=>') { |
|
index$1 += 2; |
|
return { |
|
type: TokenPunctuator, |
|
value: ch2, |
|
start: start, |
|
end: index$1 |
|
}; |
|
} // 1-character punctuators: < > = ! + - * % & | ^ / |
|
|
|
|
|
if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { |
|
++index$1; |
|
return { |
|
type: TokenPunctuator, |
|
value: ch1, |
|
start: start, |
|
end: index$1 |
|
}; |
|
} |
|
|
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} // 7.8.3 Numeric Literals |
|
|
|
|
|
function scanHexLiteral(start) { |
|
var number = ''; |
|
|
|
while (index$1 < length) { |
|
if (!isHexDigit(source$1[index$1])) { |
|
break; |
|
} |
|
|
|
number += source$1[index$1++]; |
|
} |
|
|
|
if (number.length === 0) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
if (isIdentifierStart(source$1.charCodeAt(index$1))) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
return { |
|
type: TokenNumericLiteral, |
|
value: parseInt('0x' + number, 16), |
|
start: start, |
|
end: index$1 |
|
}; |
|
} |
|
|
|
function scanOctalLiteral(start) { |
|
var number = '0' + source$1[index$1++]; |
|
|
|
while (index$1 < length) { |
|
if (!isOctalDigit(source$1[index$1])) { |
|
break; |
|
} |
|
|
|
number += source$1[index$1++]; |
|
} |
|
|
|
if (isIdentifierStart(source$1.charCodeAt(index$1)) || isDecimalDigit(source$1.charCodeAt(index$1))) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
return { |
|
type: TokenNumericLiteral, |
|
value: parseInt(number, 8), |
|
octal: true, |
|
start: start, |
|
end: index$1 |
|
}; |
|
} |
|
|
|
function scanNumericLiteral() { |
|
var number, start, ch; |
|
ch = source$1[index$1]; |
|
assert(isDecimalDigit(ch.charCodeAt(0)) || ch === '.', 'Numeric literal must start with a decimal digit or a decimal point'); |
|
start = index$1; |
|
number = ''; |
|
|
|
if (ch !== '.') { |
|
number = source$1[index$1++]; |
|
ch = source$1[index$1]; // Hex number starts with '0x'. |
|
// Octal number starts with '0'. |
|
|
|
if (number === '0') { |
|
if (ch === 'x' || ch === 'X') { |
|
++index$1; |
|
return scanHexLiteral(start); |
|
} |
|
|
|
if (isOctalDigit(ch)) { |
|
return scanOctalLiteral(start); |
|
} // decimal number starts with '0' such as '09' is illegal. |
|
|
|
|
|
if (ch && isDecimalDigit(ch.charCodeAt(0))) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
} |
|
|
|
while (isDecimalDigit(source$1.charCodeAt(index$1))) { |
|
number += source$1[index$1++]; |
|
} |
|
|
|
ch = source$1[index$1]; |
|
} |
|
|
|
if (ch === '.') { |
|
number += source$1[index$1++]; |
|
|
|
while (isDecimalDigit(source$1.charCodeAt(index$1))) { |
|
number += source$1[index$1++]; |
|
} |
|
|
|
ch = source$1[index$1]; |
|
} |
|
|
|
if (ch === 'e' || ch === 'E') { |
|
number += source$1[index$1++]; |
|
ch = source$1[index$1]; |
|
|
|
if (ch === '+' || ch === '-') { |
|
number += source$1[index$1++]; |
|
} |
|
|
|
if (isDecimalDigit(source$1.charCodeAt(index$1))) { |
|
while (isDecimalDigit(source$1.charCodeAt(index$1))) { |
|
number += source$1[index$1++]; |
|
} |
|
} else { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
} |
|
|
|
if (isIdentifierStart(source$1.charCodeAt(index$1))) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
return { |
|
type: TokenNumericLiteral, |
|
value: parseFloat(number), |
|
start: start, |
|
end: index$1 |
|
}; |
|
} // 7.8.4 String Literals |
|
|
|
|
|
function scanStringLiteral() { |
|
var str = '', |
|
quote, |
|
start, |
|
ch, |
|
code, |
|
octal = false; |
|
quote = source$1[index$1]; |
|
assert(quote === '\'' || quote === '"', 'String literal must starts with a quote'); |
|
start = index$1; |
|
++index$1; |
|
|
|
while (index$1 < length) { |
|
ch = source$1[index$1++]; |
|
|
|
if (ch === quote) { |
|
quote = ''; |
|
break; |
|
} else if (ch === '\\') { |
|
ch = source$1[index$1++]; |
|
|
|
if (!ch || !isLineTerminator(ch.charCodeAt(0))) { |
|
switch (ch) { |
|
case 'u': |
|
case 'x': |
|
if (source$1[index$1] === '{') { |
|
++index$1; |
|
str += scanUnicodeCodePointEscape(); |
|
} else { |
|
str += scanHexEscape(ch); |
|
} |
|
|
|
break; |
|
|
|
case 'n': |
|
str += '\n'; |
|
break; |
|
|
|
case 'r': |
|
str += '\r'; |
|
break; |
|
|
|
case 't': |
|
str += '\t'; |
|
break; |
|
|
|
case 'b': |
|
str += '\b'; |
|
break; |
|
|
|
case 'f': |
|
str += '\f'; |
|
break; |
|
|
|
case 'v': |
|
str += '\x0B'; |
|
break; |
|
|
|
default: |
|
if (isOctalDigit(ch)) { |
|
code = '01234567'.indexOf(ch); // \0 is not octal escape sequence |
|
|
|
if (code !== 0) { |
|
octal = true; |
|
} |
|
|
|
if (index$1 < length && isOctalDigit(source$1[index$1])) { |
|
octal = true; |
|
code = code * 8 + '01234567'.indexOf(source$1[index$1++]); // 3 digits are only allowed when string starts |
|
// with 0, 1, 2, 3 |
|
|
|
if ('0123'.indexOf(ch) >= 0 && index$1 < length && isOctalDigit(source$1[index$1])) { |
|
code = code * 8 + '01234567'.indexOf(source$1[index$1++]); |
|
} |
|
} |
|
|
|
str += String.fromCharCode(code); |
|
} else { |
|
str += ch; |
|
} |
|
|
|
break; |
|
} |
|
} else { |
|
if (ch === '\r' && source$1[index$1] === '\n') { |
|
++index$1; |
|
} |
|
} |
|
} else if (isLineTerminator(ch.charCodeAt(0))) { |
|
break; |
|
} else { |
|
str += ch; |
|
} |
|
} |
|
|
|
if (quote !== '') { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} |
|
|
|
return { |
|
type: TokenStringLiteral, |
|
value: str, |
|
octal: octal, |
|
start: start, |
|
end: index$1 |
|
}; |
|
} |
|
|
|
function testRegExp(pattern, flags) { |
|
var tmp = pattern; |
|
|
|
if (flags.indexOf('u') >= 0) { |
|
// Replace each astral symbol and every Unicode code point |
|
// escape sequence with a single ASCII symbol to avoid throwing on |
|
// regular expressions that are only valid in combination with the |
|
// `/u` flag. |
|
// Note: replacing with the ASCII symbol `x` might cause false |
|
// negatives in unlikely scenarios. For example, `[\u{61}-b]` is a |
|
// perfectly valid pattern that is equivalent to `[a-b]`, but it |
|
// would be replaced by `[x-b]` which throws an error. |
|
tmp = tmp.replace(/\\u\{([0-9a-fA-F]+)\}/g, function ($0, $1) { |
|
if (parseInt($1, 16) <= 0x10FFFF) { |
|
return 'x'; |
|
} |
|
|
|
throwError({}, MessageInvalidRegExp); |
|
}).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 'x'); |
|
} // First, detect invalid regular expressions. |
|
|
|
|
|
try { |
|
new RegExp(tmp); |
|
} catch (e) { |
|
throwError({}, MessageInvalidRegExp); |
|
} // Return a regular expression object for this pattern-flag pair, or |
|
// `null` in case the current environment doesn't support the flags it |
|
// uses. |
|
|
|
|
|
try { |
|
return new RegExp(pattern, flags); |
|
} catch (exception) { |
|
return null; |
|
} |
|
} |
|
|
|
function scanRegExpBody() { |
|
var ch, str, classMarker, terminated, body; |
|
ch = source$1[index$1]; |
|
assert(ch === '/', 'Regular expression literal must start with a slash'); |
|
str = source$1[index$1++]; |
|
classMarker = false; |
|
terminated = false; |
|
|
|
while (index$1 < length) { |
|
ch = source$1[index$1++]; |
|
str += ch; |
|
|
|
if (ch === '\\') { |
|
ch = source$1[index$1++]; // ECMA-262 7.8.5 |
|
|
|
if (isLineTerminator(ch.charCodeAt(0))) { |
|
throwError({}, MessageUnterminatedRegExp); |
|
} |
|
|
|
str += ch; |
|
} else if (isLineTerminator(ch.charCodeAt(0))) { |
|
throwError({}, MessageUnterminatedRegExp); |
|
} else if (classMarker) { |
|
if (ch === ']') { |
|
classMarker = false; |
|
} |
|
} else { |
|
if (ch === '/') { |
|
terminated = true; |
|
break; |
|
} else if (ch === '[') { |
|
classMarker = true; |
|
} |
|
} |
|
} |
|
|
|
if (!terminated) { |
|
throwError({}, MessageUnterminatedRegExp); |
|
} // Exclude leading and trailing slash. |
|
|
|
|
|
body = str.substr(1, str.length - 2); |
|
return { |
|
value: body, |
|
literal: str |
|
}; |
|
} |
|
|
|
function scanRegExpFlags() { |
|
var ch, str, flags; |
|
str = ''; |
|
flags = ''; |
|
|
|
while (index$1 < length) { |
|
ch = source$1[index$1]; |
|
|
|
if (!isIdentifierPart(ch.charCodeAt(0))) { |
|
break; |
|
} |
|
|
|
++index$1; |
|
|
|
if (ch === '\\' && index$1 < length) { |
|
throwError({}, MessageUnexpectedToken, ILLEGAL); |
|
} else { |
|
flags += ch; |
|
str += ch; |
|
} |
|
} |
|
|
|
if (flags.search(/[^gimuy]/g) >= 0) { |
|
throwError({}, MessageInvalidRegExp, flags); |
|
} |
|
|
|
return { |
|
value: flags, |
|
literal: str |
|
}; |
|
} |
|
|
|
function scanRegExp() { |
|
var start, body, flags, value; |
|
lookahead = null; |
|
skipComment(); |
|
start = index$1; |
|
body = scanRegExpBody(); |
|
flags = scanRegExpFlags(); |
|
value = testRegExp(body.value, flags.value); |
|
return { |
|
literal: body.literal + flags.literal, |
|
value: value, |
|
regex: { |
|
pattern: body.value, |
|
flags: flags.value |
|
}, |
|
start: start, |
|
end: index$1 |
|
}; |
|
} |
|
|
|
function isIdentifierName(token) { |
|
return token.type === TokenIdentifier || token.type === TokenKeyword || token.type === TokenBooleanLiteral || token.type === TokenNullLiteral; |
|
} |
|
|
|
function advance() { |
|
var ch; |
|
skipComment(); |
|
|
|
if (index$1 >= length) { |
|
return { |
|
type: TokenEOF, |
|
start: index$1, |
|
end: index$1 |
|
}; |
|
} |
|
|
|
ch = source$1.charCodeAt(index$1); |
|
|
|
if (isIdentifierStart(ch)) { |
|
return scanIdentifier(); |
|
} // Very common: ( and ) and ; |
|
|
|
|
|
if (ch === 0x28 || ch === 0x29 || ch === 0x3B) { |
|
return scanPunctuator(); |
|
} // String literal starts with single quote (U+0027) or double quote (U+0022). |
|
|
|
|
|
if (ch === 0x27 || ch === 0x22) { |
|
return scanStringLiteral(); |
|
} // Dot (.) U+002E can also start a floating-point number, hence the need |
|
// to check the next character. |
|
|
|
|
|
if (ch === 0x2E) { |
|
if (isDecimalDigit(source$1.charCodeAt(index$1 + 1))) { |
|
return scanNumericLiteral(); |
|
} |
|
|
|
return scanPunctuator(); |
|
} |
|
|
|
if (isDecimalDigit(ch)) { |
|
return scanNumericLiteral(); |
|
} |
|
|
|
return scanPunctuator(); |
|
} |
|
|
|
function lex() { |
|
var token; |
|
token = lookahead; |
|
index$1 = token.end; |
|
lookahead = advance(); |
|
index$1 = token.end; |
|
return token; |
|
} |
|
|
|
function peek$1() { |
|
var pos; |
|
pos = index$1; |
|
lookahead = advance(); |
|
index$1 = pos; |
|
} |
|
|
|
function finishArrayExpression(elements) { |
|
var node = new ASTNode(SyntaxArrayExpression); |
|
node.elements = elements; |
|
return node; |
|
} |
|
|
|
function finishBinaryExpression(operator, left, right) { |
|
var node = new ASTNode(operator === '||' || operator === '&&' ? SyntaxLogicalExpression : SyntaxBinaryExpression); |
|
node.operator = operator; |
|
node.left = left; |
|
node.right = right; |
|
return node; |
|
} |
|
|
|
function finishCallExpression(callee, args) { |
|
var node = new ASTNode(SyntaxCallExpression); |
|
node.callee = callee; |
|
node.arguments = args; |
|
return node; |
|
} |
|
|
|
function finishConditionalExpression(test, consequent, alternate) { |
|
var node = new ASTNode(SyntaxConditionalExpression); |
|
node.test = test; |
|
node.consequent = consequent; |
|
node.alternate = alternate; |
|
return node; |
|
} |
|
|
|
function finishIdentifier(name) { |
|
var node = new ASTNode(SyntaxIdentifier); |
|
node.name = name; |
|
return node; |
|
} |
|
|
|
function finishLiteral(token) { |
|
var node = new ASTNode(SyntaxLiteral); |
|
node.value = token.value; |
|
node.raw = source$1.slice(token.start, token.end); |
|
|
|
if (token.regex) { |
|
if (node.raw === '//') { |
|
node.raw = '/(?:)/'; |
|
} |
|
|
|
node.regex = token.regex; |
|
} |
|
|
|
return node; |
|
} |
|
|
|
function finishMemberExpression(accessor, object, property) { |
|
var node = new ASTNode(SyntaxMemberExpression); |
|
node.computed = accessor === '['; |
|
node.object = object; |
|
node.property = property; |
|
if (!node.computed) property.member = true; |
|
return node; |
|
} |
|
|
|
function finishObjectExpression(properties) { |
|
var node = new ASTNode(SyntaxObjectExpression); |
|
node.properties = properties; |
|
return node; |
|
} |
|
|
|
function finishProperty(kind, key, value) { |
|
var node = new ASTNode(SyntaxProperty); |
|
node.key = key; |
|
node.value = value; |
|
node.kind = kind; |
|
return node; |
|
} |
|
|
|
function finishUnaryExpression(operator, argument) { |
|
var node = new ASTNode(SyntaxUnaryExpression); |
|
node.operator = operator; |
|
node.argument = argument; |
|
node.prefix = true; |
|
return node; |
|
} // Throw an exception |
|
|
|
|
|
function throwError(token, messageFormat) { |
|
var error, |
|
args = Array.prototype.slice.call(arguments, 2), |
|
msg = messageFormat.replace(/%(\d)/g, function (whole, index) { |
|
assert(index < args.length, 'Message reference must be in range'); |
|
return args[index]; |
|
}); |
|
error = new Error(msg); |
|
error.index = index$1; |
|
error.description = msg; |
|
throw error; |
|
} // Throw an exception because of the token. |
|
|
|
|
|
function throwUnexpected(token) { |
|
if (token.type === TokenEOF) { |
|
throwError(token, MessageUnexpectedEOS); |
|
} |
|
|
|
if (token.type === TokenNumericLiteral) { |
|
throwError(token, MessageUnexpectedNumber); |
|
} |
|
|
|
if (token.type === TokenStringLiteral) { |
|
throwError(token, MessageUnexpectedString); |
|
} |
|
|
|
if (token.type === TokenIdentifier) { |
|
throwError(token, MessageUnexpectedIdentifier); |
|
} |
|
|
|
if (token.type === TokenKeyword) { |
|
throwError(token, MessageUnexpectedReserved); |
|
} // BooleanLiteral, NullLiteral, or Punctuator. |
|
|
|
|
|
throwError(token, MessageUnexpectedToken, token.value); |
|
} // Expect the next token to match the specified punctuator. |
|
// If not, an exception will be thrown. |
|
|
|
|
|
function expect(value) { |
|
var token = lex(); |
|
|
|
if (token.type !== TokenPunctuator || token.value !== value) { |
|
throwUnexpected(token); |
|
} |
|
} // Return true if the next token matches the specified punctuator. |
|
|
|
|
|
function match(value) { |
|
return lookahead.type === TokenPunctuator && lookahead.value === value; |
|
} // Return true if the next token matches the specified keyword |
|
|
|
|
|
function matchKeyword(keyword) { |
|
return lookahead.type === TokenKeyword && lookahead.value === keyword; |
|
} // 11.1.4 Array Initialiser |
|
|
|
|
|
function parseArrayInitialiser() { |
|
var elements = []; |
|
index$1 = lookahead.start; |
|
expect('['); |
|
|
|
while (!match(']')) { |
|
if (match(',')) { |
|
lex(); |
|
elements.push(null); |
|
} else { |
|
elements.push(parseConditionalExpression()); |
|
|
|
if (!match(']')) { |
|
expect(','); |
|
} |
|
} |
|
} |
|
|
|
lex(); |
|
return finishArrayExpression(elements); |
|
} // 11.1.5 Object Initialiser |
|
|
|
|
|
function parseObjectPropertyKey() { |
|
var token; |
|
index$1 = lookahead.start; |
|
token = lex(); // Note: This function is called only from parseObjectProperty(), where |
|
// EOF and Punctuator tokens are already filtered out. |
|
|
|
if (token.type === TokenStringLiteral || token.type === TokenNumericLiteral) { |
|
if (token.octal) { |
|
throwError(token, MessageStrictOctalLiteral); |
|
} |
|
|
|
return finishLiteral(token); |
|
} |
|
|
|
return finishIdentifier(token.value); |
|
} |
|
|
|
function parseObjectProperty() { |
|
var token, key, id, value; |
|
index$1 = lookahead.start; |
|
token = lookahead; |
|
|
|
if (token.type === TokenIdentifier) { |
|
id = parseObjectPropertyKey(); |
|
expect(':'); |
|
value = parseConditionalExpression(); |
|
return finishProperty('init', id, value); |
|
} |
|
|
|
if (token.type === TokenEOF || token.type === TokenPunctuator) { |
|
throwUnexpected(token); |
|
} else { |
|
key = parseObjectPropertyKey(); |
|
expect(':'); |
|
value = parseConditionalExpression(); |
|
return finishProperty('init', key, value); |
|
} |
|
} |
|
|
|
function parseObjectInitialiser() { |
|
var properties = [], |
|
property, |
|
name, |
|
key, |
|
map = {}, |
|
toString = String; |
|
index$1 = lookahead.start; |
|
expect('{'); |
|
|
|
while (!match('}')) { |
|
property = parseObjectProperty(); |
|
|
|
if (property.key.type === SyntaxIdentifier) { |
|
name = property.key.name; |
|
} else { |
|
name = toString(property.key.value); |
|
} |
|
|
|
key = '$' + name; |
|
|
|
if (Object.prototype.hasOwnProperty.call(map, key)) { |
|
throwError({}, MessageStrictDuplicateProperty); |
|
} else { |
|
map[key] = true; |
|
} |
|
|
|
properties.push(property); |
|
|
|
if (!match('}')) { |
|
expect(','); |
|
} |
|
} |
|
|
|
expect('}'); |
|
return finishObjectExpression(properties); |
|
} // 11.1.6 The Grouping Operator |
|
|
|
|
|
function parseGroupExpression() { |
|
var expr; |
|
expect('('); |
|
expr = parseExpression(); |
|
expect(')'); |
|
return expr; |
|
} // 11.1 Primary Expressions |
|
|
|
|
|
var legalKeywords = { |
|
"if": 1, |
|
"this": 1 |
|
}; |
|
|
|
function parsePrimaryExpression() { |
|
var type, token, expr; |
|
|
|
if (match('(')) { |
|
return parseGroupExpression(); |
|
} |
|
|
|
if (match('[')) { |
|
return parseArrayInitialiser(); |
|
} |
|
|
|
if (match('{')) { |
|
return parseObjectInitialiser(); |
|
} |
|
|
|
type = lookahead.type; |
|
index$1 = lookahead.start; |
|
|
|
if (type === TokenIdentifier || legalKeywords[lookahead.value]) { |
|
expr = finishIdentifier(lex().value); |
|
} else if (type === TokenStringLiteral || type === TokenNumericLiteral) { |
|
if (lookahead.octal) { |
|
throwError(lookahead, MessageStrictOctalLiteral); |
|
} |
|
|
|
expr = finishLiteral(lex()); |
|
} else if (type === TokenKeyword) { |
|
throw new Error(DISABLED); |
|
} else if (type === TokenBooleanLiteral) { |
|
token = lex(); |
|
token.value = token.value === 'true'; |
|
expr = finishLiteral(token); |
|
} else if (type === TokenNullLiteral) { |
|
token = lex(); |
|
token.value = null; |
|
expr = finishLiteral(token); |
|
} else if (match('/') || match('/=')) { |
|
expr = finishLiteral(scanRegExp()); |
|
peek$1(); |
|
} else { |
|
throwUnexpected(lex()); |
|
} |
|
|
|
return expr; |
|
} // 11.2 Left-Hand-Side Expressions |
|
|
|
|
|
function parseArguments() { |
|
var args = []; |
|
expect('('); |
|
|
|
if (!match(')')) { |
|
while (index$1 < length) { |
|
args.push(parseConditionalExpression()); |
|
|
|
if (match(')')) { |
|
break; |
|
} |
|
|
|
expect(','); |
|
} |
|
} |
|
|
|
expect(')'); |
|
return args; |
|
} |
|
|
|
function parseNonComputedProperty() { |
|
var token; |
|
index$1 = lookahead.start; |
|
token = lex(); |
|
|
|
if (!isIdentifierName(token)) { |
|
throwUnexpected(token); |
|
} |
|
|
|
return finishIdentifier(token.value); |
|
} |
|
|
|
function parseNonComputedMember() { |
|
expect('.'); |
|
return parseNonComputedProperty(); |
|
} |
|
|
|
function parseComputedMember() { |
|
var expr; |
|
expect('['); |
|
expr = parseExpression(); |
|
expect(']'); |
|
return expr; |
|
} |
|
|
|
function parseLeftHandSideExpressionAllowCall() { |
|
var expr, args, property; |
|
expr = parsePrimaryExpression(); |
|
|
|
for (;;) { |
|
if (match('.')) { |
|
property = parseNonComputedMember(); |
|
expr = finishMemberExpression('.', expr, property); |
|
} else if (match('(')) { |
|
args = parseArguments(); |
|
expr = finishCallExpression(expr, args); |
|
} else if (match('[')) { |
|
property = parseComputedMember(); |
|
expr = finishMemberExpression('[', expr, property); |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
return expr; |
|
} // 11.3 Postfix Expressions |
|
|
|
|
|
function parsePostfixExpression() { |
|
var expr = parseLeftHandSideExpressionAllowCall(); |
|
|
|
if (lookahead.type === TokenPunctuator) { |
|
if (match('++') || match('--')) { |
|
throw new Error(DISABLED); |
|
} |
|
} |
|
|
|
return expr; |
|
} // 11.4 Unary Operators |
|
|
|
|
|
function parseUnaryExpression() { |
|
var token, expr; |
|
|
|
if (lookahead.type !== TokenPunctuator && lookahead.type !== TokenKeyword) { |
|
expr = parsePostfixExpression(); |
|
} else if (match('++') || match('--')) { |
|
throw new Error(DISABLED); |
|
} else if (match('+') || match('-') || match('~') || match('!')) { |
|
token = lex(); |
|
expr = parseUnaryExpression(); |
|
expr = finishUnaryExpression(token.value, expr); |
|
} else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { |
|
throw new Error(DISABLED); |
|
} else { |
|
expr = parsePostfixExpression(); |
|
} |
|
|
|
return expr; |
|
} |
|
|
|
function binaryPrecedence(token) { |
|
var prec = 0; |
|
|
|
if (token.type !== TokenPunctuator && token.type !== TokenKeyword) { |
|
return 0; |
|
} |
|
|
|
switch (token.value) { |
|
case '||': |
|
prec = 1; |
|
break; |
|
|
|
case '&&': |
|
prec = 2; |
|
break; |
|
|
|
case '|': |
|
prec = 3; |
|
break; |
|
|
|
case '^': |
|
prec = 4; |
|
break; |
|
|
|
case '&': |
|
prec = 5; |
|
break; |
|
|
|
case '==': |
|
case '!=': |
|
case '===': |
|
case '!==': |
|
prec = 6; |
|
break; |
|
|
|
case '<': |
|
case '>': |
|
case '<=': |
|
case '>=': |
|
case 'instanceof': |
|
case 'in': |
|
prec = 7; |
|
break; |
|
|
|
case '<<': |
|
case '>>': |
|
case '>>>': |
|
prec = 8; |
|
break; |
|
|
|
case '+': |
|
case '-': |
|
prec = 9; |
|
break; |
|
|
|
case '*': |
|
case '/': |
|
case '%': |
|
prec = 11; |
|
break; |
|
} |
|
|
|
return prec; |
|
} // 11.5 Multiplicative Operators |
|
// 11.6 Additive Operators |
|
// 11.7 Bitwise Shift Operators |
|
// 11.8 Relational Operators |
|
// 11.9 Equality Operators |
|
// 11.10 Binary Bitwise Operators |
|
// 11.11 Binary Logical Operators |
|
|
|
|
|
function parseBinaryExpression() { |
|
var marker, markers, expr, token, prec, stack, right, operator, left, i; |
|
marker = lookahead; |
|
left = parseUnaryExpression(); |
|
token = lookahead; |
|
prec = binaryPrecedence(token); |
|
|
|
if (prec === 0) { |
|
return left; |
|
} |
|
|
|
token.prec = prec; |
|
lex(); |
|
markers = [marker, lookahead]; |
|
right = parseUnaryExpression(); |
|
stack = [left, token, right]; |
|
|
|
while ((prec = binaryPrecedence(lookahead)) > 0) { |
|
// Reduce: make a binary expression from the three topmost entries. |
|
while (stack.length > 2 && prec <= stack[stack.length - 2].prec) { |
|
right = stack.pop(); |
|
operator = stack.pop().value; |
|
left = stack.pop(); |
|
markers.pop(); |
|
expr = finishBinaryExpression(operator, left, right); |
|
stack.push(expr); |
|
} // Shift. |
|
|
|
|
|
token = lex(); |
|
token.prec = prec; |
|
stack.push(token); |
|
markers.push(lookahead); |
|
expr = parseUnaryExpression(); |
|
stack.push(expr); |
|
} // Final reduce to clean-up the stack. |
|
|
|
|
|
i = stack.length - 1; |
|
expr = stack[i]; |
|
markers.pop(); |
|
|
|
while (i > 1) { |
|
markers.pop(); |
|
expr = finishBinaryExpression(stack[i - 1].value, stack[i - 2], expr); |
|
i -= 2; |
|
} |
|
|
|
return expr; |
|
} // 11.12 Conditional Operator |
|
|
|
|
|
function parseConditionalExpression() { |
|
var expr, consequent, alternate; |
|
expr = parseBinaryExpression(); |
|
|
|
if (match('?')) { |
|
lex(); |
|
consequent = parseConditionalExpression(); |
|
expect(':'); |
|
alternate = parseConditionalExpression(); |
|
expr = finishConditionalExpression(expr, consequent, alternate); |
|
} |
|
|
|
return expr; |
|
} // 11.14 Comma Operator |
|
|
|
|
|
function parseExpression() { |
|
var expr = parseConditionalExpression(); |
|
|
|
if (match(',')) { |
|
throw new Error(DISABLED); // no sequence expressions |
|
} |
|
|
|
return expr; |
|
} |
|
|
|
function parse$3(code) { |
|
source$1 = code; |
|
index$1 = 0; |
|
length = source$1.length; |
|
lookahead = null; |
|
peek$1(); |
|
var expr = parseExpression(); |
|
|
|
if (lookahead.type !== TokenEOF) { |
|
throw new Error("Unexpect token after expression."); |
|
} |
|
|
|
return expr; |
|
} |
|
|
|
var constants = { |
|
NaN: 'NaN', |
|
E: 'Math.E', |
|
LN2: 'Math.LN2', |
|
LN10: 'Math.LN10', |
|
LOG2E: 'Math.LOG2E', |
|
LOG10E: 'Math.LOG10E', |
|
PI: 'Math.PI', |
|
SQRT1_2: 'Math.SQRT1_2', |
|
SQRT2: 'Math.SQRT2', |
|
MIN_VALUE: 'Number.MIN_VALUE', |
|
MAX_VALUE: 'Number.MAX_VALUE' |
|
}; |
|
|
|
function functions(codegen) { |
|
function fncall(name, args, cast, type) { |
|
var obj = codegen(args[0]); |
|
|
|
if (cast) { |
|
obj = cast + '(' + obj + ')'; |
|
if (cast.lastIndexOf('new ', 0) === 0) obj = '(' + obj + ')'; |
|
} |
|
|
|
return obj + '.' + name + (type < 0 ? '' : type === 0 ? '()' : '(' + args.slice(1).map(codegen).join(',') + ')'); |
|
} |
|
|
|
function fn(name, cast, type) { |
|
return function (args) { |
|
return fncall(name, args, cast, type); |
|
}; |
|
} |
|
|
|
var DATE = 'new Date', |
|
STRING = 'String', |
|
REGEXP = 'RegExp'; |
|
return { |
|
// MATH functions |
|
isNaN: 'Number.isNaN', |
|
isFinite: 'Number.isFinite', |
|
abs: 'Math.abs', |
|
acos: 'Math.acos', |
|
asin: 'Math.asin', |
|
atan: 'Math.atan', |
|
atan2: 'Math.atan2', |
|
ceil: 'Math.ceil', |
|
cos: 'Math.cos', |
|
exp: 'Math.exp', |
|
floor: 'Math.floor', |
|
log: 'Math.log', |
|
max: 'Math.max', |
|
min: 'Math.min', |
|
pow: 'Math.pow', |
|
random: 'Math.random', |
|
round: 'Math.round', |
|
sin: 'Math.sin', |
|
sqrt: 'Math.sqrt', |
|
tan: 'Math.tan', |
|
clamp: function clamp(args) { |
|
if (args.length < 3) error('Missing arguments to clamp function.'); |
|
if (args.length > 3) error('Too many arguments to clamp function.'); |
|
var a = args.map(codegen); |
|
return 'Math.max(' + a[1] + ', Math.min(' + a[2] + ',' + a[0] + '))'; |
|
}, |
|
// DATE functions |
|
now: 'Date.now', |
|
utc: 'Date.UTC', |
|
datetime: DATE, |
|
date: fn('getDate', DATE, 0), |
|
day: fn('getDay', DATE, 0), |
|
year: fn('getFullYear', DATE, 0), |
|
month: fn('getMonth', DATE, 0), |
|
hours: fn('getHours', DATE, 0), |
|
minutes: fn('getMinutes', DATE, 0), |
|
seconds: fn('getSeconds', DATE, 0), |
|
milliseconds: fn('getMilliseconds', DATE, 0), |
|
time: fn('getTime', DATE, 0), |
|
timezoneoffset: fn('getTimezoneOffset', DATE, 0), |
|
utcdate: fn('getUTCDate', DATE, 0), |
|
utcday: fn('getUTCDay', DATE, 0), |
|
utcyear: fn('getUTCFullYear', DATE, 0), |
|
utcmonth: fn('getUTCMonth', DATE, 0), |
|
utchours: fn('getUTCHours', DATE, 0), |
|
utcminutes: fn('getUTCMinutes', DATE, 0), |
|
utcseconds: fn('getUTCSeconds', DATE, 0), |
|
utcmilliseconds: fn('getUTCMilliseconds', DATE, 0), |
|
// sequence functions |
|
length: fn('length', null, -1), |
|
join: fn('join', null), |
|
indexof: fn('indexOf', null), |
|
lastindexof: fn('lastIndexOf', null), |
|
slice: fn('slice', null), |
|
reverse: function reverse(args) { |
|
return '(' + codegen(args[0]) + ').slice().reverse()'; |
|
}, |
|
// STRING functions |
|
parseFloat: 'parseFloat', |
|
parseInt: 'parseInt', |
|
upper: fn('toUpperCase', STRING, 0), |
|
lower: fn('toLowerCase', STRING, 0), |
|
substring: fn('substring', STRING), |
|
split: fn('split', STRING), |
|
replace: fn('replace', STRING), |
|
trim: fn('trim', STRING, 0), |
|
// REGEXP functions |
|
regexp: REGEXP, |
|
test: fn('test', REGEXP), |
|
// Control Flow functions |
|
if: function _if(args) { |
|
if (args.length < 3) error('Missing arguments to if function.'); |
|
if (args.length > 3) error('Too many arguments to if function.'); |
|
var a = args.map(codegen); |
|
return '(' + a[0] + '?' + a[1] + ':' + a[2] + ')'; |
|
} |
|
}; |
|
} |
|
|
|
function stripQuotes(s) { |
|
var n = s && s.length - 1; |
|
return n && (s[0] === '"' && s[n] === '"' || s[0] === '\'' && s[n] === '\'') ? s.slice(1, -1) : s; |
|
} |
|
|
|
function codegen(opt) { |
|
opt = opt || {}; |
|
var whitelist = opt.whitelist ? toSet(opt.whitelist) : {}, |
|
blacklist = opt.blacklist ? toSet(opt.blacklist) : {}, |
|
constants$1 = opt.constants || constants, |
|
functions$1 = (opt.functions || functions)(visit), |
|
globalvar = opt.globalvar, |
|
fieldvar = opt.fieldvar, |
|
globals = {}, |
|
fields = {}, |
|
memberDepth = 0; |
|
var outputGlobal = isFunction(globalvar) ? globalvar : function (id) { |
|
return globalvar + '["' + id + '"]'; |
|
}; |
|
|
|
function visit(ast) { |
|
if (isString(ast)) return ast; |
|
var generator = Generators[ast.type]; |
|
if (generator == null) error('Unsupported type: ' + ast.type); |
|
return generator(ast); |
|
} |
|
|
|
var Generators = { |
|
Literal: function Literal(n) { |
|
return n.raw; |
|
}, |
|
Identifier: function Identifier(n) { |
|
var id = n.name; |
|
|
|
if (memberDepth > 0) { |
|
return id; |
|
} else if (hasOwnProperty(blacklist, id)) { |
|
return error('Illegal identifier: ' + id); |
|
} else if (hasOwnProperty(constants$1, id)) { |
|
return constants$1[id]; |
|
} else if (hasOwnProperty(whitelist, id)) { |
|
return id; |
|
} else { |
|
globals[id] = 1; |
|
return outputGlobal(id); |
|
} |
|
}, |
|
MemberExpression: function MemberExpression(n) { |
|
var d = !n.computed; |
|
var o = visit(n.object); |
|
if (d) memberDepth += 1; |
|
var p = visit(n.property); |
|
|
|
if (o === fieldvar) { |
|
// strip quotes to sanitize field name (#1653) |
|
fields[stripQuotes(p)] = 1; |
|
} |
|
|
|
if (d) memberDepth -= 1; |
|
return o + (d ? '.' + p : '[' + p + ']'); |
|
}, |
|
CallExpression: function CallExpression(n) { |
|
if (n.callee.type !== 'Identifier') { |
|
error('Illegal callee type: ' + n.callee.type); |
|
} |
|
|
|
var callee = n.callee.name; |
|
var args = n.arguments; |
|
var fn = hasOwnProperty(functions$1, callee) && functions$1[callee]; |
|
if (!fn) error('Unrecognized function: ' + callee); |
|
return isFunction(fn) ? fn(args) : fn + '(' + args.map(visit).join(',') + ')'; |
|
}, |
|
ArrayExpression: function ArrayExpression(n) { |
|
return '[' + n.elements.map(visit).join(',') + ']'; |
|
}, |
|
BinaryExpression: function BinaryExpression(n) { |
|
return '(' + visit(n.left) + n.operator + visit(n.right) + ')'; |
|
}, |
|
UnaryExpression: function UnaryExpression(n) { |
|
return '(' + n.operator + visit(n.argument) + ')'; |
|
}, |
|
ConditionalExpression: function ConditionalExpression(n) { |
|
return '(' + visit(n.test) + '?' + visit(n.consequent) + ':' + visit(n.alternate) + ')'; |
|
}, |
|
LogicalExpression: function LogicalExpression(n) { |
|
return '(' + visit(n.left) + n.operator + visit(n.right) + ')'; |
|
}, |
|
ObjectExpression: function ObjectExpression(n) { |
|
return '{' + n.properties.map(visit).join(',') + '}'; |
|
}, |
|
Property: function Property(n) { |
|
memberDepth += 1; |
|
var k = visit(n.key); |
|
memberDepth -= 1; |
|
return k + ':' + visit(n.value); |
|
} |
|
}; |
|
|
|
function codegen(ast) { |
|
var result = { |
|
code: visit(ast), |
|
globals: Object.keys(globals), |
|
fields: Object.keys(fields) |
|
}; |
|
globals = {}; |
|
fields = {}; |
|
return result; |
|
} |
|
|
|
codegen.functions = functions$1; |
|
codegen.constants = constants$1; |
|
return codegen; |
|
} |
|
|
|
var Intersect = 'intersect'; |
|
var Union = 'union'; |
|
var VlMulti = 'vlMulti'; |
|
var Or = 'or'; |
|
var And = 'and'; |
|
var TYPE_ENUM = 'E', |
|
TYPE_RANGE_INC = 'R', |
|
TYPE_RANGE_EXC = 'R-E', |
|
TYPE_RANGE_LE = 'R-LE', |
|
TYPE_RANGE_RE = 'R-RE', |
|
UNIT_INDEX = 'index:unit'; // TODO: revisit date coercion? |
|
|
|
function testPoint(datum, entry) { |
|
var fields = entry.fields, |
|
values = entry.values, |
|
n = fields.length, |
|
i = 0, |
|
dval, |
|
f; |
|
|
|
for (; i < n; ++i) { |
|
f = fields[i]; |
|
f.getter = field.getter || field(f.field); |
|
dval = f.getter(datum); |
|
if (isDate(dval)) dval = toNumber(dval); |
|
if (isDate(values[i])) values[i] = toNumber(values[i]); |
|
if (isDate(values[i][0])) values[i] = values[i].map(toNumber); |
|
|
|
if (f.type === TYPE_ENUM) { |
|
// Enumerated fields can either specify individual values (single/multi selections) |
|
// or an array of values (interval selections). |
|
if (isArray(values[i]) ? values[i].indexOf(dval) < 0 : dval !== values[i]) { |
|
return false; |
|
} |
|
} else { |
|
if (f.type === TYPE_RANGE_INC) { |
|
if (!inrange(dval, values[i])) return false; |
|
} else if (f.type === TYPE_RANGE_RE) { |
|
// Discrete selection of bins test within the range [bin_start, bin_end). |
|
if (!inrange(dval, values[i], true, false)) return false; |
|
} else if (f.type === TYPE_RANGE_EXC) { |
|
// 'R-E'/'R-LE' included for completeness. |
|
if (!inrange(dval, values[i], false, false)) return false; |
|
} else if (f.type === TYPE_RANGE_LE) { |
|
if (!inrange(dval, values[i], false, true)) return false; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
/** |
|
* Tests if a tuple is contained within an interactive selection. |
|
* @param {string} name - The name of the data set representing the selection. |
|
* Tuples in the dataset are of the form |
|
* {unit: string, fields: array<fielddef>, values: array<*>}. |
|
* Fielddef is of the form |
|
* {field: string, channel: string, type: 'E' | 'R'} where |
|
* 'type' identifies whether tuples in the dataset enumerate |
|
* values for the field, or specify a continuous range. |
|
* @param {object} datum - The tuple to test for inclusion. |
|
* @param {string} op - The set operation for combining selections. |
|
* One of 'intersect' or 'union' (default). |
|
* @return {boolean} - True if the datum is in the selection, false otherwise. |
|
*/ |
|
|
|
|
|
function selectionTest(name, datum, op) { |
|
var data = this.context.data[name], |
|
entries = data ? data.values.value : [], |
|
unitIdx = data ? data[UNIT_INDEX] && data[UNIT_INDEX].value : undefined, |
|
intersect = op === Intersect, |
|
n = entries.length, |
|
i = 0, |
|
entry, |
|
miss, |
|
count, |
|
unit, |
|
b; |
|
|
|
for (; i < n; ++i) { |
|
entry = entries[i]; |
|
|
|
if (unitIdx && intersect) { |
|
// multi selections union within the same unit and intersect across units. |
|
miss = miss || {}; |
|
count = miss[unit = entry.unit] || 0; // if we've already matched this unit, skip. |
|
|
|
if (count === -1) continue; |
|
b = testPoint(datum, entry); |
|
miss[unit] = b ? -1 : ++count; // if we match and there are no other units return true |
|
// if we've missed against all tuples in this unit return false |
|
|
|
if (b && unitIdx.size === 1) return true; |
|
if (!b && count === unitIdx.get(unit).count) return false; |
|
} else { |
|
b = testPoint(datum, entry); // if we find a miss and we do require intersection return false |
|
// if we find a match and we don't require intersection return true |
|
|
|
if (intersect ^ b) return b; |
|
} |
|
} // if intersecting and we made it here, then we saw no misses |
|
// if not intersecting, then we saw no matches |
|
// if no active selections, return false |
|
|
|
|
|
return n && intersect; |
|
} |
|
/** |
|
* Resolves selection for use as a scale domain or reads via the API. |
|
* @param {string} name - The name of the dataset representing the selection |
|
* @param {string} [op='union'] - The set operation for combining selections. |
|
* One of 'intersect' or 'union' (default). |
|
* @returns {object} An object of selected fields and values. |
|
*/ |
|
|
|
|
|
function selectionResolve(name, op, isMulti) { |
|
var data = this.context.data[name], |
|
entries = data ? data.values.value : [], |
|
resolved = {}, |
|
multiRes = {}, |
|
types = {}, |
|
entry, |
|
fields, |
|
values, |
|
unit, |
|
field, |
|
res, |
|
resUnit, |
|
type, |
|
union, |
|
n = entries.length, |
|
i = 0, |
|
j, |
|
m; // First union all entries within the same unit. |
|
|
|
for (; i < n; ++i) { |
|
entry = entries[i]; |
|
unit = entry.unit; |
|
fields = entry.fields; |
|
values = entry.values; |
|
|
|
for (j = 0, m = fields.length; j < m; ++j) { |
|
field = fields[j]; |
|
res = resolved[field.field] || (resolved[field.field] = {}); |
|
resUnit = res[unit] || (res[unit] = []); |
|
types[field.field] = type = field.type.charAt(0); |
|
union = ops[type + '_union']; |
|
res[unit] = union(resUnit, array(values[j])); |
|
} // If the same multi-selection is repeated over views and projected over |
|
// an encoding, it may operate over different fields making it especially |
|
// tricky to reliably resolve it. At best, we can de-dupe identical entries |
|
// but doing so may be more computationally expensive than it is worth. |
|
// Instead, for now, we simply transform our store representation into |
|
// a more human-friendly one. |
|
|
|
|
|
if (isMulti) { |
|
resUnit = multiRes[unit] || (multiRes[unit] = []); |
|
resUnit.push(array(values).reduce(function (obj, curr, j) { |
|
return obj[fields[j].field] = curr, obj; |
|
}, {})); |
|
} |
|
} // Then resolve fields across units as per the op. |
|
|
|
|
|
op = op || Union; |
|
Object.keys(resolved).forEach(function (field) { |
|
resolved[field] = Object.keys(resolved[field]).map(function (unit) { |
|
return resolved[field][unit]; |
|
}).reduce(function (acc, curr) { |
|
return acc === undefined ? curr : ops[types[field] + '_' + op](acc, curr); |
|
}); |
|
}); |
|
entries = Object.keys(multiRes); |
|
|
|
if (isMulti && entries.length) { |
|
resolved[VlMulti] = op === Union ? _defineProperty({}, Or, entries.reduce(function (acc, k) { |
|
return acc.push.apply(acc, multiRes[k]), acc; |
|
}, [])) : _defineProperty({}, And, entries.map(function (k) { |
|
return _defineProperty({}, Or, multiRes[k]); |
|
})); |
|
} |
|
|
|
return resolved; |
|
} |
|
|
|
var ops = { |
|
E_union: function E_union(base, value) { |
|
if (!base.length) return value; |
|
var i = 0, |
|
n = value.length; |
|
|
|
for (; i < n; ++i) { |
|
if (base.indexOf(value[i]) < 0) base.push(value[i]); |
|
} |
|
|
|
return base; |
|
}, |
|
E_intersect: function E_intersect(base, value) { |
|
return !base.length ? value : base.filter(function (v) { |
|
return value.indexOf(v) >= 0; |
|
}); |
|
}, |
|
R_union: function R_union(base, value) { |
|
var lo = toNumber(value[0]), |
|
hi = toNumber(value[1]); |
|
|
|
if (lo > hi) { |
|
lo = value[1]; |
|
hi = value[0]; |
|
} |
|
|
|
if (!base.length) return [lo, hi]; |
|
if (base[0] > lo) base[0] = lo; |
|
if (base[1] < hi) base[1] = hi; |
|
return base; |
|
}, |
|
R_intersect: function R_intersect(base, value) { |
|
var lo = toNumber(value[0]), |
|
hi = toNumber(value[1]); |
|
|
|
if (lo > hi) { |
|
lo = value[1]; |
|
hi = value[0]; |
|
} |
|
|
|
if (!base.length) return [lo, hi]; |
|
|
|
if (hi < base[0] || base[1] < lo) { |
|
return []; |
|
} else { |
|
if (base[0] < lo) base[0] = lo; |
|
if (base[1] > hi) base[1] = hi; |
|
} |
|
|
|
return base; |
|
} |
|
}; |
|
var DataPrefix = ':', |
|
IndexPrefix = '@'; |
|
|
|
function selectionVisitor(name, args, scope, params) { |
|
if (args[0].type !== Literal) error('First argument to selection functions must be a string literal.'); |
|
var data = args[0].value, |
|
op = args.length >= 2 && peek(args).value, |
|
field = 'unit', |
|
indexName = IndexPrefix + field, |
|
dataName = DataPrefix + data; // eslint-disable-next-line no-prototype-builtins |
|
|
|
if (op === Intersect && !hasOwnProperty(params, indexName)) { |
|
params[indexName] = scope.getData(data).indataRef(scope, field); |
|
} // eslint-disable-next-line no-prototype-builtins |
|
|
|
|
|
if (!hasOwnProperty(params, dataName)) { |
|
params[dataName] = scope.getData(data).tuplesRef(); |
|
} |
|
} // https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef |
|
|
|
|
|
function channel_luminance_value(channelValue) { |
|
var val = channelValue / 255; |
|
|
|
if (val <= 0.03928) { |
|
return val / 12.92; |
|
} |
|
|
|
return Math.pow((val + 0.055) / 1.055, 2.4); |
|
} |
|
|
|
function luminance(color) { |
|
var 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) { |
|
var lum1 = luminance(color1), |
|
lum2 = luminance(color2), |
|
lumL = Math.max(lum1, lum2), |
|
lumD = Math.min(lum1, lum2); |
|
return (lumL + 0.05) / (lumD + 0.05); |
|
} |
|
|
|
function data$1(name) { |
|
var data = this.context.data[name]; |
|
return data ? data.values.value : []; |
|
} |
|
|
|
function indata(name, field, value) { |
|
var index = this.context.data[name]['index:' + field], |
|
entry = index ? index.value.get(value) : undefined; |
|
return entry ? entry.count : entry; |
|
} |
|
|
|
function setdata(name, tuples) { |
|
var df = this.context.dataflow, |
|
data = this.context.data[name], |
|
input = data.input; |
|
df.pulse(input, df.changeset().remove(truthy).insert(tuples)); |
|
return 1; |
|
} |
|
|
|
function encode$1(item, name, retval) { |
|
if (item) { |
|
var df = this.context.dataflow, |
|
target = item.mark.source; |
|
df.pulse(target, df.changeset().encode(item, name)); |
|
} |
|
|
|
return retval !== undefined ? retval : item; |
|
} |
|
|
|
var formatCache = {}; |
|
|
|
function formatter$1(type, method, specifier) { |
|
var k = type + ':' + specifier, |
|
e = formatCache[k]; |
|
|
|
if (!e || e[0] !== method) { |
|
formatCache[k] = e = [method, method(specifier)]; |
|
} |
|
|
|
return e[1]; |
|
} |
|
|
|
function format$2(_, specifier) { |
|
return formatter$1('format', format$1, specifier)(_); |
|
} |
|
|
|
function timeFormat$2(_, specifier) { |
|
return formatter$1('timeFormat', timeFormat$1, specifier)(_); |
|
} |
|
|
|
function utcFormat$2(_, specifier) { |
|
return formatter$1('utcFormat', utcFormat$1, specifier)(_); |
|
} |
|
|
|
function timeParse$1(_, specifier) { |
|
return formatter$1('timeParse', timeParse, specifier)(_); |
|
} |
|
|
|
function utcParse$1(_, specifier) { |
|
return formatter$1('utcParse', utcParse, specifier)(_); |
|
} |
|
|
|
var dateObj = new Date(2000, 0, 1); |
|
|
|
function time$1(month, day, specifier) { |
|
if (!Number.isInteger(month) || !Number.isInteger(day)) return ''; |
|
dateObj.setYear(2000); |
|
dateObj.setMonth(month); |
|
dateObj.setDate(day); |
|
return timeFormat$2(dateObj, specifier); |
|
} |
|
|
|
function monthFormat(month) { |
|
return time$1(month, 1, '%B'); |
|
} |
|
|
|
function monthAbbrevFormat(month) { |
|
return time$1(month, 1, '%b'); |
|
} |
|
|
|
function dayFormat(day) { |
|
return time$1(0, 2 + day, '%A'); |
|
} |
|
|
|
function dayAbbrevFormat(day) { |
|
return time$1(0, 2 + day, '%a'); |
|
} |
|
|
|
function getScale(name, ctx) { |
|
var s; |
|
return isFunction(name) ? name : isString(name) ? (s = ctx.scales[name]) && s.value : undefined; |
|
} |
|
|
|
function range$3(name, group) { |
|
var s = getScale(name, (group || this).context); |
|
return s && s.range ? s.range() : []; |
|
} |
|
|
|
function domain(name, group) { |
|
var s = getScale(name, (group || this).context); |
|
return s ? s.domain() : []; |
|
} |
|
|
|
function bandwidth(name, group) { |
|
var s = getScale(name, (group || this).context); |
|
return s && s.bandwidth ? s.bandwidth() : 0; |
|
} |
|
|
|
function bandspace(count, paddingInner, paddingOuter) { |
|
return bandSpace(count || 0, paddingInner || 0, paddingOuter || 0); |
|
} |
|
|
|
function copy$2(name, group) { |
|
var s = getScale(name, (group || this).context); |
|
return s ? s.copy() : undefined; |
|
} |
|
|
|
function scale$3(name, value, group) { |
|
var s = getScale(name, (group || this).context); |
|
return s && value !== undefined ? s(value) : undefined; |
|
} |
|
|
|
function invert(name, range, group) { |
|
var s = getScale(name, (group || this).context); |
|
return !s ? undefined : isArray(range) ? (s.invertRange || s.invert)(range) : (s.invert || s.invertExtent)(range); |
|
} |
|
|
|
function geoMethod(methodName, globalMethod) { |
|
return function (projection, geojson, group) { |
|
if (projection) { |
|
// projection defined, use it |
|
var p = getScale(projection, (group || this).context); |
|
return p && p.path[methodName](geojson); |
|
} else { |
|
// projection undefined, use global method |
|
return globalMethod(geojson); |
|
} |
|
}; |
|
} |
|
|
|
var geoArea = geoMethod('area', area$3); |
|
var geoBounds = geoMethod('bounds', bounds$1); |
|
var geoCentroid = geoMethod('centroid', centroid); |
|
|
|
function inScope(item) { |
|
var group = this.context.group, |
|
value = false; |
|
if (group) while (item) { |
|
if (item === group) { |
|
value = true; |
|
break; |
|
} |
|
|
|
item = item.mark.group; |
|
} |
|
return value; |
|
} |
|
|
|
function intersect$3(b, opt, group) { |
|
if (!b) return []; |
|
|
|
var _b4 = _slicedToArray(b, 2), |
|
u = _b4[0], |
|
v = _b4[1], |
|
box = new Bounds().set(u[0], u[1], v[0], v[1]), |
|
scene = group || this.context.dataflow.scenegraph().root; |
|
|
|
return intersect$1(scene, box, filter$2(opt)); |
|
} |
|
|
|
function filter$2(opt) { |
|
var p = null; |
|
|
|
if (opt) { |
|
var types = array(opt.marktype), |
|
names = array(opt.markname); |
|
|
|
p = function p(_) { |
|
return (!types.length || types.some(function (t) { |
|
return _.marktype === t; |
|
})) && (!names.length || names.some(function (s) { |
|
return _.name === s; |
|
})); |
|
}; |
|
} |
|
|
|
return p; |
|
} |
|
|
|
function log$4(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$4(this.context.dataflow, 'warn', arguments); |
|
} |
|
|
|
function info() { |
|
return log$4(this.context.dataflow, 'info', arguments); |
|
} |
|
|
|
function debug() { |
|
return log$4(this.context.dataflow, 'debug', arguments); |
|
} |
|
|
|
function merge$3() { |
|
var args = [].slice.call(arguments); |
|
args.unshift({}); |
|
return extend.apply(null, args); |
|
} |
|
|
|
function equal(a, b) { |
|
return a === b || a !== a && b !== b ? true : isArray(a) ? isArray(b) && a.length === b.length ? equalArray(a, b) : false : isObject(a) && isObject(b) ? equalObject(a, b) : false; |
|
} |
|
|
|
function equalArray(a, b) { |
|
for (var i = 0, n = a.length; i < n; ++i) { |
|
if (!equal(a[i], b[i])) return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function equalObject(a, b) { |
|
for (var _key4 in a) { |
|
if (!equal(a[_key4], b[_key4])) return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function removePredicate(props) { |
|
return function (_) { |
|
return equalObject(props, _); |
|
}; |
|
} |
|
|
|
function modify(name, insert, remove, toggle, modify, values) { |
|
var df = this.context.dataflow, |
|
data = this.context.data[name], |
|
input = data.input, |
|
changes = data.changes, |
|
stamp = df.stamp(), |
|
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(function () { |
|
data.modified = true; |
|
df.pulse(input, changes).run(); |
|
}, true, 1); |
|
} |
|
|
|
if (remove) { |
|
predicate = remove === true ? truthy : isArray(remove) || 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) { |
|
var t = event.touches, |
|
dx = t[0].clientX - t[1].clientX, |
|
dy = t[0].clientY - t[1].clientY; |
|
return Math.sqrt(dx * dx + dy * dy); |
|
} |
|
|
|
function pinchAngle(event) { |
|
var t = event.touches; |
|
return Math.atan2(t[0].clientY - t[1].clientY, t[0].clientX - t[1].clientX); |
|
} |
|
|
|
function scaleGradient(scale, p0, p1, count, group) { |
|
scale = getScale(scale, (group || this).context); |
|
var gradient = Gradient(p0, p1); |
|
var stops = scale.domain(), |
|
min = stops[0], |
|
max = peek(stops), |
|
fraction = identity; |
|
|
|
if (!(max - min)) { |
|
// expand scale if domain has zero span, fix #1479 |
|
scale = (scale.interpolator ? scale$2('sequential')().interpolator(scale.interpolator()) : scale$2('linear')().interpolate(scale.interpolate()).range(scale.range())).domain([min = 0, max = 1]); |
|
} else { |
|
fraction = scaleFraction(scale, min, max); |
|
} |
|
|
|
if (scale.ticks) { |
|
stops = scale.ticks(+count || 15); |
|
if (min !== stops[0]) stops.unshift(min); |
|
if (max !== peek(stops)) stops.push(max); |
|
} |
|
|
|
stops.forEach(function (_) { |
|
return gradient.stop(fraction(_), scale(_)); |
|
}); |
|
return gradient; |
|
} |
|
|
|
function geoShape(projection, geojson, group) { |
|
var p = getScale(projection, (group || this).context); |
|
return function (context) { |
|
return p ? p.path.context(context)(geojson) : ''; |
|
}; |
|
} |
|
|
|
function pathShape(path) { |
|
var p = null; |
|
return function (context) { |
|
return context ? pathRender(context, p = p || pathParse(path)) : path; |
|
}; |
|
} |
|
|
|
var EMPTY = {}; |
|
|
|
function datum(d) { |
|
return d.data; |
|
} |
|
|
|
function treeNodes(name, context) { |
|
var tree = data$1.call(context, name); |
|
return tree.root && tree.root.lookup || EMPTY; |
|
} |
|
|
|
function treePath(name, source, target) { |
|
var nodes = treeNodes(name, this), |
|
s = nodes[source], |
|
t = nodes[target]; |
|
return s && t ? s.path(t).map(datum) : undefined; |
|
} |
|
|
|
function treeAncestors(name, node) { |
|
var n = treeNodes(name, this)[node]; |
|
return n ? n.ancestors().map(datum) : undefined; |
|
} |
|
|
|
var _window = typeof window !== 'undefined' && window || null; |
|
|
|
function screen() { |
|
return _window ? _window.screen : {}; |
|
} |
|
|
|
function windowSize() { |
|
return _window ? [_window.innerWidth, _window.innerHeight] : [undefined, undefined]; |
|
} |
|
|
|
function containerSize() { |
|
var view = this.context.dataflow, |
|
el = view.container && view.container(); |
|
return el ? [el.clientWidth, el.clientHeight] : [undefined, undefined]; |
|
} |
|
|
|
var DataPrefix$1 = ':'; |
|
var IndexPrefix$1 = '@'; |
|
var ScalePrefix = '%'; |
|
var SignalPrefix = '$'; |
|
|
|
function dataVisitor(name, args, scope, params) { |
|
if (args[0].type !== Literal) { |
|
error('First argument to data functions must be a string literal.'); |
|
} |
|
|
|
var data = args[0].value, |
|
dataName = DataPrefix$1 + data; |
|
|
|
if (!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 !== Literal) error('First argument to indata must be a string literal.'); |
|
if (args[1].type !== Literal) error('Second argument to indata must be a string literal.'); |
|
var data = args[0].value, |
|
field = args[1].value, |
|
indexName = IndexPrefix$1 + field; |
|
|
|
if (!hasOwnProperty(indexName, params)) { |
|
params[indexName] = scope.getData(data).indataRef(scope, field); |
|
} |
|
} |
|
|
|
function scaleVisitor(name, args, scope, params) { |
|
if (args[0].type === Literal) { |
|
// add scale dependency |
|
addScaleDependency(scope, params, args[0].value); |
|
} else if (args[0].type === Identifier$1) { |
|
// indirect scale lookup; add all scales as parameters |
|
for (name in scope.scales) { |
|
addScaleDependency(scope, params, name); |
|
} |
|
} |
|
} |
|
|
|
function addScaleDependency(scope, params, name) { |
|
var scaleName = ScalePrefix + name; |
|
|
|
if (!hasOwnProperty(params, scaleName)) { |
|
try { |
|
params[scaleName] = scope.scaleRef(name); |
|
} catch (err) {// TODO: error handling? warning? |
|
} |
|
} |
|
} // Expression function context object |
|
|
|
|
|
var functionContext = { |
|
random: function random() { |
|
return exports.random(); |
|
}, |
|
// override default |
|
cumulativeNormal: cumulativeNormal, |
|
cumulativeLogNormal: cumulativeLogNormal, |
|
cumulativeUniform: cumulativeUniform, |
|
densityNormal: densityNormal, |
|
densityLogNormal: densityLogNormal, |
|
densityUniform: densityUniform, |
|
quantileNormal: quantileNormal, |
|
quantileLogNormal: quantileLogNormal, |
|
quantileUniform: quantileUniform, |
|
sampleNormal: sampleNormal, |
|
sampleLogNormal: sampleLogNormal, |
|
sampleUniform: sampleUniform, |
|
isArray: isArray, |
|
isBoolean: isBoolean, |
|
isDate: isDate, |
|
isDefined: function isDefined(_) { |
|
return _ !== undefined; |
|
}, |
|
isNumber: isNumber, |
|
isObject: isObject, |
|
isRegExp: isRegExp, |
|
isString: isString, |
|
isTuple: isTuple, |
|
isValid: function isValid(_) { |
|
return _ != null && _ === _; |
|
}, |
|
toBoolean: toBoolean, |
|
toDate: toDate, |
|
toNumber: toNumber, |
|
toString: toString, |
|
flush: flush, |
|
lerp: lerp, |
|
merge: merge$3, |
|
pad: pad, |
|
peek: peek, |
|
span: span, |
|
inrange: inrange, |
|
truncate: truncate, |
|
rgb: rgb, |
|
lab: lab, |
|
hcl: hcl, |
|
hsl: hsl, |
|
luminance: luminance, |
|
contrast: contrast, |
|
sequence: sequence, |
|
format: format$2, |
|
utcFormat: utcFormat$2, |
|
utcParse: utcParse$1, |
|
utcOffset: utcOffset, |
|
utcSequence: utcSequence, |
|
timeFormat: timeFormat$2, |
|
timeParse: timeParse$1, |
|
timeOffset: timeOffset, |
|
timeSequence: timeSequence, |
|
timeUnitSpecifier: timeUnitSpecifier, |
|
monthFormat: monthFormat, |
|
monthAbbrevFormat: monthAbbrevFormat, |
|
dayFormat: dayFormat, |
|
dayAbbrevFormat: dayAbbrevFormat, |
|
quarter: quarter, |
|
utcquarter: utcquarter, |
|
warn: warn, |
|
info: info, |
|
debug: debug, |
|
extent: extent, |
|
inScope: inScope, |
|
intersect: intersect$3, |
|
clampRange: clampRange, |
|
pinchDistance: pinchDistance, |
|
pinchAngle: pinchAngle, |
|
screen: screen, |
|
containerSize: containerSize, |
|
windowSize: windowSize, |
|
bandspace: bandspace, |
|
setdata: setdata, |
|
pathShape: pathShape, |
|
panLinear: panLinear, |
|
panLog: panLog, |
|
panPow: panPow, |
|
panSymlog: panSymlog, |
|
zoomLinear: zoomLinear, |
|
zoomLog: zoomLog, |
|
zoomPow: zoomPow, |
|
zoomSymlog: zoomSymlog, |
|
encode: encode$1, |
|
modify: modify |
|
}; |
|
var eventFunctions = ['view', 'item', 'group', 'xy', 'x', 'y'], |
|
// event functions |
|
eventPrefix = 'event.vega.', |
|
// event function prefix |
|
thisPrefix = 'this.', |
|
// function context prefix |
|
astVisitors = {}; // AST visitors for dependency analysis |
|
// Build expression function registry |
|
|
|
function buildFunctions(codegen) { |
|
var fn = functions(codegen); |
|
eventFunctions.forEach(function (name) { |
|
return fn[name] = eventPrefix + name; |
|
}); |
|
|
|
for (var name in functionContext) { |
|
fn[name] = thisPrefix + name; |
|
} |
|
|
|
return fn; |
|
} // Register an expression function |
|
|
|
|
|
function expressionFunction(name, fn, visitor) { |
|
if (arguments.length === 1) { |
|
return functionContext[name]; |
|
} // register with the functionContext |
|
|
|
|
|
functionContext[name] = fn; // if there is an astVisitor register that, too |
|
|
|
if (visitor) astVisitors[name] = visitor; // if the code generator has already been initialized, |
|
// we need to also register the function with it |
|
|
|
if (codeGenerator) codeGenerator.functions[name] = thisPrefix + name; |
|
return this; |
|
} // register expression functions with ast visitors |
|
|
|
|
|
expressionFunction('bandwidth', bandwidth, scaleVisitor); |
|
expressionFunction('copy', copy$2, scaleVisitor); |
|
expressionFunction('domain', domain, scaleVisitor); |
|
expressionFunction('range', range$3, scaleVisitor); |
|
expressionFunction('invert', invert, scaleVisitor); |
|
expressionFunction('scale', scale$3, scaleVisitor); |
|
expressionFunction('gradient', scaleGradient, scaleVisitor); |
|
expressionFunction('geoArea', geoArea, scaleVisitor); |
|
expressionFunction('geoBounds', geoBounds, scaleVisitor); |
|
expressionFunction('geoCentroid', geoCentroid, scaleVisitor); |
|
expressionFunction('geoShape', geoShape, scaleVisitor); |
|
expressionFunction('indata', indata, indataVisitor); |
|
expressionFunction('data', data$1, dataVisitor); |
|
expressionFunction('treePath', treePath, dataVisitor); |
|
expressionFunction('treeAncestors', treeAncestors, dataVisitor); // register Vega-Lite selection functions |
|
|
|
expressionFunction('vlSelectionTest', selectionTest, selectionVisitor); |
|
expressionFunction('vlSelectionResolve', selectionResolve, selectionVisitor); // Export code generator and parameters |
|
|
|
var codegenParams = { |
|
blacklist: ['_'], |
|
whitelist: ['datum', 'event', 'item'], |
|
fieldvar: 'datum', |
|
globalvar: function globalvar(id) { |
|
return '_[' + $(SignalPrefix + id) + ']'; |
|
}, |
|
functions: buildFunctions, |
|
constants: constants, |
|
visitors: astVisitors |
|
}; |
|
var codeGenerator = codegen(codegenParams); |
|
/** |
|
* Parse an expression given the argument signature and body code. |
|
*/ |
|
|
|
function expression(args, code, ctx) { |
|
// wrap code in return statement if expression does not terminate |
|
if (code[code.length - 1] !== ';') { |
|
code = 'return(' + code + ');'; |
|
} |
|
|
|
var fn = Function.apply(null, args.concat(code)); |
|
return ctx && ctx.functions ? fn.bind(ctx.functions) : fn; |
|
} |
|
/** |
|
* Parse an expression used to update an operator value. |
|
*/ |
|
|
|
|
|
function operatorExpression(code, ctx) { |
|
return expression(['_'], code, ctx); |
|
} |
|
/** |
|
* Parse an expression provided as an operator parameter value. |
|
*/ |
|
|
|
|
|
function parameterExpression(code, ctx) { |
|
return expression(['datum', '_'], code, ctx); |
|
} |
|
/** |
|
* Parse an expression applied to an event stream. |
|
*/ |
|
|
|
|
|
function eventExpression(code, ctx) { |
|
return expression(['event'], code, ctx); |
|
} |
|
/** |
|
* Parse an expression used to handle an event-driven operator update. |
|
*/ |
|
|
|
|
|
function handlerExpression(code, ctx) { |
|
return expression(['_', 'event'], code, ctx); |
|
} |
|
/** |
|
* Parse an expression that performs visual encoding. |
|
*/ |
|
|
|
|
|
function encodeExpression(code, ctx) { |
|
return expression(['item', '_'], code, ctx); |
|
} |
|
/** |
|
* Parse a set of operator parameters. |
|
*/ |
|
|
|
|
|
function parseParameters(spec, ctx, params) { |
|
params = params || {}; |
|
var key, value; |
|
|
|
for (key in spec) { |
|
value = spec[key]; |
|
params[key] = isArray(value) ? value.map(function (v) { |
|
return parseParameter(v, ctx, params); |
|
}) : parseParameter(value, ctx, params); |
|
} |
|
|
|
return params; |
|
} |
|
/** |
|
* Parse a single parameter. |
|
*/ |
|
|
|
|
|
function parseParameter(spec, ctx, params) { |
|
if (!spec || !isObject(spec)) return spec; |
|
|
|
for (var i = 0, n = PARSERS.length, p; i < n; ++i) { |
|
p = PARSERS[i]; |
|
|
|
if (hasOwnProperty(spec, p.key)) { |
|
return p.parse(spec, ctx, params); |
|
} |
|
} |
|
|
|
return spec; |
|
} |
|
/** Reference parsers. */ |
|
|
|
|
|
var PARSERS = [{ |
|
key: '$ref', |
|
parse: getOperator |
|
}, { |
|
key: '$key', |
|
parse: getKey |
|
}, { |
|
key: '$expr', |
|
parse: getExpression |
|
}, { |
|
key: '$field', |
|
parse: getField$1 |
|
}, { |
|
key: '$encode', |
|
parse: getEncode |
|
}, { |
|
key: '$compare', |
|
parse: getCompare |
|
}, { |
|
key: '$context', |
|
parse: getContext |
|
}, { |
|
key: '$subflow', |
|
parse: getSubflow |
|
}, { |
|
key: '$tupleid', |
|
parse: getTupleId |
|
}]; |
|
/** |
|
* Resolve an operator reference. |
|
*/ |
|
|
|
function getOperator(_, ctx) { |
|
return ctx.get(_.$ref) || error('Operator not defined: ' + _.$ref); |
|
} |
|
/** |
|
* Resolve an expression reference. |
|
*/ |
|
|
|
|
|
function getExpression(_, ctx, params) { |
|
if (_.$params) { |
|
// parse expression parameters |
|
parseParameters(_.$params, ctx, params); |
|
} |
|
|
|
var k = 'e:' + _.$expr + '_' + _.$name; |
|
return ctx.fn[k] || (ctx.fn[k] = accessor(parameterExpression(_.$expr, ctx), _.$fields, _.$name)); |
|
} |
|
/** |
|
* Resolve a key accessor reference. |
|
*/ |
|
|
|
|
|
function getKey(_, ctx) { |
|
var k = 'k:' + _.$key + '_' + !!_.$flat; |
|
return ctx.fn[k] || (ctx.fn[k] = key(_.$key, _.$flat)); |
|
} |
|
/** |
|
* Resolve a field accessor reference. |
|
*/ |
|
|
|
|
|
function getField$1(_, ctx) { |
|
if (!_.$field) return null; |
|
var k = 'f:' + _.$field + '_' + _.$name; |
|
return ctx.fn[k] || (ctx.fn[k] = field(_.$field, _.$name)); |
|
} |
|
/** |
|
* Resolve a comparator function reference. |
|
*/ |
|
|
|
|
|
function getCompare(_, ctx) { |
|
// As of Vega 5.5.3, $tupleid sort is no longer used. |
|
// Keep here for now for backwards compatibility. |
|
var k = 'c:' + _.$compare + '_' + _.$order, |
|
c = array(_.$compare).map(function (_) { |
|
return _ && _.$tupleid ? tupleid : _; |
|
}); |
|
return ctx.fn[k] || (ctx.fn[k] = compare(c, _.$order)); |
|
} |
|
/** |
|
* Resolve an encode operator reference. |
|
*/ |
|
|
|
|
|
function getEncode(_, ctx) { |
|
var spec = _.$encode, |
|
encode = {}, |
|
name, |
|
enc; |
|
|
|
for (name in spec) { |
|
enc = spec[name]; |
|
encode[name] = accessor(encodeExpression(enc.$expr, ctx), enc.$fields); |
|
encode[name].output = enc.$output; |
|
} |
|
|
|
return encode; |
|
} |
|
/** |
|
* Resolve a context reference. |
|
*/ |
|
|
|
|
|
function getContext(_, ctx) { |
|
return ctx; |
|
} |
|
/** |
|
* Resolve a recursive subflow specification. |
|
*/ |
|
|
|
|
|
function getSubflow(_, ctx) { |
|
var spec = _.$subflow; |
|
return function (dataflow, key, parent) { |
|
var subctx = parse$4(spec, ctx.fork()), |
|
op = subctx.get(spec.operators[0].id), |
|
p = subctx.signals.parent; |
|
if (p) p.set(parent); |
|
return op; |
|
}; |
|
} |
|
/** |
|
* Resolve a tuple id reference. |
|
*/ |
|
|
|
|
|
function getTupleId() { |
|
return tupleid; |
|
} |
|
|
|
function canonicalType(type) { |
|
return (type + '').toLowerCase(); |
|
} |
|
|
|
function isOperator(type) { |
|
return canonicalType(type) === 'operator'; |
|
} |
|
|
|
function isCollect(type) { |
|
return canonicalType(type) === 'collect'; |
|
} |
|
/** |
|
* Parse a dataflow operator. |
|
*/ |
|
|
|
|
|
function parseOperator(spec, ctx) { |
|
if (isOperator(spec.type) || !spec.type) { |
|
ctx.operator(spec, spec.update ? operatorExpression(spec.update, ctx) : null); |
|
} else { |
|
ctx.transform(spec, spec.type); |
|
} |
|
} |
|
/** |
|
* Parse and assign operator parameters. |
|
*/ |
|
|
|
|
|
function parseOperatorParameters(spec, ctx) { |
|
if (spec.params) { |
|
var op = ctx.get(spec.id); |
|
if (!op) error('Invalid operator id: ' + spec.id); |
|
ctx.dataflow.connect(op, op.parameters(parseParameters(spec.params, ctx), spec.react, spec.initonly)); |
|
} |
|
} |
|
/** |
|
* Parse an event stream specification. |
|
*/ |
|
|
|
|
|
function parseStream(spec, ctx) { |
|
var filter = spec.filter != null ? eventExpression(spec.filter, ctx) : undefined, |
|
stream = spec.stream != null ? ctx.get(spec.stream) : undefined, |
|
args; |
|
|
|
if (spec.source) { |
|
stream = ctx.events(spec.source, spec.type, filter); |
|
} else if (spec.merge) { |
|
args = spec.merge.map(ctx.get.bind(ctx)); |
|
stream = args[0].merge.apply(args[0], args.slice(1)); |
|
} |
|
|
|
if (spec.between) { |
|
args = spec.between.map(ctx.get.bind(ctx)); |
|
stream = stream.between(args[0], args[1]); |
|
} |
|
|
|
if (spec.filter) { |
|
stream = stream.filter(filter); |
|
} |
|
|
|
if (spec.throttle != null) { |
|
stream = stream.throttle(+spec.throttle); |
|
} |
|
|
|
if (spec.debounce != null) { |
|
stream = stream.debounce(+spec.debounce); |
|
} |
|
|
|
if (stream == null) { |
|
error('Invalid stream definition: ' + JSON.stringify(spec)); |
|
} |
|
|
|
if (spec.consume) stream.consume(true); |
|
ctx.stream(spec, stream); |
|
} |
|
/** |
|
* Parse an event-driven operator update. |
|
*/ |
|
|
|
|
|
function parseUpdate(spec, ctx) { |
|
var srcid = isObject(srcid = spec.source) ? srcid.$ref : srcid, |
|
source = ctx.get(srcid), |
|
target = null, |
|
update = spec.update, |
|
params = undefined; |
|
if (!source) error('Source not defined: ' + spec.source); |
|
|
|
if (spec.target && spec.target.$expr) { |
|
target = eventExpression(spec.target.$expr, ctx); |
|
} else { |
|
target = ctx.get(spec.target); |
|
} |
|
|
|
if (update && update.$expr) { |
|
if (update.$params) { |
|
params = parseParameters(update.$params, ctx); |
|
} |
|
|
|
update = handlerExpression(update.$expr, ctx); |
|
} |
|
|
|
ctx.update(spec, source, target, update, params); |
|
} |
|
/** |
|
* Parse a serialized dataflow specification. |
|
*/ |
|
|
|
|
|
function parse$4(spec, ctx) { |
|
var operators = spec.operators || []; // parse background |
|
|
|
if (spec.background) { |
|
ctx.background = spec.background; |
|
} // parse event configuration |
|
|
|
|
|
if (spec.eventConfig) { |
|
ctx.eventConfig = spec.eventConfig; |
|
} // parse operators |
|
|
|
|
|
operators.forEach(function (entry) { |
|
parseOperator(entry, ctx); |
|
}); // parse operator parameters |
|
|
|
operators.forEach(function (entry) { |
|
parseOperatorParameters(entry, ctx); |
|
}); // parse streams |
|
|
|
(spec.streams || []).forEach(function (entry) { |
|
parseStream(entry, ctx); |
|
}); // parse updates |
|
|
|
(spec.updates || []).forEach(function (entry) { |
|
parseUpdate(entry, ctx); |
|
}); |
|
return ctx.resolve(); |
|
} |
|
|
|
var SKIP$3 = { |
|
skip: true |
|
}; |
|
|
|
function getState(options) { |
|
var ctx = this, |
|
state = {}; |
|
|
|
if (options.signals) { |
|
var signals = state.signals = {}; |
|
Object.keys(ctx.signals).forEach(function (key) { |
|
var op = ctx.signals[key]; |
|
|
|
if (options.signals(key, op)) { |
|
signals[key] = op.value; |
|
} |
|
}); |
|
} |
|
|
|
if (options.data) { |
|
var data = state.data = {}; |
|
Object.keys(ctx.data).forEach(function (key) { |
|
var dataset = ctx.data[key]; |
|
|
|
if (options.data(key, dataset)) { |
|
data[key] = dataset.input.value; |
|
} |
|
}); |
|
} |
|
|
|
if (ctx.subcontext && options.recurse !== false) { |
|
state.subcontext = ctx.subcontext.map(function (ctx) { |
|
return ctx.getState(options); |
|
}); |
|
} |
|
|
|
return state; |
|
} |
|
|
|
function setState(state) { |
|
var ctx = this, |
|
df = ctx.dataflow, |
|
data = state.data, |
|
signals = state.signals; |
|
Object.keys(signals || {}).forEach(function (key) { |
|
df.update(ctx.signals[key], signals[key], SKIP$3); |
|
}); |
|
Object.keys(data || {}).forEach(function (key) { |
|
df.pulse(ctx.data[key].input, df.changeset().remove(truthy).insert(data[key])); |
|
}); |
|
(state.subcontext || []).forEach(function (substate, i) { |
|
var subctx = ctx.subcontext[i]; |
|
if (subctx) subctx.setState(substate); |
|
}); |
|
} |
|
/** |
|
* Context objects store the current parse state. |
|
* Enables lookup of parsed operators, event streams, accessors, etc. |
|
* Provides a 'fork' method for creating child contexts for subflows. |
|
*/ |
|
|
|
|
|
function context$2(df, transforms, functions) { |
|
return new Context(df, transforms, functions); |
|
} |
|
|
|
function Context(df, transforms, functions) { |
|
this.dataflow = df; |
|
this.transforms = transforms; |
|
this.events = df.events.bind(df); |
|
this.signals = {}; |
|
this.scales = {}; |
|
this.nodes = {}; |
|
this.data = {}; |
|
this.fn = {}; |
|
|
|
if (functions) { |
|
this.functions = Object.create(functions); |
|
this.functions.context = this; |
|
} |
|
} |
|
|
|
function ContextFork(ctx) { |
|
this.dataflow = ctx.dataflow; |
|
this.transforms = ctx.transforms; |
|
this.functions = ctx.functions; |
|
this.events = ctx.events; |
|
this.signals = Object.create(ctx.signals); |
|
this.scales = Object.create(ctx.scales); |
|
this.nodes = Object.create(ctx.nodes); |
|
this.data = Object.create(ctx.data); |
|
this.fn = Object.create(ctx.fn); |
|
|
|
if (ctx.functions) { |
|
this.functions = Object.create(ctx.functions); |
|
this.functions.context = this; |
|
} |
|
} |
|
|
|
Context.prototype = ContextFork.prototype = { |
|
fork: function fork() { |
|
var ctx = new ContextFork(this); |
|
(this.subcontext || (this.subcontext = [])).push(ctx); |
|
return ctx; |
|
}, |
|
get: function get(id) { |
|
return this.nodes[id]; |
|
}, |
|
set: function set(id, node) { |
|
return this.nodes[id] = node; |
|
}, |
|
add: function add(spec, op) { |
|
var ctx = this, |
|
df = ctx.dataflow, |
|
data; |
|
ctx.set(spec.id, op); |
|
|
|
if (isCollect(spec.type) && (data = spec.value)) { |
|
if (data.$ingest) { |
|
df.ingest(op, data.$ingest, data.$format); |
|
} else if (data.$request) { |
|
df.preload(op, data.$request, data.$format); |
|
} else { |
|
df.pulse(op, df.changeset().insert(data)); |
|
} |
|
} |
|
|
|
if (spec.root) { |
|
ctx.root = op; |
|
} |
|
|
|
if (spec.parent) { |
|
var p = ctx.get(spec.parent.$ref); |
|
|
|
if (p) { |
|
df.connect(p, [op]); |
|
op.targets().add(p); |
|
} else { |
|
(ctx.unresolved = ctx.unresolved || []).push(function () { |
|
p = ctx.get(spec.parent.$ref); |
|
df.connect(p, [op]); |
|
op.targets().add(p); |
|
}); |
|
} |
|
} |
|
|
|
if (spec.signal) { |
|
ctx.signals[spec.signal] = op; |
|
} |
|
|
|
if (spec.scale) { |
|
ctx.scales[spec.scale] = op; |
|
} |
|
|
|
if (spec.data) { |
|
for (var name in spec.data) { |
|
data = ctx.data[name] || (ctx.data[name] = {}); |
|
spec.data[name].forEach(function (role) { |
|
data[role] = op; |
|
}); |
|
} |
|
} |
|
}, |
|
resolve: function resolve() { |
|
(this.unresolved || []).forEach(function (fn) { |
|
fn(); |
|
}); |
|
delete this.unresolved; |
|
return this; |
|
}, |
|
operator: function operator(spec, update) { |
|
this.add(spec, this.dataflow.add(spec.value, update)); |
|
}, |
|
transform: function transform(spec, type) { |
|
this.add(spec, this.dataflow.add(this.transforms[canonicalType(type)])); |
|
}, |
|
stream: function stream(spec, _stream2) { |
|
this.set(spec.id, _stream2); |
|
}, |
|
update: function update(spec, stream, target, _update2, params) { |
|
this.dataflow.on(stream, target, _update2, params, spec.options); |
|
}, |
|
getState: getState, |
|
setState: setState |
|
}; |
|
|
|
function runtime(view, spec, functions) { |
|
var fn = functions || functionContext; |
|
return parse$4(spec, context$2(view, transforms, fn)); |
|
} |
|
|
|
function scale$4(name) { |
|
var scales = this._runtime.scales; |
|
|
|
if (!hasOwnProperty(scales, name)) { |
|
error('Unrecognized scale or projection: ' + name); |
|
} |
|
|
|
return scales[name].value; |
|
} |
|
|
|
var Width = 'width', |
|
Height = 'height', |
|
Padding$1 = 'padding', |
|
Skip = { |
|
skip: true |
|
}; |
|
|
|
function viewWidth(view, width) { |
|
var a = view.autosize(), |
|
p = view.padding(); |
|
return width - (a && a.contains === Padding$1 ? p.left + p.right : 0); |
|
} |
|
|
|
function viewHeight(view, height) { |
|
var a = view.autosize(), |
|
p = view.padding(); |
|
return height - (a && a.contains === Padding$1 ? p.top + p.bottom : 0); |
|
} |
|
|
|
function initializeResize(view) { |
|
var s = view._signals, |
|
w = s[Width], |
|
h = s[Height], |
|
p = s[Padding$1]; |
|
|
|
function resetSize() { |
|
view._autosize = view._resize = 1; |
|
} // respond to width signal |
|
|
|
|
|
view._resizeWidth = view.add(null, function (_) { |
|
view._width = _.size; |
|
view._viewWidth = viewWidth(view, _.size); |
|
resetSize(); |
|
}, { |
|
size: w |
|
}); // respond to height signal |
|
|
|
view._resizeHeight = view.add(null, function (_) { |
|
view._height = _.size; |
|
view._viewHeight = viewHeight(view, _.size); |
|
resetSize(); |
|
}, { |
|
size: h |
|
}); // respond to padding signal |
|
|
|
var resizePadding = view.add(null, resetSize, { |
|
pad: p |
|
}); // set rank to run immediately after source signal |
|
|
|
view._resizeWidth.rank = w.rank + 1; |
|
view._resizeHeight.rank = h.rank + 1; |
|
resizePadding.rank = p.rank + 1; |
|
} |
|
|
|
function resizeView(viewWidth, viewHeight, width, height, origin, auto) { |
|
this.runAfter(function (view) { |
|
var rerun = 0; // reset autosize flag |
|
|
|
view._autosize = 0; // width value changed: update signal, skip resize op |
|
|
|
if (view.width() !== width) { |
|
rerun = 1; |
|
view.signal(Width, width, Skip); // set width, skip update calc |
|
|
|
view._resizeWidth.skip(true); // skip width resize handler |
|
|
|
} // height value changed: update signal, skip resize op |
|
|
|
|
|
if (view.height() !== height) { |
|
rerun = 1; |
|
view.signal(Height, height, Skip); // set height, skip update calc |
|
|
|
view._resizeHeight.skip(true); // skip height resize handler |
|
|
|
} // view width changed: update view property, set resize flag |
|
|
|
|
|
if (view._viewWidth !== viewWidth) { |
|
view._resize = 1; |
|
view._viewWidth = viewWidth; |
|
} // view height changed: update view property, set resize flag |
|
|
|
|
|
if (view._viewHeight !== viewHeight) { |
|
view._resize = 1; |
|
view._viewHeight = viewHeight; |
|
} // origin changed: update view property, set resize flag |
|
|
|
|
|
if (view._origin[0] !== origin[0] || view._origin[1] !== origin[1]) { |
|
view._resize = 1; |
|
view._origin = origin; |
|
} // run dataflow on width/height signal change |
|
|
|
|
|
if (rerun) view.run('enter'); |
|
if (auto) view.runAfter(function (v) { |
|
return v.resize(); |
|
}); |
|
}, false, 1); |
|
} |
|
/** |
|
* Get the current view state, consisting of signal values and/or data sets. |
|
* @param {object} [options] - Options flags indicating which state to export. |
|
* If unspecified, all signals and data sets will be exported. |
|
* @param {function(string, Operator):boolean} [options.signals] - Optional |
|
* predicate function for testing if a signal should be included in the |
|
* exported state. If unspecified, all signals will be included, except for |
|
* those named 'parent' or those which refer to a Transform value. |
|
* @param {function(string, object):boolean} [options.data] - Optional |
|
* predicate function for testing if a data set's input should be included |
|
* in the exported state. If unspecified, all data sets that have been |
|
* explicitly modified will be included. |
|
* @param {boolean} [options.recurse=true] - Flag indicating if the exported |
|
* state should recursively include state from group mark sub-contexts. |
|
* @return {object} - An object containing the exported state values. |
|
*/ |
|
|
|
|
|
function getState$1(options) { |
|
return this._runtime.getState(options || { |
|
data: dataTest, |
|
signals: signalTest, |
|
recurse: true |
|
}); |
|
} |
|
|
|
function dataTest(name, data) { |
|
return data.modified && isArray(data.input.value) && name.indexOf('_:vega:_'); |
|
} |
|
|
|
function signalTest(name, op) { |
|
return !(name === 'parent' || op instanceof transforms.proxy); |
|
} |
|
/** |
|
* Sets the current view state and updates the view by invoking run. |
|
* @param {object} state - A state object containing signal and/or |
|
* data set values, following the format used by the getState method. |
|
* @return {View} - This view instance. |
|
*/ |
|
|
|
|
|
function setState$1(state) { |
|
this.runAsync(null, function (v) { |
|
v._trigger = false; |
|
|
|
v._runtime.setState(state); |
|
}, function (v) { |
|
v._trigger = true; |
|
}); |
|
return this; |
|
} |
|
|
|
function timer$1(callback, delay) { |
|
function tick(elapsed) { |
|
callback({ |
|
timestamp: Date.now(), |
|
elapsed: elapsed |
|
}); |
|
} |
|
|
|
this._timers.push(interval$1(tick, delay)); |
|
} |
|
|
|
function defaultTooltip$1(handler, event, item, value) { |
|
var el = handler.element(); |
|
if (el) el.setAttribute('title', formatTooltip(value)); |
|
} |
|
|
|
function formatTooltip(value) { |
|
return value == null ? '' : isArray(value) ? formatArray(value) : isObject(value) && !isDate(value) ? formatObject(value) : value + ''; |
|
} |
|
|
|
function formatObject(obj) { |
|
return Object.keys(obj).map(function (key) { |
|
var v = obj[key]; |
|
return key + ': ' + (isArray(v) ? formatArray(v) : formatValue$1(v)); |
|
}).join('\n'); |
|
} |
|
|
|
function formatArray(value) { |
|
return '[' + value.map(formatValue$1).join(', ') + ']'; |
|
} |
|
|
|
function formatValue$1(value) { |
|
return isArray(value) ? "[\u2026]" : isObject(value) && !isDate(value) ? "{\u2026}" : value; |
|
} |
|
/** |
|
* Create a new View instance from a Vega dataflow runtime specification. |
|
* The generated View will not immediately be ready for display. Callers |
|
* should also invoke the initialize method (e.g., to set the parent |
|
* DOM element in browser-based deployment) and then invoke the run |
|
* method to evaluate the dataflow graph. Rendering will automatically |
|
* be peformed upon dataflow runs. |
|
* @constructor |
|
* @param {object} spec - The Vega dataflow runtime specification. |
|
*/ |
|
|
|
|
|
function View(spec, options) { |
|
var view = this; |
|
options = options || {}; |
|
Dataflow.call(view); |
|
if (options.loader) view.loader(options.loader); |
|
if (options.logger) view.logger(options.logger); |
|
if (options.logLevel != null) view.logLevel(options.logLevel); |
|
view._el = null; |
|
view._elBind = null; |
|
view._renderType = options.renderer || RenderType.Canvas; |
|
view._scenegraph = new Scenegraph(); |
|
var root = view._scenegraph.root; // initialize renderer, handler and event management |
|
|
|
view._renderer = null; |
|
view._tooltip = options.tooltip || defaultTooltip$1, view._redraw = true; |
|
view._handler = new CanvasHandler().scene(root); |
|
view._preventDefault = false; |
|
view._timers = []; |
|
view._eventListeners = []; |
|
view._resizeListeners = []; // initialize event configuration |
|
|
|
view._eventConfig = initializeEventConfig(spec.eventConfig); // initialize dataflow graph |
|
|
|
var ctx = runtime(view, spec, options.functions); |
|
view._runtime = ctx; |
|
view._signals = ctx.signals; |
|
view._bind = (spec.bindings || []).map(function (_) { |
|
return { |
|
state: null, |
|
param: extend({}, _) |
|
}; |
|
}); // initialize scenegraph |
|
|
|
if (ctx.root) ctx.root.set(root); |
|
root.source = ctx.data.root.input; |
|
view.pulse(ctx.data.root.input, view.changeset().insert(root.items)); // initialize background color |
|
|
|
view._background = options.background || ctx.background || null; // initialize view size |
|
|
|
view._width = view.width(); |
|
view._height = view.height(); |
|
view._viewWidth = viewWidth(view, view._width); |
|
view._viewHeight = viewHeight(view, view._height); |
|
view._origin = [0, 0]; |
|
view._resize = 0; |
|
view._autosize = 1; |
|
initializeResize(view); // initialize cursor |
|
|
|
cursor(view); // initialize hover proessing, if requested |
|
|
|
if (options.hover) view.hover(); // initialize DOM container(s) and renderer |
|
|
|
if (options.container) view.initialize(options.container, options.bind); |
|
} |
|
|
|
var prototype$1s = inherits(View, Dataflow); // -- DATAFLOW / RENDERING ---- |
|
|
|
prototype$1s.evaluate = |
|
/*#__PURE__*/ |
|
function () { |
|
var _ref19 = _asyncToGenerator( |
|
/*#__PURE__*/ |
|
regeneratorRuntime.mark(function _callee2(encode, prerun, postrun) { |
|
return regeneratorRuntime.wrap(function _callee2$(_context8) { |
|
while (1) { |
|
switch (_context8.prev = _context8.next) { |
|
case 0: |
|
_context8.next = 2; |
|
return Dataflow.prototype.evaluate.call(this, encode, prerun); |
|
|
|
case 2: |
|
if (!(this._redraw || this._resize)) { |
|
_context8.next = 14; |
|
break; |
|
} |
|
|
|
_context8.prev = 3; |
|
|
|
if (!this._renderer) { |
|
_context8.next = 8; |
|
break; |
|
} |
|
|
|
if (this._resize) { |
|
this._resize = 0; |
|
resizeRenderer(this); |
|
} |
|
|
|
_context8.next = 8; |
|
return this._renderer.renderAsync(this._scenegraph.root); |
|
|
|
case 8: |
|
this._redraw = false; |
|
_context8.next = 14; |
|
break; |
|
|
|
case 11: |
|
_context8.prev = 11; |
|
_context8.t0 = _context8["catch"](3); |
|
this.error(_context8.t0); |
|
|
|
case 14: |
|
// evaluate postrun |
|
if (postrun) asyncCallback(this, postrun); |
|
return _context8.abrupt("return", this); |
|
|
|
case 16: |
|
case "end": |
|
return _context8.stop(); |
|
} |
|
} |
|
}, _callee2, this, [[3, 11]]); |
|
})); |
|
|
|
return function (_x41, _x42, _x43) { |
|
return _ref19.apply(this, arguments); |
|
}; |
|
}(); |
|
|
|
prototype$1s.dirty = function (item) { |
|
this._redraw = true; |
|
this._renderer && this._renderer.dirty(item); |
|
}; // -- GET / SET ---- |
|
|
|
|
|
prototype$1s.container = function () { |
|
return this._el; |
|
}; |
|
|
|
prototype$1s.scenegraph = function () { |
|
return this._scenegraph; |
|
}; |
|
|
|
prototype$1s.origin = function () { |
|
return this._origin.slice(); |
|
}; |
|
|
|
function lookupSignal(view, name) { |
|
return hasOwnProperty(view._signals, name) ? view._signals[name] : error('Unrecognized signal name: ' + $(name)); |
|
} |
|
|
|
prototype$1s.signal = function (name, value, options) { |
|
var op = lookupSignal(this, name); |
|
return arguments.length === 1 ? op.value : this.update(op, value, options); |
|
}; |
|
|
|
prototype$1s.background = function (_) { |
|
if (arguments.length) { |
|
this._background = _; |
|
this._resize = 1; |
|
return this; |
|
} else { |
|
return this._background; |
|
} |
|
}; |
|
|
|
prototype$1s.width = function (_) { |
|
return arguments.length ? this.signal('width', _) : this.signal('width'); |
|
}; |
|
|
|
prototype$1s.height = function (_) { |
|
return arguments.length ? this.signal('height', _) : this.signal('height'); |
|
}; |
|
|
|
prototype$1s.padding = function (_) { |
|
return arguments.length ? this.signal('padding', _) : this.signal('padding'); |
|
}; |
|
|
|
prototype$1s.autosize = function (_) { |
|
return arguments.length ? this.signal('autosize', _) : this.signal('autosize'); |
|
}; |
|
|
|
prototype$1s.renderer = function (type) { |
|
if (!arguments.length) return this._renderType; |
|
if (!renderModule(type)) error('Unrecognized renderer type: ' + type); |
|
|
|
if (type !== this._renderType) { |
|
this._renderType = type; |
|
|
|
this._resetRenderer(); |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
prototype$1s.tooltip = function (handler) { |
|
if (!arguments.length) return this._tooltip; |
|
|
|
if (handler !== this._tooltip) { |
|
this._tooltip = handler; |
|
|
|
this._resetRenderer(); |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
prototype$1s.loader = function (loader) { |
|
if (!arguments.length) return this._loader; |
|
|
|
if (loader !== this._loader) { |
|
Dataflow.prototype.loader.call(this, loader); |
|
|
|
this._resetRenderer(); |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
prototype$1s.resize = function () { |
|
// set flag to perform autosize |
|
this._autosize = 1; // touch autosize signal to ensure top-level ViewLayout runs |
|
|
|
return this.touch(lookupSignal(this, 'autosize')); |
|
}; |
|
|
|
prototype$1s._resetRenderer = function () { |
|
if (this._renderer) { |
|
this._renderer = null; |
|
this.initialize(this._el, this._elBind); |
|
} |
|
}; // -- SIZING ---- |
|
|
|
|
|
prototype$1s._resizeView = resizeView; // -- EVENT HANDLING ---- |
|
|
|
prototype$1s.addEventListener = function (type, handler, options) { |
|
var callback = handler; |
|
|
|
if (!(options && options.trap === false)) { |
|
// wrap callback in error handler |
|
callback = trap(this, handler); |
|
callback.raw = handler; |
|
} |
|
|
|
this._handler.on(type, callback); |
|
|
|
return this; |
|
}; |
|
|
|
prototype$1s.removeEventListener = function (type, handler) { |
|
var handlers = this._handler.handlers(type), |
|
i = handlers.length, |
|
h, |
|
t; // search registered handlers, remove if match found |
|
|
|
|
|
while (--i >= 0) { |
|
t = handlers[i].type; |
|
h = handlers[i].handler; |
|
|
|
if (type === t && (handler === h || handler === h.raw)) { |
|
this._handler.off(t, h); |
|
|
|
break; |
|
} |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
prototype$1s.addResizeListener = function (handler) { |
|
var l = this._resizeListeners; |
|
|
|
if (l.indexOf(handler) < 0) { |
|
// add handler if it isn't already registered |
|
// note: error trapping handled elsewhere, so |
|
// no need to wrap handlers here |
|
l.push(handler); |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
prototype$1s.removeResizeListener = function (handler) { |
|
var l = this._resizeListeners, |
|
i = l.indexOf(handler); |
|
|
|
if (i >= 0) { |
|
l.splice(i, 1); |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
function findOperatorHandler(op, handler) { |
|
var t = op._targets || [], |
|
h = t.filter(function (op) { |
|
var u = op._update; |
|
return u && u.handler === handler; |
|
}); |
|
return h.length ? h[0] : null; |
|
} |
|
|
|
function addOperatorListener(view, name, op, handler) { |
|
var h = findOperatorHandler(op, handler); |
|
|
|
if (!h) { |
|
h = trap(this, function () { |
|
handler(name, op.value); |
|
}); |
|
h.handler = handler; |
|
view.on(op, null, h); |
|
} |
|
|
|
return view; |
|
} |
|
|
|
function removeOperatorListener(view, op, handler) { |
|
var h = findOperatorHandler(op, handler); |
|
if (h) op._targets.remove(h); |
|
return view; |
|
} |
|
|
|
prototype$1s.addSignalListener = function (name, handler) { |
|
return addOperatorListener(this, name, lookupSignal(this, name), handler); |
|
}; |
|
|
|
prototype$1s.removeSignalListener = function (name, handler) { |
|
return removeOperatorListener(this, lookupSignal(this, name), handler); |
|
}; |
|
|
|
prototype$1s.addDataListener = function (name, handler) { |
|
return addOperatorListener(this, name, dataref(this, name).values, handler); |
|
}; |
|
|
|
prototype$1s.removeDataListener = function (name, handler) { |
|
return removeOperatorListener(this, dataref(this, name).values, handler); |
|
}; |
|
|
|
prototype$1s.preventDefault = function (_) { |
|
if (arguments.length) { |
|
this._preventDefault = _; |
|
return this; |
|
} else { |
|
return this._preventDefault; |
|
} |
|
}; |
|
|
|
prototype$1s.timer = timer$1; |
|
prototype$1s.events = events$1; |
|
prototype$1s.finalize = finalize; |
|
prototype$1s.hover = hover; // -- DATA ---- |
|
|
|
prototype$1s.data = data; |
|
prototype$1s.change = change; |
|
prototype$1s.insert = insert; |
|
prototype$1s.remove = remove; // -- SCALES -- |
|
|
|
prototype$1s.scale = scale$4; // -- INITIALIZATION ---- |
|
|
|
prototype$1s.initialize = initialize$1; // -- HEADLESS RENDERING ---- |
|
|
|
prototype$1s.toImageURL = renderToImageURL; |
|
prototype$1s.toCanvas = renderToCanvas; |
|
prototype$1s.toSVG = renderToSVG; // -- SAVE / RESTORE STATE ---- |
|
|
|
prototype$1s.getState = getState$1; |
|
prototype$1s.setState = setState$1; |
|
|
|
function parseAutosize(spec, config) { |
|
spec = spec || config.autosize; |
|
return isObject(spec) ? spec : { |
|
type: spec || 'pad' |
|
}; |
|
} |
|
|
|
function parsePadding(spec, config) { |
|
spec = spec || config.padding; |
|
return isObject(spec) ? { |
|
top: number$5(spec.top), |
|
bottom: number$5(spec.bottom), |
|
left: number$5(spec.left), |
|
right: number$5(spec.right) |
|
} : paddingObject(number$5(spec)); |
|
} |
|
|
|
function number$5(_) { |
|
return +_ || 0; |
|
} |
|
|
|
function paddingObject(_) { |
|
return { |
|
top: _, |
|
bottom: _, |
|
left: _, |
|
right: _ |
|
}; |
|
} |
|
|
|
var OUTER = 'outer', |
|
OUTER_INVALID = ['value', 'update', 'init', 'react', 'bind']; |
|
|
|
function outerError(prefix, name) { |
|
error(prefix + ' for "outer" push: ' + $(name)); |
|
} |
|
|
|
function parseSignal(signal, scope) { |
|
var name = signal.name; |
|
|
|
if (signal.push === OUTER) { |
|
// signal must already be defined, raise error if not |
|
if (!scope.signals[name]) outerError('No prior signal definition', name); // signal push must not use properties reserved for standard definition |
|
|
|
OUTER_INVALID.forEach(function (prop) { |
|
if (signal[prop] !== undefined) outerError('Invalid property ', prop); |
|
}); |
|
} else { |
|
// define a new signal in the current scope |
|
var op = scope.addSignal(name, signal.value); |
|
if (signal.react === false) op.react = false; |
|
if (signal.bind) scope.addBinding(name, signal.bind); |
|
} |
|
} |
|
|
|
function parseExpression$1(expr, scope, preamble) { |
|
var params = {}, |
|
ast, |
|
gen; // parse the expression to an abstract syntax tree (ast) |
|
|
|
try { |
|
expr = isString(expr) ? expr : $(expr) + ''; |
|
ast = parse$3(expr); |
|
} catch (err) { |
|
error('Expression parse error: ' + expr); |
|
} // analyze ast function calls for dependencies |
|
|
|
|
|
ast.visit(function visitor(node) { |
|
if (node.type !== CallExpression) return; |
|
var name = node.callee.name, |
|
visit = codegenParams.visitors[name]; |
|
if (visit) visit(name, node.arguments, scope, params); |
|
}); // perform code generation |
|
|
|
gen = codeGenerator(ast); // collect signal dependencies |
|
|
|
gen.globals.forEach(function (name) { |
|
var signalName = SignalPrefix + name; |
|
|
|
if (!hasOwnProperty(params, signalName) && scope.getSignal(name)) { |
|
params[signalName] = scope.signalRef(name); |
|
} |
|
}); // return generated expression code and dependencies |
|
|
|
return { |
|
$expr: preamble ? preamble + 'return(' + gen.code + ');' : gen.code, |
|
$fields: gen.fields, |
|
$params: params |
|
}; |
|
} |
|
|
|
function Entry(type, value, params, parent) { |
|
this.id = -1; |
|
this.type = type; |
|
this.value = value; |
|
this.params = params; |
|
if (parent) this.parent = parent; |
|
} |
|
|
|
function entry(type, value, params, parent) { |
|
return new Entry(type, value, params, parent); |
|
} |
|
|
|
function operator(value, params) { |
|
return entry('operator', value, params); |
|
} // ----- |
|
|
|
|
|
function ref(op) { |
|
var ref = { |
|
$ref: op.id |
|
}; // if operator not yet registered, cache ref to resolve later |
|
|
|
if (op.id < 0) (op.refs = op.refs || []).push(ref); |
|
return ref; |
|
} |
|
|
|
function fieldRef(field, name) { |
|
return name ? { |
|
$field: field, |
|
$name: name |
|
} : { |
|
$field: field |
|
}; |
|
} |
|
|
|
var keyFieldRef = fieldRef('key'); |
|
|
|
function compareRef(fields, orders) { |
|
return { |
|
$compare: fields, |
|
$order: orders |
|
}; |
|
} |
|
|
|
function keyRef(fields, flat) { |
|
var ref = { |
|
$key: fields |
|
}; |
|
if (flat) ref.$flat = true; |
|
return ref; |
|
} // ----- |
|
|
|
|
|
var Ascending = 'ascending'; |
|
var Descending = 'descending'; |
|
|
|
function sortKey(sort) { |
|
return !isObject(sort) ? '' : (sort.order === Descending ? '-' : '+') + aggrField(sort.op, sort.field); |
|
} |
|
|
|
function aggrField(op, field) { |
|
return (op && op.signal ? '$' + op.signal : op || '') + (op && field ? '_' : '') + (field && field.signal ? '$' + field.signal : field || ''); |
|
} // ----- |
|
|
|
|
|
var Scope = 'scope'; |
|
var View$1 = 'view'; |
|
|
|
function isSignal(_) { |
|
return _ && _.signal; |
|
} |
|
|
|
function isExpr(_) { |
|
return _ && _.expr; |
|
} |
|
|
|
function hasSignal(_) { |
|
if (isSignal(_)) return true; |
|
if (isObject(_)) for (var key in _) { |
|
if (hasSignal(_[key])) return true; |
|
} |
|
return false; |
|
} |
|
|
|
function value$1(specValue, defaultValue) { |
|
return specValue != null ? specValue : defaultValue; |
|
} |
|
|
|
function deref(v) { |
|
return v && v.signal || v; |
|
} |
|
|
|
var Timer$1 = 'timer'; |
|
|
|
function parseStream$1(stream, scope) { |
|
var method = stream.merge ? mergeStream : stream.stream ? nestedStream : stream.type ? eventStream : error('Invalid stream specification: ' + $(stream)); |
|
return method(stream, scope); |
|
} |
|
|
|
function eventSource(source) { |
|
return source === Scope ? View$1 : source || View$1; |
|
} |
|
|
|
function mergeStream(stream, scope) { |
|
var list = stream.merge.map(function (s) { |
|
return parseStream$1(s, scope); |
|
}), |
|
entry = streamParameters({ |
|
merge: list |
|
}, stream, scope); |
|
return scope.addStream(entry).id; |
|
} |
|
|
|
function nestedStream(stream, scope) { |
|
var id = parseStream$1(stream.stream, scope), |
|
entry = streamParameters({ |
|
stream: id |
|
}, stream, scope); |
|
return scope.addStream(entry).id; |
|
} |
|
|
|
function eventStream(stream, scope) { |
|
var id, entry; |
|
|
|
if (stream.type === Timer$1) { |
|
id = scope.event(Timer$1, stream.throttle); |
|
stream = { |
|
between: stream.between, |
|
filter: stream.filter |
|
}; |
|
} else { |
|
id = scope.event(eventSource(stream.source), stream.type); |
|
} |
|
|
|
entry = streamParameters({ |
|
stream: id |
|
}, stream, scope); |
|
return Object.keys(entry).length === 1 ? id : scope.addStream(entry).id; |
|
} |
|
|
|
function streamParameters(entry, stream, scope) { |
|
var param = stream.between; |
|
|
|
if (param) { |
|
if (param.length !== 2) { |
|
error('Stream "between" parameter must have 2 entries: ' + $(stream)); |
|
} |
|
|
|
entry.between = [parseStream$1(param[0], scope), parseStream$1(param[1], scope)]; |
|
} |
|
|
|
param = stream.filter ? [].concat(stream.filter) : []; |
|
|
|
if (stream.marktype || stream.markname || stream.markrole) { |
|
// add filter for mark type, name and/or role |
|
param.push(filterMark(stream.marktype, stream.markname, stream.markrole)); |
|
} |
|
|
|
if (stream.source === Scope) { |
|
// add filter to limit events from sub-scope only |
|
param.push('inScope(event.item)'); |
|
} |
|
|
|
if (param.length) { |
|
entry.filter = parseExpression$1('(' + param.join(')&&(') + ')').$expr; |
|
} |
|
|
|
if ((param = stream.throttle) != null) { |
|
entry.throttle = +param; |
|
} |
|
|
|
if ((param = stream.debounce) != null) { |
|
entry.debounce = +param; |
|
} |
|
|
|
if (stream.consume) { |
|
entry.consume = true; |
|
} |
|
|
|
return entry; |
|
} |
|
|
|
function filterMark(type, name, role) { |
|
var item = 'event.item'; |
|
return item + (type && type !== '*' ? '&&' + item + '.mark.marktype===\'' + type + '\'' : '') + (role ? '&&' + item + '.mark.role===\'' + role + '\'' : '') + (name ? '&&' + item + '.mark.name===\'' + name + '\'' : ''); |
|
} |
|
/** |
|
* Parse an event selector string. |
|
* Returns an array of event stream definitions. |
|
*/ |
|
|
|
|
|
function selector(selector, source, marks) { |
|
DEFAULT_SOURCE = source || VIEW$1; |
|
MARKS = marks || DEFAULT_MARKS; |
|
return parseMerge(selector.trim()).map(parseSelector); |
|
} |
|
|
|
var VIEW$1 = 'view', |
|
LBRACK = '[', |
|
RBRACK = ']', |
|
LBRACE = '{', |
|
RBRACE = '}', |
|
COLON = ':', |
|
COMMA = ',', |
|
NAME = '@', |
|
GT = '>', |
|
ILLEGAL$1 = /[[\]{}]/, |
|
DEFAULT_SOURCE, |
|
MARKS, |
|
DEFAULT_MARKS = { |
|
'*': 1, |
|
arc: 1, |
|
area: 1, |
|
group: 1, |
|
image: 1, |
|
line: 1, |
|
path: 1, |
|
rect: 1, |
|
rule: 1, |
|
shape: 1, |
|
symbol: 1, |
|
text: 1, |
|
trail: 1 |
|
}; |
|
|
|
function isMarkType(type) { |
|
return MARKS[type]; |
|
} |
|
|
|
function find$2(s, i, endChar, pushChar, popChar) { |
|
var count = 0, |
|
n = s.length, |
|
c; |
|
|
|
for (; i < n; ++i) { |
|
c = s[i]; |
|
if (!count && c === endChar) return i;else if (popChar && popChar.indexOf(c) >= 0) --count;else if (pushChar && pushChar.indexOf(c) >= 0) ++count; |
|
} |
|
|
|
return i; |
|
} |
|
|
|
function parseMerge(s) { |
|
var output = [], |
|
start = 0, |
|
n = s.length, |
|
i = 0; |
|
|
|
while (i < n) { |
|
i = find$2(s, i, COMMA, LBRACK + LBRACE, RBRACK + RBRACE); |
|
output.push(s.substring(start, i).trim()); |
|
start = ++i; |
|
} |
|
|
|
if (output.length === 0) { |
|
throw 'Empty event selector: ' + s; |
|
} |
|
|
|
return output; |
|
} |
|
|
|
function parseSelector(s) { |
|
return s[0] === '[' ? parseBetween(s) : parseStream$2(s); |
|
} |
|
|
|
function parseBetween(s) { |
|
var n = s.length, |
|
i = 1, |
|
b, |
|
stream; |
|
i = find$2(s, i, RBRACK, LBRACK, RBRACK); |
|
|
|
if (i === n) { |
|
throw 'Empty between selector: ' + s; |
|
} |
|
|
|
b = parseMerge(s.substring(1, i)); |
|
|
|
if (b.length !== 2) { |
|
throw 'Between selector must have two elements: ' + s; |
|
} |
|
|
|
s = s.slice(i + 1).trim(); |
|
|
|
if (s[0] !== GT) { |
|
throw 'Expected \'>\' after between selector: ' + s; |
|
} |
|
|
|
b = b.map(parseSelector); |
|
stream = parseSelector(s.slice(1).trim()); |
|
|
|
if (stream.between) { |
|
return { |
|
between: b, |
|
stream: stream |
|
}; |
|
} else { |
|
stream.between = b; |
|
} |
|
|
|
return stream; |
|
} |
|
|
|
function parseStream$2(s) { |
|
var stream = { |
|
source: DEFAULT_SOURCE |
|
}, |
|
source = [], |
|
throttle = [0, 0], |
|
markname = 0, |
|
start = 0, |
|
n = s.length, |
|
i = 0, |
|
j, |
|
filter; // extract throttle from end |
|
|
|
if (s[n - 1] === RBRACE) { |
|
i = s.lastIndexOf(LBRACE); |
|
|
|
if (i >= 0) { |
|
try { |
|
throttle = parseThrottle(s.substring(i + 1, n - 1)); |
|
} catch (e) { |
|
throw 'Invalid throttle specification: ' + s; |
|
} |
|
|
|
s = s.slice(0, i).trim(); |
|
n = s.length; |
|
} else throw 'Unmatched right brace: ' + s; |
|
|
|
i = 0; |
|
} |
|
|
|
if (!n) throw s; // set name flag based on first char |
|
|
|
if (s[0] === NAME) markname = ++i; // extract first part of multi-part stream selector |
|
|
|
j = find$2(s, i, COLON); |
|
|
|
if (j < n) { |
|
source.push(s.substring(start, j).trim()); |
|
start = i = ++j; |
|
} // extract remaining part of stream selector |
|
|
|
|
|
i = find$2(s, i, LBRACK); |
|
|
|
if (i === n) { |
|
source.push(s.substring(start, n).trim()); |
|
} else { |
|
source.push(s.substring(start, i).trim()); |
|
filter = []; |
|
start = ++i; |
|
if (start === n) throw 'Unmatched left bracket: ' + s; |
|
} // extract filters |
|
|
|
|
|
while (i < n) { |
|
i = find$2(s, i, RBRACK); |
|
if (i === n) throw 'Unmatched left bracket: ' + s; |
|
filter.push(s.substring(start, i).trim()); |
|
if (i < n - 1 && s[++i] !== LBRACK) throw 'Expected left bracket: ' + s; |
|
start = ++i; |
|
} // marshall event stream specification |
|
|
|
|
|
if (!(n = source.length) || ILLEGAL$1.test(source[n - 1])) { |
|
throw 'Invalid event selector: ' + s; |
|
} |
|
|
|
if (n > 1) { |
|
stream.type = source[1]; |
|
|
|
if (markname) { |
|
stream.markname = source[0].slice(1); |
|
} else if (isMarkType(source[0])) { |
|
stream.marktype = source[0]; |
|
} else { |
|
stream.source = source[0]; |
|
} |
|
} else { |
|
stream.type = source[0]; |
|
} |
|
|
|
if (stream.type.slice(-1) === '!') { |
|
stream.consume = true; |
|
stream.type = stream.type.slice(0, -1); |
|
} |
|
|
|
if (filter != null) stream.filter = filter; |
|
if (throttle[0]) stream.throttle = throttle[0]; |
|
if (throttle[1]) stream.debounce = throttle[1]; |
|
return stream; |
|
} |
|
|
|
function parseThrottle(s) { |
|
var a = s.split(COMMA); |
|
if (!s.length || a.length > 2) throw s; |
|
return a.map(function (_) { |
|
var x = +_; |
|
if (x !== x) throw s; |
|
return x; |
|
}); |
|
} |
|
|
|
var preamble = 'var datum=event.item&&event.item.datum;'; |
|
|
|
function parseUpdate$1(spec, scope, target) { |
|
var events = spec.events, |
|
update = spec.update, |
|
encode = spec.encode, |
|
sources = [], |
|
entry = { |
|
target: target |
|
}; |
|
|
|
if (!events) { |
|
error('Signal update missing events specification.'); |
|
} // interpret as an event selector string |
|
|
|
|
|
if (isString(events)) { |
|
events = selector(events, scope.isSubscope() ? Scope : View$1); |
|
} // separate event streams from signal updates |
|
|
|
|
|
events = array(events).filter(function (s) { |
|
return s.signal || s.scale ? (sources.push(s), 0) : 1; |
|
}); // merge internal operator listeners |
|
|
|
if (sources.length > 1) { |
|
sources = [mergeSources(sources)]; |
|
} // merge event streams, include as source |
|
|
|
|
|
if (events.length) { |
|
sources.push(events.length > 1 ? { |
|
merge: events |
|
} : events[0]); |
|
} |
|
|
|
if (encode != null) { |
|
if (update) error('Signal encode and update are mutually exclusive.'); |
|
update = 'encode(item(),' + $(encode) + ')'; |
|
} // resolve update value |
|
|
|
|
|
entry.update = isString(update) ? parseExpression$1(update, scope, preamble) : update.expr != null ? parseExpression$1(update.expr, scope, preamble) : update.value != null ? update.value : update.signal != null ? { |
|
$expr: '_.value', |
|
$params: { |
|
value: scope.signalRef(update.signal) |
|
} |
|
} : error('Invalid signal update specification.'); |
|
|
|
if (spec.force) { |
|
entry.options = { |
|
force: true |
|
}; |
|
} |
|
|
|
sources.forEach(function (source) { |
|
scope.addUpdate(extend(streamSource(source, scope), entry)); |
|
}); |
|
} |
|
|
|
function streamSource(stream, scope) { |
|
return { |
|
source: stream.signal ? scope.signalRef(stream.signal) : stream.scale ? scope.scaleRef(stream.scale) : parseStream$1(stream, scope) |
|
}; |
|
} |
|
|
|
function mergeSources(sources) { |
|
return { |
|
signal: '[' + sources.map(function (s) { |
|
return s.scale ? 'scale("' + s.scale + '")' : s.signal; |
|
}) + ']' |
|
}; |
|
} |
|
|
|
function parseSignalUpdates(signal, scope) { |
|
var op = scope.getSignal(signal.name), |
|
expr = signal.update; |
|
|
|
if (signal.init) { |
|
if (expr) { |
|
error('Signals can not include both init and update expressions.'); |
|
} else { |
|
expr = signal.init; |
|
op.initonly = true; |
|
} |
|
} |
|
|
|
if (expr) { |
|
expr = parseExpression$1(expr, scope); |
|
op.update = expr.$expr; |
|
op.params = expr.$params; |
|
} |
|
|
|
if (signal.on) { |
|
signal.on.forEach(function (_) { |
|
parseUpdate$1(_, scope, op.id); |
|
}); |
|
} |
|
} |
|
|
|
function transform$3(name) { |
|
return function (params, value, parent) { |
|
return entry(name, value, params || undefined, parent); |
|
}; |
|
} |
|
|
|
var Aggregate$1 = transform$3('aggregate'); |
|
var AxisTicks$1 = transform$3('axisticks'); |
|
var Bound$1 = transform$3('bound'); |
|
var Collect$1 = transform$3('collect'); |
|
var Compare$1 = transform$3('compare'); |
|
var DataJoin$1 = transform$3('datajoin'); |
|
var Encode$1 = transform$3('encode'); |
|
var Expression$1 = transform$3('expression'); |
|
var Facet$1 = transform$3('facet'); |
|
var Field$1 = transform$3('field'); |
|
var Key$1 = transform$3('key'); |
|
var LegendEntries$1 = transform$3('legendentries'); |
|
var Load$1 = transform$3('load'); |
|
var Mark$1 = transform$3('mark'); |
|
var MultiExtent$1 = transform$3('multiextent'); |
|
var MultiValues$1 = transform$3('multivalues'); |
|
var Overlap$1 = transform$3('overlap'); |
|
var Params$2 = transform$3('params'); |
|
var PreFacet$1 = transform$3('prefacet'); |
|
var Projection$1 = transform$3('projection'); |
|
var Proxy$1 = transform$3('proxy'); |
|
var Relay$1 = transform$3('relay'); |
|
var Render$1 = transform$3('render'); |
|
var Scale$1 = transform$3('scale'); |
|
var Sieve$1 = transform$3('sieve'); |
|
var SortItems$1 = transform$3('sortitems'); |
|
var ViewLayout$1 = transform$3('viewlayout'); |
|
var Values$1 = transform$3('values'); |
|
var FIELD_REF_ID = 0; |
|
var MULTIDOMAIN_SORT_OPS = { |
|
min: 'min', |
|
max: 'max', |
|
count: 'sum' |
|
}; |
|
|
|
function initScale(spec, scope) { |
|
var type = spec.type || 'linear'; |
|
|
|
if (!isValidScaleType(type)) { |
|
error('Unrecognized scale type: ' + $(type)); |
|
} |
|
|
|
scope.addScale(spec.name, { |
|
type: type, |
|
domain: undefined |
|
}); |
|
} |
|
|
|
function parseScale(spec, scope) { |
|
var params = scope.getScale(spec.name).params, |
|
key; |
|
params.domain = parseScaleDomain(spec.domain, spec, scope); |
|
|
|
if (spec.range != null) { |
|
params.range = parseScaleRange(spec, scope, params); |
|
} |
|
|
|
if (spec.interpolate != null) { |
|
parseScaleInterpolate(spec.interpolate, params); |
|
} |
|
|
|
if (spec.nice != null) { |
|
params.nice = parseScaleNice(spec.nice); |
|
} |
|
|
|
if (spec.bins != null) { |
|
params.bins = parseScaleBins(spec.bins, scope); |
|
} |
|
|
|
for (key in spec) { |
|
if (hasOwnProperty(params, key) || key === 'name') continue; |
|
params[key] = parseLiteral(spec[key], scope); |
|
} |
|
} |
|
|
|
function parseLiteral(v, scope) { |
|
return !isObject(v) ? v : v.signal ? scope.signalRef(v.signal) : error('Unsupported object: ' + $(v)); |
|
} |
|
|
|
function parseArray(v, scope) { |
|
return v.signal ? scope.signalRef(v.signal) : v.map(function (v) { |
|
return parseLiteral(v, scope); |
|
}); |
|
} |
|
|
|
function dataLookupError(name) { |
|
error('Can not find data set: ' + $(name)); |
|
} // -- SCALE DOMAIN ---- |
|
|
|
|
|
function parseScaleDomain(domain, spec, scope) { |
|
if (!domain) { |
|
if (spec.domainMin != null || spec.domainMax != null) { |
|
error('No scale domain defined for domainMin/domainMax to override.'); |
|
} |
|
|
|
return; // default domain |
|
} |
|
|
|
return domain.signal ? scope.signalRef(domain.signal) : (isArray(domain) ? explicitDomain : domain.fields ? multipleDomain : singularDomain)(domain, spec, scope); |
|
} |
|
|
|
function explicitDomain(domain, spec, scope) { |
|
return domain.map(function (v) { |
|
return parseLiteral(v, scope); |
|
}); |
|
} |
|
|
|
function singularDomain(domain, spec, scope) { |
|
var data = scope.getData(domain.data); |
|
if (!data) dataLookupError(domain.data); |
|
return isDiscrete(spec.type) ? data.valuesRef(scope, domain.field, parseSort(domain.sort, false)) : isQuantile(spec.type) ? data.domainRef(scope, domain.field) : data.extentRef(scope, domain.field); |
|
} |
|
|
|
function multipleDomain(domain, spec, scope) { |
|
var data = domain.data, |
|
fields = domain.fields.reduce(function (dom, d) { |
|
d = isString(d) ? { |
|
data: data, |
|
field: d |
|
} : isArray(d) || d.signal ? fieldRef$1(d, scope) : d; |
|
dom.push(d); |
|
return dom; |
|
}, []); |
|
return (isDiscrete(spec.type) ? ordinalMultipleDomain : isQuantile(spec.type) ? quantileMultipleDomain : numericMultipleDomain)(domain, scope, fields); |
|
} |
|
|
|
function fieldRef$1(data, scope) { |
|
var name = '_:vega:_' + FIELD_REF_ID++, |
|
coll = Collect$1({}); |
|
|
|
if (isArray(data)) { |
|
coll.value = { |
|
$ingest: data |
|
}; |
|
} else if (data.signal) { |
|
var code = 'setdata(' + $(name) + ',' + data.signal + ')'; |
|
coll.params.input = scope.signalRef(code); |
|
} |
|
|
|
scope.addDataPipeline(name, [coll, Sieve$1({})]); |
|
return { |
|
data: name, |
|
field: 'data' |
|
}; |
|
} |
|
|
|
function ordinalMultipleDomain(domain, scope, fields) { |
|
var sort = parseSort(domain.sort, true), |
|
counts, |
|
p, |
|
a, |
|
c, |
|
v; // get value counts for each domain field |
|
|
|
counts = fields.map(function (f) { |
|
var data = scope.getData(f.data); |
|
if (!data) dataLookupError(f.data); |
|
return data.countsRef(scope, f.field, sort); |
|
}); // aggregate the results from each domain field |
|
|
|
p = { |
|
groupby: keyFieldRef, |
|
pulse: counts |
|
}; |
|
|
|
if (sort) { |
|
a = sort.op || 'count'; |
|
v = sort.field ? aggrField(a, sort.field) : 'count'; |
|
p.ops = [MULTIDOMAIN_SORT_OPS[a]]; |
|
p.fields = [scope.fieldRef(v)]; |
|
p.as = [v]; |
|
} |
|
|
|
a = scope.add(Aggregate$1(p)); // collect aggregate output |
|
|
|
c = scope.add(Collect$1({ |
|
pulse: ref(a) |
|
})); // extract values for combined domain |
|
|
|
v = scope.add(Values$1({ |
|
field: keyFieldRef, |
|
sort: scope.sortRef(sort), |
|
pulse: ref(c) |
|
})); |
|
return ref(v); |
|
} |
|
|
|
function parseSort(sort, multidomain) { |
|
if (sort) { |
|
if (!sort.field && !sort.op) { |
|
if (isObject(sort)) sort.field = 'key';else sort = { |
|
field: 'key' |
|
}; |
|
} else if (!sort.field && sort.op !== 'count') { |
|
error('No field provided for sort aggregate op: ' + sort.op); |
|
} else if (multidomain && sort.field) { |
|
if (sort.op && !MULTIDOMAIN_SORT_OPS[sort.op]) { |
|
error('Multiple domain scales can not be sorted using ' + sort.op); |
|
} |
|
} |
|
} |
|
|
|
return sort; |
|
} |
|
|
|
function quantileMultipleDomain(domain, scope, fields) { |
|
// get value arrays for each domain field |
|
var values = fields.map(function (f) { |
|
var data = scope.getData(f.data); |
|
if (!data) dataLookupError(f.data); |
|
return data.domainRef(scope, f.field); |
|
}); // combine value arrays |
|
|
|
return ref(scope.add(MultiValues$1({ |
|
values: values |
|
}))); |
|
} |
|
|
|
function numericMultipleDomain(domain, scope, fields) { |
|
// get extents for each domain field |
|
var extents = fields.map(function (f) { |
|
var data = scope.getData(f.data); |
|
if (!data) dataLookupError(f.data); |
|
return data.extentRef(scope, f.field); |
|
}); // combine extents |
|
|
|
return ref(scope.add(MultiExtent$1({ |
|
extents: extents |
|
}))); |
|
} // -- SCALE BINS ----- |
|
|
|
|
|
function parseScaleBins(v, scope) { |
|
return v.signal || isArray(v) ? parseArray(v, scope) : scope.objectProperty(v); |
|
} // -- SCALE NICE ----- |
|
|
|
|
|
function parseScaleNice(nice) { |
|
return isObject(nice) ? { |
|
interval: parseLiteral(nice.interval), |
|
step: parseLiteral(nice.step) |
|
} : parseLiteral(nice); |
|
} // -- SCALE INTERPOLATION ----- |
|
|
|
|
|
function parseScaleInterpolate(interpolate, params) { |
|
params.interpolate = parseLiteral(interpolate.type || interpolate); |
|
|
|
if (interpolate.gamma != null) { |
|
params.interpolateGamma = parseLiteral(interpolate.gamma); |
|
} |
|
} // -- SCALE RANGE ----- |
|
|
|
|
|
function parseScaleRange(spec, scope, params) { |
|
var range = spec.range, |
|
config = scope.config.range; |
|
|
|
if (range.signal) { |
|
return scope.signalRef(range.signal); |
|
} else if (isString(range)) { |
|
if (config && hasOwnProperty(config, range)) { |
|
spec = extend({}, spec, { |
|
range: config[range] |
|
}); |
|
return parseScaleRange(spec, scope, params); |
|
} else if (range === 'width') { |
|
range = [0, { |
|
signal: 'width' |
|
}]; |
|
} else if (range === 'height') { |
|
range = isDiscrete(spec.type) ? [0, { |
|
signal: 'height' |
|
}] : [{ |
|
signal: 'height' |
|
}, 0]; |
|
} else { |
|
error('Unrecognized scale range value: ' + $(range)); |
|
} |
|
} else if (range.scheme) { |
|
params.scheme = isArray(range.scheme) ? parseArray(range.scheme, scope) : parseLiteral(range.scheme, scope); |
|
if (range.extent) params.schemeExtent = parseArray(range.extent, scope); |
|
if (range.count) params.schemeCount = parseLiteral(range.count, scope); |
|
return; |
|
} else if (range.step) { |
|
params.rangeStep = parseLiteral(range.step, scope); |
|
return; |
|
} else if (isDiscrete(spec.type) && !isArray(range)) { |
|
return parseScaleDomain(range, spec, scope); |
|
} else if (!isArray(range)) { |
|
error('Unsupported range type: ' + $(range)); |
|
} |
|
|
|
return range.map(function (v) { |
|
return (isArray(v) ? parseArray : parseLiteral)(v, scope); |
|
}); |
|
} |
|
|
|
function parseProjection(proj, scope) { |
|
var config = scope.config.projection || {}, |
|
params = {}; |
|
|
|
for (var name in proj) { |
|
if (name === 'name') continue; |
|
params[name] = parseParameter$1(proj[name], name, scope); |
|
} // apply projection defaults from config |
|
|
|
|
|
for (name in config) { |
|
if (params[name] == null) { |
|
params[name] = parseParameter$1(config[name], name, scope); |
|
} |
|
} |
|
|
|
scope.addProjection(proj.name, params); |
|
} |
|
|
|
function parseParameter$1(_, name, scope) { |
|
return isArray(_) ? _.map(function (_) { |
|
return parseParameter$1(_, name, scope); |
|
}) : !isObject(_) ? _ : _.signal ? scope.signalRef(_.signal) : name === 'fit' ? _ : error('Unsupported parameter object: ' + $(_)); |
|
} |
|
|
|
var Top$1 = 'top'; |
|
var Left$1 = 'left'; |
|
var Right$1 = 'right'; |
|
var Bottom$1 = 'bottom'; |
|
var Center$1 = 'center'; |
|
var Vertical = 'vertical'; |
|
var Start$1 = 'start'; |
|
var Middle$1 = 'middle'; |
|
var End$1 = 'end'; |
|
var Index = 'index'; |
|
var Label = 'label'; |
|
var Offset = 'offset'; |
|
var Perc = 'perc'; |
|
var Perc2 = 'perc2'; |
|
var Size = 'size'; |
|
var Value = 'value'; |
|
var GuideLabelStyle = 'guide-label'; |
|
var GuideTitleStyle = 'guide-title'; |
|
var GroupTitleStyle = 'group-title'; |
|
var GroupSubtitleStyle = 'group-subtitle'; |
|
var Symbols$2 = 'symbol'; |
|
var Gradient$2 = 'gradient'; |
|
var Discrete$2 = 'discrete'; // Encoding channels supported by legends |
|
// In priority order of 'canonical' scale |
|
|
|
var LegendScales = ['size', 'shape', 'fill', 'stroke', 'strokeWidth', 'strokeDash', 'opacity']; |
|
var Skip$1 = { |
|
name: 1, |
|
style: 1, |
|
interactive: 1 |
|
}; |
|
var zero$2 = { |
|
value: 0 |
|
}; |
|
var one$2 = { |
|
value: 1 |
|
}; |
|
var Skip$2 = toSet(['rule']), |
|
Swap = toSet(['group', 'image', 'rect']); |
|
|
|
function adjustSpatial(encode, marktype) { |
|
var code = ''; |
|
if (Skip$2[marktype]) return code; |
|
|
|
if (encode.x2) { |
|
if (encode.x) { |
|
if (Swap[marktype]) { |
|
code += 'if(o.x>o.x2)$=o.x,o.x=o.x2,o.x2=$;'; |
|
} |
|
|
|
code += 'o.width=o.x2-o.x;'; |
|
} else { |
|
code += 'o.x=o.x2-(o.width||0);'; |
|
} |
|
} |
|
|
|
if (encode.xc) { |
|
code += 'o.x=o.xc-(o.width||0)/2;'; |
|
} |
|
|
|
if (encode.y2) { |
|
if (encode.y) { |
|
if (Swap[marktype]) { |
|
code += 'if(o.y>o.y2)$=o.y,o.y=o.y2,o.y2=$;'; |
|
} |
|
|
|
code += 'o.height=o.y2-o.y;'; |
|
} else { |
|
code += 'o.y=o.y2-(o.height||0);'; |
|
} |
|
} |
|
|
|
if (encode.yc) { |
|
code += 'o.y=o.yc-(o.height||0)/2;'; |
|
} |
|
|
|
return code; |
|
} |
|
|
|
function color$2(enc, scope, params, fields) { |
|
function color(type, x, y, z) { |
|
var a = entry$1(null, x, scope, params, fields), |
|
b = entry$1(null, y, scope, params, fields), |
|
c = entry$1(null, z, scope, params, fields); |
|
return 'this.' + type + '(' + [a, b, c].join(',') + ').toString()'; |
|
} |
|
|
|
return enc.c ? color('hcl', enc.h, enc.c, enc.l) : enc.h || enc.s ? color('hsl', enc.h, enc.s, enc.l) : enc.l || enc.a ? color('lab', enc.l, enc.a, enc.b) : enc.r || enc.g || enc.b ? color('rgb', enc.r, enc.g, enc.b) : null; |
|
} |
|
|
|
function expression$1(code, scope, params, fields) { |
|
var expr = parseExpression$1(code, scope); |
|
expr.$fields.forEach(function (name) { |
|
fields[name] = 1; |
|
}); |
|
extend(params, expr.$params); |
|
return expr.$expr; |
|
} |
|
|
|
function field$1(ref, scope, params, fields) { |
|
return resolve$1(isObject(ref) ? ref : { |
|
datum: ref |
|
}, scope, params, fields); |
|
} |
|
|
|
function resolve$1(ref, scope, params, fields) { |
|
var object, level, field; |
|
|
|
if (ref.signal) { |
|
object = 'datum'; |
|
field = expression$1(ref.signal, scope, params, fields); |
|
} else if (ref.group || ref.parent) { |
|
level = Math.max(1, ref.level || 1); |
|
object = 'item'; |
|
|
|
while (level-- > 0) { |
|
object += '.mark.group'; |
|
} |
|
|
|
if (ref.parent) { |
|
field = ref.parent; |
|
object += '.datum'; |
|
} else { |
|
field = ref.group; |
|
} |
|
} else if (ref.datum) { |
|
object = 'datum'; |
|
field = ref.datum; |
|
} else { |
|
error('Invalid field reference: ' + $(ref)); |
|
} |
|
|
|
if (!ref.signal) { |
|
if (isString(field)) { |
|
fields[field] = 1; // TODO review field tracking? |
|
|
|
field = splitAccessPath(field).map($).join(']['); |
|
} else { |
|
field = resolve$1(field, scope, params, fields); |
|
} |
|
} |
|
|
|
return object + '[' + field + ']'; |
|
} |
|
|
|
function property(property, scope, params, fields) { |
|
return isObject(property) ? '(' + entry$1(null, property, scope, params, fields) + ')' : property; |
|
} |
|
|
|
function scale$5(enc, value, scope, params, fields) { |
|
var scale = getScale$1(enc.scale, scope, params, fields), |
|
interp, |
|
func, |
|
flag; |
|
|
|
if (enc.range != null) { |
|
// pull value from scale range |
|
interp = +enc.range; |
|
func = scale + '.range()'; |
|
value = interp === 0 ? func + '[0]' : '($=' + func + ',' + (interp === 1 ? '$[$.length-1]' : '$[0]+' + interp + '*($[$.length-1]-$[0])') + ')'; |
|
} else { |
|
// run value through scale and/or pull scale bandwidth |
|
if (value !== undefined) value = scale + '(' + value + ')'; |
|
|
|
if (enc.band && (flag = hasBandwidth(enc.scale, scope))) { |
|
func = scale + '.bandwidth'; |
|
|
|
if (enc.band.signal) { |
|
interp = func + '()*' + property(enc.band, scope, params, fields); |
|
} else { |
|
interp = +enc.band; |
|
interp = func + '()' + (interp === 1 ? '' : '*' + interp); |
|
} // if we don't know the scale type, check for bandwidth |
|
|
|
|
|
if (flag < 0) interp = '(' + func + '?' + interp + ':0)'; |
|
value = (value ? value + '+' : '') + interp; |
|
|
|
if (enc.extra) { |
|
// include logic to handle extraneous elements |
|
value = '(datum.extra?' + scale + '(datum.extra.value):' + value + ')'; |
|
} |
|
} |
|
|
|
if (value == null) value = '0'; |
|
} |
|
|
|
return value; |
|
} |
|
|
|
function hasBandwidth(name, scope) { |
|
if (!isString(name)) return -1; |
|
var type = scope.scaleType(name); |
|
return type === 'band' || type === 'point' ? 1 : 0; |
|
} |
|
|
|
function getScale$1(name, scope, params, fields) { |
|
var scaleName; |
|
|
|
if (isString(name)) { |
|
// direct scale lookup; add scale as parameter |
|
scaleName = ScalePrefix + name; |
|
|
|
if (!hasOwnProperty(params, scaleName)) { |
|
params[scaleName] = scope.scaleRef(name); |
|
} |
|
|
|
scaleName = $(scaleName); |
|
} else { |
|
// indirect scale lookup; add all scales as parameters |
|
for (scaleName in scope.scales) { |
|
params[ScalePrefix + scaleName] = scope.scaleRef(scaleName); |
|
} |
|
|
|
scaleName = $(ScalePrefix) + '+' + (name.signal ? '(' + expression$1(name.signal, scope, params, fields) + ')' : field$1(name, scope, params, fields)); |
|
} |
|
|
|
return '_[' + scaleName + ']'; |
|
} |
|
|
|
function gradient$1(enc, scope, params, fields) { |
|
return 'this.gradient(' + getScale$1(enc.gradient, scope, params, fields) + ',' + $(enc.start) + ',' + $(enc.stop) + ',' + $(enc.count) + ')'; |
|
} |
|
|
|
function entry$1(channel, enc, scope, params, fields) { |
|
if (enc.gradient != null) { |
|
return gradient$1(enc, scope, params, fields); |
|
} |
|
|
|
var value = enc.signal ? expression$1(enc.signal, scope, params, fields) : enc.color ? color$2(enc.color, scope, params, fields) : enc.field != null ? field$1(enc.field, scope, params, fields) : enc.value !== undefined ? $(enc.value) : undefined; |
|
|
|
if (enc.scale != null) { |
|
value = scale$5(enc, value, scope, params, fields); |
|
} |
|
|
|
if (value === undefined) { |
|
value = null; |
|
} |
|
|
|
if (enc.exponent != null) { |
|
value = 'Math.pow(' + value + ',' + property(enc.exponent, scope, params, fields) + ')'; |
|
} |
|
|
|
if (enc.mult != null) { |
|
value += '*' + property(enc.mult, scope, params, fields); |
|
} |
|
|
|
if (enc.offset != null) { |
|
value += '+' + property(enc.offset, scope, params, fields); |
|
} |
|
|
|
if (enc.round) { |
|
value = 'Math.round(' + value + ')'; |
|
} |
|
|
|
return value; |
|
} |
|
|
|
function set$3(obj, key, value) { |
|
var o = obj + '[' + $(key) + ']'; |
|
return "$=".concat(value, ";if(").concat(o, "!==$)").concat(o, "=$,m=1;"); |
|
} |
|
|
|
function rule$1(channel, rules, scope, params, fields) { |
|
var code = ''; |
|
rules.forEach(function (rule) { |
|
var value = entry$1(channel, rule, scope, params, fields); |
|
code += rule.test ? expression$1(rule.test, scope, params, fields) + '?' + value + ':' : value; |
|
}); // if no else clause, terminate with null (vega/vega#1366) |
|
|
|
if (peek(code) === ':') { |
|
code += 'null'; |
|
} |
|
|
|
return set$3('o', channel, code); |
|
} |
|
|
|
function parseEncode(encode, marktype, params, scope) { |
|
var fields = {}, |
|
code = 'var o=item,datum=o.datum,m=0,$;', |
|
channel, |
|
enc, |
|
value; |
|
|
|
for (channel in encode) { |
|
enc = encode[channel]; |
|
|
|
if (isArray(enc)) { |
|
// rule |
|
code += rule$1(channel, enc, scope, params, fields); |
|
} else { |
|
value = entry$1(channel, enc, scope, params, fields); |
|
code += set$3('o', channel, value); |
|
} |
|
} |
|
|
|
code += adjustSpatial(encode, marktype); |
|
code += 'return m;'; |
|
return { |
|
$expr: code, |
|
$fields: Object.keys(fields), |
|
$output: Object.keys(encode) |
|
}; |
|
} |
|
|
|
var MarkRole = 'mark'; |
|
var FrameRole$1 = 'frame'; |
|
var ScopeRole$1 = 'scope'; |
|
var AxisRole$1 = 'axis'; |
|
var AxisDomainRole = 'axis-domain'; |
|
var AxisGridRole = 'axis-grid'; |
|
var AxisLabelRole = 'axis-label'; |
|
var AxisTickRole = 'axis-tick'; |
|
var AxisTitleRole = 'axis-title'; |
|
var LegendRole$1 = 'legend'; |
|
var LegendBandRole = 'legend-band'; |
|
var LegendEntryRole = 'legend-entry'; |
|
var LegendGradientRole = 'legend-gradient'; |
|
var LegendLabelRole = 'legend-label'; |
|
var LegendSymbolRole = 'legend-symbol'; |
|
var LegendTitleRole = 'legend-title'; |
|
var TitleRole$1 = 'title'; |
|
var TitleTextRole = 'title-text'; |
|
var TitleSubtitleRole = 'title-subtitle'; |
|
|
|
function encoder(_) { |
|
return isObject(_) && !isArray(_) ? extend({}, _) : { |
|
value: _ |
|
}; |
|
} |
|
|
|
function addEncode(object, name, value, set) { |
|
if (value != null) { |
|
if (isObject(value) && !isArray(value)) { |
|
object.update[name] = value; |
|
} else { |
|
object[set || 'enter'][name] = { |
|
value: value |
|
}; |
|
} |
|
|
|
return 1; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
function addEncoders(object, enter, update) { |
|
for (var name in enter) { |
|
addEncode(object, name, enter[name]); |
|
} |
|
|
|
for (var _name in update) { |
|
addEncode(object, _name, update[_name], 'update'); |
|
} |
|
} |
|
|
|
function extendEncode(encode, extra, skip) { |
|
for (var name in extra) { |
|
if (skip && hasOwnProperty(skip, name)) continue; |
|
encode[name] = extend(encode[name] || {}, extra[name]); |
|
} |
|
|
|
return encode; |
|
} |
|
|
|
function encoders(encode, type, role, style, scope, params) { |
|
var enc, key; |
|
params = params || {}; |
|
params.encoders = { |
|
$encode: enc = {} |
|
}; |
|
encode = applyDefaults(encode, type, role, style, scope.config); |
|
|
|
for (key in encode) { |
|
enc[key] = parseEncode(encode[key], type, params, scope); |
|
} |
|
|
|
return params; |
|
} |
|
|
|
function applyDefaults(encode, type, role, style, config) { |
|
var defaults = {}, |
|
enter = {}, |
|
update, |
|
key, |
|
skip, |
|
props; // ignore legend and axis |
|
|
|
if (role == 'legend' || String(role).indexOf('axis') === 0) { |
|
role = null; |
|
} // resolve mark config |
|
|
|
|
|
props = role === FrameRole$1 ? config.group : role === MarkRole ? extend({}, config.mark, config[type]) : null; |
|
|
|
for (key in props) { |
|
// do not apply defaults if relevant fields are defined |
|
skip = has(key, encode) || (key === 'fill' || key === 'stroke') && (has('fill', encode) || has('stroke', encode)); |
|
if (!skip) applyDefault(defaults, key, props[key]); |
|
} // resolve styles, apply with increasing precedence |
|
|
|
|
|
array(style).forEach(function (name) { |
|
var props = config.style && config.style[name]; |
|
|
|
for (var key in props) { |
|
if (!has(key, encode)) { |
|
applyDefault(defaults, key, props[key]); |
|
} |
|
} |
|
}); |
|
encode = extend({}, encode); // defensive copy |
|
|
|
for (key in defaults) { |
|
props = defaults[key]; |
|
|
|
if (props.signal) { |
|
(update = update || {})[key] = props; |
|
} else { |
|
enter[key] = props; |
|
} |
|
} |
|
|
|
encode.enter = extend(enter, encode.enter); |
|
if (update) encode.update = extend(update, encode.update); |
|
return encode; |
|
} |
|
|
|
function applyDefault(defaults, key, value) { |
|
defaults[key] = value && value.signal ? { |
|
signal: value.signal |
|
} : { |
|
value: value |
|
}; |
|
} |
|
|
|
function has(key, encode) { |
|
return encode && (encode.enter && encode.enter[key] || encode.update && encode.update[key]); |
|
} |
|
|
|
function guideMark(type, role, style, key, dataRef, encode, extras) { |
|
return { |
|
type: type, |
|
name: extras ? extras.name : undefined, |
|
role: role, |
|
style: extras && extras.style || style, |
|
key: key, |
|
from: dataRef, |
|
interactive: !!(extras && extras.interactive), |
|
encode: extendEncode(encode, extras, Skip$1) |
|
}; |
|
} |
|
|
|
function lookup$5(spec, config) { |
|
var _ = function _(name, dflt) { |
|
return value$1(spec[name], value$1(config[name], dflt)); |
|
}; |
|
|
|
_.isVertical = function (s) { |
|
return Vertical === value$1(spec.direction, config.direction || (s ? config.symbolDirection : config.gradientDirection)); |
|
}; |
|
|
|
_.gradientLength = function () { |
|
return value$1(spec.gradientLength, config.gradientLength || config.gradientWidth); |
|
}; |
|
|
|
_.gradientThickness = function () { |
|
return value$1(spec.gradientThickness, config.gradientThickness || config.gradientHeight); |
|
}; |
|
|
|
_.entryColumns = function () { |
|
return value$1(spec.columns, value$1(config.columns, +_.isVertical(true))); |
|
}; |
|
|
|
return _; |
|
} |
|
|
|
function getEncoding(name, encode) { |
|
var v = encode && (encode.update && encode.update[name] || encode.enter && encode.enter[name]); |
|
return v && v.signal ? v : v ? v.value : null; |
|
} |
|
|
|
function getStyle(name, scope, style) { |
|
var s = scope.config.style[style]; |
|
return s && s[name]; |
|
} |
|
|
|
function anchorExpr(s, e, m) { |
|
return "item.anchor === \"".concat(Start$1, "\" ? ").concat(s, " : item.anchor === \"").concat(End$1, "\" ? ").concat(e, " : ").concat(m); |
|
} |
|
|
|
var alignExpr = anchorExpr($(Left$1), $(Right$1), $(Center$1)); |
|
|
|
function tickBand(_) { |
|
var v = _('tickBand'), |
|
offset = _('tickOffset'), |
|
band, |
|
extra; |
|
|
|
if (!v) { |
|
// if no tick band entry, fall back on other properties |
|
band = _('bandPosition'); |
|
extra = _('tickExtra'); |
|
} else if (v.signal) { |
|
// if signal, augment code to interpret values |
|
band = { |
|
signal: "(".concat(v.signal, ")==='extent'?1:0.5") |
|
}; |
|
extra = { |
|
signal: "(".concat(v.signal, ")==='extent'?true:false") |
|
}; |
|
|
|
if (!isObject(offset)) { |
|
offset = { |
|
signal: "(".concat(v.signal, ")==='extent'?0:").concat(offset) |
|
}; |
|
} |
|
} else if (v === 'extent') { |
|
// if constant, simply set values |
|
band = 1; |
|
extra = true; |
|
offset = 0; |
|
} else { |
|
band = 0.5; |
|
extra = false; |
|
} |
|
|
|
return { |
|
extra: extra, |
|
band: band, |
|
offset: offset |
|
}; |
|
} |
|
|
|
var GroupMark = 'group'; |
|
var RectMark = 'rect'; |
|
var RuleMark = 'rule'; |
|
var SymbolMark = 'symbol'; |
|
var TextMark = 'text'; |
|
|
|
function legendGradient(spec, scale, config, userEncode) { |
|
var _ = lookup$5(spec, config), |
|
vertical = _.isVertical(), |
|
thickness = _.gradientThickness(), |
|
length = _.gradientLength(), |
|
encode, |
|
enter, |
|
start, |
|
stop, |
|
width, |
|
height; |
|
|
|
if (vertical) { |
|
start = [0, 1]; |
|
stop = [0, 0]; |
|
width = thickness; |
|
height = length; |
|
} else { |
|
start = [0, 0]; |
|
stop = [1, 0]; |
|
width = length; |
|
height = thickness; |
|
} |
|
|
|
encode = { |
|
enter: enter = { |
|
opacity: zero$2, |
|
x: zero$2, |
|
y: zero$2, |
|
width: encoder(width), |
|
height: encoder(height) |
|
}, |
|
update: extend({}, enter, { |
|
opacity: one$2, |
|
fill: { |
|
gradient: scale, |
|
start: start, |
|
stop: stop |
|
} |
|
}), |
|
exit: { |
|
opacity: zero$2 |
|
} |
|
}; |
|
addEncoders(encode, { |
|
stroke: _('gradientStrokeColor'), |
|
strokeWidth: _('gradientStrokeWidth') |
|
}, { |
|
// update |
|
opacity: _('gradientOpacity') |
|
}); |
|
return guideMark(RectMark, LegendGradientRole, null, undefined, undefined, encode, userEncode); |
|
} |
|
|
|
function legendGradientDiscrete(spec, scale, config, userEncode, dataRef) { |
|
var _ = lookup$5(spec, config), |
|
vertical = _.isVertical(), |
|
thickness = _.gradientThickness(), |
|
length = _.gradientLength(), |
|
encode, |
|
enter, |
|
u, |
|
v, |
|
uu, |
|
vv, |
|
adjust = ''; |
|
|
|
vertical ? (u = 'y', uu = 'y2', v = 'x', vv = 'width', adjust = '1-') : (u = 'x', uu = 'x2', v = 'y', vv = 'height'); |
|
enter = { |
|
opacity: zero$2, |
|
fill: { |
|
scale: scale, |
|
field: Value |
|
} |
|
}; |
|
enter[u] = { |
|
signal: adjust + 'datum.' + Perc, |
|
mult: length |
|
}; |
|
enter[v] = zero$2; |
|
enter[uu] = { |
|
signal: adjust + 'datum.' + Perc2, |
|
mult: length |
|
}; |
|
enter[vv] = encoder(thickness); |
|
encode = { |
|
enter: enter, |
|
update: extend({}, enter, { |
|
opacity: one$2 |
|
}), |
|
exit: { |
|
opacity: zero$2 |
|
} |
|
}; |
|
addEncoders(encode, { |
|
stroke: _('gradientStrokeColor'), |
|
strokeWidth: _('gradientStrokeWidth') |
|
}, { |
|
// update |
|
opacity: _('gradientOpacity') |
|
}); |
|
return guideMark(RectMark, LegendBandRole, null, Value, dataRef, encode, userEncode); |
|
} |
|
|
|
var alignExpr$1 = "datum.".concat(Perc, "<=0?\"").concat(Left$1, "\":datum.").concat(Perc, ">=1?\"").concat(Right$1, "\":\"").concat(Center$1, "\""), |
|
baselineExpr = "datum.".concat(Perc, "<=0?\"").concat(Bottom$1, "\":datum.").concat(Perc, ">=1?\"").concat(Top$1, "\":\"").concat(Middle$1, "\""); |
|
|
|
function legendGradientLabels(spec, config, userEncode, dataRef) { |
|
var _ = lookup$5(spec, config), |
|
vertical = _.isVertical(), |
|
thickness = encoder(_.gradientThickness()), |
|
length = _.gradientLength(), |
|
overlap = _('labelOverlap'), |
|
separation = _('labelSeparation'), |
|
encode, |
|
enter, |
|
update, |
|
u, |
|
v, |
|
adjust = ''; |
|
|
|
encode = { |
|
enter: enter = { |
|
opacity: zero$2 |
|
}, |
|
update: update = { |
|
opacity: one$2, |
|
text: { |
|
field: Label |
|
} |
|
}, |
|
exit: { |
|
opacity: zero$2 |
|
} |
|
}; |
|
addEncoders(encode, { |
|
fill: _('labelColor'), |
|
fillOpacity: _('labelOpacity'), |
|
font: _('labelFont'), |
|
fontSize: _('labelFontSize'), |
|
fontStyle: _('labelFontStyle'), |
|
fontWeight: _('labelFontWeight'), |
|
limit: value$1(spec.labelLimit, config.gradientLabelLimit) |
|
}); |
|
|
|
if (vertical) { |
|
enter.align = { |
|
value: 'left' |
|
}; |
|
enter.baseline = update.baseline = { |
|
signal: baselineExpr |
|
}; |
|
u = 'y'; |
|
v = 'x'; |
|
adjust = '1-'; |
|
} else { |
|
enter.align = update.align = { |
|
signal: alignExpr$1 |
|
}; |
|
enter.baseline = { |
|
value: 'top' |
|
}; |
|
u = 'x'; |
|
v = 'y'; |
|
} |
|
|
|
enter[u] = update[u] = { |
|
signal: adjust + 'datum.' + Perc, |
|
mult: length |
|
}; |
|
enter[v] = update[v] = thickness; |
|
thickness.offset = value$1(spec.labelOffset, config.gradientLabelOffset) || 0; |
|
spec = guideMark(TextMark, LegendLabelRole, GuideLabelStyle, Value, dataRef, encode, userEncode); |
|
|
|
if (overlap) { |
|
spec.overlap = { |
|
separation: separation, |
|
method: overlap, |
|
order: 'datum.' + Index |
|
}; |
|
} |
|
|
|
return spec; |
|
} |
|
|
|
function guideGroup(role, style, name, dataRef, interactive, encode, marks, layout) { |
|
return { |
|
type: GroupMark, |
|
name: name, |
|
role: role, |
|
style: style, |
|
from: dataRef, |
|
interactive: interactive || false, |
|
encode: encode, |
|
marks: marks, |
|
layout: layout |
|
}; |
|
} // userEncode is top-level, includes entries, symbols, labels |
|
|
|
|
|
function legendSymbolGroups(spec, config, userEncode, dataRef, columns) { |
|
var _ = lookup$5(spec, config), |
|
entries = userEncode.entries, |
|
interactive = !!(entries && entries.interactive), |
|
name = entries ? entries.name : undefined, |
|
height = _('clipHeight'), |
|
symbolOffset = _('symbolOffset'), |
|
valueRef = { |
|
data: 'value' |
|
}, |
|
encode = {}, |
|
xSignal = "".concat(columns, " ? datum.").concat(Offset, " : datum.").concat(Size), |
|
yEncode = height ? encoder(height) : { |
|
field: Size |
|
}, |
|
index = "datum.".concat(Index), |
|
ncols = "max(1, ".concat(columns, ")"), |
|
enter, |
|
update, |
|
labelOffset, |
|
symbols, |
|
labels, |
|
nrows, |
|
sort; |
|
|
|
yEncode.mult = 0.5; // -- LEGEND SYMBOLS -- |
|
|
|
encode = { |
|
enter: enter = { |
|
opacity: zero$2, |
|
x: { |
|
signal: xSignal, |
|
mult: 0.5, |
|
offset: symbolOffset |
|
}, |
|
y: yEncode |
|
}, |
|
update: update = { |
|
opacity: one$2, |
|
x: enter.x, |
|
y: enter.y |
|
}, |
|
exit: { |
|
opacity: zero$2 |
|
} |
|
}; |
|
var baseFill = null, |
|
baseStroke = null; |
|
|
|
if (!spec.fill) { |
|
baseFill = config.symbolBaseFillColor; |
|
baseStroke = config.symbolBaseStrokeColor; |
|
} |
|
|
|
addEncoders(encode, { |
|
fill: _('symbolFillColor', baseFill), |
|
shape: _('symbolType'), |
|
size: _('symbolSize'), |
|
stroke: _('symbolStrokeColor', baseStroke), |
|
strokeDash: _('symbolDash'), |
|
strokeDashOffset: _('symbolDashOffset'), |
|
strokeWidth: _('symbolStrokeWidth') |
|
}, { |
|
// update |
|
opacity: _('symbolOpacity') |
|
}); |
|
LegendScales.forEach(function (scale) { |
|
if (spec[scale]) { |
|
update[scale] = enter[scale] = { |
|
scale: spec[scale], |
|
field: Value |
|
}; |
|
} |
|
}); |
|
symbols = guideMark(SymbolMark, LegendSymbolRole, null, Value, valueRef, encode, userEncode.symbols); |
|
if (height) symbols.clip = true; // -- LEGEND LABELS -- |
|
|
|
labelOffset = encoder(symbolOffset); |
|
labelOffset.offset = _('labelOffset'); |
|
encode = { |
|
enter: enter = { |
|
opacity: zero$2, |
|
x: { |
|
signal: xSignal, |
|
offset: labelOffset |
|
}, |
|
y: yEncode |
|
}, |
|
update: update = { |
|
opacity: one$2, |
|
text: { |
|
field: Label |
|
}, |
|
x: enter.x, |
|
y: enter.y |
|
}, |
|
exit: { |
|
opacity: zero$2 |
|
} |
|
}; |
|
addEncoders(encode, { |
|
align: _('labelAlign'), |
|
baseline: _('labelBaseline'), |
|
fill: _('labelColor'), |
|
fillOpacity: _('labelOpacity'), |
|
font: _('labelFont'), |
|
fontSize: _('labelFontSize'), |
|
fontStyle: _('labelFontStyle'), |
|
fontWeight: _('labelFontWeight'), |
|
limit: _('labelLimit') |
|
}); |
|
labels = guideMark(TextMark, LegendLabelRole, GuideLabelStyle, Value, valueRef, encode, userEncode.labels); // -- LEGEND ENTRY GROUPS -- |
|
|
|
encode = { |
|
enter: { |
|
noBound: { |
|
value: !height |
|
}, |
|
// ignore width/height in bounds calc |
|
width: zero$2, |
|
height: height ? encoder(height) : zero$2, |
|
opacity: zero$2 |
|
}, |
|
exit: { |
|
opacity: zero$2 |
|
}, |
|
update: update = { |
|
opacity: one$2, |
|
row: { |
|
signal: null |
|
}, |
|
column: { |
|
signal: null |
|
} |
|
} |
|
}; // annotate and sort groups to ensure correct ordering |
|
|
|
if (_.isVertical(true)) { |
|
nrows = "ceil(item.mark.items.length / ".concat(ncols, ")"); |
|
update.row.signal = "".concat(index, "%").concat(nrows); |
|
update.column.signal = "floor(".concat(index, " / ").concat(nrows, ")"); |
|
sort = { |
|
field: ['row', index] |
|
}; |
|
} else { |
|
update.row.signal = "floor(".concat(index, " / ").concat(ncols, ")"); |
|
update.column.signal = "".concat(index, " % ").concat(ncols); |
|
sort = { |
|
field: index |
|
}; |
|
} // handle zero column case (implies infinite columns) |
|
|
|
|
|
update.column.signal = "".concat(columns, "?").concat(update.column.signal, ":").concat(index); // facet legend entries into sub-groups |
|
|
|
dataRef = { |
|
facet: { |
|
data: dataRef, |
|
name: 'value', |
|
groupby: Index |
|
} |
|
}; |
|
spec = guideGroup(ScopeRole$1, null, name, dataRef, interactive, extendEncode(encode, entries, Skip$1), [symbols, labels]); |
|
spec.sort = sort; |
|
return spec; |
|
} |
|
|
|
function legendSymbolLayout(spec, config) { |
|
var _ = lookup$5(spec, config); // layout parameters for legend entries |
|
|
|
|
|
return { |
|
align: _('gridAlign'), |
|
columns: _.entryColumns(), |
|
center: { |
|
row: true, |
|
column: false |
|
}, |
|
padding: { |
|
row: _('rowPadding'), |
|
column: _('columnPadding') |
|
} |
|
}; |
|
} // expression logic for align, anchor, angle, and baseline calculation |
|
|
|
|
|
var isL = 'item.orient === "left"', |
|
isR = 'item.orient === "right"', |
|
isLR = "(".concat(isL, " || ").concat(isR, ")"), |
|
isVG = "datum.vgrad && ".concat(isLR), |
|
baseline = anchorExpr('"top"', '"bottom"', '"middle"'), |
|
alignFlip = anchorExpr('"right"', '"left"', '"center"'), |
|
exprAlign = "datum.vgrad && ".concat(isR, " ? (").concat(alignFlip, ") : (").concat(isLR, " && !(datum.vgrad && ").concat(isL, ")) ? \"left\" : ").concat(alignExpr), |
|
exprAnchor = "item._anchor || (".concat(isLR, " ? \"middle\" : \"start\")"), |
|
exprAngle = "".concat(isVG, " ? (").concat(isL, " ? -90 : 90) : 0"), |
|
exprBaseline = "".concat(isLR, " ? (datum.vgrad ? (").concat(isR, " ? \"bottom\" : \"top\") : ").concat(baseline, ") : \"top\""); |
|
|
|
function legendTitle(spec, config, userEncode, dataRef) { |
|
var _ = lookup$5(spec, config), |
|
encode; |
|
|
|
encode = { |
|
enter: { |
|
opacity: zero$2 |
|
}, |
|
update: { |
|
opacity: one$2, |
|
x: { |
|
field: { |
|
group: 'padding' |
|
} |
|
}, |
|
y: { |
|
field: { |
|
group: 'padding' |
|
} |
|
} |
|
}, |
|
exit: { |
|
opacity: zero$2 |
|
} |
|
}; |
|
addEncoders(encode, { |
|
orient: _('titleOrient'), |
|
_anchor: _('titleAnchor'), |
|
anchor: { |
|
signal: exprAnchor |
|
}, |
|
angle: { |
|
signal: exprAngle |
|
}, |
|
align: { |
|
signal: exprAlign |
|
}, |
|
baseline: { |
|
signal: exprBaseline |
|
}, |
|
text: spec.title, |
|
fill: _('titleColor'), |
|
fillOpacity: _('titleOpacity'), |
|
font: _('titleFont'), |
|
fontSize: _('titleFontSize'), |
|
fontStyle: _('titleFontStyle'), |
|
fontWeight: _('titleFontWeight'), |
|
limit: _('titleLimit'), |
|
lineHeight: _('titleLineHeight') |
|
}, { |
|
// require update |
|
align: _('titleAlign'), |
|
baseline: _('titleBaseline') |
|
}); |
|
return guideMark(TextMark, LegendTitleRole, GuideTitleStyle, null, dataRef, encode, userEncode); |
|
} |
|
|
|
function clip$3(clip, scope) { |
|
var expr; |
|
|
|
if (isObject(clip)) { |
|
if (clip.signal) { |
|
expr = clip.signal; |
|
} else if (clip.path) { |
|
expr = 'pathShape(' + param(clip.path) + ')'; |
|
} else if (clip.sphere) { |
|
expr = 'geoShape(' + param(clip.sphere) + ', {type: "Sphere"})'; |
|
} |
|
} |
|
|
|
return expr ? scope.signalRef(expr) : !!clip; |
|
} |
|
|
|
function param(value) { |
|
return isObject(value) && value.signal ? value.signal : $(value); |
|
} |
|
|
|
function getRole(spec) { |
|
var role = spec.role || ''; |
|
return !role.indexOf('axis') || !role.indexOf('legend') || !role.indexOf('title') ? role : spec.type === GroupMark ? ScopeRole$1 : role || MarkRole; |
|
} |
|
|
|
function definition$1(spec) { |
|
return { |
|
marktype: spec.type, |
|
name: spec.name || undefined, |
|
role: spec.role || getRole(spec), |
|
zindex: +spec.zindex || undefined |
|
}; |
|
} |
|
|
|
function interactive(spec, scope) { |
|
return spec && spec.signal ? scope.signalRef(spec.signal) : spec === false ? false : true; |
|
} |
|
/** |
|
* Parse a data transform specification. |
|
*/ |
|
|
|
|
|
function parseTransform(spec, scope) { |
|
var def = definition(spec.type); |
|
if (!def) error('Unrecognized transform type: ' + $(spec.type)); |
|
var t = entry(def.type.toLowerCase(), null, parseParameters$1(def, spec, scope)); |
|
if (spec.signal) scope.addSignal(spec.signal, scope.proxy(t)); |
|
t.metadata = def.metadata || {}; |
|
return t; |
|
} |
|
/** |
|
* Parse all parameters of a data transform. |
|
*/ |
|
|
|
|
|
function parseParameters$1(def, spec, scope) { |
|
var params = {}, |
|
pdef, |
|
i, |
|
n; |
|
|
|
for (i = 0, n = def.params.length; i < n; ++i) { |
|
pdef = def.params[i]; |
|
params[pdef.name] = parseParameter$2(pdef, spec, scope); |
|
} |
|
|
|
return params; |
|
} |
|
/** |
|
* Parse a data transform parameter. |
|
*/ |
|
|
|
|
|
function parseParameter$2(def, spec, scope) { |
|
var type = def.type, |
|
value = spec[def.name]; |
|
|
|
if (type === 'index') { |
|
return parseIndexParameter(def, spec, scope); |
|
} else if (value === undefined) { |
|
if (def.required) { |
|
error('Missing required ' + $(spec.type) + ' parameter: ' + $(def.name)); |
|
} |
|
|
|
return; |
|
} else if (type === 'param') { |
|
return parseSubParameters(def, spec, scope); |
|
} else if (type === 'projection') { |
|
return scope.projectionRef(spec[def.name]); |
|
} |
|
|
|
return def.array && !isSignal(value) ? value.map(function (v) { |
|
return parameterValue(def, v, scope); |
|
}) : parameterValue(def, value, scope); |
|
} |
|
/** |
|
* Parse a single parameter value. |
|
*/ |
|
|
|
|
|
function parameterValue(def, value, scope) { |
|
var type = def.type; |
|
|
|
if (isSignal(value)) { |
|
return isExpr$1(type) ? error('Expression references can not be signals.') : isField(type) ? scope.fieldRef(value) : isCompare(type) ? scope.compareRef(value) : scope.signalRef(value.signal); |
|
} else { |
|
var expr = def.expr || isField(type); |
|
return expr && outerExpr(value) ? scope.exprRef(value.expr, value.as) : expr && outerField(value) ? fieldRef(value.field, value.as) : isExpr$1(type) ? parseExpression$1(value, scope) : isData(type) ? ref(scope.getData(value).values) : isField(type) ? fieldRef(value) : isCompare(type) ? scope.compareRef(value) : value; |
|
} |
|
} |
|
/** |
|
* Parse parameter for accessing an index of another data set. |
|
*/ |
|
|
|
|
|
function parseIndexParameter(def, spec, scope) { |
|
if (!isString(spec.from)) { |
|
error('Lookup "from" parameter must be a string literal.'); |
|
} |
|
|
|
return scope.getData(spec.from).lookupRef(scope, spec.key); |
|
} |
|
/** |
|
* Parse a parameter that contains one or more sub-parameter objects. |
|
*/ |
|
|
|
|
|
function parseSubParameters(def, spec, scope) { |
|
var value = spec[def.name]; |
|
|
|
if (def.array) { |
|
if (!isArray(value)) { |
|
// signals not allowed! |
|
error('Expected an array of sub-parameters. Instead: ' + $(value)); |
|
} |
|
|
|
return value.map(function (v) { |
|
return parseSubParameter(def, v, scope); |
|
}); |
|
} else { |
|
return parseSubParameter(def, value, scope); |
|
} |
|
} |
|
/** |
|
* Parse a sub-parameter object. |
|
*/ |
|
|
|
|
|
function parseSubParameter(def, value, scope) { |
|
var params, pdef, k, i, n; // loop over defs to find matching key |
|
|
|
for (i = 0, n = def.params.length; i < n; ++i) { |
|
pdef = def.params[i]; |
|
|
|
for (k in pdef.key) { |
|
if (pdef.key[k] !== value[k]) { |
|
pdef = null; |
|
break; |
|
} |
|
} |
|
|
|
if (pdef) break; |
|
} // raise error if matching key not found |
|
|
|
|
|
if (!pdef) error('Unsupported parameter: ' + $(value)); // parse params, create Params transform, return ref |
|
|
|
params = extend(parseParameters$1(pdef, value, scope), pdef.key); |
|
return ref(scope.add(Params$2(params))); |
|
} // -- Utilities ----- |
|
|
|
|
|
function outerExpr(_) { |
|
return _ && _.expr; |
|
} |
|
|
|
function outerField(_) { |
|
return _ && _.field; |
|
} |
|
|
|
function isData(_) { |
|
return _ === 'data'; |
|
} |
|
|
|
function isExpr$1(_) { |
|
return _ === 'expr'; |
|
} |
|
|
|
function isField(_) { |
|
return _ === 'field'; |
|
} |
|
|
|
function isCompare(_) { |
|
return _ === 'compare'; |
|
} |
|
|
|
function parseData(from, group, scope) { |
|
var facet, key, op, dataRef, parent; // if no source data, generate singleton datum |
|
|
|
if (!from) { |
|
dataRef = ref(scope.add(Collect$1(null, [{}]))); |
|
} // if faceted, process facet specification |
|
else if (facet = from.facet) { |
|
if (!group) error('Only group marks can be faceted.'); // use pre-faceted source data, if available |
|
|
|
if (facet.field != null) { |
|
dataRef = parent = getDataRef(facet, scope); |
|
} else { |
|
// generate facet aggregates if no direct data specification |
|
if (!from.data) { |
|
op = parseTransform(extend({ |
|
type: 'aggregate', |
|
groupby: array(facet.groupby) |
|
}, facet.aggregate), scope); |
|
op.params.key = scope.keyRef(facet.groupby); |
|
op.params.pulse = getDataRef(facet, scope); |
|
dataRef = parent = ref(scope.add(op)); |
|
} else { |
|
parent = ref(scope.getData(from.data).aggregate); |
|
} |
|
|
|
key = scope.keyRef(facet.groupby, true); |
|
} |
|
} // if not yet defined, get source data reference |
|
|
|
|
|
if (!dataRef) { |
|
dataRef = getDataRef(from, scope); |
|
} |
|
|
|
return { |
|
key: key, |
|
pulse: dataRef, |
|
parent: parent |
|
}; |
|
} |
|
|
|
function getDataRef(from, scope) { |
|
return from.$ref ? from : from.data && from.data.$ref ? from.data : ref(scope.getData(from.data).output); |
|
} |
|
|
|
function DataScope(scope, input, output, values, aggr) { |
|
this.scope = scope; // parent scope object |
|
|
|
this.input = input; // first operator in pipeline (tuple input) |
|
|
|
this.output = output; // last operator in pipeline (tuple output) |
|
|
|
this.values = values; // operator for accessing tuples (but not tuple flow) |
|
// last aggregate in transform pipeline |
|
|
|
this.aggregate = aggr; // lookup table of field indices |
|
|
|
this.index = {}; |
|
} |
|
|
|
DataScope.fromEntries = function (scope, entries) { |
|
var n = entries.length, |
|
i = 1, |
|
input = entries[0], |
|
values = entries[n - 1], |
|
output = entries[n - 2], |
|
aggr = null; |
|
|
|
if (input && input.type === 'load') { |
|
input = entries[1]; |
|
} // add operator entries to this scope, wire up pulse chain |
|
|
|
|
|
scope.add(entries[0]); |
|
|
|
for (; i < n; ++i) { |
|
entries[i].params.pulse = ref(entries[i - 1]); |
|
scope.add(entries[i]); |
|
if (entries[i].type === 'aggregate') aggr = entries[i]; |
|
} |
|
|
|
return new DataScope(scope, input, output, values, aggr); |
|
}; |
|
|
|
var prototype$1t = DataScope.prototype; |
|
|
|
prototype$1t.countsRef = function (scope, field, sort) { |
|
var ds = this, |
|
cache = ds.counts || (ds.counts = {}), |
|
k = fieldKey(field), |
|
v, |
|
a, |
|
p; |
|
|
|
if (k != null) { |
|
scope = ds.scope; |
|
v = cache[k]; |
|
} |
|
|
|
if (!v) { |
|
p = { |
|
groupby: scope.fieldRef(field, 'key'), |
|
pulse: ref(ds.output) |
|
}; |
|
if (sort && sort.field) addSortField(scope, p, sort); |
|
a = scope.add(Aggregate$1(p)); |
|
v = scope.add(Collect$1({ |
|
pulse: ref(a) |
|
})); |
|
v = { |
|
agg: a, |
|
ref: ref(v) |
|
}; |
|
if (k != null) cache[k] = v; |
|
} else if (sort && sort.field) { |
|
addSortField(scope, v.agg.params, sort); |
|
} |
|
|
|
return v.ref; |
|
}; |
|
|
|
function fieldKey(field) { |
|
return isString(field) ? field : null; |
|
} |
|
|
|
function addSortField(scope, p, sort) { |
|
var as = aggrField(sort.op, sort.field), |
|
s; |
|
|
|
if (p.ops) { |
|
for (var i = 0, n = p.as.length; i < n; ++i) { |
|
if (p.as[i] === as) return; |
|
} |
|
} else { |
|
p.ops = ['count']; |
|
p.fields = [null]; |
|
p.as = ['count']; |
|
} |
|
|
|
if (sort.op) { |
|
p.ops.push((s = sort.op.signal) ? scope.signalRef(s) : sort.op); |
|
p.fields.push(scope.fieldRef(sort.field)); |
|
p.as.push(as); |
|
} |
|
} |
|
|
|
function cache(scope, ds, name, optype, field, counts, index) { |
|
var cache = ds[name] || (ds[name] = {}), |
|
sort = sortKey(counts), |
|
k = fieldKey(field), |
|
v, |
|
op; |
|
|
|
if (k != null) { |
|
scope = ds.scope; |
|
k = k + (sort ? '|' + sort : ''); |
|
v = cache[k]; |
|
} |
|
|
|
if (!v) { |
|
var params = counts ? { |
|
field: keyFieldRef, |
|
pulse: ds.countsRef(scope, field, counts) |
|
} : { |
|
field: scope.fieldRef(field), |
|
pulse: ref(ds.output) |
|
}; |
|
if (sort) params.sort = scope.sortRef(counts); |
|
op = scope.add(entry(optype, undefined, params)); |
|
if (index) ds.index[field] = op; |
|
v = ref(op); |
|
if (k != null) cache[k] = v; |
|
} |
|
|
|
return v; |
|
} |
|
|
|
prototype$1t.tuplesRef = function () { |
|
return ref(this.values); |
|
}; |
|
|
|
prototype$1t.extentRef = function (scope, field) { |
|
return cache(scope, this, 'extent', 'extent', field, false); |
|
}; |
|
|
|
prototype$1t.domainRef = function (scope, field) { |
|
return cache(scope, this, 'domain', 'values', field, false); |
|
}; |
|
|
|
prototype$1t.valuesRef = function (scope, field, sort) { |
|
return cache(scope, this, 'vals', 'values', field, sort || true); |
|
}; |
|
|
|
prototype$1t.lookupRef = function (scope, field) { |
|
return cache(scope, this, 'lookup', 'tupleindex', field, false); |
|
}; |
|
|
|
prototype$1t.indataRef = function (scope, field) { |
|
return cache(scope, this, 'indata', 'tupleindex', field, true, true); |
|
}; |
|
|
|
function parseFacet(spec, scope, group) { |
|
var facet = spec.from.facet, |
|
name = facet.name, |
|
data = getDataRef(facet, scope), |
|
subscope, |
|
source, |
|
values, |
|
op; |
|
|
|
if (!facet.name) { |
|
error('Facet must have a name: ' + $(facet)); |
|
} |
|
|
|
if (!facet.data) { |
|
error('Facet must reference a data set: ' + $(facet)); |
|
} |
|
|
|
if (facet.field) { |
|
op = scope.add(PreFacet$1({ |
|
field: scope.fieldRef(facet.field), |
|
pulse: data |
|
})); |
|
} else if (facet.groupby) { |
|
op = scope.add(Facet$1({ |
|
key: scope.keyRef(facet.groupby), |
|
group: ref(scope.proxy(group.parent)), |
|
pulse: data |
|
})); |
|
} else { |
|
error('Facet must specify groupby or field: ' + $(facet)); |
|
} // initialize facet subscope |
|
|
|
|
|
subscope = scope.fork(); |
|
source = subscope.add(Collect$1()); |
|
values = subscope.add(Sieve$1({ |
|
pulse: ref(source) |
|
})); |
|
subscope.addData(name, new DataScope(subscope, source, source, values)); |
|
subscope.addSignal('parent', null); // parse faceted subflow |
|
|
|
op.params.subflow = { |
|
$subflow: parseSpec(spec, subscope).toRuntime() |
|
}; |
|
} |
|
|
|
function parseSubflow(spec, scope, input) { |
|
var op = scope.add(PreFacet$1({ |
|
pulse: input.pulse |
|
})), |
|
subscope = scope.fork(); |
|
subscope.add(Sieve$1()); |
|
subscope.addSignal('parent', null); // parse group mark subflow |
|
|
|
op.params.subflow = { |
|
$subflow: parseSpec(spec, subscope).toRuntime() |
|
}; |
|
} |
|
|
|
function parseTrigger(spec, scope, name) { |
|
var remove = spec.remove, |
|
insert = spec.insert, |
|
toggle = spec.toggle, |
|
modify = spec.modify, |
|
values = spec.values, |
|
op = scope.add(operator()), |
|
update, |
|
expr; |
|
update = 'if(' + spec.trigger + ',modify("' + name + '",' + [insert, remove, toggle, modify, values].map(function (_) { |
|
return _ == null ? 'null' : _; |
|
}).join(',') + '),0)'; |
|
expr = parseExpression$1(update, scope); |
|
op.update = expr.$expr; |
|
op.params = expr.$params; |
|
} |
|
|
|
function parseMark(spec, scope) { |
|
var role = getRole(spec), |
|
group = spec.type === GroupMark, |
|
facet = spec.from && spec.from.facet, |
|
layout = spec.layout || role === ScopeRole$1 || role === FrameRole$1, |
|
nested = role === MarkRole || layout || facet, |
|
overlap = spec.overlap, |
|
ops, |
|
op, |
|
input, |
|
store, |
|
enc, |
|
bound, |
|
render, |
|
sieve, |
|
name, |
|
joinRef, |
|
markRef, |
|
encodeRef, |
|
layoutRef, |
|
boundRef; // resolve input data |
|
|
|
input = parseData(spec.from, group, scope); // data join to map tuples to visual items |
|
|
|
op = scope.add(DataJoin$1({ |
|
key: input.key || (spec.key ? fieldRef(spec.key) : undefined), |
|
pulse: input.pulse, |
|
clean: !group |
|
})); |
|
joinRef = ref(op); // collect visual items |
|
|
|
op = store = scope.add(Collect$1({ |
|
pulse: joinRef |
|
})); // connect visual items to scenegraph |
|
|
|
op = scope.add(Mark$1({ |
|
markdef: definition$1(spec), |
|
interactive: interactive(spec.interactive, scope), |
|
clip: clip$3(spec.clip, scope), |
|
context: { |
|
$context: true |
|
}, |
|
groups: scope.lookup(), |
|
parent: scope.signals.parent ? scope.signalRef('parent') : null, |
|
index: scope.markpath(), |
|
pulse: ref(op) |
|
})); |
|
markRef = ref(op); // add visual encoders |
|
|
|
op = enc = scope.add(Encode$1(encoders(spec.encode, spec.type, role, spec.style, scope, { |
|
mod: false, |
|
pulse: markRef |
|
}))); // monitor parent marks to propagate changes |
|
|
|
op.params.parent = scope.encode(); // add post-encoding transforms, if defined |
|
|
|
if (spec.transform) { |
|
spec.transform.forEach(function (_) { |
|
var tx = parseTransform(_, scope), |
|
md = tx.metadata; |
|
|
|
if (md.generates || md.changes) { |
|
error('Mark transforms should not generate new data.'); |
|
} |
|
|
|
if (!md.nomod) enc.params.mod = true; // update encode mod handling |
|
|
|
tx.params.pulse = ref(op); |
|
scope.add(op = tx); |
|
}); |
|
} // if item sort specified, perform post-encoding |
|
|
|
|
|
if (spec.sort) { |
|
op = scope.add(SortItems$1({ |
|
sort: scope.compareRef(spec.sort), |
|
pulse: ref(op) |
|
})); |
|
} |
|
|
|
encodeRef = ref(op); // add view layout operator if needed |
|
|
|
if (facet || layout) { |
|
layout = scope.add(ViewLayout$1({ |
|
layout: scope.objectProperty(spec.layout), |
|
legends: scope.legends, |
|
mark: markRef, |
|
pulse: encodeRef |
|
})); |
|
layoutRef = ref(layout); |
|
} // compute bounding boxes |
|
|
|
|
|
bound = scope.add(Bound$1({ |
|
mark: markRef, |
|
pulse: layoutRef || encodeRef |
|
})); |
|
boundRef = ref(bound); // if group mark, recurse to parse nested content |
|
|
|
if (group) { |
|
// juggle layout & bounds to ensure they run *after* any faceting transforms |
|
if (nested) { |
|
ops = scope.operators; |
|
ops.pop(); |
|
if (layout) ops.pop(); |
|
} |
|
|
|
scope.pushState(encodeRef, layoutRef || boundRef, joinRef); |
|
facet ? parseFacet(spec, scope, input) // explicit facet |
|
: nested ? parseSubflow(spec, scope, input) // standard mark group |
|
: parseSpec(spec, scope); // guide group, we can avoid nested scopes |
|
|
|
scope.popState(); |
|
|
|
if (nested) { |
|
if (layout) ops.push(layout); |
|
ops.push(bound); |
|
} |
|
} // if requested, add overlap removal transform |
|
|
|
|
|
if (overlap) { |
|
boundRef = parseOverlap(overlap, boundRef, scope); |
|
} // render / sieve items |
|
|
|
|
|
render = scope.add(Render$1({ |
|
pulse: boundRef |
|
})); |
|
sieve = scope.add(Sieve$1({ |
|
pulse: ref(render) |
|
}, undefined, scope.parent())); // if mark is named, make accessible as reactive geometry |
|
// add trigger updates if defined |
|
|
|
if (spec.name != null) { |
|
name = spec.name; |
|
scope.addData(name, new DataScope(scope, store, render, sieve)); |
|
if (spec.on) spec.on.forEach(function (on) { |
|
if (on.insert || on.remove || on.toggle) { |
|
error('Marks only support modify triggers.'); |
|
} |
|
|
|
parseTrigger(on, scope, name); |
|
}); |
|
} |
|
} |
|
|
|
function parseOverlap(overlap, source, scope) { |
|
var method = overlap.method, |
|
bound = overlap.bound, |
|
sep = overlap.separation, |
|
tol; |
|
var params = { |
|
separation: isSignal(sep) ? scope.signalRef(sep.signal) : sep, |
|
method: isSignal(method) ? scope.signalRef(method.signal) : method, |
|
pulse: source |
|
}; |
|
|
|
if (overlap.order) { |
|
params.sort = scope.compareRef({ |
|
field: overlap.order |
|
}); |
|
} |
|
|
|
if (bound) { |
|
tol = bound.tolerance; |
|
params.boundTolerance = isSignal(tol) ? scope.signalRef(tol.signal) : +tol; |
|
params.boundScale = scope.scaleRef(bound.scale); |
|
params.boundOrient = bound.orient; |
|
} |
|
|
|
return ref(scope.add(Overlap$1(params))); |
|
} |
|
|
|
function parseLegend(spec, scope) { |
|
var config = scope.config.legend, |
|
encode = spec.encode || {}, |
|
legendEncode = encode.legend || {}, |
|
name = legendEncode.name || undefined, |
|
interactive = legendEncode.interactive, |
|
style = legendEncode.style, |
|
_ = lookup$5(spec, config), |
|
entryEncode, |
|
entryLayout, |
|
params, |
|
children, |
|
type, |
|
datum, |
|
dataRef, |
|
entryRef, |
|
group; // resolve 'canonical' scale name |
|
|
|
|
|
var scale = LegendScales.reduce(function (a, b) { |
|
return a || spec[b]; |
|
}, 0); |
|
if (!scale) error('Missing valid scale for legend.'); // resolve legend type (symbol, gradient, or discrete gradient) |
|
|
|
type = legendType(spec, scope.scaleType(scale)); // single-element data source for legend group |
|
|
|
datum = { |
|
title: spec.title != null, |
|
type: type, |
|
vgrad: type !== 'symbol' && _.isVertical() |
|
}; |
|
dataRef = ref(scope.add(Collect$1(null, [datum]))); // encoding properties for legend group |
|
|
|
legendEncode = extendEncode(buildLegendEncode(_, config), legendEncode, Skip$1); // encoding properties for legend entry sub-group |
|
|
|
entryEncode = { |
|
enter: { |
|
x: { |
|
value: 0 |
|
}, |
|
y: { |
|
value: 0 |
|
} |
|
} |
|
}; // data source for legend values |
|
|
|
entryRef = ref(scope.add(LegendEntries$1(params = { |
|
type: type, |
|
scale: scope.scaleRef(scale), |
|
count: scope.objectProperty(_('tickCount')), |
|
limit: scope.property(_('symbolLimit')), |
|
values: scope.objectProperty(spec.values), |
|
minstep: scope.property(spec.tickMinStep), |
|
formatType: scope.property(spec.formatType), |
|
formatSpecifier: scope.property(spec.format) |
|
}))); // continuous gradient legend |
|
|
|
if (type === Gradient$2) { |
|
children = [legendGradient(spec, scale, config, encode.gradient), legendGradientLabels(spec, config, encode.labels, entryRef)]; // adjust default tick count based on the gradient length |
|
|
|
params.count = params.count || scope.signalRef("max(2,2*floor((".concat(deref(_.gradientLength()), ")/100))")); |
|
} // discrete gradient legend |
|
else if (type === Discrete$2) { |
|
children = [legendGradientDiscrete(spec, scale, config, encode.gradient, entryRef), legendGradientLabels(spec, config, encode.labels, entryRef)]; |
|
} // symbol legend |
|
else { |
|
// determine legend symbol group layout |
|
entryLayout = legendSymbolLayout(spec, config); |
|
children = [legendSymbolGroups(spec, config, encode, entryRef, deref(entryLayout.columns))]; // pass symbol size information to legend entry generator |
|
|
|
params.size = sizeExpression(spec, scope, children[0].marks); |
|
} // generate legend marks |
|
|
|
|
|
children = [guideGroup(LegendEntryRole, null, null, dataRef, interactive, entryEncode, children, entryLayout)]; // include legend title if defined |
|
|
|
if (datum.title) { |
|
children.push(legendTitle(spec, config, encode.title, dataRef)); |
|
} // build legend specification |
|
|
|
|
|
group = guideGroup(LegendRole$1, style, name, dataRef, interactive, legendEncode, children); |
|
if (spec.zindex) group.zindex = spec.zindex; // parse legend specification |
|
|
|
return parseMark(group, scope); |
|
} |
|
|
|
function legendType(spec, scaleType) { |
|
var type = spec.type || Symbols$2; |
|
|
|
if (!spec.type && scaleCount(spec) === 1 && (spec.fill || spec.stroke)) { |
|
type = isContinuous(scaleType) ? Gradient$2 : isDiscretizing(scaleType) ? Discrete$2 : Symbols$2; |
|
} |
|
|
|
return type !== Gradient$2 ? type : isDiscretizing(scaleType) ? Discrete$2 : Gradient$2; |
|
} |
|
|
|
function scaleCount(spec) { |
|
return LegendScales.reduce(function (count, type) { |
|
return count + (spec[type] ? 1 : 0); |
|
}, 0); |
|
} |
|
|
|
function buildLegendEncode(_, config) { |
|
var encode = { |
|
enter: {}, |
|
update: {} |
|
}; |
|
addEncoders(encode, { |
|
orient: _('orient'), |
|
offset: _('offset'), |
|
padding: _('padding'), |
|
titlePadding: _('titlePadding'), |
|
cornerRadius: _('cornerRadius'), |
|
fill: _('fillColor'), |
|
stroke: _('strokeColor'), |
|
strokeWidth: config.strokeWidth, |
|
strokeDash: config.strokeDash, |
|
x: _('legendX'), |
|
y: _('legendY') |
|
}); |
|
return encode; |
|
} |
|
|
|
function sizeExpression(spec, scope, marks) { |
|
var size = deref(getChannel('size', spec, marks)), |
|
strokeWidth = deref(getChannel('strokeWidth', spec, marks)), |
|
fontSize = deref(getFontSize(marks[1].encode, scope, GuideLabelStyle)); |
|
return parseExpression$1("max(ceil(sqrt(".concat(size, ")+").concat(strokeWidth, "),").concat(fontSize, ")"), scope); |
|
} |
|
|
|
function getChannel(name, spec, marks) { |
|
return spec[name] ? "scale(\"".concat(spec[name], "\",datum)") : getEncoding(name, marks[0].encode); |
|
} |
|
|
|
function getFontSize(encode, scope, style) { |
|
return getEncoding('fontSize', encode) || getStyle('fontSize', scope, style); |
|
} |
|
|
|
var angleExpr = "item.orient===\"".concat(Left$1, "\"?-90:item.orient===\"").concat(Right$1, "\"?90:0"); |
|
|
|
function parseTitle(spec, scope) { |
|
spec = isString(spec) ? { |
|
text: spec |
|
} : spec; |
|
|
|
var _ = lookup$5(spec, scope.config.title), |
|
encode = spec.encode || {}, |
|
userEncode = encode.group || {}, |
|
name = userEncode.name || undefined, |
|
interactive = userEncode.interactive, |
|
style = userEncode.style, |
|
children = [], |
|
dataRef, |
|
group; // single-element data source for group title |
|
|
|
|
|
dataRef = ref(scope.add(Collect$1(null, [{}]))); // include title text |
|
|
|
children.push(buildTitle(spec, _, titleEncode(spec), dataRef)); // include subtitle text |
|
|
|
if (spec.subtitle) { |
|
children.push(buildSubTitle(spec, _, encode.subtitle, dataRef)); |
|
} // build title specification |
|
|
|
|
|
group = guideGroup(TitleRole$1, style, name, dataRef, interactive, groupEncode(_, userEncode), children); |
|
if (spec.zindex) group.zindex = spec.zindex; // parse title specification |
|
|
|
return parseMark(group, scope); |
|
} // provide backwards-compatibility for title custom encode; |
|
// the top-level encode block has been *deprecated*. |
|
|
|
|
|
function titleEncode(spec) { |
|
var encode = spec.encode; |
|
return encode && encode.title || extend({ |
|
name: spec.name, |
|
interactive: spec.interactive, |
|
style: spec.style |
|
}, encode); |
|
} |
|
|
|
function groupEncode(_, userEncode) { |
|
var encode = { |
|
enter: {}, |
|
update: {} |
|
}; |
|
addEncoders(encode, { |
|
orient: _('orient'), |
|
anchor: _('anchor'), |
|
align: { |
|
signal: alignExpr |
|
}, |
|
angle: { |
|
signal: angleExpr |
|
}, |
|
limit: _('limit'), |
|
frame: _('frame'), |
|
offset: _('offset') || 0, |
|
padding: _('subtitlePadding') |
|
}); |
|
return extendEncode(encode, userEncode, Skip$1); |
|
} |
|
|
|
function buildTitle(spec, _, userEncode, dataRef) { |
|
var zero = { |
|
value: 0 |
|
}, |
|
text = spec.text, |
|
encode = { |
|
enter: { |
|
opacity: zero |
|
}, |
|
update: { |
|
opacity: { |
|
value: 1 |
|
} |
|
}, |
|
exit: { |
|
opacity: zero |
|
} |
|
}; |
|
addEncoders(encode, { |
|
text: text, |
|
align: { |
|
signal: 'item.mark.group.align' |
|
}, |
|
angle: { |
|
signal: 'item.mark.group.angle' |
|
}, |
|
limit: { |
|
signal: 'item.mark.group.limit' |
|
}, |
|
baseline: 'top', |
|
dx: _('dx'), |
|
dy: _('dy'), |
|
fill: _('color'), |
|
font: _('font'), |
|
fontSize: _('fontSize'), |
|
fontStyle: _('fontStyle'), |
|
fontWeight: _('fontWeight'), |
|
lineHeight: _('lineHeight') |
|
}, { |
|
// update |
|
align: _('align'), |
|
angle: _('angle'), |
|
baseline: _('baseline') |
|
}); |
|
return guideMark(TextMark, TitleTextRole, GroupTitleStyle, null, dataRef, encode, userEncode); |
|
} |
|
|
|
function buildSubTitle(spec, _, userEncode, dataRef) { |
|
var zero = { |
|
value: 0 |
|
}, |
|
text = spec.subtitle, |
|
encode = { |
|
enter: { |
|
opacity: zero |
|
}, |
|
update: { |
|
opacity: { |
|
value: 1 |
|
} |
|
}, |
|
exit: { |
|
opacity: zero |
|
} |
|
}; |
|
addEncoders(encode, { |
|
text: text, |
|
align: { |
|
signal: 'item.mark.group.align' |
|
}, |
|
angle: { |
|
signal: 'item.mark.group.angle' |
|
}, |
|
limit: { |
|
signal: 'item.mark.group.limit' |
|
}, |
|
baseline: 'top', |
|
dx: _('dx'), |
|
dy: _('dy'), |
|
fill: _('subtitleColor'), |
|
font: _('subtitleFont'), |
|
fontSize: _('subtitleFontSize'), |
|
fontStyle: _('subtitleFontStyle'), |
|
fontWeight: _('subtitleFontWeight'), |
|
lineHeight: _('subtitleLineHeight') |
|
}, { |
|
// update |
|
align: _('align'), |
|
angle: _('angle'), |
|
baseline: _('baseline') |
|
}); |
|
return guideMark(TextMark, TitleSubtitleRole, GroupSubtitleStyle, null, dataRef, encode, userEncode); |
|
} |
|
|
|
function parseData$1(data, scope) { |
|
var transforms = []; |
|
|
|
if (data.transform) { |
|
data.transform.forEach(function (tx) { |
|
transforms.push(parseTransform(tx, scope)); |
|
}); |
|
} |
|
|
|
if (data.on) { |
|
data.on.forEach(function (on) { |
|
parseTrigger(on, scope, data.name); |
|
}); |
|
} |
|
|
|
scope.addDataPipeline(data.name, analyze(data, scope, transforms)); |
|
} |
|
/** |
|
* Analyze a data pipeline, add needed operators. |
|
*/ |
|
|
|
|
|
function analyze(data, scope, ops) { |
|
var output = [], |
|
source = null, |
|
modify = false, |
|
generate = false, |
|
upstream, |
|
i, |
|
n, |
|
t, |
|
m; |
|
|
|
if (data.values) { |
|
// hard-wired input data set |
|
if (hasSignal(data.values) || hasSignal(data.format)) { |
|
// if either values or format has signal, use dynamic loader |
|
output.push(load$1(scope, data)); |
|
output.push(source = collect()); |
|
} else { |
|
// otherwise, ingest upon dataflow init |
|
output.push(source = collect({ |
|
$ingest: data.values, |
|
$format: data.format |
|
})); |
|
} |
|
} else if (data.url) { |
|
// load data from external source |
|
if (hasSignal(data.url) || hasSignal(data.format)) { |
|
// if either url or format has signal, use dynamic loader |
|
output.push(load$1(scope, data)); |
|
output.push(source = collect()); |
|
} else { |
|
// otherwise, request load upon dataflow init |
|
output.push(source = collect({ |
|
$request: data.url, |
|
$format: data.format |
|
})); |
|
} |
|
} else if (data.source) { |
|
// derives from one or more other data sets |
|
source = upstream = array(data.source).map(function (d) { |
|
return ref(scope.getData(d).output); |
|
}); |
|
output.push(null); // populate later |
|
} // scan data transforms, add collectors as needed |
|
|
|
|
|
for (i = 0, n = ops.length; i < n; ++i) { |
|
t = ops[i]; |
|
m = t.metadata; |
|
|
|
if (!source && !m.source) { |
|
output.push(source = collect()); |
|
} |
|
|
|
output.push(t); |
|
if (m.generates) generate = true; |
|
if (m.modifies && !generate) modify = true; |
|
if (m.source) source = t;else if (m.changes) source = null; |
|
} |
|
|
|
if (upstream) { |
|
n = upstream.length - 1; |
|
output[0] = Relay$1({ |
|
derive: modify, |
|
pulse: n ? upstream : upstream[0] |
|
}); |
|
|
|
if (modify || n) { |
|
// collect derived and multi-pulse tuples |
|
output.splice(1, 0, collect()); |
|
} |
|
} |
|
|
|
if (!source) output.push(collect()); |
|
output.push(Sieve$1({})); |
|
return output; |
|
} |
|
|
|
function collect(values) { |
|
var s = Collect$1({}, values); |
|
s.metadata = { |
|
source: true |
|
}; |
|
return s; |
|
} |
|
|
|
function load$1(scope, data) { |
|
return Load$1({ |
|
url: data.url ? scope.property(data.url) : undefined, |
|
async: data.async ? scope.property(data.async) : undefined, |
|
values: data.values ? scope.property(data.values) : undefined, |
|
format: scope.objectProperty(data.format) |
|
}); |
|
} |
|
|
|
function axisConfig(spec, scope) { |
|
var config = scope.config, |
|
orient = spec.orient, |
|
xy = orient === Top$1 || orient === Bottom$1 ? config.axisX : config.axisY, |
|
or = config['axis' + orient[0].toUpperCase() + orient.slice(1)], |
|
band = scope.scaleType(spec.scale) === 'band' && config.axisBand; |
|
return xy || or || band ? extend({}, config.axis, xy, or, band) : config.axis; |
|
} |
|
|
|
function axisDomain(spec, config, userEncode, dataRef) { |
|
var _ = lookup$5(spec, config), |
|
orient = spec.orient, |
|
encode, |
|
enter, |
|
update, |
|
u, |
|
u2, |
|
v; |
|
|
|
encode = { |
|
enter: enter = { |
|
opacity: zero$2 |
|
}, |
|
update: update = { |
|
opacity: one$2 |
|
}, |
|
exit: { |
|
opacity: zero$2 |
|
} |
|
}; |
|
addEncoders(encode, { |
|
stroke: _('domainColor'), |
|
strokeDash: _('domainDash'), |
|
strokeDashOffset: _('domainDashOffset'), |
|
strokeWidth: _('domainWidth'), |
|
strokeOpacity: _('domainOpacity') |
|
}); |
|
|
|
if (orient === Top$1 || orient === Bottom$1) { |
|
u = 'x'; |
|
v = 'y'; |
|
} else { |
|
u = 'y'; |
|
v = 'x'; |
|
} |
|
|
|
u2 = u + '2'; |
|
enter[v] = zero$2; |
|
update[u] = enter[u] = position(spec, 0); |
|
update[u2] = enter[u2] = position(spec, 1); |
|
return guideMark(RuleMark, AxisDomainRole, null, null, dataRef, encode, userEncode); |
|
} |
|
|
|
function position(spec, pos) { |
|
return { |
|
scale: spec.scale, |
|
range: pos |
|
}; |
|
} |
|
|
|
function axisGrid(spec, config, userEncode, dataRef, band) { |
|
var _ = lookup$5(spec, config), |
|
orient = spec.orient, |
|
vscale = spec.gridScale, |
|
sign = orient === Left$1 || orient === Top$1 ? 1 : -1, |
|
offset = offsetValue$1(spec.offset, sign), |
|
encode, |
|
enter, |
|
exit, |
|
update, |
|
tickPos, |
|
u, |
|
v, |
|
v2, |
|
s; |
|
|
|
encode = { |
|
enter: enter = { |
|
opacity: zero$2 |
|
}, |
|
update: update = { |
|
opacity: one$2 |
|
}, |
|
exit: exit = { |
|
opacity: zero$2 |
|
} |
|
}; |
|
addEncoders(encode, { |
|
stroke: _('gridColor'), |
|
strokeDash: _('gridDash'), |
|
strokeDashOffset: _('gridDashOffset'), |
|
strokeOpacity: _('gridOpacity'), |
|
strokeWidth: _('gridWidth') |
|
}); |
|
tickPos = { |
|
scale: spec.scale, |
|
field: Value, |
|
band: band.band, |
|
extra: band.extra, |
|
offset: band.offset, |
|
round: _('tickRound') |
|
}; |
|
|
|
if (orient === Top$1 || orient === Bottom$1) { |
|
u = 'x'; |
|
v = 'y'; |
|
s = 'height'; |
|
} else { |
|
u = 'y'; |
|
v = 'x'; |
|
s = 'width'; |
|
} |
|
|
|
v2 = v + '2'; |
|
update[u] = enter[u] = exit[u] = tickPos; |
|
|
|
if (vscale) { |
|
update[v] = enter[v] = { |
|
scale: vscale, |
|
range: 0, |
|
mult: sign, |
|
offset: offset |
|
}; |
|
update[v2] = enter[v2] = { |
|
scale: vscale, |
|
range: 1, |
|
mult: sign, |
|
offset: offset |
|
}; |
|
} else { |
|
update[v] = enter[v] = { |
|
value: 0, |
|
offset: offset |
|
}; |
|
update[v2] = enter[v2] = { |
|
signal: s, |
|
mult: sign, |
|
offset: offset |
|
}; |
|
} |
|
|
|
return guideMark(RuleMark, AxisGridRole, null, Value, dataRef, encode, userEncode); |
|
} |
|
|
|
function offsetValue$1(offset, sign) { |
|
if (sign === 1) ;else if (!isObject(offset)) { |
|
offset = sign * (offset || 0); |
|
} else { |
|
var entry = offset = extend({}, offset); |
|
|
|
while (entry.mult != null) { |
|
if (!isObject(entry.mult)) { |
|
entry.mult *= sign; |
|
return offset; |
|
} else { |
|
entry = entry.mult = extend({}, entry.mult); |
|
} |
|
} |
|
|
|
entry.mult = sign; |
|
} |
|
return offset; |
|
} |
|
|
|
function axisTicks(spec, config, userEncode, dataRef, size, band) { |
|
var _ = lookup$5(spec, config), |
|
orient = spec.orient, |
|
sign = orient === Left$1 || orient === Top$1 ? -1 : 1, |
|
encode, |
|
enter, |
|
exit, |
|
update, |
|
tickSize, |
|
tickPos; |
|
|
|
encode = { |
|
enter: enter = { |
|
opacity: zero$2 |
|
}, |
|
update: update = { |
|
opacity: one$2 |
|
}, |
|
exit: exit = { |
|
opacity: zero$2 |
|
} |
|
}; |
|
addEncoders(encode, { |
|
stroke: _('tickColor'), |
|
strokeDash: _('tickDash'), |
|
strokeDashOffset: _('tickDashOffset'), |
|
strokeOpacity: _('tickOpacity'), |
|
strokeWidth: _('tickWidth') |
|
}); |
|
tickSize = encoder(size); |
|
tickSize.mult = sign; |
|
tickPos = { |
|
scale: spec.scale, |
|
field: Value, |
|
band: band.band, |
|
extra: band.extra, |
|
offset: band.offset, |
|
round: _('tickRound') |
|
}; |
|
|
|
if (orient === Top$1 || orient === Bottom$1) { |
|
update.y = enter.y = zero$2; |
|
update.y2 = enter.y2 = tickSize; |
|
update.x = enter.x = exit.x = tickPos; |
|
} else { |
|
update.x = enter.x = zero$2; |
|
update.x2 = enter.x2 = tickSize; |
|
update.y = enter.y = exit.y = tickPos; |
|
} |
|
|
|
return guideMark(RuleMark, AxisTickRole, null, Value, dataRef, encode, userEncode); |
|
} |
|
|
|
function flushExpr(scale, threshold, a, b, c) { |
|
return { |
|
signal: 'flush(range("' + scale + '"), ' + 'scale("' + scale + '", datum.value), ' + threshold + ',' + a + ',' + b + ',' + c + ')' |
|
}; |
|
} |
|
|
|
function axisLabels(spec, config, userEncode, dataRef, size, band) { |
|
var _addEncoders; |
|
|
|
var _ = lookup$5(spec, config), |
|
orient = spec.orient, |
|
sign = orient === Left$1 || orient === Top$1 ? -1 : 1, |
|
isXAxis = orient === Top$1 || orient === Bottom$1, |
|
scale = spec.scale, |
|
flush = deref(_('labelFlush')), |
|
flushOffset = deref(_('labelFlushOffset')), |
|
flushOn = flush === 0 || !!flush, |
|
labelAlign = _('labelAlign'), |
|
labelBaseline = _('labelBaseline'), |
|
encode, |
|
enter, |
|
tickSize, |
|
tickPos, |
|
align, |
|
baseline, |
|
offset, |
|
bound, |
|
overlap, |
|
separation; |
|
|
|
tickSize = encoder(size); |
|
tickSize.mult = sign; |
|
tickSize.offset = encoder(_('labelPadding') || 0); |
|
tickSize.offset.mult = sign; |
|
tickPos = { |
|
scale: scale, |
|
field: Value, |
|
band: 0.5, |
|
offset: band.offset |
|
}; |
|
|
|
if (isXAxis) { |
|
align = labelAlign || (flushOn ? flushExpr(scale, flush, '"left"', '"right"', '"center"') : 'center'); |
|
baseline = labelBaseline || (orient === Top$1 ? 'bottom' : 'top'); |
|
offset = !labelAlign; |
|
} else { |
|
align = labelAlign || (orient === Right$1 ? 'left' : 'right'); |
|
baseline = labelBaseline || (flushOn ? flushExpr(scale, flush, '"top"', '"bottom"', '"middle"') : 'middle'); |
|
offset = !labelBaseline; |
|
} |
|
|
|
offset = offset && flushOn && flushOffset ? flushExpr(scale, flush, '-(' + flushOffset + ')', flushOffset, 0) : null; |
|
encode = { |
|
enter: enter = { |
|
opacity: zero$2, |
|
x: isXAxis ? tickPos : tickSize, |
|
y: isXAxis ? tickSize : tickPos |
|
}, |
|
update: { |
|
opacity: one$2, |
|
text: { |
|
field: Label |
|
}, |
|
x: enter.x, |
|
y: enter.y |
|
}, |
|
exit: { |
|
opacity: zero$2, |
|
x: enter.x, |
|
y: enter.y |
|
} |
|
}; |
|
addEncoders(encode, (_addEncoders = {}, _defineProperty(_addEncoders, isXAxis ? 'dx' : 'dy', offset), _defineProperty(_addEncoders, "align", align), _defineProperty(_addEncoders, "baseline", baseline), _defineProperty(_addEncoders, "angle", _('labelAngle')), _defineProperty(_addEncoders, "fill", _('labelColor')), _defineProperty(_addEncoders, "fillOpacity", _('labelOpacity')), _defineProperty(_addEncoders, "font", _('labelFont')), _defineProperty(_addEncoders, "fontSize", _('labelFontSize')), _defineProperty(_addEncoders, "fontWeight", _('labelFontWeight')), _defineProperty(_addEncoders, "fontStyle", _('labelFontStyle')), _defineProperty(_addEncoders, "limit", _('labelLimit')), _addEncoders)); |
|
bound = _('labelBound'); |
|
overlap = _('labelOverlap'); |
|
separation = _('labelSeparation'); |
|
spec = guideMark(TextMark, AxisLabelRole, GuideLabelStyle, Value, dataRef, encode, userEncode); // if overlap method or bound defined, request label overlap removal |
|
|
|
if (overlap || bound) { |
|
spec.overlap = { |
|
separation: separation, |
|
method: overlap, |
|
order: 'datum.index', |
|
bound: bound ? { |
|
scale: scale, |
|
orient: orient, |
|
tolerance: bound |
|
} : null |
|
}; |
|
} |
|
|
|
return spec; |
|
} |
|
|
|
function axisTitle(spec, config, userEncode, dataRef) { |
|
var _ = lookup$5(spec, config), |
|
orient = spec.orient, |
|
sign = orient === Left$1 || orient === Top$1 ? -1 : 1, |
|
horizontal = orient === Top$1 || orient === Bottom$1, |
|
encode, |
|
enter, |
|
update, |
|
titlePos; |
|
|
|
encode = { |
|
enter: enter = { |
|
opacity: zero$2, |
|
anchor: encoder(_('titleAnchor')), |
|
align: { |
|
signal: alignExpr |
|
} |
|
}, |
|
update: update = extend({}, enter, { |
|
opacity: one$2, |
|
text: encoder(spec.title) |
|
}), |
|
exit: { |
|
opacity: zero$2 |
|
} |
|
}; |
|
titlePos = { |
|
signal: "lerp(range(\"".concat(spec.scale, "\"), ").concat(anchorExpr(0, 1, 0.5), ")") |
|
}; |
|
|
|
if (horizontal) { |
|
update.x = titlePos; |
|
enter.angle = { |
|
value: 0 |
|
}; |
|
enter.baseline = { |
|
value: orient === Top$1 ? 'bottom' : 'top' |
|
}; |
|
} else { |
|
update.y = titlePos; |
|
enter.angle = { |
|
value: sign * 90 |
|
}; |
|
enter.baseline = { |
|
value: 'bottom' |
|
}; |
|
} |
|
|
|
addEncoders(encode, { |
|
angle: _('titleAngle'), |
|
baseline: _('titleBaseline'), |
|
fill: _('titleColor'), |
|
fillOpacity: _('titleOpacity'), |
|
font: _('titleFont'), |
|
fontSize: _('titleFontSize'), |
|
fontStyle: _('titleFontStyle'), |
|
fontWeight: _('titleFontWeight'), |
|
limit: _('titleLimit'), |
|
lineHeight: _('titleLineHeight') |
|
}, { |
|
// require update |
|
align: _('titleAlign') |
|
}); |
|
!addEncode(encode, 'x', _('titleX'), 'update') && !horizontal && !has('x', userEncode) && (encode.enter.auto = { |
|
value: true |
|
}); |
|
!addEncode(encode, 'y', _('titleY'), 'update') && horizontal && !has('y', userEncode) && (encode.enter.auto = { |
|
value: true |
|
}); |
|
return guideMark(TextMark, AxisTitleRole, GuideTitleStyle, null, dataRef, encode, userEncode); |
|
} |
|
|
|
function parseAxis(spec, scope) { |
|
var config = axisConfig(spec, scope), |
|
encode = spec.encode || {}, |
|
axisEncode = encode.axis || {}, |
|
name = axisEncode.name || undefined, |
|
interactive = axisEncode.interactive, |
|
style = axisEncode.style, |
|
_ = lookup$5(spec, config), |
|
band = tickBand(_), |
|
datum, |
|
dataRef, |
|
ticksRef, |
|
size, |
|
group, |
|
children; // single-element data source for axis group |
|
|
|
|
|
datum = { |
|
orient: spec.orient, |
|
ticks: !!_('ticks'), |
|
labels: !!_('labels'), |
|
grid: !!_('grid'), |
|
domain: !!_('domain'), |
|
title: spec.title != null, |
|
translate: _('translate') |
|
}; |
|
dataRef = ref(scope.add(Collect$1({}, [datum]))); // encoding properties for axis group item |
|
|
|
axisEncode = extendEncode({ |
|
update: { |
|
offset: encoder(_('offset') || 0), |
|
position: encoder(value$1(spec.position, 0)), |
|
titlePadding: encoder(_('titlePadding')), |
|
minExtent: encoder(_('minExtent')), |
|
maxExtent: encoder(_('maxExtent')), |
|
range: { |
|
signal: "abs(span(range(\"".concat(spec.scale, "\")))") |
|
} |
|
} |
|
}, encode.axis, Skip$1); // data source for axis ticks |
|
|
|
ticksRef = ref(scope.add(AxisTicks$1({ |
|
scale: scope.scaleRef(spec.scale), |
|
extra: scope.property(band.extra), |
|
count: scope.objectProperty(spec.tickCount), |
|
values: scope.objectProperty(spec.values), |
|
minstep: scope.property(spec.tickMinStep), |
|
formatType: scope.property(spec.formatType), |
|
formatSpecifier: scope.property(spec.format) |
|
}))); // generate axis marks |
|
|
|
children = []; // include axis gridlines if requested |
|
|
|
if (datum.grid) { |
|
children.push(axisGrid(spec, config, encode.grid, ticksRef, band)); |
|
} // include axis ticks if requested |
|
|
|
|
|
if (datum.ticks) { |
|
size = _('tickSize'); |
|
children.push(axisTicks(spec, config, encode.ticks, ticksRef, size, band)); |
|
} // include axis labels if requested |
|
|
|
|
|
if (datum.labels) { |
|
size = datum.ticks ? size : 0; |
|
children.push(axisLabels(spec, config, encode.labels, ticksRef, size, band)); |
|
} // include axis domain path if requested |
|
|
|
|
|
if (datum.domain) { |
|
children.push(axisDomain(spec, config, encode.domain, dataRef)); |
|
} // include axis title if defined |
|
|
|
|
|
if (datum.title) { |
|
children.push(axisTitle(spec, config, encode.title, dataRef)); |
|
} // build axis specification |
|
|
|
|
|
group = guideGroup(AxisRole$1, style, name, dataRef, interactive, axisEncode, children); |
|
if (spec.zindex) group.zindex = spec.zindex; // parse axis specification |
|
|
|
return parseMark(group, scope); |
|
} |
|
|
|
function parseSpec(spec, scope, preprocessed) { |
|
var signals = array(spec.signals), |
|
scales = array(spec.scales); // parse signal definitions, if not already preprocessed |
|
|
|
if (!preprocessed) signals.forEach(function (_) { |
|
return parseSignal(_, scope); |
|
}); // parse cartographic projection definitions |
|
|
|
array(spec.projections).forEach(function (_) { |
|
return parseProjection(_, scope); |
|
}); // initialize scale references |
|
|
|
scales.forEach(function (_) { |
|
return initScale(_, scope); |
|
}); // parse data sources |
|
|
|
array(spec.data).forEach(function (_) { |
|
return parseData$1(_, scope); |
|
}); // parse scale definitions |
|
|
|
scales.forEach(function (_) { |
|
return parseScale(_, scope); |
|
}); // parse signal updates |
|
|
|
(preprocessed || signals).forEach(function (_) { |
|
return parseSignalUpdates(_, scope); |
|
}); // parse axis definitions |
|
|
|
array(spec.axes).forEach(function (_) { |
|
return parseAxis(_, scope); |
|
}); // parse mark definitions |
|
|
|
array(spec.marks).forEach(function (_) { |
|
return parseMark(_, scope); |
|
}); // parse legend definitions |
|
|
|
array(spec.legends).forEach(function (_) { |
|
return parseLegend(_, scope); |
|
}); // parse title, if defined |
|
|
|
if (spec.title) parseTitle(spec.title, scope); // parse collected lambda (anonymous) expressions |
|
|
|
scope.parseLambdas(); |
|
return scope; |
|
} |
|
|
|
var defined = toSet(['width', 'height', 'padding', 'autosize']); |
|
|
|
function parseView(spec, scope) { |
|
var config = scope.config, |
|
op, |
|
input, |
|
encode, |
|
parent, |
|
root, |
|
signals; |
|
scope.background = spec.background || config.background; |
|
scope.eventConfig = config.events; |
|
root = ref(scope.root = scope.add(operator())); |
|
scope.addSignal('width', spec.width || 0); |
|
scope.addSignal('height', spec.height || 0); |
|
scope.addSignal('padding', parsePadding(spec.padding, config)); |
|
scope.addSignal('autosize', parseAutosize(spec.autosize, config)); |
|
scope.legends = scope.objectProperty(config.legend && config.legend.layout); // parse signal definitions, including config entries |
|
|
|
signals = addSignals(scope, spec.signals, config.signals); // Store root group item |
|
|
|
input = scope.add(Collect$1()); // Encode root group item |
|
|
|
encode = extendEncode({ |
|
enter: { |
|
x: { |
|
value: 0 |
|
}, |
|
y: { |
|
value: 0 |
|
} |
|
}, |
|
update: { |
|
width: { |
|
signal: 'width' |
|
}, |
|
height: { |
|
signal: 'height' |
|
} |
|
} |
|
}, spec.encode); |
|
encode = scope.add(Encode$1(encoders(encode, GroupMark, FrameRole$1, spec.style, scope, { |
|
pulse: ref(input) |
|
}))); // Perform view layout |
|
|
|
parent = scope.add(ViewLayout$1({ |
|
layout: scope.objectProperty(spec.layout), |
|
legends: scope.legends, |
|
autosize: scope.signalRef('autosize'), |
|
mark: root, |
|
pulse: ref(encode) |
|
})); |
|
scope.operators.pop(); // Parse remainder of specification |
|
|
|
scope.pushState(ref(encode), ref(parent), null); |
|
parseSpec(spec, scope, signals); |
|
scope.operators.push(parent); // Bound / render / sieve root item |
|
|
|
op = scope.add(Bound$1({ |
|
mark: root, |
|
pulse: ref(parent) |
|
})); |
|
op = scope.add(Render$1({ |
|
pulse: ref(op) |
|
})); |
|
op = scope.add(Sieve$1({ |
|
pulse: ref(op) |
|
})); // Track metadata for root item |
|
|
|
scope.addData('root', new DataScope(scope, input, input, op)); |
|
return scope; |
|
} |
|
|
|
function addSignals(scope, signals, config) { |
|
// signals defined in the spec take priority |
|
array(signals).forEach(function (_) { |
|
if (!defined[_.name]) parseSignal(_, scope); |
|
}); |
|
if (!config) return signals; |
|
var out = array(signals).slice(); // add config signals if not already defined |
|
|
|
array(config).forEach(function (_) { |
|
if (!scope.hasOwnSignal(_.name)) { |
|
parseSignal(_, scope); |
|
out.push(_); |
|
} |
|
}); |
|
return out; |
|
} |
|
|
|
function Scope$1(config) { |
|
this.config = config; |
|
this.bindings = []; |
|
this.field = {}; |
|
this.signals = {}; |
|
this.lambdas = {}; |
|
this.scales = {}; |
|
this.events = {}; |
|
this.data = {}; |
|
this.streams = []; |
|
this.updates = []; |
|
this.operators = []; |
|
this.background = null; |
|
this.eventConfig = null; |
|
this._id = 0; |
|
this._subid = 0; |
|
this._nextsub = [0]; |
|
this._parent = []; |
|
this._encode = []; |
|
this._lookup = []; |
|
this._markpath = []; |
|
} |
|
|
|
function Subscope(scope) { |
|
this.config = scope.config; |
|
this.legends = scope.legends; |
|
this.field = Object.create(scope.field); |
|
this.signals = Object.create(scope.signals); |
|
this.lambdas = Object.create(scope.lambdas); |
|
this.scales = Object.create(scope.scales); |
|
this.events = Object.create(scope.events); |
|
this.data = Object.create(scope.data); |
|
this.streams = []; |
|
this.updates = []; |
|
this.operators = []; |
|
this._id = 0; |
|
this._subid = ++scope._nextsub[0]; |
|
this._nextsub = scope._nextsub; |
|
this._parent = scope._parent.slice(); |
|
this._encode = scope._encode.slice(); |
|
this._lookup = scope._lookup.slice(); |
|
this._markpath = scope._markpath; |
|
} |
|
|
|
var prototype$1u = Scope$1.prototype = Subscope.prototype; // ---- |
|
|
|
prototype$1u.fork = function () { |
|
return new Subscope(this); |
|
}; |
|
|
|
prototype$1u.isSubscope = function () { |
|
return this._subid > 0; |
|
}; |
|
|
|
prototype$1u.toRuntime = function () { |
|
this.finish(); |
|
return { |
|
background: this.background, |
|
operators: this.operators, |
|
streams: this.streams, |
|
updates: this.updates, |
|
bindings: this.bindings, |
|
eventConfig: this.eventConfig |
|
}; |
|
}; |
|
|
|
prototype$1u.id = function () { |
|
return (this._subid ? this._subid + ':' : 0) + this._id++; |
|
}; |
|
|
|
prototype$1u.add = function (op) { |
|
this.operators.push(op); |
|
op.id = this.id(); // if pre-registration references exist, resolve them now |
|
|
|
if (op.refs) { |
|
op.refs.forEach(function (ref) { |
|
ref.$ref = op.id; |
|
}); |
|
op.refs = null; |
|
} |
|
|
|
return op; |
|
}; |
|
|
|
prototype$1u.proxy = function (op) { |
|
var vref = op instanceof Entry ? ref(op) : op; |
|
return this.add(Proxy$1({ |
|
value: vref |
|
})); |
|
}; |
|
|
|
prototype$1u.addStream = function (stream) { |
|
this.streams.push(stream); |
|
stream.id = this.id(); |
|
return stream; |
|
}; |
|
|
|
prototype$1u.addUpdate = function (update) { |
|
this.updates.push(update); |
|
return update; |
|
}; // Apply metadata |
|
|
|
|
|
prototype$1u.finish = function () { |
|
var name, ds; // annotate root |
|
|
|
if (this.root) this.root.root = true; // annotate signals |
|
|
|
for (name in this.signals) { |
|
this.signals[name].signal = name; |
|
} // annotate scales |
|
|
|
|
|
for (name in this.scales) { |
|
this.scales[name].scale = name; |
|
} // annotate data sets |
|
|
|
|
|
function annotate(op, name, type) { |
|
var data, list; |
|
|
|
if (op) { |
|
data = op.data || (op.data = {}); |
|
list = data[name] || (data[name] = []); |
|
list.push(type); |
|
} |
|
} |
|
|
|
for (name in this.data) { |
|
ds = this.data[name]; |
|
annotate(ds.input, name, 'input'); |
|
annotate(ds.output, name, 'output'); |
|
annotate(ds.values, name, 'values'); |
|
|
|
for (var field in ds.index) { |
|
annotate(ds.index[field], name, 'index:' + field); |
|
} |
|
} |
|
|
|
return this; |
|
}; // ---- |
|
|
|
|
|
prototype$1u.pushState = function (encode, parent, lookup) { |
|
this._encode.push(ref(this.add(Sieve$1({ |
|
pulse: encode |
|
})))); |
|
|
|
this._parent.push(parent); |
|
|
|
this._lookup.push(lookup ? ref(this.proxy(lookup)) : null); |
|
|
|
this._markpath.push(-1); |
|
}; |
|
|
|
prototype$1u.popState = function () { |
|
this._encode.pop(); |
|
|
|
this._parent.pop(); |
|
|
|
this._lookup.pop(); |
|
|
|
this._markpath.pop(); |
|
}; |
|
|
|
prototype$1u.parent = function () { |
|
return peek(this._parent); |
|
}; |
|
|
|
prototype$1u.encode = function () { |
|
return peek(this._encode); |
|
}; |
|
|
|
prototype$1u.lookup = function () { |
|
return peek(this._lookup); |
|
}; |
|
|
|
prototype$1u.markpath = function () { |
|
var p = this._markpath; |
|
return ++p[p.length - 1]; |
|
}; // ---- |
|
|
|
|
|
prototype$1u.fieldRef = function (field, name) { |
|
if (isString(field)) return fieldRef(field, name); |
|
|
|
if (!field.signal) { |
|
error('Unsupported field reference: ' + $(field)); |
|
} |
|
|
|
var s = field.signal, |
|
f = this.field[s], |
|
params; |
|
|
|
if (!f) { |
|
params = { |
|
name: this.signalRef(s) |
|
}; |
|
if (name) params.as = name; |
|
this.field[s] = f = ref(this.add(Field$1(params))); |
|
} |
|
|
|
return f; |
|
}; |
|
|
|
prototype$1u.compareRef = function (cmp) { |
|
function check(_) { |
|
if (isSignal(_)) { |
|
signal = true; |
|
return scope.signalRef(_.signal); |
|
} else if (isExpr(_)) { |
|
signal = true; |
|
return scope.exprRef(_.expr); |
|
} else { |
|
return _; |
|
} |
|
} |
|
|
|
var scope = this, |
|
signal = false, |
|
fields = array(cmp.field).map(check), |
|
orders = array(cmp.order).map(check); |
|
return signal ? ref(this.add(Compare$1({ |
|
fields: fields, |
|
orders: orders |
|
}))) : compareRef(fields, orders); |
|
}; |
|
|
|
prototype$1u.keyRef = function (fields, flat) { |
|
function check(_) { |
|
if (isSignal(_)) { |
|
signal = true; |
|
return ref(sig[_.signal]); |
|
} else { |
|
return _; |
|
} |
|
} |
|
|
|
var sig = this.signals, |
|
signal = false; |
|
fields = array(fields).map(check); |
|
return signal ? ref(this.add(Key$1({ |
|
fields: fields, |
|
flat: flat |
|
}))) : keyRef(fields, flat); |
|
}; |
|
|
|
prototype$1u.sortRef = function (sort) { |
|
if (!sort) return sort; // including id ensures stable sorting |
|
|
|
var a = aggrField(sort.op, sort.field), |
|
o = sort.order || Ascending; |
|
return o.signal ? ref(this.add(Compare$1({ |
|
fields: a, |
|
orders: this.signalRef(o.signal) |
|
}))) : compareRef(a, o); |
|
}; // ---- |
|
|
|
|
|
prototype$1u.event = function (source, type) { |
|
var key = source + ':' + type; |
|
|
|
if (!this.events[key]) { |
|
var id = this.id(); |
|
this.streams.push({ |
|
id: id, |
|
source: source, |
|
type: type |
|
}); |
|
this.events[key] = id; |
|
} |
|
|
|
return this.events[key]; |
|
}; // ---- |
|
|
|
|
|
prototype$1u.hasOwnSignal = function (name) { |
|
return hasOwnProperty(this.signals, name); |
|
}; |
|
|
|
prototype$1u.addSignal = function (name, value) { |
|
if (this.hasOwnSignal(name)) { |
|
error('Duplicate signal name: ' + $(name)); |
|
} |
|
|
|
var op = value instanceof Entry ? value : this.add(operator(value)); |
|
return this.signals[name] = op; |
|
}; |
|
|
|
prototype$1u.getSignal = function (name) { |
|
if (!this.signals[name]) { |
|
error('Unrecognized signal name: ' + $(name)); |
|
} |
|
|
|
return this.signals[name]; |
|
}; |
|
|
|
prototype$1u.signalRef = function (s) { |
|
if (this.signals[s]) { |
|
return ref(this.signals[s]); |
|
} else if (!hasOwnProperty(this.lambdas, s)) { |
|
this.lambdas[s] = this.add(operator(null)); |
|
} |
|
|
|
return ref(this.lambdas[s]); |
|
}; |
|
|
|
prototype$1u.parseLambdas = function () { |
|
var code = Object.keys(this.lambdas); |
|
|
|
for (var i = 0, n = code.length; i < n; ++i) { |
|
var s = code[i], |
|
e = parseExpression$1(s, this), |
|
op = this.lambdas[s]; |
|
op.params = e.$params; |
|
op.update = e.$expr; |
|
} |
|
}; |
|
|
|
prototype$1u.property = function (spec) { |
|
return spec && spec.signal ? this.signalRef(spec.signal) : spec; |
|
}; |
|
|
|
prototype$1u.objectProperty = function (spec) { |
|
return !spec || !isObject(spec) ? spec : this.signalRef(spec.signal || propertyLambda(spec)); |
|
}; |
|
|
|
function propertyLambda(spec) { |
|
return (isArray(spec) ? arrayLambda : objectLambda)(spec); |
|
} |
|
|
|
function arrayLambda(array) { |
|
var code = '[', |
|
i = 0, |
|
n = array.length, |
|
value; |
|
|
|
for (; i < n; ++i) { |
|
value = array[i]; |
|
code += (i > 0 ? ',' : '') + (isObject(value) ? value.signal || propertyLambda(value) : $(value)); |
|
} |
|
|
|
return code + ']'; |
|
} |
|
|
|
function objectLambda(obj) { |
|
var code = '{', |
|
i = 0, |
|
key, |
|
value; |
|
|
|
for (key in obj) { |
|
value = obj[key]; |
|
code += (++i > 1 ? ',' : '') + $(key) + ':' + (isObject(value) ? value.signal || propertyLambda(value) : $(value)); |
|
} |
|
|
|
return code + '}'; |
|
} |
|
|
|
prototype$1u.exprRef = function (code, name) { |
|
var params = { |
|
expr: parseExpression$1(code, this) |
|
}; |
|
if (name) params.expr.$name = name; |
|
return ref(this.add(Expression$1(params))); |
|
}; |
|
|
|
prototype$1u.addBinding = function (name, bind) { |
|
if (!this.bindings) { |
|
error('Nested signals do not support binding: ' + $(name)); |
|
} |
|
|
|
this.bindings.push(extend({ |
|
signal: name |
|
}, bind)); |
|
}; // ---- |
|
|
|
|
|
prototype$1u.addScaleProj = function (name, transform) { |
|
if (hasOwnProperty(this.scales, name)) { |
|
error('Duplicate scale or projection name: ' + $(name)); |
|
} |
|
|
|
this.scales[name] = this.add(transform); |
|
}; |
|
|
|
prototype$1u.addScale = function (name, params) { |
|
this.addScaleProj(name, Scale$1(params)); |
|
}; |
|
|
|
prototype$1u.addProjection = function (name, params) { |
|
this.addScaleProj(name, Projection$1(params)); |
|
}; |
|
|
|
prototype$1u.getScale = function (name) { |
|
if (!this.scales[name]) { |
|
error('Unrecognized scale name: ' + $(name)); |
|
} |
|
|
|
return this.scales[name]; |
|
}; |
|
|
|
prototype$1u.projectionRef = prototype$1u.scaleRef = function (name) { |
|
return ref(this.getScale(name)); |
|
}; |
|
|
|
prototype$1u.projectionType = prototype$1u.scaleType = function (name) { |
|
return this.getScale(name).params.type; |
|
}; // ---- |
|
|
|
|
|
prototype$1u.addData = function (name, dataScope) { |
|
if (hasOwnProperty(this.data, name)) { |
|
error('Duplicate data set name: ' + $(name)); |
|
} |
|
|
|
return this.data[name] = dataScope; |
|
}; |
|
|
|
prototype$1u.getData = function (name) { |
|
if (!this.data[name]) { |
|
error('Undefined data set name: ' + $(name)); |
|
} |
|
|
|
return this.data[name]; |
|
}; |
|
|
|
prototype$1u.addDataPipeline = function (name, entries) { |
|
if (hasOwnProperty(this.data, name)) { |
|
error('Duplicate data set name: ' + $(name)); |
|
} |
|
|
|
return this.addData(name, DataScope.fromEntries(this, entries)); |
|
}; |
|
|
|
var defaultFont = 'sans-serif', |
|
defaultSymbolSize = 30, |
|
defaultStrokeWidth = 2, |
|
defaultColor = '#4c78a8', |
|
black = '#000', |
|
gray = '#888', |
|
lightGray = '#ddd'; |
|
/** |
|
* Standard configuration defaults for Vega specification parsing. |
|
* Users can provide their own (sub-)set of these default values |
|
* by passing in a config object to the top-level parse method. |
|
*/ |
|
|
|
function defaults() { |
|
return { |
|
// default padding around visualization |
|
padding: 0, |
|
// default for automatic sizing; options: 'none', 'pad', 'fit' |
|
// or provide an object (e.g., {'type': 'pad', 'resize': true}) |
|
autosize: 'pad', |
|
// default view background color |
|
// covers the entire view component |
|
background: null, |
|
// default event handling configuration |
|
// preventDefault for view-sourced event types except 'wheel' |
|
events: { |
|
defaults: { |
|
allow: ['wheel'] |
|
} |
|
}, |
|
// defaults for top-level group marks |
|
// accepts mark properties (fill, stroke, etc) |
|
// covers the data rectangle within group width/height |
|
group: null, |
|
// defaults for basic mark types |
|
// each subset accepts mark properties (fill, stroke, etc) |
|
mark: null, |
|
arc: { |
|
fill: defaultColor |
|
}, |
|
area: { |
|
fill: defaultColor |
|
}, |
|
image: null, |
|
line: { |
|
stroke: defaultColor, |
|
strokeWidth: defaultStrokeWidth |
|
}, |
|
path: { |
|
stroke: defaultColor |
|
}, |
|
rect: { |
|
fill: defaultColor |
|
}, |
|
rule: { |
|
stroke: black |
|
}, |
|
shape: { |
|
stroke: defaultColor |
|
}, |
|
symbol: { |
|
fill: defaultColor, |
|
size: 64 |
|
}, |
|
text: { |
|
fill: black, |
|
font: defaultFont, |
|
fontSize: 11 |
|
}, |
|
// style definitions |
|
style: { |
|
// axis & legend labels |
|
'guide-label': { |
|
fill: black, |
|
font: defaultFont, |
|
fontSize: 10 |
|
}, |
|
// axis & legend titles |
|
'guide-title': { |
|
fill: black, |
|
font: defaultFont, |
|
fontSize: 11, |
|
fontWeight: 'bold' |
|
}, |
|
// headers, including chart title |
|
'group-title': { |
|
fill: black, |
|
font: defaultFont, |
|
fontSize: 13, |
|
fontWeight: 'bold' |
|
}, |
|
// chart subtitle |
|
'group-subtitle': { |
|
fill: black, |
|
font: defaultFont, |
|
fontSize: 12 |
|
}, |
|
// defaults for styled point marks in Vega-Lite |
|
point: { |
|
size: defaultSymbolSize, |
|
strokeWidth: defaultStrokeWidth, |
|
shape: 'circle' |
|
}, |
|
circle: { |
|
size: defaultSymbolSize, |
|
strokeWidth: defaultStrokeWidth |
|
}, |
|
square: { |
|
size: defaultSymbolSize, |
|
strokeWidth: defaultStrokeWidth, |
|
shape: 'square' |
|
}, |
|
// defaults for styled group marks in Vega-Lite |
|
cell: { |
|
fill: 'transparent', |
|
stroke: lightGray |
|
} |
|
}, |
|
// defaults for title |
|
title: { |
|
orient: 'top', |
|
anchor: 'middle', |
|
offset: 4, |
|
subtitlePadding: 3 |
|
}, |
|
// defaults for axes |
|
axis: { |
|
minExtent: 0, |
|
maxExtent: 200, |
|
bandPosition: 0.5, |
|
domain: true, |
|
domainWidth: 1, |
|
domainColor: gray, |
|
grid: false, |
|
gridWidth: 1, |
|
gridColor: lightGray, |
|
labels: true, |
|
labelAngle: 0, |
|
labelLimit: 180, |
|
labelPadding: 2, |
|
ticks: true, |
|
tickColor: gray, |
|
tickOffset: 0, |
|
tickRound: true, |
|
tickSize: 5, |
|
tickWidth: 1, |
|
titlePadding: 4 |
|
}, |
|
// correction for centering bias |
|
axisBand: { |
|
tickOffset: -0.5 |
|
}, |
|
// defaults for cartographic projection |
|
projection: { |
|
type: 'mercator' |
|
}, |
|
// defaults for legends |
|
legend: { |
|
orient: 'right', |
|
padding: 0, |
|
gridAlign: 'each', |
|
columnPadding: 10, |
|
rowPadding: 2, |
|
symbolDirection: 'vertical', |
|
gradientDirection: 'vertical', |
|
gradientLength: 200, |
|
gradientThickness: 16, |
|
gradientStrokeColor: lightGray, |
|
gradientStrokeWidth: 0, |
|
gradientLabelOffset: 2, |
|
labelAlign: 'left', |
|
labelBaseline: 'middle', |
|
labelLimit: 160, |
|
labelOffset: 4, |
|
labelOverlap: true, |
|
symbolLimit: 30, |
|
symbolType: 'circle', |
|
symbolSize: 100, |
|
symbolOffset: 0, |
|
symbolStrokeWidth: 1.5, |
|
symbolBaseFillColor: 'transparent', |
|
symbolBaseStrokeColor: gray, |
|
titleLimit: 180, |
|
titleOrient: 'top', |
|
titlePadding: 5, |
|
layout: { |
|
offset: 18, |
|
direction: 'horizontal', |
|
left: { |
|
direction: 'vertical' |
|
}, |
|
right: { |
|
direction: 'vertical' |
|
} |
|
} |
|
}, |
|
// defaults for scale ranges |
|
range: { |
|
category: { |
|
scheme: 'tableau10' |
|
}, |
|
ordinal: { |
|
scheme: 'blues' |
|
}, |
|
heatmap: { |
|
scheme: 'yellowgreenblue' |
|
}, |
|
ramp: { |
|
scheme: 'blues' |
|
}, |
|
diverging: { |
|
scheme: 'blueorange', |
|
extent: [1, 0] |
|
}, |
|
symbol: ['circle', 'square', 'triangle-up', 'cross', 'diamond', 'triangle-right', 'triangle-down', 'triangle-left'] |
|
} |
|
}; |
|
} |
|
|
|
function parse$5(spec, config) { |
|
if (!isObject(spec)) { |
|
error('Input Vega specification must be an object.'); |
|
} |
|
|
|
config = mergeConfig(defaults(), config, spec.config); |
|
return parseView(spec, new Scope$1(config)).toRuntime(); |
|
} // -- Transforms ----- |
|
|
|
|
|
extend(transforms, tx, vtx, encode, geo, force, tree$1, reg, voronoi, wordcloud, xf); |
|
exports.Bounds = Bounds; |
|
exports.CanvasHandler = CanvasHandler; |
|
exports.CanvasRenderer = CanvasRenderer; |
|
exports.Dataflow = Dataflow; |
|
exports.Debug = Debug; |
|
exports.Error = Error$1; |
|
exports.EventStream = EventStream; |
|
exports.Gradient = Gradient; |
|
exports.GroupItem = GroupItem; |
|
exports.Handler = Handler; |
|
exports.Info = Info; |
|
exports.Item = Item; |
|
exports.Marks = Marks; |
|
exports.MultiPulse = MultiPulse; |
|
exports.None = None; |
|
exports.Operator = Operator; |
|
exports.Parameters = Parameters; |
|
exports.Pulse = Pulse; |
|
exports.RenderType = RenderType; |
|
exports.Renderer = Renderer; |
|
exports.ResourceLoader = ResourceLoader; |
|
exports.SVGHandler = SVGHandler; |
|
exports.SVGRenderer = SVGRenderer; |
|
exports.SVGStringRenderer = SVGStringRenderer; |
|
exports.Scenegraph = Scenegraph; |
|
exports.Transform = Transform; |
|
exports.View = View; |
|
exports.Warn = Warn; |
|
exports.accessor = accessor; |
|
exports.accessorFields = accessorFields; |
|
exports.accessorName = accessorName; |
|
exports.array = array; |
|
exports.bandwidthNRD = bandwidthNRD; |
|
exports.bin = bin; |
|
exports.bootstrapCI = bootstrapCI; |
|
exports.boundClip = boundClip; |
|
exports.boundContext = context; |
|
exports.boundItem = boundItem; |
|
exports.boundMark = boundMark; |
|
exports.boundStroke = boundStroke; |
|
exports.changeset = changeset; |
|
exports.clampRange = clampRange; |
|
exports.closeTag = closeTag; |
|
exports.compare = compare; |
|
exports.constant = constant; |
|
exports.cumulativeLogNormal = cumulativeLogNormal; |
|
exports.cumulativeNormal = cumulativeNormal; |
|
exports.cumulativeUniform = cumulativeUniform; |
|
exports.debounce = debounce; |
|
exports.definition = definition; |
|
exports.densityLogNormal = densityLogNormal; |
|
exports.densityNormal = densityNormal; |
|
exports.densityUniform = densityUniform; |
|
exports.domChild = domChild; |
|
exports.domClear = domClear; |
|
exports.domCreate = domCreate; |
|
exports.domFind = domFind; |
|
exports.dotbin = dotbin; |
|
exports.error = error; |
|
exports.expressionFunction = expressionFunction; |
|
exports.extend = extend; |
|
exports.extent = extent; |
|
exports.extentIndex = extentIndex; |
|
exports.falsy = falsy; |
|
exports.fastmap = fastmap; |
|
exports.field = field; |
|
exports.flush = flush; |
|
exports.font = font; |
|
exports.fontFamily = fontFamily; |
|
exports.fontSize = fontSize; |
|
exports.format = format; |
|
exports.formatLocale = defaultLocale$1; |
|
exports.formats = formats; |
|
exports.hasOwnProperty = hasOwnProperty; |
|
exports.id = id; |
|
exports.identity = identity; |
|
exports.inferType = inferType; |
|
exports.inferTypes = inferTypes; |
|
exports.ingest = ingest; |
|
exports.inherits = inherits; |
|
exports.inrange = inrange; |
|
exports.interpolate = interpolate$1; |
|
exports.interpolateColors = interpolateColors; |
|
exports.interpolateRange = interpolateRange; |
|
exports.intersect = intersect$1; |
|
exports.intersectBoxLine = intersectBoxLine; |
|
exports.intersectPath = intersectPath; |
|
exports.intersectPoint = intersectPoint; |
|
exports.intersectRule = intersectRule; |
|
exports.isArray = isArray; |
|
exports.isBoolean = isBoolean; |
|
exports.isDate = isDate; |
|
exports.isFunction = isFunction; |
|
exports.isNumber = isNumber; |
|
exports.isObject = isObject; |
|
exports.isRegExp = isRegExp; |
|
exports.isString = isString; |
|
exports.isTuple = isTuple; |
|
exports.key = key; |
|
exports.lerp = lerp; |
|
exports.lineHeight = lineHeight; |
|
exports.loader = loader; |
|
exports.logger = logger; |
|
exports.merge = merge; |
|
exports.mergeConfig = mergeConfig; |
|
exports.multiLineOffset = multiLineOffset; |
|
exports.one = one; |
|
exports.openTag = openTag; |
|
exports.pad = pad; |
|
exports.panLinear = panLinear; |
|
exports.panLog = panLog; |
|
exports.panPow = panPow; |
|
exports.panSymlog = panSymlog; |
|
exports.parse = parse$5; |
|
exports.pathCurves = curves; |
|
exports.pathEqual = pathEqual; |
|
exports.pathParse = pathParse; |
|
exports.pathRectangle = vg_rect; |
|
exports.pathRender = pathRender; |
|
exports.pathSymbols = symbols; |
|
exports.pathTrail = vg_trail; |
|
exports.peek = peek; |
|
exports.point = point$4; |
|
exports.projection = projection$1; |
|
exports.quantileLogNormal = quantileLogNormal; |
|
exports.quantileNormal = quantileNormal; |
|
exports.quantileUniform = quantileUniform; |
|
exports.quantiles = quantiles; |
|
exports.quantizeInterpolator = quantizeInterpolator; |
|
exports.quarter = quarter; |
|
exports.quartiles = quartiles; |
|
exports.randomInteger = integer; |
|
exports.randomKDE = randomKDE; |
|
exports.randomLCG = lcg; |
|
exports.randomLogNormal = randomLogNormal; |
|
exports.randomMixture = randomMixture; |
|
exports.randomNormal = randomNormal; |
|
exports.randomUniform = randomUniform; |
|
exports.read = read; |
|
exports.regressionExp = regressionExp; |
|
exports.regressionLinear = regressionLinear; |
|
exports.regressionLoess = regressionLoess; |
|
exports.regressionLog = regressionLog; |
|
exports.regressionPoly = regressionPoly; |
|
exports.regressionPow = regressionPow; |
|
exports.regressionQuad = regressionQuad; |
|
exports.renderModule = renderModule; |
|
exports.repeat = repeat; |
|
exports.resetSVGClipId = resetSVGClipId; |
|
exports.responseType = responseType; |
|
exports.runtime = parse$4; |
|
exports.runtimeContext = context$2; |
|
exports.sampleCurve = sampleCurve; |
|
exports.sampleLogNormal = sampleLogNormal; |
|
exports.sampleNormal = sampleNormal; |
|
exports.sampleUniform = sampleUniform; |
|
exports.scale = scale$2; |
|
exports.sceneEqual = sceneEqual; |
|
exports.sceneFromJSON = sceneFromJSON; |
|
exports.scenePickVisit = pickVisit; |
|
exports.sceneToJSON = sceneToJSON; |
|
exports.sceneVisit = visit; |
|
exports.sceneZOrder = zorder; |
|
exports.scheme = scheme; |
|
exports.setRandom = setRandom; |
|
exports.span = span; |
|
exports.splitAccessPath = splitAccessPath; |
|
exports.stringValue = $; |
|
exports.textMetrics = textMetrics; |
|
exports.timeBin = timeBin; |
|
exports.timeFloor = timeFloor; |
|
exports.timeFormat = timeFormat$1; |
|
exports.timeFormatLocale = defaultLocale; |
|
exports.timeInterval = timeInterval; |
|
exports.timeOffset = timeOffset; |
|
exports.timeSequence = timeSequence; |
|
exports.timeUnitSpecifier = timeUnitSpecifier; |
|
exports.timeUnits = timeUnits; |
|
exports.toBoolean = toBoolean; |
|
exports.toDate = toDate; |
|
exports.toNumber = toNumber; |
|
exports.toSet = toSet; |
|
exports.toString = toString; |
|
exports.transform = transform$1; |
|
exports.transforms = transforms; |
|
exports.truncate = truncate; |
|
exports.truthy = truthy; |
|
exports.tupleid = tupleid; |
|
exports.typeParsers = typeParsers; |
|
exports.utcFloor = utcFloor; |
|
exports.utcFormat = utcFormat$1; |
|
exports.utcInterval = utcInterval; |
|
exports.utcOffset = utcOffset; |
|
exports.utcSequence = utcSequence; |
|
exports.utcquarter = utcquarter; |
|
exports.version = version; |
|
exports.visitArray = visitArray; |
|
exports.writeConfig = writeConfig; |
|
exports.zero = zero; |
|
exports.zoomLinear = zoomLinear; |
|
exports.zoomLog = zoomLog; |
|
exports.zoomPow = zoomPow; |
|
exports.zoomSymlog = zoomSymlog; |
|
Object.defineProperty(exports, '__esModule', { |
|
value: true |
|
}); |
|
}); |