regl-scatterplot
Version:
A WebGL-Powered Scalable Interactive Scatter Plot Library
1,562 lines (1,503 loc) • 353 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('pub-sub-es'), require('regl')) :
typeof define === 'function' && define.amd ? define(['exports', 'pub-sub-es', 'regl'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.createScatterplot = {}, global.createPubSub, global.createREGL));
})(this, (function (exports, createPubSub, createOriginalRegl) { 'use strict';
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
var createPubSub__default = /*#__PURE__*/_interopDefaultCompat(createPubSub);
var createOriginalRegl__default = /*#__PURE__*/_interopDefaultCompat(createOriginalRegl);
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
function _arrayWithoutHoles(r) {
if (Array.isArray(r)) return _arrayLikeToArray(r);
}
function asyncGeneratorStep(n, t, e, r, o, a, c) {
try {
var i = n[a](c),
u = i.value;
} catch (n) {
return void e(n);
}
i.done ? t(u) : Promise.resolve(u).then(r, o);
}
function _asyncToGenerator(n) {
return function () {
var t = this,
e = arguments;
return new Promise(function (r, o) {
var a = n.apply(t, e);
function _next(n) {
asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
}
function _throw(n) {
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
}
_next(void 0);
});
};
}
function _classCallCheck(a, n) {
if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
}
function _defineProperties(e, r) {
for (var t = 0; t < r.length; t++) {
var o = r[t];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);
}
}
function _createClass(e, r, t) {
return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
function _createForOfIteratorHelper(r, e) {
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (!t) {
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
t && (r = t);
var n = 0,
F = function () {};
return {
s: F,
n: function () {
return n >= r.length ? {
done: !0
} : {
done: !1,
value: r[n++]
};
},
e: function (r) {
throw r;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var o,
a = !0,
u = !1;
return {
s: function () {
t = t.call(r);
},
n: function () {
var r = t.next();
return a = r.done, r;
},
e: function (r) {
u = !0, o = r;
},
f: function () {
try {
a || null == t.return || t.return();
} finally {
if (u) throw o;
}
}
};
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _iterableToArray(r) {
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function ownKeys(e, r) {
var t = Object.keys(e);
if (Object.getOwnPropertySymbols) {
var o = Object.getOwnPropertySymbols(e);
r && (o = o.filter(function (r) {
return Object.getOwnPropertyDescriptor(e, r).enumerable;
})), t.push.apply(t, o);
}
return t;
}
function _objectSpread2(e) {
for (var r = 1; r < arguments.length; r++) {
var t = null != arguments[r] ? arguments[r] : {};
r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
_defineProperty(e, r, t[r]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
});
}
return e;
}
function _regeneratorRuntime() {
_regeneratorRuntime = function () {
return e;
};
var t,
e = {},
r = Object.prototype,
n = r.hasOwnProperty,
o = Object.defineProperty || function (t, e, r) {
t[e] = r.value;
},
i = "function" == typeof Symbol ? Symbol : {},
a = i.iterator || "@@iterator",
c = i.asyncIterator || "@@asyncIterator",
u = i.toStringTag || "@@toStringTag";
function define(t, e, r) {
return Object.defineProperty(t, e, {
value: r,
enumerable: !0,
configurable: !0,
writable: !0
}), t[e];
}
try {
define({}, "");
} catch (t) {
define = function (t, e, r) {
return t[e] = r;
};
}
function wrap(t, e, r, n) {
var i = e && e.prototype instanceof Generator ? e : Generator,
a = Object.create(i.prototype),
c = new Context(n || []);
return o(a, "_invoke", {
value: makeInvokeMethod(t, r, c)
}), a;
}
function tryCatch(t, e, r) {
try {
return {
type: "normal",
arg: t.call(e, r)
};
} catch (t) {
return {
type: "throw",
arg: t
};
}
}
e.wrap = wrap;
var h = "suspendedStart",
l = "suspendedYield",
f = "executing",
s = "completed",
y = {};
function Generator() {}
function GeneratorFunction() {}
function GeneratorFunctionPrototype() {}
var p = {};
define(p, a, function () {
return this;
});
var d = Object.getPrototypeOf,
v = d && d(d(values([])));
v && v !== r && n.call(v, a) && (p = v);
var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p);
function defineIteratorMethods(t) {
["next", "throw", "return"].forEach(function (e) {
define(t, e, function (t) {
return this._invoke(e, t);
});
});
}
function AsyncIterator(t, e) {
function invoke(r, o, i, a) {
var c = tryCatch(t[r], t, o);
if ("throw" !== c.type) {
var u = c.arg,
h = u.value;
return h && "object" == typeof h && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) {
invoke("next", t, i, a);
}, function (t) {
invoke("throw", t, i, a);
}) : e.resolve(h).then(function (t) {
u.value = t, i(u);
}, function (t) {
return invoke("throw", t, i, a);
});
}
a(c.arg);
}
var r;
o(this, "_invoke", {
value: function (t, n) {
function callInvokeWithMethodAndArg() {
return new e(function (e, r) {
invoke(t, n, e, r);
});
}
return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg();
}
});
}
function makeInvokeMethod(e, r, n) {
var o = h;
return function (i, a) {
if (o === f) throw Error("Generator is already running");
if (o === s) {
if ("throw" === i) throw a;
return {
value: t,
done: !0
};
}
for (n.method = i, n.arg = a;;) {
var c = n.delegate;
if (c) {
var u = maybeInvokeDelegate(c, n);
if (u) {
if (u === y) continue;
return u;
}
}
if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) {
if (o === h) throw o = s, n.arg;
n.dispatchException(n.arg);
} else "return" === n.method && n.abrupt("return", n.arg);
o = f;
var p = tryCatch(e, r, n);
if ("normal" === p.type) {
if (o = n.done ? s : l, p.arg === y) continue;
return {
value: p.arg,
done: n.done
};
}
"throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg);
}
};
}
function maybeInvokeDelegate(e, r) {
var n = r.method,
o = e.iterator[n];
if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y;
var i = tryCatch(o, e.iterator, r.arg);
if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y;
var a = i.arg;
return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y);
}
function pushTryEntry(t) {
var e = {
tryLoc: t[0]
};
1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e);
}
function resetTryEntry(t) {
var e = t.completion || {};
e.type = "normal", delete e.arg, t.completion = e;
}
function Context(t) {
this.tryEntries = [{
tryLoc: "root"
}], t.forEach(pushTryEntry, this), this.reset(!0);
}
function values(e) {
if (e || "" === e) {
var r = e[a];
if (r) return r.call(e);
if ("function" == typeof e.next) return e;
if (!isNaN(e.length)) {
var o = -1,
i = function next() {
for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next;
return next.value = t, next.done = !0, next;
};
return i.next = i;
}
}
throw new TypeError(typeof e + " is not iterable");
}
return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", {
value: GeneratorFunctionPrototype,
configurable: !0
}), o(GeneratorFunctionPrototype, "constructor", {
value: GeneratorFunction,
configurable: !0
}), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) {
var e = "function" == typeof t && t.constructor;
return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name));
}, e.mark = function (t) {
return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t;
}, e.awrap = function (t) {
return {
__await: t
};
}, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () {
return this;
}), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) {
void 0 === i && (i = Promise);
var a = new AsyncIterator(wrap(t, r, n, o), i);
return e.isGeneratorFunction(r) ? a : a.next().then(function (t) {
return t.done ? t.value : a.next();
});
}, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () {
return this;
}), define(g, "toString", function () {
return "[object Generator]";
}), e.keys = function (t) {
var e = Object(t),
r = [];
for (var n in e) r.push(n);
return r.reverse(), function next() {
for (; r.length;) {
var t = r.pop();
if (t in e) return next.value = t, next.done = !1, next;
}
return next.done = !0, next;
};
}, e.values = values, Context.prototype = {
constructor: Context,
reset: function (e) {
if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t);
},
stop: function () {
this.done = !0;
var t = this.tryEntries[0].completion;
if ("throw" === t.type) throw t.arg;
return this.rval;
},
dispatchException: function (e) {
if (this.done) throw e;
var r = this;
function handle(n, o) {
return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o;
}
for (var o = this.tryEntries.length - 1; o >= 0; --o) {
var i = this.tryEntries[o],
a = i.completion;
if ("root" === i.tryLoc) return handle("end");
if (i.tryLoc <= this.prev) {
var c = n.call(i, "catchLoc"),
u = n.call(i, "finallyLoc");
if (c && u) {
if (this.prev < i.catchLoc) return handle(i.catchLoc, !0);
if (this.prev < i.finallyLoc) return handle(i.finallyLoc);
} else if (c) {
if (this.prev < i.catchLoc) return handle(i.catchLoc, !0);
} else {
if (!u) throw Error("try statement without catch or finally");
if (this.prev < i.finallyLoc) return handle(i.finallyLoc);
}
}
}
},
abrupt: function (t, e) {
for (var r = this.tryEntries.length - 1; r >= 0; --r) {
var o = this.tryEntries[r];
if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) {
var i = o;
break;
}
}
i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null);
var a = i ? i.completion : {};
return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a);
},
complete: function (t, e) {
if ("throw" === t.type) throw t.arg;
return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y;
},
finish: function (t) {
for (var e = this.tryEntries.length - 1; e >= 0; --e) {
var r = this.tryEntries[e];
if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y;
}
},
catch: function (t) {
for (var e = this.tryEntries.length - 1; e >= 0; --e) {
var r = this.tryEntries[e];
if (r.tryLoc === t) {
var n = r.completion;
if ("throw" === n.type) {
var o = n.arg;
resetTryEntry(r);
}
return o;
}
}
throw Error("illegal catch attempt");
},
delegateYield: function (e, r, n) {
return this.delegate = {
iterator: values(e),
resultName: r,
nextLoc: n
}, "next" === this.method && (this.arg = t), y;
}
}, e;
}
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _toConsumableArray(r) {
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != typeof i) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == typeof i ? i : i + "";
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
// @flekschas/utils v0.32.2 Copyright 2023 Fritz Lekschas
/* eslint no-param-reassign:0 */
/**
* Cubic in easing function
* @param {number} t - The input time to be eased. Must be in [0, 1] where `0`
* refers to the start and `1` to the end
* @return {number} The eased time
*/
const cubicIn = t => t * t * t;
/**
* Cubic in and out easing function
* @param {number} t - The input time to be eased. Must be in [0, 1] where `0`
* refers to the start and `1` to the end
* @return {number} The eased time
*/
const cubicInOut = t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
/**
* Cubic out easing function
* @param {number} t - The input time to be eased. Must be in [0, 1] where `0`
* refers to the start and `1` to the end
* @return {number} The eased time
*/
const cubicOut = t => --t * t * t + 1;
/**
* Linear easing function
* @param {number} t - The input time to be eased. Must be in [0, 1] where `0`
* refers to the start and `1` to the end
* @return {number} Same as the input
*/
const linear = t => t;
/**
* Quadratic in easing function
* @param {number} t - The input time to be eased. Must be in [0, 1] where `0`
* refers to the start and `1` to the end
* @return {number} The eased time
*/
const quadIn = t => t * t;
/**
* Quadratic in and out easing function
* @param {number} t - The input time to be eased. Must be in [0, 1] where `0`
* refers to the start and `1` to the end
* @return {number} The eased time
*/
const quadInOut = t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
/**
* Quadratic out easing function
* @param {number} t - The input time to be eased. Must be in [0, 1] where `0`
* refers to the start and `1` to the end
* @return {number} The eased time
*/
const quadOut = t => t * (2 - t);
/**
* Identity function
* @type {<T>(x: T) => T}
* @param {*} x - Any kind of value
* @return {*} `x`
*/
const identity = x => x;
/**
* Check if two arrays contain the same elements
* @type {<T>(a: T[], b: T[]) => Boolean}
* @param {array} a - First array
* @param {array} b - Second array
* @return {boolean} If `true` the two arrays contain the same elements
*/
const hasSameElements = (a, b) => {
if (a === b) return true;
if (a.length !== b.length) return false;
const aSet = new Set(a);
const bSet = new Set(b);
// Since the arrays could contain duplicates, we have to check the set length
// as well
if (aSet.size !== bSet.size) return false;
return b.every(element => aSet.has(element));
};
/**
* Vector L2 norm
*
* @description
* This is identical but much faster than `Math.hypot(...v)`
*
* @param {number[]} v - Numerical vector
* @return {number} L2 norm
*/
const l2Norm = v => Math.sqrt(v.reduce((sum, x) => sum + x ** 2, 0));
/**
* Get the maximum number of a vector while ignoring NaNs
*
* @description
* This version is muuuch faster than `Math.max(...v)` and supports vectors
* longer than 256^2, which is a limitation of `Math.max.apply(null, v)`.
*
* @param {number[]} v - Numerical vector
* @return {number} The largest number
*/
const max$1 = v => v.reduce((_max, a) => a > _max ? a : _max, -Infinity);
/**
* Initialize an array of a certain length using a mapping function
*
* @description
* This is equivalent to `Array.from({ length }, mapFn)` but about 60% faster
*
* @param {number} length - Size of the array
* @param {function} mapFn - Mapping function
* @return {array} Initialized array
* @type {<T = number>(length: number, mapFn: (i: number, length: number) => T) => T[]}
*/
const rangeMap = (length, mapFn = x => x) => {
const out = [];
for (let i = 0; i < length; i++) {
out.push(mapFn(i, length));
}
return out;
};
/**
* Get the unique union of two vectors of integers
* @param {number[]} v - First vector of integers
* @param {number[]} w - Second vector of integers
* @return {number[]} Unique union of `v` and `w`
*/
const unionIntegers = (v, w) => {
const a = [];
v.forEach(x => {
a[x] = true;
});
w.forEach(x => {
a[x] = true;
});
return a.reduce((union, value, i) => {
if (value) union.push(i);
return union;
}, []);
};
/**
* Assign properties, constructors, etc. to an object
*
* @param {object} target - The target object that gets `sources` assigned to it
* @param {}
* @return {object}
*/
const assign = (target, ...sources) => {
sources.forEach(source => {
// eslint-disable-next-line no-shadow
const descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, {});
// By default, Object.assign copies enumerable Symbols, too
Object.getOwnPropertySymbols(source).forEach(symbol => {
const descriptor = Object.getOwnPropertyDescriptor(source, symbol);
if (descriptor.enumerable) {
descriptors[symbol] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
};
/**
* Convenience function to compose functions
* @param {...function} fns - Array of functions
* @return {function} The composed function
*/
const pipe = (...fns) =>
/**
* @param {*} x - Some value
* @return {*} Output of the composed function
*/
x => fns.reduce((y, f) => f(y), x);
/**
* Assign a constructor to the object
* @param {function} constructor - Constructor functions
*/
const withConstructor = constructor => self => assign({
__proto__: {
constructor
}
}, self);
/**
* Assign a static property to an object
* @param {string} name - Name of the property
* @param {*} value - Static value
*/
const withStaticProperty = (name, value) => self => assign(self, {
get [name]() {
return value;
}
});
/**
* L2 distance between a pair of points
*
* @description
* Identical but much faster than `l2Dist([fromX, fromY], [toX, toY])`
*
* @param {number} fromX - X coordinate of the first point
* @param {number} fromY - Y coordinate of the first point
* @param {number} toX - X coordinate of the second point
* @param {number} toY - Y coordinate of the first point
* @return {number} L2 distance
*/
const l2PointDist = (fromX, fromY, toX, toY) => Math.sqrt((fromX - toX) ** 2 + (fromY - toY) ** 2);
/**
* Create a worker from a function
* @param {function} fn - Function to be turned into a worker
* @return {Worker} Worker function
*/
const createWorker$1 = fn => new Worker(window.URL.createObjectURL(new Blob([`(${fn.toString()})()`], {
type: 'text/javascript'
})));
/**
* Get a promise that resolves after the next `n` animation frames
* @param {number} n - Number of animation frames to wait
* @return {Promise} A promise that resolves after the next `n` animation frames
*/
const nextAnimationFrame = (n = 1) => new Promise(resolve => {
let i = 0;
const raf = () => requestAnimationFrame(() => {
i++;
if (i < n) raf();else resolve();
});
raf();
});
/**
* Throttle and debounce a function call
*
* Throttling a function call means that the function is called at most every
* `interval` milliseconds no matter how frequently you trigger a call.
* Debouncing a function call means that the function is called the earliest
* after `finalWait` milliseconds wait time where the function was not called.
* Combining the two ensures that the function is called at most every
* `interval` milliseconds and is ensured to be called with the very latest
* arguments after after `finalWait` milliseconds wait time at the end.
*
* The following imaginary scenario describes the behavior:
*
* MS | throttleTime=3 and debounceTime=3
* 1. y(f, 3, 3)(args1) => f(args1) called
* 2. y(f, 3, 3)(args2) => call ignored due to throttling
* 3. y(f, 3, 3)(args3) => call ignored due to throttling
* 4. y(f, 3, 3)(args4) => f(args4) called
* 5. y(f, 3, 3)(args5) => all ignored due to throttling
* 6. No call => nothing
* 7. No call => f(args5) called due to debouncing
*
* @param {functon} fn - Function to be throttled and debounced
* @param {number} throttleTime - Throttle intevals in milliseconds
* @param {number} debounceTime - Debounce wait time in milliseconds. By default
* this is the same as `throttleTime`.
* @return {function} - Throttled and debounced function
*/
const throttleAndDebounce = (fn, throttleTime, debounceTime = null) => {
let timeout;
let blockedCalls = 0;
// eslint-disable-next-line no-param-reassign
debounceTime = debounceTime === null ? throttleTime : debounceTime;
const debounced = (...args) => {
const later = () => {
// Since we throttle and debounce we should check whether there were
// actually multiple attempts to call this function after the most recent
// throttled call. If there were no more calls we don't have to call
// the function again.
if (blockedCalls > 0) {
fn(...args);
blockedCalls = 0;
}
};
clearTimeout(timeout);
timeout = setTimeout(later, debounceTime);
};
let isWaiting = false;
const throttledAndDebounced = (...args) => {
if (!isWaiting) {
fn(...args);
debounced(...args);
isWaiting = true;
blockedCalls = 0;
setTimeout(() => {
isWaiting = false;
}, throttleTime);
} else {
blockedCalls++;
debounced(...args);
}
};
throttledAndDebounced.reset = () => {
isWaiting = false;
};
throttledAndDebounced.cancel = () => {
clearTimeout(timeout);
};
throttledAndDebounced.now = (...args) => fn(...args);
return throttledAndDebounced;
};
/**
* Common utilities
* @module glMatrix
*/
// Configuration Constants
var EPSILON = 0.000001;
var ARRAY_TYPE = typeof Float32Array !== 'undefined' ? Float32Array : Array;
if (!Math.hypot) Math.hypot = function () {
var y = 0,
i = arguments.length;
while (i--) {
y += arguments[i] * arguments[i];
}
return Math.sqrt(y);
};
/**
* 4x4 Matrix<br>Format: column-major, when typed out it looks like row-major<br>The matrices are being post multiplied.
* @module mat4
*/
/**
* Creates a new identity mat4
*
* @returns {mat4} a new 4x4 matrix
*/
function create$2() {
var out = new ARRAY_TYPE(16);
if (ARRAY_TYPE != Float32Array) {
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[11] = 0;
out[12] = 0;
out[13] = 0;
out[14] = 0;
}
out[0] = 1;
out[5] = 1;
out[10] = 1;
out[15] = 1;
return out;
}
/**
* Creates a new mat4 initialized with values from an existing matrix
*
* @param {ReadonlyMat4} a matrix to clone
* @returns {mat4} a new 4x4 matrix
*/
function clone(a) {
var out = new ARRAY_TYPE(16);
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
out[4] = a[4];
out[5] = a[5];
out[6] = a[6];
out[7] = a[7];
out[8] = a[8];
out[9] = a[9];
out[10] = a[10];
out[11] = a[11];
out[12] = a[12];
out[13] = a[13];
out[14] = a[14];
out[15] = a[15];
return out;
}
/**
* Inverts a mat4
*
* @param {mat4} out the receiving matrix
* @param {ReadonlyMat4} a the source matrix
* @returns {mat4} out
*/
function invert(out, a) {
var a00 = a[0],
a01 = a[1],
a02 = a[2],
a03 = a[3];
var a10 = a[4],
a11 = a[5],
a12 = a[6],
a13 = a[7];
var a20 = a[8],
a21 = a[9],
a22 = a[10],
a23 = a[11];
var a30 = a[12],
a31 = a[13],
a32 = a[14],
a33 = a[15];
var b00 = a00 * a11 - a01 * a10;
var b01 = a00 * a12 - a02 * a10;
var b02 = a00 * a13 - a03 * a10;
var b03 = a01 * a12 - a02 * a11;
var b04 = a01 * a13 - a03 * a11;
var b05 = a02 * a13 - a03 * a12;
var b06 = a20 * a31 - a21 * a30;
var b07 = a20 * a32 - a22 * a30;
var b08 = a20 * a33 - a23 * a30;
var b09 = a21 * a32 - a22 * a31;
var b10 = a21 * a33 - a23 * a31;
var b11 = a22 * a33 - a23 * a32; // Calculate the determinant
var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
if (!det) {
return null;
}
det = 1.0 / det;
out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
return out;
}
/**
* Multiplies two mat4s
*
* @param {mat4} out the receiving matrix
* @param {ReadonlyMat4} a the first operand
* @param {ReadonlyMat4} b the second operand
* @returns {mat4} out
*/
function multiply(out, a, b) {
var a00 = a[0],
a01 = a[1],
a02 = a[2],
a03 = a[3];
var a10 = a[4],
a11 = a[5],
a12 = a[6],
a13 = a[7];
var a20 = a[8],
a21 = a[9],
a22 = a[10],
a23 = a[11];
var a30 = a[12],
a31 = a[13],
a32 = a[14],
a33 = a[15]; // Cache only the current line of the second matrix
var b0 = b[0],
b1 = b[1],
b2 = b[2],
b3 = b[3];
out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = b[4];
b1 = b[5];
b2 = b[6];
b3 = b[7];
out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = b[8];
b1 = b[9];
b2 = b[10];
b3 = b[11];
out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = b[12];
b1 = b[13];
b2 = b[14];
b3 = b[15];
out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
return out;
}
/**
* Creates a matrix from a vector translation
* This is equivalent to (but much faster than):
*
* mat4.identity(dest);
* mat4.translate(dest, dest, vec);
*
* @param {mat4} out mat4 receiving operation result
* @param {ReadonlyVec3} v Translation vector
* @returns {mat4} out
*/
function fromTranslation(out, v) {
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = 1;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 1;
out[11] = 0;
out[12] = v[0];
out[13] = v[1];
out[14] = v[2];
out[15] = 1;
return out;
}
/**
* Creates a matrix from a vector scaling
* This is equivalent to (but much faster than):
*
* mat4.identity(dest);
* mat4.scale(dest, dest, vec);
*
* @param {mat4} out mat4 receiving operation result
* @param {ReadonlyVec3} v Scaling vector
* @returns {mat4} out
*/
function fromScaling(out, v) {
out[0] = v[0];
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = v[1];
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = v[2];
out[11] = 0;
out[12] = 0;
out[13] = 0;
out[14] = 0;
out[15] = 1;
return out;
}
/**
* Creates a matrix from a given angle around a given axis
* This is equivalent to (but much faster than):
*
* mat4.identity(dest);
* mat4.rotate(dest, dest, rad, axis);
*
* @param {mat4} out mat4 receiving operation result
* @param {Number} rad the angle to rotate the matrix by
* @param {ReadonlyVec3} axis the axis to rotate around
* @returns {mat4} out
*/
function fromRotation(out, rad, axis) {
var x = axis[0],
y = axis[1],
z = axis[2];
var len = Math.hypot(x, y, z);
var s, c, t;
if (len < EPSILON) {
return null;
}
len = 1 / len;
x *= len;
y *= len;
z *= len;
s = Math.sin(rad);
c = Math.cos(rad);
t = 1 - c; // Perform rotation-specific matrix multiplication
out[0] = x * x * t + c;
out[1] = y * x * t + z * s;
out[2] = z * x * t - y * s;
out[3] = 0;
out[4] = x * y * t - z * s;
out[5] = y * y * t + c;
out[6] = z * y * t + x * s;
out[7] = 0;
out[8] = x * z * t + y * s;
out[9] = y * z * t - x * s;
out[10] = z * z * t + c;
out[11] = 0;
out[12] = 0;
out[13] = 0;
out[14] = 0;
out[15] = 1;
return out;
}
/**
* Returns the translation vector component of a transformation
* matrix. If a matrix is built with fromRotationTranslation,
* the returned vector will be the same as the translation vector
* originally supplied.
* @param {vec3} out Vector to receive translation component
* @param {ReadonlyMat4} mat Matrix to be decomposed (input)
* @return {vec3} out
*/
function getTranslation(out, mat) {
out[0] = mat[12];
out[1] = mat[13];
out[2] = mat[14];
return out;
}
/**
* Returns the scaling factor component of a transformation
* matrix. If a matrix is built with fromRotationTranslationScale
* with a normalized Quaternion paramter, the returned vector will be
* the same as the scaling vector
* originally supplied.
* @param {vec3} out Vector to receive scaling factor component
* @param {ReadonlyMat4} mat Matrix to be decomposed (input)
* @return {vec3} out
*/
function getScaling(out, mat) {
var m11 = mat[0];
var m12 = mat[1];
var m13 = mat[2];
var m21 = mat[4];
var m22 = mat[5];
var m23 = mat[6];
var m31 = mat[8];
var m32 = mat[9];
var m33 = mat[10];
out[0] = Math.hypot(m11, m12, m13);
out[1] = Math.hypot(m21, m22, m23);
out[2] = Math.hypot(m31, m32, m33);
return out;
}
/**
* 4 Dimensional Vector
* @module vec4
*/
/**
* Creates a new, empty vec4
*
* @returns {vec4} a new 4D vector
*/
function create$1() {
var out = new ARRAY_TYPE(4);
if (ARRAY_TYPE != Float32Array) {
out[0] = 0;
out[1] = 0;
out[2] = 0;
out[3] = 0;
}
return out;
}
/**
* Transforms the vec4 with a mat4.
*
* @param {vec4} out the receiving vector
* @param {ReadonlyVec4} a the vector to transform
* @param {ReadonlyMat4} m matrix to transform with
* @returns {vec4} out
*/
function transformMat4(out, a, m) {
var x = a[0],
y = a[1],
z = a[2],
w = a[3];
out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
return out;
}
/**
* Perform some operation over an array of vec4s.
*
* @param {Array} a the array of vectors to iterate over
* @param {Number} stride Number of elements between the start of each vec4. If 0 assumes tightly packed
* @param {Number} offset Number of elements to skip at the beginning of the array
* @param {Number} count Number of vec4s to iterate over. If 0 iterates over entire array
* @param {Function} fn Function to call for each vector in the array
* @param {Object} [arg] additional argument to pass to fn
* @returns {Array} a
* @function
*/
(function () {
var vec = create$1();
return function (a, stride, offset, count, fn, arg) {
var i, l;
if (!stride) {
stride = 4;
}
if (!offset) {
offset = 0;
}
if (count) {
l = Math.min(count * stride + offset, a.length);
} else {
l = a.length;
}
for (i = offset; i < l; i += stride) {
vec[0] = a[i];
vec[1] = a[i + 1];
vec[2] = a[i + 2];
vec[3] = a[i + 3];
fn(vec, vec, arg);
a[i] = vec[0];
a[i + 1] = vec[1];
a[i + 2] = vec[2];
a[i + 3] = vec[3];
}
return a;
};
})();
/**
* 2 Dimensional Vector
* @module vec2
*/
/**
* Creates a new, empty vec2
*
* @returns {vec2} a new 2D vector
*/
function create() {
var out = new ARRAY_TYPE(2);
if (ARRAY_TYPE != Float32Array) {
out[0] = 0;
out[1] = 0;
}
return out;
}
/**
* Get the angle between two 2D vectors
* @param {ReadonlyVec2} a The first operand
* @param {ReadonlyVec2} b The second operand
* @returns {Number} The angle in radians
*/
function angle(a, b) {
var x1 = a[0],
y1 = a[1],
x2 = b[0],
y2 = b[1],
// mag is the product of the magnitudes of a and b
mag = Math.sqrt(x1 * x1 + y1 * y1) * Math.sqrt(x2 * x2 + y2 * y2),
// mag &&.. short circuits if mag == 0
cosine = mag && (x1 * x2 + y1 * y2) / mag; // Math.min(Math.max(cosine, -1), 1) clamps the cosine between -1 and 1
return Math.acos(Math.min(Math.max(cosine, -1), 1));
}
/**
* Perform some operation over an array of vec2s.
*
* @param {Array} a the array of vectors to iterate over
* @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed
* @param {Number} offset Number of elements to skip at the beginning of the array
* @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array
* @param {Function} fn Function to call for each vector in the array
* @param {Object} [arg] additional argument to pass to fn
* @returns {Array} a
* @function
*/
(function () {
var vec = create();
return function (a, stride, offset, count, fn, arg) {
var i, l;
if (!stride) {
stride = 2;
}
if (!offset) {
offset = 0;
}
if (count) {
l = Math.min(count * stride + offset, a.length);
} else {
l = a.length;
}
for (i = offset; i < l; i += stride) {
vec[0] = a[i];
vec[1] = a[i + 1];
fn(vec, vec, arg);
a[i] = vec[0];
a[i + 1] = vec[1];
}
return a;
};
})();
const createCamera = (initTarget = [0, 0], initDistance = 1, initRotation = 0, initViewCenter = [0, 0], initScaleBounds = [[0, Infinity], [0, Infinity]], initTranslationBounds = [[-Infinity, Infinity], [-Infinity, Infinity]]) => {
// Scratch variables
const scratch0 = new Float32Array(16);
const scratch1 = new Float32Array(16);
const scratch2 = new Float32Array(16);
let view = create$2();
let viewCenter = [...initViewCenter.slice(0, 2), 0, 1];
const scaleXBounds = Array.isArray(initScaleBounds[0]) ? [...initScaleBounds[0]] : [...initScaleBounds];
const scaleYBounds = Array.isArray(initScaleBounds[0]) ? [...initScaleBounds[1]] : [...initScaleBounds];
const translationXBounds = Array.isArray(initTranslationBounds[0]) ? [...initTranslationBounds[0]] : [...initTranslationBounds];
const translationYBounds = Array.isArray(initTranslationBounds[0]) ? [...initTranslationBounds[1]] : [...initTranslationBounds];
const getScaling$1 = () => getScaling(scratch0, view).slice(0, 2);
const getMinScaling = () => {
const scaling = getScaling$1();
return Math.min(scaling[0], scaling[1]);
};
const getMaxScaling = () => {
const scaling = getScaling$1();
return Math.max(scaling[0], scaling[1]);
};
const getRotation = () => Math.acos(view[0] / getMaxScaling());
const getScaleBounds = () => [[...scaleXBounds], [...scaleYBounds]];
const getTranslationBounds = () => [[...translationXBounds], [...translationYBounds]];
const getDistance = () => {
const scaling = getScaling$1();
return [1 / scaling[0], 1 / scaling[1]];
};
const getMinDistance = () => 1 / getMinScaling();
const getMaxDistance = () => 1 / getMaxScaling();
const getTranslation$1 = () => getTranslation(scratch0, view).slice(0, 2);
const getTarget = () => transformMat4(scratch0, viewCenter, invert(scratch2, view)).slice(0, 2);
const getView = () => view;
const getViewCenter = () => viewCenter.slice(0, 2);
const lookAt = ([x = 0, y = 0] = [], newDistance = 1, newRotation = 0) => {
// Reset the view
view = create$2();
translate([-x, -y]);
rotate(newRotation);
scale(1 / newDistance);
};
const translate = ([x = 0, y = 0] = []) => {
scratch0[0] = x;
scratch0[1] = y;
scratch0[2] = 0;
const t = fromTranslation(scratch1, scratch0);
// Translate about the viewport center
// This is identical to `i * t * i * view` where `i` is the identity matrix
multiply(view, t, view);
};
const scale = (d, mousePos) => {
const isArray = Array.isArray(d);
let dx = isArray ? d[0] : d;
let dy = isArray ? d[1] : d;
if (dx <= 0 || dy <= 0 || dx === 1 && dy === 1) return;
const scaling = getScaling$1();
const newXScale = scaling[0] * dx;
const newYScale = scaling[1] * dy;
dx = Math.max(scaleXBounds[0], Math.min(newXScale, scaleXBounds[1])) / scaling[0];
dy = Math.max(scaleYBounds[0], Math.min(newYScale, scaleYBounds[1])) / scaling[1];
if (dx === 1 && dy === 1) return; // There is nothing to do
scratch0[0] = dx;
scratch0[1] = dy;
scratch0[2] = 1;
const s = fromScaling(scratch1, scratch0);
const scaleCenter = mousePos ? [...mousePos, 0] : viewCenter;
const a = fromTranslation(scratch0, scaleCenter);
// Translate about the scale center
// I.e., the mouse position or the view center
multiply(view, a, multiply(view, s, multiply(view, invert(scratch2, a), view)));
};
const rotate = rad => {
const r = create$2();
fromRotation(r, rad, [0, 0, 1]);
// Rotate about the viewport center
// This is identical to `i * r * i * view` where `i` is the identity matrix
multiply(view, r, view);
};
const setScaleBounds = newBounds => {
const isArray = Array.isArray(newBounds[0]);
scaleXBounds[0] = isArray ? newBounds[0][0] : newBounds[0];
scaleXBounds[1] = isArray ? newBounds[0][1] : newBounds[1];
scaleYBounds[0] = isArray ? newBounds[1][0] : newBounds[0];
scaleYBounds[1] = isArray ? newBounds[1][1] : newBounds[1];
};
const setTranslationBounds = newBounds => {
const isArray = Array.isArray(newBounds[0]);
translationXBounds[0] = isArray ? newBounds[0][0] : newBounds[0];
translationXBounds[1] = isArray ? newBounds[0][1] : newBounds[1];
translationYBounds[0] = isArray ? newBounds[1][0] : newBounds[0];
translationYBounds[1] = isArray ? newBounds[1][1] : newBounds[1];
};
const setView = newView => {
if (!newView || newView.length < 16) return;
view = newView;
};
const setViewCenter = newViewCenter => {
viewCenter = [...newViewCenter.slice(0, 2), 0, 1];
};
const reset = () => {
lookAt(initTarget, initDistance, initRotation);
};
// Init
lookAt(initTarget, initDistance, initRotation);
return {
get translation() {
return getTranslation$1();
},
get target() {
return getTarget();
},
get scaling() {
return getScaling$1();
},
get minScaling() {
return getMinScaling();
},
get maxScaling() {
return getMaxScaling();
},
get scaleBounds() {
return getScaleBounds();
},
get translationBounds() {
return getTranslationBounds();
},
get distance() {
return getDistance();
},
get minDistance() {
return getMinDistance();
},
get maxDistance() {
return getMaxDistance();
},
get rotation() {
return getRotation();
},
get view() {
return getView();
},
get viewCenter() {
return getViewCenter();
},
lookAt,
translate,
pan: translate,
rotate,
scale,
zoom: scale,
reset,
set: (...args) => {
console.warn('`set()` is deprecated. Please use `setView()` instead.');
return setView(...args);
},
setScaleBounds,
setTranslationBounds,
setView,
setViewCenter
};
};
const MOUSE_DOWN_MOVE_ACTIONS = ["pan", "rotate"];
const KEY_MAP = {
alt: "altKey",
cmd: "metaKey",
ctrl: "ctrlKey",
meta: "metaKey",
shift: "shiftKey"
};
const dom2dCamera = (element, {
distance = 1.0,
target = [0, 0],
rotation = 0,
isNdc = true,
isFixed = false,
isPan = true,
isPanInverted = [false, true],
panSpeed = 1,
isRotate = true,
rotateSpeed = 1,
defaultMouseDownMoveAction = "pan",
mouseDownMoveModKey = "alt",
isZoom = true,
zoomSpeed = 1,
viewCenter,
scaleBounds,
translationBounds,
onKeyDown = () => {},
onKeyUp = () => {},
onMouseDown = () => {},
onMouseUp = () => {},
onMouseMove = () => {},
onWheel = () => {}
} = {}) => {
let camera = createCamera(target, distance, rotation, viewCenter, scaleBounds, translationBounds);
let mouseX = 0;
let mouseY = 0;
let mouseRelX = 0;
let mouseRelY = 0;
let prevMouseX = 0;
let prevMouseY = 0;
let isLeftMousePressed = false;
let scrollDist = 0;
let width = 1;
let height = 1;
let aspectRatio = 1;
let isInteractivelyChanged = false;
let isProgrammaticallyChanged = false;
let isMouseDownMoveModActive = false;
let panOnMouseDownMove = defaultMouseDownMoveAction === "pan";
let isPanX = isPan;
let isPanY = isPan;
let isPanXInverted = isPanInverted;
let isPanYInverted = isPanInverted;
let isZoomX = isZoom;
let isZoomY = isZoom;
const spreadXYSettings = () => {
isPanX = Array.isArray(isPan) ? Boolean(isPan[0]) : isPan;
isPanY = Array.isArray(isPan) ? Boolean(isPan[1]) : isPan;
isPanXInverted = Array.isArray(isPanInverted) ? Boolean(isPanInverted[0]) : isPanInverted;
isPanYInverted = Array.isArray(isPanInverted) ? Boolean(isPanInverted[1]) : isPanInverted;
isZoomX = Array.isArray(isZoom) ? Boolean(isZoom[0]) : isZoom;
isZoomY = Array.isArray(isZoom) ? Boolean(isZoom[1]) : isZoom;
};
spreadXYSettings();
const transformPanX = isNdc ? dX => dX / width * 2 * aspectRatio // to no