sigma-next
Version:
A JavaScript library dedicated to graph drawing.
1,688 lines (1,460 loc) • 126 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var events = require('events');
var extent = require('graphology-metrics/extent');
var isGraph = _interopDefault(require('graphology-utils/is-graph'));
/**
* Sigma.js Utils
* ===============
*
* Various helper functions & classes used throughout the library.
*/
/**
* Very simple Object.assign-like function.
*
* @param {object} target - First object.
* @param {object} [...objects] - Objects to merge.
* @return {object}
*/
function assign(target) {
target = target || {};
for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
objects[_key - 1] = arguments[_key];
}
for (var i = 0, l = objects.length; i < l; i++) {
if (!objects[i]) continue;
for (var k in objects[i]) {
target[k] = objects[i][k];
}
}
return target;
}
function extend(array, values) {
var l1 = array.length;
var l2 = values.length;
if (l2 === 0) return;
array.length += values.length;
for (var i = 0; i < l2; i++) {
array[l1 + i] = values[i];
}
}
/**
* Sigma.js Easings
* =================
*
* Handy collection of easing functions.
*/
var linear = function linear(k) {
return k;
};
var quadraticIn = function quadraticIn(k) {
return k * k;
};
var quadraticOut = function quadraticOut(k) {
return k * (2 - k);
};
var quadraticInOut = function quadraticInOut(k) {
k *= 2;
if (k < 1) return 0.5 * k * k;
return -0.5 * (--k * (k - 2) - 1);
};
var cubicIn = function cubicIn(k) {
return k * k * k;
};
var cubicOut = function cubicOut(k) {
return --k * k * k + 1;
};
var cubicInOut = function cubicInOut(k) {
k *= 2;
if (k < 1) return 0.5 * k * k * k;
return 0.5 * ((k -= 2) * k * k + 2);
};
var easings = /*#__PURE__*/Object.freeze({
linear: linear,
quadraticIn: quadraticIn,
quadraticOut: quadraticOut,
quadraticInOut: quadraticInOut,
cubicIn: cubicIn,
cubicOut: cubicOut,
cubicInOut: cubicInOut
});
/**
* Sigma.js Animation Helpers
* ===========================
*
* Handy helper functions dealing with nodes & edges attributes animation.
*/
/**
* Defaults.
*/
var ANIMATE_DEFAULTS = {
easing: 'quadraticInOut',
duration: 150
};
/**
* Function used to animate the nodes.
*/
function animateNodes(graph, targets, options, callback) {
options = assign({}, ANIMATE_DEFAULTS, options);
var easing = typeof options.easing === 'function' ? options.easing : easings[options.easing];
var start = Date.now();
var startPositions = {};
for (var node in targets) {
var attrs = targets[node];
startPositions[node] = {};
for (var k in attrs) {
startPositions[node][k] = graph.getNodeAttribute(node, k);
}
}
var frame = null;
var step = function step() {
var p = (Date.now() - start) / options.duration;
if (p >= 1) {
// Animation is done
for (var _node in targets) {
var _attrs = targets[_node];
for (var _k in _attrs) {
graph.setNodeAttribute(_node, _k, _attrs[_k]);
}
}
if (typeof callback === 'function') callback();
return;
}
p = easing(p);
for (var _node2 in targets) {
var _attrs2 = targets[_node2];
var s = startPositions[_node2];
for (var _k2 in _attrs2) {
graph.setNodeAttribute(_node2, _k2, _attrs2[_k2] * p + s[_k2] * (1 - p));
}
}
frame = requestAnimationFrame(step);
};
step();
return function () {
if (frame) cancelAnimationFrame(frame);
};
}
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 _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 ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(source, true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(source).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();
}
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 _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {
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 _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
/**
* Renderer class.
*
* @constructor
*/
var Renderer =
/*#__PURE__*/
function (_EventEmitter) {
_inherits(Renderer, _EventEmitter);
function Renderer() {
_classCallCheck(this, Renderer);
return _possibleConstructorReturn(this, _getPrototypeOf(Renderer).apply(this, arguments));
}
return Renderer;
}(events.EventEmitter);
/**
* Defaults.
*/
var ANIMATE_DEFAULTS$1 = {
easing: 'quadraticInOut',
duration: 150
};
var DEFAULT_ZOOMING_RATIO = 1.5; // TODO: animate options = number polymorphism?
// TODO: pan, zoom, unzoom, reset, rotate, zoomTo
// TODO: add width / height to camera and add #.resize
// TODO: bind camera to renderer rather than sigma
// TODO: add #.graphToDisplay, #.displayToGraph, batch methods later
/**
* Camera class
*
* @constructor
*/
var Camera =
/*#__PURE__*/
function (_EventEmitter) {
_inherits(Camera, _EventEmitter);
function Camera() {
var _this;
_classCallCheck(this, Camera);
_this = _possibleConstructorReturn(this, _getPrototypeOf(Camera).call(this)); // Properties
_this.x = 0.5;
_this.y = 0.5;
_this.angle = 0;
_this.ratio = 1; // State
_this.nextFrame = null;
_this.previousState = _this.getState();
_this.enabled = true;
return _this;
}
/**
* Method used to enable the camera.
*
* @return {Camera}
*/
_createClass(Camera, [{
key: "enable",
value: function enable() {
this.enabled = true;
return this;
}
/**
* Method used to disable the camera.
*
* @return {Camera}
*/
}, {
key: "disable",
value: function disable() {
this.enabled = false;
return this;
}
/**
* Method used to retrieve the camera's current state.
*
* @return {object}
*/
}, {
key: "getState",
value: function getState() {
return {
x: this.x,
y: this.y,
angle: this.angle,
ratio: this.ratio
};
}
/**
* Method used to retrieve the camera's previous state.
*
* @return {object}
*/
}, {
key: "getPreviousState",
value: function getPreviousState() {
var state = this.previousState;
return {
x: state.x,
y: state.y,
angle: state.angle,
ratio: state.ratio
};
}
/**
* Method used to check whether the camera is currently being animated.
*
* @return {boolean}
*/
}, {
key: "isAnimated",
value: function isAnimated() {
return !!this.nextFrame;
}
/**
* Method returning the coordinates of a point from the graph frame to the
* viewport.
*
* @param {object} dimensions - Dimensions of the viewport.
* @param {number} x - The X coordinate.
* @param {number} y - The Y coordinate.
* @return {object} - The point coordinates in the viewport.
*/
// TODO: assign to gain one object
// TODO: angles
}, {
key: "graphToViewport",
value: function graphToViewport(dimensions, x, y) {
var smallestDimension = Math.min(dimensions.width, dimensions.height);
var dx = smallestDimension / dimensions.width;
var dy = smallestDimension / dimensions.height; // TODO: we keep on the upper left corner!
// TODO: how to normalize sizes?
return {
x: (x - this.x + this.ratio / 2 / dx) * (smallestDimension / this.ratio),
y: (this.y - y + this.ratio / 2 / dy) * (smallestDimension / this.ratio)
};
}
/**
* Method returning the coordinates of a point from the viewport frame to the
* graph frame.
*
* @param {object} dimensions - Dimensions of the viewport.
* @param {number} x - The X coordinate.
* @param {number} y - The Y coordinate.
* @return {object} - The point coordinates in the graph frame.
*/
// TODO: angles
}, {
key: "viewportToGraph",
value: function viewportToGraph(dimensions, x, y) {
var smallestDimension = Math.min(dimensions.width, dimensions.height);
var dx = smallestDimension / dimensions.width;
var dy = smallestDimension / dimensions.height;
return {
x: this.ratio / smallestDimension * x + this.x - this.ratio / 2 / dx,
y: -(this.ratio / smallestDimension * y - this.y - this.ratio / 2 / dy)
};
}
/**
* Method returning the abstract rectangle containing the graph according
* to the camera's state.
*
* @return {object} - The view's rectangle.
*/
// TODO: angle
}, {
key: "viewRectangle",
value: function viewRectangle(dimensions) {
// TODO: reduce relative margin?
var marginX = 0; // (0 * dimensions.width) / 8;
var marginY = 0; // (0 * dimensions.height) / 8;
var p1 = this.viewportToGraph(dimensions, 0 - marginX, 0 - marginY);
var p2 = this.viewportToGraph(dimensions, dimensions.width + marginX, 0 - marginY);
var h = this.viewportToGraph(dimensions, 0, dimensions.height + marginY);
return {
x1: p1.x,
y1: p1.y,
x2: p2.x,
y2: p2.y,
height: p2.y - h.y
};
}
/**
* Method used to set the camera's state.
*
* @param {object} state - New state.
* @return {Camera}
*/
}, {
key: "setState",
value: function setState(state) {
if (!this.enabled) return this; // TODO: validations
// TODO: update by function
// Keeping track of last state
this.previousState = this.getState();
if ('x' in state) this.x = state.x;
if ('y' in state) this.y = state.y;
if ('angle' in state) this.angle = state.angle;
if ('ratio' in state) this.ratio = state.ratio; // Emitting
// TODO: don't emit if nothing changed?
this.emit('updated', this.getState());
return this;
}
/**
* Method used to animate the camera.
*
* @param {object} state - State to reach eventually.
* @param {object} options - Options:
* @param {number} duration - Duration of the animation.
* @param {function} callback - Callback
* @return {function} - Return a function to cancel the animation.
*/
}, {
key: "animate",
value: function animate(state, options, callback) {
var _this2 = this;
if (!this.enabled) return; // TODO: validation
options = assign({}, ANIMATE_DEFAULTS$1, options);
var easing = typeof options.easing === 'function' ? options.easing : easings[options.easing]; // Canceling previous animation if needed
if (this.nextFrame) cancelAnimationFrame(this.nextFrame); // State
var start = Date.now();
var initialState = this.getState(); // Function performing the animation
var fn = function fn() {
var t = (Date.now() - start) / options.duration; // The animation is over:
if (t >= 1) {
_this2.nextFrame = null;
_this2.setState(state);
if (typeof callback === 'function') callback();
return;
}
var coefficient = easing(t);
var newState = {};
if ('x' in state) newState.x = initialState.x + (state.x - initialState.x) * coefficient;
if ('y' in state) newState.y = initialState.y + (state.y - initialState.y) * coefficient;
if ('angle' in state) newState.angle = initialState.angle + (state.angle - initialState.angle) * coefficient;
if ('ratio' in state) newState.ratio = initialState.ratio + (state.ratio - initialState.ratio) * coefficient;
_this2.setState(newState);
_this2.nextFrame = requestAnimationFrame(fn);
};
if (this.nextFrame) {
cancelAnimationFrame(this.nextFrame);
this.nextFrame = requestAnimationFrame(fn);
} else {
fn();
}
}
/**
* Method used to zoom the camera.
*
* @param {number|object} factorOrOptions - Factor or options.
* @return {function}
*/
}, {
key: "animatedZoom",
value: function animatedZoom(factorOrOptions) {
if (!factorOrOptions) {
return this.animate({
ratio: this.ratio / DEFAULT_ZOOMING_RATIO
});
} else if (typeof factorOrOptions === 'number') return this.animate({
ratio: this.ratio / factorOrOptions
});else return this.animate({
ratio: this.ratio / (factorOrOptions.factor || DEFAULT_ZOOMING_RATIO)
}, factorOrOptions);
}
/**
* Method used to unzoom the camera.
*
* @param {number|object} factorOrOptions - Factor or options.
* @return {function}
*/
}, {
key: "animatedUnzoom",
value: function animatedUnzoom(factorOrOptions) {
if (!factorOrOptions) {
return this.animate({
ratio: this.ratio * DEFAULT_ZOOMING_RATIO
});
} else if (typeof factorOrOptions === 'number') return this.animate({
ratio: this.ratio * factorOrOptions
});else return this.animate({
ratio: this.ratio * (factorOrOptions.factor || DEFAULT_ZOOMING_RATIO)
}, factorOrOptions);
}
/**
* Method used to reset the camera.
*
* @param {object} options - Options.
* @return {function}
*/
}, {
key: "animatedReset",
value: function animatedReset(options) {
return this.animate({
x: 0.5,
y: 0.5,
ratio: 1,
angle: 0
}, options);
}
}]);
return Camera;
}(events.EventEmitter);
// sight.
// TODO: a square can be represented as topleft + width, saying for the quad blocks (reduce mem)
// TODO: jsdoc
// TODO: be sure we can handle cases overcoming boundaries (because of size) or use a maxed size
// TODO: filtering unwanted labels beforehand through the filter function
// NOTE: this is basically a MX-CIF Quadtree at this point
// NOTE: need to explore R-Trees for edges
// NOTE: need to explore 2d segment tree for edges
// NOTE: probably can do faster using spatial hashing
/**
* Constants.
*
* Note that since we are representing a static 4-ary tree, the indices of the
* quadrants are the following:
* - TOP_LEFT: 4i + b
* - TOP_RIGHT: 4i + 2b
* - BOTTOM_LEFT: 4i + 3b
* - BOTTOM_RIGHT: 4i + 4b
*/
var BLOCKS = 4;
var MAX_LEVEL = 5;
var X_OFFSET = 0;
var Y_OFFSET = 1;
var WIDTH_OFFSET = 2;
var HEIGHT_OFFSET = 3;
var TOP_LEFT = 1;
var TOP_RIGHT = 2;
var BOTTOM_LEFT = 3;
var BOTTOM_RIGHT = 4;
/**
* Geometry helpers.
*/
/**
* Function returning whether the given rectangle is axis-aligned.
*
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @return {boolean}
*/
function isAxisAligned(x1, y1, x2, y2) {
return x1 === x2 || y1 === y2;
}
function squareCollidesWithQuad(x1, y1, w, qx, qy, qw, qh) {
return x1 < qx + qw && x1 + w > qx && y1 < qy + qh && y1 + w > qy;
}
function rectangleCollidesWithQuad(x1, y1, w, h, qx, qy, qw, qh) {
return x1 < qx + qw && x1 + w > qx && y1 < qy + qh && y1 + h > qy;
}
function pointIsInQuad(x, y, qx, qy, qw, qh) {
var xmp = qx + qw / 2;
var ymp = qy + qh / 2;
var top = y < ymp;
var left = x < xmp;
return top ? left ? TOP_LEFT : TOP_RIGHT : left ? BOTTOM_LEFT : BOTTOM_RIGHT;
}
/**
* Helper functions that are not bound to the class so an external user
* cannot mess with them.
*/
function buildQuadrants(maxLevel, data) {
// [block, level]
var stack = [0, 0];
while (stack.length) {
var level = stack.pop();
var block = stack.pop();
var topLeftBlock = 4 * block + BLOCKS;
var topRightBlock = 4 * block + 2 * BLOCKS;
var bottomLeftBlock = 4 * block + 3 * BLOCKS;
var bottomRightBlock = 4 * block + 4 * BLOCKS;
var x = data[block + X_OFFSET];
var y = data[block + Y_OFFSET];
var width = data[block + WIDTH_OFFSET];
var height = data[block + HEIGHT_OFFSET];
var hw = width / 2;
var hh = height / 2;
data[topLeftBlock + X_OFFSET] = x;
data[topLeftBlock + Y_OFFSET] = y;
data[topLeftBlock + WIDTH_OFFSET] = hw;
data[topLeftBlock + HEIGHT_OFFSET] = hh;
data[topRightBlock + X_OFFSET] = x + hw;
data[topRightBlock + Y_OFFSET] = y;
data[topRightBlock + WIDTH_OFFSET] = hw;
data[topRightBlock + HEIGHT_OFFSET] = hh;
data[bottomLeftBlock + X_OFFSET] = x;
data[bottomLeftBlock + Y_OFFSET] = y + hh;
data[bottomLeftBlock + WIDTH_OFFSET] = hw;
data[bottomLeftBlock + HEIGHT_OFFSET] = hh;
data[bottomRightBlock + X_OFFSET] = x + hw;
data[bottomRightBlock + Y_OFFSET] = y + hh;
data[bottomRightBlock + WIDTH_OFFSET] = hw;
data[bottomRightBlock + HEIGHT_OFFSET] = hh;
if (level < maxLevel - 1) {
stack.push(bottomRightBlock, level + 1);
stack.push(bottomLeftBlock, level + 1);
stack.push(topRightBlock, level + 1);
stack.push(topLeftBlock, level + 1);
}
}
}
function insertNode(maxLevel, data, containers, key, x, y, size) {
var x1 = x - size;
var y1 = y - size;
var w = size * 2;
var level = 0;
var block = 0;
while (true) {
// If we reached max level
if (level >= maxLevel) {
containers[block] = containers[block] || [];
containers[block].push(key);
return;
}
var topLeftBlock = 4 * block + BLOCKS;
var topRightBlock = 4 * block + 2 * BLOCKS;
var bottomLeftBlock = 4 * block + 3 * BLOCKS;
var bottomRightBlock = 4 * block + 4 * BLOCKS;
var collidingWithTopLeft = squareCollidesWithQuad(x1, y1, w, data[topLeftBlock + X_OFFSET], data[topLeftBlock + Y_OFFSET], data[topLeftBlock + WIDTH_OFFSET], data[topLeftBlock + HEIGHT_OFFSET]);
var collidingWithTopRight = squareCollidesWithQuad(x1, y1, w, data[topRightBlock + X_OFFSET], data[topRightBlock + Y_OFFSET], data[topRightBlock + WIDTH_OFFSET], data[topRightBlock + HEIGHT_OFFSET]);
var collidingWithBottomLeft = squareCollidesWithQuad(x1, y1, w, data[bottomLeftBlock + X_OFFSET], data[bottomLeftBlock + Y_OFFSET], data[bottomLeftBlock + WIDTH_OFFSET], data[bottomLeftBlock + HEIGHT_OFFSET]);
var collidingWithBottomRight = squareCollidesWithQuad(x1, y1, w, data[bottomRightBlock + X_OFFSET], data[bottomRightBlock + Y_OFFSET], data[bottomRightBlock + WIDTH_OFFSET], data[bottomRightBlock + HEIGHT_OFFSET]);
var collisions = collidingWithTopLeft + collidingWithTopRight + collidingWithBottomLeft + collidingWithBottomRight; // If we don't have at least a collision, there is an issue
if (collisions === 0) throw new Error("sigma/quadtree.insertNode: no collision (level: ".concat(level, ", key: ").concat(key, ", x: ").concat(x, ", y: ").concat(y, ", size: ").concat(size, ").")); // If we have 3 collisions, we have a geometry problem obviously
if (collisions === 3) throw new Error("sigma/quadtree.insertNode: 3 impossible collisions (level: ".concat(level, ", key: ").concat(key, ", x: ").concat(x, ", y: ").concat(y, ", size: ").concat(size, ").")); // If we have more that one collision, we stop here and store the node
// in the relevant containers
if (collisions > 1) {
// NOTE: this is a nice way to optimize for hover, but not for frustum
// since it requires to uniq the collected nodes
// if (collisions < 4) {
// // If we intersect two quads, we place the node in those two
// if (collidingWithTopLeft) {
// containers[topLeftBlock] = containers[topLeftBlock] || [];
// containers[topLeftBlock].push(key);
// }
// if (collidingWithTopRight) {
// containers[topRightBlock] = containers[topRightBlock] || [];
// containers[topRightBlock].push(key);
// }
// if (collidingWithBottomLeft) {
// containers[bottomLeftBlock] = containers[bottomLeftBlock] || [];
// containers[bottomLeftBlock].push(key);
// }
// if (collidingWithBottomRight) {
// containers[bottomRightBlock] = containers[bottomRightBlock] || [];
// containers[bottomRightBlock].push(key);
// }
// }
// else {
// // Else we keep the node where it is to avoid more pointless computations
// containers[block] = containers[block] || [];
// containers[block].push(key);
// }
containers[block] = containers[block] || [];
containers[block].push(key);
return;
} else {
level++;
} // Else we recurse into the correct quads
if (collidingWithTopLeft) block = topLeftBlock;
if (collidingWithTopRight) block = topRightBlock;
if (collidingWithBottomLeft) block = bottomLeftBlock;
if (collidingWithBottomRight) block = bottomRightBlock;
}
}
function getNodesInAxisAlignedRectangleArea(maxLevel, data, containers, x1, y1, w, h) {
// [block, level]
var stack = [0, 0];
var collectedNodes = [];
var container;
while (stack.length) {
var level = stack.pop();
var block = stack.pop(); // Collecting nodes
container = containers[block];
if (container) extend(collectedNodes, container); // If we reached max level
if (level >= maxLevel) continue;
var topLeftBlock = 4 * block + BLOCKS;
var topRightBlock = 4 * block + 2 * BLOCKS;
var bottomLeftBlock = 4 * block + 3 * BLOCKS;
var bottomRightBlock = 4 * block + 4 * BLOCKS;
var collidingWithTopLeft = rectangleCollidesWithQuad(x1, y1, w, h, data[topLeftBlock + X_OFFSET], data[topLeftBlock + Y_OFFSET], data[topLeftBlock + WIDTH_OFFSET], data[topLeftBlock + HEIGHT_OFFSET]);
var collidingWithTopRight = rectangleCollidesWithQuad(x1, y1, w, h, data[topRightBlock + X_OFFSET], data[topRightBlock + Y_OFFSET], data[topRightBlock + WIDTH_OFFSET], data[topRightBlock + HEIGHT_OFFSET]);
var collidingWithBottomLeft = rectangleCollidesWithQuad(x1, y1, w, h, data[bottomLeftBlock + X_OFFSET], data[bottomLeftBlock + Y_OFFSET], data[bottomLeftBlock + WIDTH_OFFSET], data[bottomLeftBlock + HEIGHT_OFFSET]);
var collidingWithBottomRight = rectangleCollidesWithQuad(x1, y1, w, h, data[bottomRightBlock + X_OFFSET], data[bottomRightBlock + Y_OFFSET], data[bottomRightBlock + WIDTH_OFFSET], data[bottomRightBlock + HEIGHT_OFFSET]);
if (collidingWithTopLeft) stack.push(topLeftBlock, level + 1);
if (collidingWithTopRight) stack.push(topRightBlock, level + 1);
if (collidingWithBottomLeft) stack.push(bottomLeftBlock, level + 1);
if (collidingWithBottomRight) stack.push(bottomRightBlock, level + 1);
}
return collectedNodes;
}
/**
* QuadTree class.
*
* @constructor
* @param {object} boundaries - The graph boundaries.
*/
var QuadTree =
/*#__PURE__*/
function () {
function QuadTree(params) {
_classCallCheck(this, QuadTree);
params = params || {}; // Allocating the underlying byte array
var L = Math.pow(4, MAX_LEVEL);
this.data = new Float32Array(BLOCKS * ((4 * L - 1) / 3));
this.containers = {};
this.cache = null;
this.lastRectangle = null;
if (params.boundaries) this.resize(params.boundaries);else this.resize({
x: 0,
y: 0,
width: 1,
height: 1
});
if (typeof params.filter === 'function') this.nodeFilter = params.filter;
}
_createClass(QuadTree, [{
key: "add",
value: function add(key, x, y, size) {
insertNode(MAX_LEVEL, this.data, this.containers, key, x, y, size);
return this;
}
}, {
key: "resize",
value: function resize(boundaries) {
this.clear(); // Building the quadrants
this.data[X_OFFSET] = boundaries.x;
this.data[Y_OFFSET] = boundaries.y;
this.data[WIDTH_OFFSET] = boundaries.width;
this.data[HEIGHT_OFFSET] = boundaries.height;
buildQuadrants(MAX_LEVEL, this.data);
}
}, {
key: "clear",
value: function clear() {
this.containers = {};
return this;
}
}, {
key: "point",
value: function point(x, y) {
var nodes = [];
var block = 0;
var level = 0;
do {
if (this.containers[block]) nodes.push.apply(nodes, _toConsumableArray(this.containers[block]));
var quad = pointIsInQuad(x, y, this.data[block + X_OFFSET], this.data[block + Y_OFFSET], this.data[block + WIDTH_OFFSET], this.data[block + HEIGHT_OFFSET]);
block = 4 * block + quad * BLOCKS;
level++;
} while (level <= MAX_LEVEL);
return nodes;
}
}, {
key: "rectangle",
value: function rectangle(x1, y1, x2, y2, height) {
var lr = this.lastRectangle;
if (lr && x1 === lr.x1 && x2 === lr.x2 && y1 === lr.y1 && y2 === lr.y2 && height === lr.height) {
return this.cache;
}
this.lastRectangle = {
x1: x1,
y1: y1,
x2: x2,
y2: y2,
height: height
}; // Is the rectangle axis aligned?
if (!isAxisAligned(x1, y1, x2, y2)) throw new Error('sigma/quadtree.rectangle: shifted view is not yet implemented.');
var collectedNodes = getNodesInAxisAlignedRectangleArea(MAX_LEVEL, this.data, this.containers, x1, y1, Math.abs(x1 - x2) || Math.abs(y1 - y2), height);
this.cache = collectedNodes;
return this.cache;
}
}]);
return QuadTree;
}();
var Captor =
/*#__PURE__*/
function (_EventEmitter) {
_inherits(Captor, _EventEmitter);
function Captor(container, camera) {
var _this;
_classCallCheck(this, Captor);
_this = _possibleConstructorReturn(this, _getPrototypeOf(Captor).call(this)); // Properties
_this.container = container;
_this.camera = camera;
return _this;
}
return Captor;
}(events.EventEmitter);
/**
* Sigma.js Rendering Utils
* ===========================
*
* Helpers used by most renderers.
*/
/**
* Function used to create DOM elements easily.
*
* @param {string} tag - Tag name of the element to create.
* @param {object} attributes - Attributes map.
* @return {HTMLElement}
*/
function createElement(tag, attributes) {
var element = document.createElement(tag);
if (!attributes) return element;
for (var k in attributes) {
if (k === 'style') {
for (var s in attributes[k]) {
element.style[s] = attributes[k][s];
}
} else {
element.setAttribute(k, attributes[k]);
}
}
return element;
}
/**
* Function returning the browser's pixel ratio.
*
* @return {number}
*/
function getPixelRatio() {
var _window = window,
screen = _window.screen;
if (typeof screen.deviceXDPI !== 'undefined' && typeof screen.logicalXDPI !== 'undefined' && screen.deviceXDPI > screen.logicalXDPI) return screen.systemXDPI / screen.logicalXDPI;else if (typeof window.devicePixelRatio !== 'undefined') return window.devicePixelRatio;
return 1;
}
/**
* Factory returning a function normalizing the given node's position & size.
*
* @param {object} extent - Extent of the graph.
* @return {function}
*/
function createNormalizationFunction(extent) {
var _extent$x = _slicedToArray(extent.x, 2),
minX = _extent$x[0],
maxX = _extent$x[1],
_extent$y = _slicedToArray(extent.y, 2),
minY = _extent$y[0],
maxY = _extent$y[1];
var ratio = Math.max(maxX - minX, maxY - minY);
if (ratio === 0) ratio = 1;
var dX = (maxX + minX) / 2;
var dY = (maxY + minY) / 2;
var fn = function fn(data) {
return {
x: 0.5 + (data.x - dX) / ratio,
y: 0.5 + (data.y - dY) / ratio
};
}; // TODO: possibility to apply this in batch over array of indices
fn.applyTo = function (data) {
data.x = 0.5 + (data.x - dX) / ratio;
data.y = 0.5 + (data.y - dY) / ratio;
};
fn.inverse = function (data) {
return {
x: dX + ratio * (data.x - 0.5),
y: dY + ratio * (data.y - 0.5)
};
};
fn.ratio = ratio;
return fn;
}
/**
* Sigma.js Captor Utils
* ======================
*
* Miscellenous helper functions related to the captors.
*/
/**
* Extract the local X position from a mouse or touch event.
*
* @param {event} e - A mouse or touch event.
* @return {number} The local X value of the mouse.
*/
function getX(e) {
if (typeof e.offsetX !== 'undefined') return e.offsetX;
if (typeof e.layerX !== 'undefined') return e.layerX;
if (typeof e.clientX !== 'undefined') return e.clientX;
throw new Error('sigma/captors/utils.getX: could not extract x from event.');
}
/**
* Extract the local Y position from a mouse or touch event.
*
* @param {event} e - A mouse or touch event.
* @return {number} The local Y value of the mouse.
*/
function getY(e) {
if (typeof e.offsetY !== 'undefined') return e.offsetY;
if (typeof e.layerY !== 'undefined') return e.layerY;
if (typeof e.clientY !== 'undefined') return e.clientY;
throw new Error('sigma/captors/utils.getY: could not extract y from event.');
}
/**
* Extract the width from a mouse or touch event.
*
* @param {event} e - A mouse or touch event.
* @return {number} The width of the event's target.
*/
function getWidth(e) {
var w = !e.target.ownerSVGElement ? e.target.width : e.target.ownerSVGElement.width;
if (typeof w === 'number') return w;
if (w !== undefined && w.baseVal !== undefined) return w.baseVal.value;
throw new Error('sigma/captors/utils.getWidth: could not extract width from event.');
}
/**
* Extract the height from a mouse or touch event.
*
* @param {event} e - A mouse or touch event.
* @return {number} The height of the event's target.
*/
function getHeight(e) {
var w = !e.target.ownerSVGElement ? e.target.height : e.target.ownerSVGElement.height;
if (typeof w === 'number') return w;
if (w !== undefined && w.baseVal !== undefined) return w.baseVal.value;
throw new Error('sigma/captors/utils.getHeight: could not extract height from event.');
}
/**
* Extract the center from a mouse or touch event.
*
* @param {event} e - A mouse or touch event.
* @return {object} The center of the event's target.
*/
function getCenter(e) {
var ratio = e.target.namespaceURI.indexOf('svg') !== -1 ? 1 : getPixelRatio();
return {
x: getWidth(e) / (2 * ratio),
y: getHeight(e) / (2 * ratio)
};
}
/**
* Convert mouse coords to sigma coords.
*
* @param {event} e - A mouse or touch event.
* @param {number} [x] - The x coord to convert
* @param {number} [y] - The y coord to convert
*
* @return {object}
*/
function getMouseCoords(e) {
return {
x: getX(e),
y: getY(e),
clientX: e.clientX,
clientY: e.clientY,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
altKey: e.altKey,
shiftKey: e.shiftKey
};
}
/**
* Extract the wheel delta from a mouse or touch event.
*
* @param {event} e - A mouse or touch event.
* @return {number} The wheel delta of the mouse.
*/
function getWheelDelta(e) {
if (typeof e.wheelDelta !== 'undefined') return e.wheelDelta / 360;
if (typeof e.detail !== 'undefined') return e.detail / -9;
throw new Error('sigma/captors/utils.getDelta: could not extract delta from event.');
}
/**
* Constants.
*/
var DRAG_TIMEOUT = 200;
var MOUSE_INERTIA_DURATION = 200;
var MOUSE_INERTIA_RATIO = 3;
var MOUSE_ZOOM_DURATION = 200;
var ZOOMING_RATIO = 1.7;
var DOUBLE_CLICK_TIMEOUT = 300;
var DOUBLE_CLICK_ZOOMING_RATIO = 2.2;
var DOUBLE_CLICK_ZOOMING_DURATION = 200;
/**
* Mouse captor class.
*
* @constructor
*/
var MouseCaptor =
/*#__PURE__*/
function (_Captor) {
_inherits(MouseCaptor, _Captor);
function MouseCaptor(container, camera) {
var _this;
_classCallCheck(this, MouseCaptor);
_this = _possibleConstructorReturn(this, _getPrototypeOf(MouseCaptor).call(this, container, camera)); // Properties
_this.container = container;
_this.camera = camera; // State
_this.enabled = true;
_this.hasDragged = false;
_this.downStartTime = null;
_this.lastMouseX = null;
_this.lastMouseY = null;
_this.isMouseDown = false;
_this.isMoving = false;
_this.movingTimeout = null;
_this.startCameraState = null;
_this.lastCameraState = null;
_this.clicks = 0;
_this.doubleClickTimeout = null;
_this.wheelLock = false; // Binding methods
_this.handleClick = _this.handleClick.bind(_assertThisInitialized(_this));
_this.handleDown = _this.handleDown.bind(_assertThisInitialized(_this));
_this.handleUp = _this.handleUp.bind(_assertThisInitialized(_this));
_this.handleMove = _this.handleMove.bind(_assertThisInitialized(_this));
_this.handleWheel = _this.handleWheel.bind(_assertThisInitialized(_this));
_this.handleOut = _this.handleOut.bind(_assertThisInitialized(_this)); // Binding events
container.addEventListener('click', _this.handleClick, false);
container.addEventListener('mousedown', _this.handleDown, false);
container.addEventListener('mousemove', _this.handleMove, false);
container.addEventListener('DOMMouseScroll', _this.handleWheel, false);
container.addEventListener('mousewheel', _this.handleWheel, false);
container.addEventListener('mouseout', _this.handleOut, false);
document.addEventListener('mouseup', _this.handleUp, false);
return _this;
}
_createClass(MouseCaptor, [{
key: "kill",
value: function kill() {
var container = this.container;
container.removeEventListener('click', this.handleClick);
container.removeEventListener('mousedown', this.handleDown);
container.removeEventListener('mousemove', this.handleMove);
container.removeEventListener('DOMMouseScroll', this.handleWheel);
container.removeEventListener('mousewheel', this.handleWheel);
container.removeEventListener('mouseout', this.handleOut);
document.removeEventListener('mouseup', this.handleUp);
}
}, {
key: "handleClick",
value: function handleClick(e) {
var _this2 = this;
if (!this.enabled) return;
this.clicks++;
if (this.clicks === 2) {
this.clicks = 0;
clearTimeout(this.doubleClickTimeout);
this.doubleClickTimeout = null;
return this.handleDoubleClick(e);
}
setTimeout(function () {
_this2.clicks = 0;
_this2.doubleClickTimeout = null;
}, DOUBLE_CLICK_TIMEOUT); // NOTE: this is here to prevent click events on drag
if (!this.hasDragged) this.emit('click', getMouseCoords(e));
}
}, {
key: "handleDoubleClick",
value: function handleDoubleClick(e) {
if (!this.enabled) return;
var center = getCenter(e);
var cameraState = this.camera.getState();
var newRatio = cameraState.ratio / DOUBLE_CLICK_ZOOMING_RATIO; // TODO: factorize
var dimensions = {
width: this.container.offsetWidth,
height: this.container.offsetHeight
};
var clickX = getX(e);
var clickY = getY(e); // TODO: baaaad we mustn't mutate the camera, create a Camera.from or #.copy
// TODO: factorize pan & zoomTo
var cameraWithNewRatio = new Camera();
cameraWithNewRatio.ratio = newRatio;
cameraWithNewRatio.x = cameraState.x;
cameraWithNewRatio.y = cameraState.y;
var clickGraph = this.camera.viewportToGraph(dimensions, clickX, clickY);
var centerGraph = this.camera.viewportToGraph(dimensions, center.x, center.y);
var clickGraphNew = cameraWithNewRatio.viewportToGraph(dimensions, clickX, clickY);
var centerGraphNew = cameraWithNewRatio.viewportToGraph(dimensions, center.x, center.y);
var deltaX = clickGraphNew.x - centerGraphNew.x - clickGraph.x + centerGraph.x;
var deltaY = clickGraphNew.y - centerGraphNew.y - clickGraph.y + centerGraph.y;
this.camera.animate({
x: cameraState.x - deltaX,
y: cameraState.y - deltaY,
ratio: newRatio
}, {
easing: 'quadraticInOut',
duration: DOUBLE_CLICK_ZOOMING_DURATION
});
if (e.preventDefault) e.preventDefault();else e.returnValue = false;
e.stopPropagation();
return false;
}
}, {
key: "handleDown",
value: function handleDown(e) {
if (!this.enabled) return;
this.startCameraState = this.camera.getState();
this.lastCameraState = this.startCameraState;
this.lastMouseX = getX(e);
this.lastMouseY = getY(e);
this.hasDragged = false;
this.downStartTime = Date.now(); // TODO: dispatch events
switch (e.which) {
default:
// Left button pressed
this.isMouseDown = true;
this.emit('mousedown', getMouseCoords(e));
}
}
}, {
key: "handleUp",
value: function handleUp(e) {
var _this3 = this;
if (!this.enabled || !this.isMouseDown) return;
this.isMouseDown = false;
if (this.movingTimeout) {
this.movingTimeout = null;
clearTimeout(this.movingTimeout);
}
var x = getX(e);
var y = getY(e);
var cameraState = this.camera.getState();
var previousCameraState = this.camera.getPreviousState();
if (this.isMoving) {
this.camera.animate({
x: cameraState.x + MOUSE_INERTIA_RATIO * (cameraState.x - previousCameraState.x),
y: cameraState.y + MOUSE_INERTIA_RATIO * (cameraState.y - previousCameraState.y)
}, {
duration: MOUSE_INERTIA_DURATION,
easing: 'quadraticOut'
});
} else if (this.lastMouseX !== x || this.lastMouseY !== y) {
this.camera.setState({
x: cameraState.x,
y: cameraState.y
});
}
this.isMoving = false;
setImmediate(function () {
return _this3.hasDragged = false;
});
this.emit('mouseup', getMouseCoords(e));
}
}, {
key: "handleMove",
value: function handleMove(e) {
var _this4 = this;
if (!this.enabled) return;
this.emit('mousemove', getMouseCoords(e));
if (this.isMouseDown) {
// TODO: dispatch events
this.isMoving = true;
this.hasDragged = true;
if (this.movingTimeout) clearTimeout(this.movingTimeout);
this.movingTimeout = setTimeout(function () {
_this4.movingTimeout = null;
_this4.isMoving = false;
}, DRAG_TIMEOUT);
var dimensions = {
width: this.container.offsetWidth,
height: this.container.offsetHeight
};
var eX = getX(e);
var eY = getY(e);
var lastMouse = this.camera.viewportToGraph(dimensions, this.lastMouseX, this.lastMouseY);
var mouse = this.camera.viewportToGraph(dimensions, eX, eY);
var offsetX = lastMouse.x - mouse.x;
var offsetY = lastMouse.y - mouse.y;
var cameraState = this.camera.getState();
var x = cameraState.x + offsetX;
var y = cameraState.y + offsetY;
this.camera.setState({
x: x,
y: y
});
this.lastMouseX = eX;
this.lastMouseY = eY;
}
if (e.preventDefault) e.preventDefault();else e.returnValue = false;
e.stopPropagation();
return false;
}
}, {
key: "handleWheel",
value: function handleWheel(e) {
var _this5 = this;
if (e.preventDefault) e.preventDefault();else e.returnValue = false;
e.stopPropagation();
if (!this.enabled) return false;
var delta = getWheelDelta(e);
if (!delta) return false;
if (this.wheelLock) return false;
this.wheelLock = true; // TODO: handle max zoom
var ratio = delta > 0 ? 1 / ZOOMING_RATIO : ZOOMING_RATIO;
var cameraState = this.camera.getState();
var newRatio = ratio * cameraState.ratio;
var center = getCenter(e);
var dimensions = {
width: this.container.offsetWidth,
height: this.container.offsetHeight
};
var clickX = getX(e);
var clickY = getY(e); // TODO: baaaad we mustn't mutate the camera, create a Camera.from or #.copy
// TODO: factorize pan & zoomTo
var cameraWithNewRatio = new Camera();
cameraWithNewRatio.ratio = newRatio;
cameraWithNewRatio.x = cameraState.x;
cameraWithNewRatio.y = cameraState.y;
var clickGraph = this.camera.viewportToGraph(dimensions, clickX, clickY);
var centerGraph = this.camera.viewportToGraph(dimensions, center.x, center.y);
var clickGraphNew = cameraWithNewRatio.viewportToGraph(dimensions, clickX, clickY);
var centerGraphNew = cameraWithNewRatio.viewportToGraph(dimensions, center.x, center.y);
var deltaX = clickGraphNew.x - centerGraphNew.x - clickGraph.x + centerGraph.x;
var deltaY = clickGraphNew.y - centerGraphNew.y - clickGraph.y + centerGraph.y;
this.camera.animate({
x: cameraState.x - deltaX,
y: cameraState.y - deltaY,
ratio: newRatio
}, {
easing: 'linear',
duration: MOUSE_ZOOM_DURATION
}, function () {
return _this5.wheelLock = false;
});
return false;
}
}, {
key: "handleOut",
value: function handleOut() {// TODO: dispatch event
}
}]);
return MouseCaptor;
}(Captor);
/**
* Sigma.js Display Data Classes
* ==============================
*
* Classes representing nodes & edges display data aiming at facilitating
* the engine's memory representation and keep them in a pool to avoid
* requiring to allocate memory too often.
*
* NOTE: it's possible to optimize this further by maintaining display data
* in byte arrays but this would prove more tedious for the rendering logic
* afterwards.
*/
var NodeDisplayData =
/*#__PURE__*/
function () {
function NodeDisplayData(index, settings) {
_classCallCheck(this, NodeDisplayData);
this.index = index;
this.x = 0;
this.y = 0;
this.size = 2;
this.color = settings.defaultNodeColor;
this.hidden = false;
this.label = '';
}
_createClass(NodeDisplayData, [{
key: "assign",
value: function assign(data) {
if ('x' in data) this.x = data.x;
if ('y' in data) this.y = data.y;
if ('size' in data) this.size = data.size;
if ('color' in data) this.color = data.color;
if ('hidden' in data) this.hidden = data.hidden;
if ('label' in data) this.label = data.label;
}
}]);
return NodeDisplayData;
}();
var EdgeDisplayData =
/*#__PURE__*/
function () {
function EdgeDisplayData(index, settings) {
_classCallCheck(this, EdgeDisplayData);
this.index = index;
this.size = 1;
this.color = settings.defaultEdgeColor;
this.hidden = false;
}
_createClass(EdgeDisplayData, [{
key: "assign",
value: function assign(data) {
if ('size' in data) this.size = data.size;
if ('color' in data) this.color = data.color;
if ('hidden' in data) this.hidden = data.hidden;
}
}]);
return EdgeDisplayData;
}();
/**
* Sigma.js Shader Utils
* ======================
*
* Code used to load sigma's shaders.
*/
/**
* Function used to load a shader.
*/
function loadShader(type, gl, source) {
var glType = type === 'VERTEX' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER; // Creating the shader
var shader = gl.createShader(glType); // Loading source
gl.shaderSource(shader, source); // Compiling the shader
gl.compileShader(shader); // Retrieving compilation status
var successfullyCompiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); // Throwing if something went awry
if (!successfullyCompiled) {
var infoLog = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error("sigma/renderers/webgl/shaders/utils.loadShader: error while compiling the shader:\n".concat(infoLog, "\n").concat(source));
}
return shader;
}
var loadVertexShader = loadShader.bind(null, 'VERTEX');
var loadFragmentShader = loadShader.bind(null, 'FRAGMENT');
/**
* Function used to load a program.
*/
function loadProgram(gl, shaders) {
var program = gl.createProgram();
var i;
var l; // Attaching the shaders
for (i = 0, l = shaders.length; i < l; i++) {
gl.attachShader(program, shaders[i]);
}
gl.linkProgram(program); // Checking status
var successfullyLinked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!successfullyLinked) {
gl.deleteProgram(program);
throw new Error('sigma/renderers/webgl/shaders/utils.loadProgram: error while linking the program.');
}
return program;
}
/**
* Program class.
*
* @constructor
*/
var Program =
/*#__PURE__*/
function () {
function Program(gl, vertexShaderSource, fragmentShaderSource) {
_classCallCheck(this, Program);
this.vertexShaderSource = vertexShaderSource;
this.fragmentShaderSource = fragmentShaderSource;
this.load(gl);
}
/**
* Method used to load the program into a webgl context.
*
* @param {WebGLContext} gl - The WebGL context.
* @return {WebGLProgram}
*/
_createClass(Program, [{
key: "load",
value: function load(gl) {
this.vertexShader = loadVertexShader(gl, this.vertexShaderSource);
this.fragmentShader = loadFragmentShader(gl, this.fragmentShaderSource);
this.program = loadProgram(gl, [this.vertexShader, this.fragmentShader]);
return this.program;
}
}]);
return Program;
}();
function createCompoundProgram(programClasses) {
return (
/*#__PURE__*/
function () {
function CompoundProgram(gl) {
_classCallCheck(this, CompoundProgram);
this.programs = programClasses.map(function (ProgramClass) {
return new ProgramClass(gl);
});
}
_createClass(CompoundProgram, [{
key: "allocate",
value: function allocate(capacity) {
this.programs.forEach(function (program) {
return program.allocate(capacity);
});
}
}, {
key: "process",
value: function process() {
// eslint-disable-next-line prefer-rest-params
var args = arguments;
this.programs.forEach(function (program) {
return program.process.apply(program, _toConsumable