iwmlib/pixi/lib/graphology-layout-forceatlas2.js
2019-03-21 09:57:27 +01:00

1239 lines
37 KiB
JavaScript

(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["forceAtlas2"] = factory();
else
root["forceAtlas2"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
/**
* Graphology ForceAtlas2 Layout
* ==============================
*
* Library endpoint.
*/
var isGraph = __webpack_require__(1),
iterate = __webpack_require__(2),
helpers = __webpack_require__(3);
var DEFAULT_SETTINGS = __webpack_require__(4);
/**
* Asbtract function used to run a certain number of iterations.
*
* @param {boolean} assign - Whether to assign positions.
* @param {Graph} graph - Target graph.
* @param {object|number} params - If number, params.iterations, else:
* @param {number} iterations - Number of iterations.
* @param {object} [settings] - Settings.
* @return {object|undefined}
*/
function abstractSynchronousLayout(assign, graph, params) {
if (!isGraph(graph))
throw new Error('graphology-layout-forceatlas2: the given graph is not a valid graphology instance.');
if (typeof params === 'number')
params = {iterations: params};
var iterations = params.iterations;
if (typeof iterations !== 'number')
throw new Error('graphology-layout-forceatlas2: invalid number of iterations.');
if (iterations <= 0)
throw new Error('graphology-layout-forceatlas2: you should provide a positive number of iterations.');
// Validating settings
var settings = helpers.assign({}, DEFAULT_SETTINGS, params.settings),
validationError = helpers.validateSettings(settings);
if (validationError)
throw new Error('graphology-layout-forceatlas2: ' + validationError.message);
// Building matrices
var matrices = helpers.graphToByteArrays(graph),
i;
// Iterating
for (i = 0; i < iterations; i++)
iterate(settings, matrices.nodes, matrices.edges);
// Applying
if (assign) {
helpers.applyLayoutChanges(graph, matrices.nodes);
return;
}
return helpers.collectLayoutChanges(graph, matrices.nodes);
}
/**
* Function returning sane layout settings for the given graph.
*
* @param {Graph} graph - Target graph.
* @return {object}
*/
function inferSettings(graph) {
var order = graph.order;
return {
barnesHutOptimize: order > 2000,
strongGravityMode: true,
gravity: 0.05,
scalingRatio: 10,
slowDown: 1 + Math.log(order)
};
}
/**
* Exporting.
*/
var synchronousLayout = abstractSynchronousLayout.bind(null, false);
synchronousLayout.assign = abstractSynchronousLayout.bind(null, true);
synchronousLayout.inferSettings = inferSettings;
module.exports = synchronousLayout;
/***/ }),
/* 1 */
/***/ (function(module, exports) {
/**
* Graphology isGraph
* ===================
*
* Very simple function aiming at ensuring the given variable is a
* graphology instance.
*/
/**
* Checking the value is a graphology instance.
*
* @param {any} value - Target value.
* @return {boolean}
*/
module.exports = function isGraph(value) {
return (
value !== null &&
typeof value === 'object' &&
typeof value.addUndirectedEdgeWithKey === 'function' &&
typeof value.dropNode === 'function' &&
typeof value.multi === 'boolean'
);
};
/***/ }),
/* 2 */
/***/ (function(module, exports) {
/* eslint no-constant-condition: 0 */
/**
* Graphology ForceAtlas2 Iteration
* =================================
*
* Function used to perform a single iteration of the algorithm.
*/
/**
* Matrices properties accessors.
*/
var NODE_X = 0,
NODE_Y = 1,
NODE_DX = 2,
NODE_DY = 3,
NODE_OLD_DX = 4,
NODE_OLD_DY = 5,
NODE_MASS = 6,
NODE_CONVERGENCE = 7,
NODE_SIZE = 8,
NODE_FIXED = 9;
var EDGE_SOURCE = 0,
EDGE_TARGET = 1,
EDGE_WEIGHT = 2;
var REGION_NODE = 0,
REGION_CENTER_X = 1,
REGION_CENTER_Y = 2,
REGION_SIZE = 3,
REGION_NEXT_SIBLING = 4,
REGION_FIRST_CHILD = 5,
REGION_MASS = 6,
REGION_MASS_CENTER_X = 7,
REGION_MASS_CENTER_Y = 8;
var SUBDIVISION_ATTEMPTS = 3;
/**
* Constants.
*/
var PPN = 10,
PPE = 3,
PPR = 9;
var MAX_FORCE = 10;
/**
* Function used to perform a single interation of the algorithm.
*
* @param {object} options - Layout options.
* @param {Float32Array} NodeMatrix - Node data.
* @param {Float32Array} EdgeMatrix - Edge data.
* @return {object} - Some metadata.
*/
module.exports = function iterate(options, NodeMatrix, EdgeMatrix) {
// Initializing variables
var l, r, n, n1, n2, e, w, g;
var order = NodeMatrix.length,
size = EdgeMatrix.length;
var outboundAttCompensation,
coefficient,
xDist,
yDist,
ewc,
distance,
factor;
var RegionMatrix = [];
// 1) Initializing layout data
//-----------------------------
// Resetting positions & computing max values
for (n = 0; n < order; n += PPN) {
NodeMatrix[n + NODE_OLD_DX] = NodeMatrix[n + NODE_DX];
NodeMatrix[n + NODE_OLD_DY] = NodeMatrix[n + NODE_DY];
NodeMatrix[n + NODE_DX] = 0;
NodeMatrix[n + NODE_DY] = 0;
}
// If outbound attraction distribution, compensate
if (options.outboundAttractionDistribution) {
outboundAttCompensation = 0;
for (n = 0; n < order; n += PPN) {
outboundAttCompensation += NodeMatrix[n + NODE_MASS];
}
outboundAttCompensation /= order;
}
// 1.bis) Barnes-Hut computation
//------------------------------
if (options.barnesHutOptimize) {
// Setting up
var minX = Infinity,
maxX = -Infinity,
minY = Infinity,
maxY = -Infinity,
q, q2, subdivisionAttempts;
// Computing min and max values
for (n = 0; n < order; n += PPN) {
minX = Math.min(minX, NodeMatrix[n + NODE_X]);
maxX = Math.max(maxX, NodeMatrix[n + NODE_X]);
minY = Math.min(minY, NodeMatrix[n + NODE_Y]);
maxY = Math.max(maxY, NodeMatrix[n + NODE_Y]);
}
// squarify bounds, it's a quadtree
var dx = maxX - minX, dy = maxY - minY;
if (dx > dy) {
minY -= (dx - dy) / 2;
maxY = minY + dx;
}
else {
minX -= (dy - dx) / 2;
maxX = minX + dy;
}
// Build the Barnes Hut root region
RegionMatrix[0 + REGION_NODE] = -1;
RegionMatrix[0 + REGION_CENTER_X] = (minX + maxX) / 2;
RegionMatrix[0 + REGION_CENTER_Y] = (minY + maxY) / 2;
RegionMatrix[0 + REGION_SIZE] = Math.max(maxX - minX, maxY - minY);
RegionMatrix[0 + REGION_NEXT_SIBLING] = -1;
RegionMatrix[0 + REGION_FIRST_CHILD] = -1;
RegionMatrix[0 + REGION_MASS] = 0;
RegionMatrix[0 + REGION_MASS_CENTER_X] = 0;
RegionMatrix[0 + REGION_MASS_CENTER_Y] = 0;
// Add each node in the tree
l = 1;
for (n = 0; n < order; n += PPN) {
// Current region, starting with root
r = 0;
subdivisionAttempts = SUBDIVISION_ATTEMPTS;
while (true) {
// Are there sub-regions?
// We look at first child index
if (RegionMatrix[r + REGION_FIRST_CHILD] >= 0) {
// There are sub-regions
// We just iterate to find a "leaf" of the tree
// that is an empty region or a region with a single node
// (see next case)
// Find the quadrant of n
if (NodeMatrix[n + NODE_X] < RegionMatrix[r + REGION_CENTER_X]) {
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Left quarter
q = RegionMatrix[r + REGION_FIRST_CHILD];
}
else {
// Bottom Left quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR;
}
}
else {
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Right quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 2;
}
else {
// Bottom Right quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 3;
}
}
// Update center of mass and mass (we only do it for non-leave regions)
RegionMatrix[r + REGION_MASS_CENTER_X] =
(RegionMatrix[r + REGION_MASS_CENTER_X] * RegionMatrix[r + REGION_MASS] +
NodeMatrix[n + NODE_X] * NodeMatrix[n + NODE_MASS]) /
(RegionMatrix[r + REGION_MASS] + NodeMatrix[n + NODE_MASS]);
RegionMatrix[r + REGION_MASS_CENTER_Y] =
(RegionMatrix[r + REGION_MASS_CENTER_Y] * RegionMatrix[r + REGION_MASS] +
NodeMatrix[n + NODE_Y] * NodeMatrix[n + NODE_MASS]) /
(RegionMatrix[r + REGION_MASS] + NodeMatrix[n + NODE_MASS]);
RegionMatrix[r + REGION_MASS] += NodeMatrix[n + NODE_MASS];
// Iterate on the right quadrant
r = q;
continue;
}
else {
// There are no sub-regions: we are in a "leaf"
// Is there a node in this leave?
if (RegionMatrix[r + REGION_NODE] < 0) {
// There is no node in region:
// we record node n and go on
RegionMatrix[r + REGION_NODE] = n;
break;
}
else {
// There is a node in this region
// We will need to create sub-regions, stick the two
// nodes (the old one r[0] and the new one n) in two
// subregions. If they fall in the same quadrant,
// we will iterate.
// Create sub-regions
RegionMatrix[r + REGION_FIRST_CHILD] = l * PPR;
w = RegionMatrix[r + REGION_SIZE] / 2; // new size (half)
// NOTE: we use screen coordinates
// from Top Left to Bottom Right
// Top Left sub-region
g = RegionMatrix[r + REGION_FIRST_CHILD];
RegionMatrix[g + REGION_NODE] = -1;
RegionMatrix[g + REGION_CENTER_X] = RegionMatrix[r + REGION_CENTER_X] - w;
RegionMatrix[g + REGION_CENTER_Y] = RegionMatrix[r + REGION_CENTER_Y] - w;
RegionMatrix[g + REGION_SIZE] = w;
RegionMatrix[g + REGION_NEXT_SIBLING] = g + PPR;
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
RegionMatrix[g + REGION_MASS] = 0;
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
// Bottom Left sub-region
g += PPR;
RegionMatrix[g + REGION_NODE] = -1;
RegionMatrix[g + REGION_CENTER_X] = RegionMatrix[r + REGION_CENTER_X] - w;
RegionMatrix[g + REGION_CENTER_Y] = RegionMatrix[r + REGION_CENTER_Y] + w;
RegionMatrix[g + REGION_SIZE] = w;
RegionMatrix[g + REGION_NEXT_SIBLING] = g + PPR;
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
RegionMatrix[g + REGION_MASS] = 0;
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
// Top Right sub-region
g += PPR;
RegionMatrix[g + REGION_NODE] = -1;
RegionMatrix[g + REGION_CENTER_X] = RegionMatrix[r + REGION_CENTER_X] + w;
RegionMatrix[g + REGION_CENTER_Y] = RegionMatrix[r + REGION_CENTER_Y] - w;
RegionMatrix[g + REGION_SIZE] = w;
RegionMatrix[g + REGION_NEXT_SIBLING] = g + PPR;
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
RegionMatrix[g + REGION_MASS] = 0;
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
// Bottom Right sub-region
g += PPR;
RegionMatrix[g + REGION_NODE] = -1;
RegionMatrix[g + REGION_CENTER_X] = RegionMatrix[r + REGION_CENTER_X] + w;
RegionMatrix[g + REGION_CENTER_Y] = RegionMatrix[r + REGION_CENTER_Y] + w;
RegionMatrix[g + REGION_SIZE] = w;
RegionMatrix[g + REGION_NEXT_SIBLING] = RegionMatrix[r + REGION_NEXT_SIBLING];
RegionMatrix[g + REGION_FIRST_CHILD] = -1;
RegionMatrix[g + REGION_MASS] = 0;
RegionMatrix[g + REGION_MASS_CENTER_X] = 0;
RegionMatrix[g + REGION_MASS_CENTER_Y] = 0;
l += 4;
// Now the goal is to find two different sub-regions
// for the two nodes: the one previously recorded (r[0])
// and the one we want to add (n)
// Find the quadrant of the old node
if (NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_X] < RegionMatrix[r + REGION_CENTER_X]) {
if (NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Left quarter
q = RegionMatrix[r + REGION_FIRST_CHILD];
}
else {
// Bottom Left quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR;
}
}
else {
if (NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Right quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 2;
}
else {
// Bottom Right quarter
q = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 3;
}
}
// We remove r[0] from the region r, add its mass to r and record it in q
RegionMatrix[r + REGION_MASS] = NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_MASS];
RegionMatrix[r + REGION_MASS_CENTER_X] = NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_X];
RegionMatrix[r + REGION_MASS_CENTER_Y] = NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y];
RegionMatrix[q + REGION_NODE] = RegionMatrix[r + REGION_NODE];
RegionMatrix[r + REGION_NODE] = -1;
// Find the quadrant of n
if (NodeMatrix[n + NODE_X] < RegionMatrix[r + REGION_CENTER_X]) {
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Left quarter
q2 = RegionMatrix[r + REGION_FIRST_CHILD];
}
else {
// Bottom Left quarter
q2 = RegionMatrix[r + REGION_FIRST_CHILD] + PPR;
}
}
else {
if (NodeMatrix[n + NODE_Y] < RegionMatrix[r + REGION_CENTER_Y]) {
// Top Right quarter
q2 = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 2;
}
else {
// Bottom Right quarter
q2 = RegionMatrix[r + REGION_FIRST_CHILD] + PPR * 3;
}
}
if (q === q2) {
// If both nodes are in the same quadrant,
// we have to try it again on this quadrant
if (subdivisionAttempts--) {
r = q;
continue; // while
}
else {
// we are out of precision here, and we cannot subdivide anymore
// but we have to break the loop anyway
subdivisionAttempts = SUBDIVISION_ATTEMPTS;
break; // while
}
}
// If both quadrants are different, we record n
// in its quadrant
RegionMatrix[q2 + REGION_NODE] = n;
break;
}
}
}
}
}
// 2) Repulsion
//--------------
// NOTES: adjustSizes = antiCollision & scalingRatio = coefficient
if (options.barnesHutOptimize) {
coefficient = options.scalingRatio;
// Applying repulsion through regions
for (n = 0; n < order; n += PPN) {
// Computing leaf quad nodes iteration
r = 0; // Starting with root region
while (true) {
if (RegionMatrix[r + REGION_FIRST_CHILD] >= 0) {
// The region has sub-regions
// We run the Barnes Hut test to see if we are at the right distance
distance = Math.sqrt(
(Math.pow(NodeMatrix[n + NODE_X] - RegionMatrix[r + REGION_MASS_CENTER_X], 2)) +
(Math.pow(NodeMatrix[n + NODE_Y] - RegionMatrix[r + REGION_MASS_CENTER_Y], 2))
);
if (2 * RegionMatrix[r + REGION_SIZE] / distance < options.barnesHutTheta) {
// We treat the region as a single body, and we repulse
xDist = NodeMatrix[n + NODE_X] - RegionMatrix[r + REGION_MASS_CENTER_X];
yDist = NodeMatrix[n + NODE_Y] - RegionMatrix[r + REGION_MASS_CENTER_Y];
if (options.adjustSizes) {
//-- Linear Anti-collision Repulsion
if (distance > 0) {
factor = coefficient * NodeMatrix[n + NODE_MASS] *
RegionMatrix[r + REGION_MASS] / distance / distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
else if (distance < 0) {
factor = -coefficient * NodeMatrix[n + NODE_MASS] *
RegionMatrix[r + REGION_MASS] / distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
}
else {
//-- Linear Repulsion
if (distance > 0) {
factor = coefficient * NodeMatrix[n + NODE_MASS] *
RegionMatrix[r + REGION_MASS] / distance / distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
}
// When this is done, we iterate. We have to look at the next sibling.
if (RegionMatrix[r + REGION_NEXT_SIBLING] < 0)
break; // No next sibling: we have finished the tree
r = RegionMatrix[r + REGION_NEXT_SIBLING];
continue;
}
else {
// The region is too close and we have to look at sub-regions
r = RegionMatrix[r + REGION_FIRST_CHILD];
continue;
}
}
else {
// The region has no sub-region
// If there is a node r[0] and it is not n, then repulse
if (RegionMatrix[r + REGION_NODE] >= 0 && RegionMatrix[r + REGION_NODE] !== n) {
xDist = NodeMatrix[n + NODE_X] - NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_X];
yDist = NodeMatrix[n + NODE_Y] - NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_Y];
distance = Math.sqrt(xDist * xDist + yDist * yDist);
if (options.adjustSizes) {
//-- Linear Anti-collision Repulsion
if (distance > 0) {
factor = coefficient * NodeMatrix[n + NODE_MASS] *
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_MASS] / distance / distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
else if (distance < 0) {
factor = -coefficient * NodeMatrix[n + NODE_MASS] *
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_MASS] / distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
}
else {
//-- Linear Repulsion
if (distance > 0) {
factor = coefficient * NodeMatrix[n + NODE_MASS] *
NodeMatrix[RegionMatrix[r + REGION_NODE] + NODE_MASS] / distance / distance;
NodeMatrix[n + NODE_DX] += xDist * factor;
NodeMatrix[n + NODE_DY] += yDist * factor;
}
}
}
// When this is done, we iterate. We have to look at the next sibling.
if (RegionMatrix[r + REGION_NEXT_SIBLING] < 0)
break; // No next sibling: we have finished the tree
r = RegionMatrix[r + REGION_NEXT_SIBLING];
continue;
}
}
}
}
else {
coefficient = options.scalingRatio;
// Square iteration
for (n1 = 0; n1 < order; n1 += PPN) {
for (n2 = 0; n2 < n1; n2 += PPN) {
// Common to both methods
xDist = NodeMatrix[n1 + NODE_X] - NodeMatrix[n2 + NODE_X];
yDist = NodeMatrix[n1 + NODE_Y] - NodeMatrix[n2 + NODE_Y];
if (options.adjustSizes) {
//-- Anticollision Linear Repulsion
distance = Math.sqrt(xDist * xDist + yDist * yDist) -
NodeMatrix[n1 + NODE_SIZE] -
NodeMatrix[n2 + NODE_SIZE];
if (distance > 0) {
factor = coefficient *
NodeMatrix[n1 + NODE_MASS] *
NodeMatrix[n2 + NODE_MASS] /
distance / distance;
// Updating nodes' dx and dy
NodeMatrix[n1 + NODE_DX] += xDist * factor;
NodeMatrix[n1 + NODE_DY] += yDist * factor;
NodeMatrix[n2 + NODE_DX] += xDist * factor;
NodeMatrix[n2 + NODE_DY] += yDist * factor;
}
else if (distance < 0) {
factor = 100 * coefficient *
NodeMatrix[n1 + NODE_MASS] *
NodeMatrix[n2 + NODE_MASS];
// Updating nodes' dx and dy
NodeMatrix[n1 + NODE_DX] += xDist * factor;
NodeMatrix[n1 + NODE_DY] += yDist * factor;
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
}
}
else {
//-- Linear Repulsion
distance = Math.sqrt(xDist * xDist + yDist * yDist);
if (distance > 0) {
factor = coefficient *
NodeMatrix[n1 + NODE_MASS] *
NodeMatrix[n2 + NODE_MASS] /
distance / distance;
// Updating nodes' dx and dy
NodeMatrix[n1 + NODE_DX] += xDist * factor;
NodeMatrix[n1 + NODE_DY] += yDist * factor;
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
}
}
}
}
}
// 3) Gravity
//------------
g = options.gravity / options.scalingRatio;
coefficient = options.scalingRatio;
for (n = 0; n < order; n += PPN) {
factor = 0;
// Common to both methods
xDist = NodeMatrix[n + NODE_X];
yDist = NodeMatrix[n + NODE_Y];
distance = Math.sqrt(
Math.pow(xDist, 2) + Math.pow(yDist, 2)
);
if (options.strongGravityMode) {
//-- Strong gravity
if (distance > 0)
factor = coefficient * NodeMatrix[n + NODE_MASS] * g;
}
else {
//-- Linear Anti-collision Repulsion n
if (distance > 0)
factor = coefficient * NodeMatrix[n + NODE_MASS] * g / distance;
}
// Updating node's dx and dy
NodeMatrix[n + NODE_DX] -= xDist * factor;
NodeMatrix[n + NODE_DY] -= yDist * factor;
}
// 4) Attraction
//---------------
coefficient = 1 *
(options.outboundAttractionDistribution ?
outboundAttCompensation :
1);
// TODO: simplify distance
// TODO: coefficient is always used as -c --> optimize?
for (e = 0; e < size; e += PPE) {
n1 = EdgeMatrix[e + EDGE_SOURCE];
n2 = EdgeMatrix[e + EDGE_TARGET];
w = EdgeMatrix[e + EDGE_WEIGHT];
// Edge weight influence
ewc = Math.pow(w, options.edgeWeightInfluence);
// Common measures
xDist = NodeMatrix[n1 + NODE_X] - NodeMatrix[n2 + NODE_X];
yDist = NodeMatrix[n1 + NODE_Y] - NodeMatrix[n2 + NODE_Y];
// Applying attraction to nodes
if (options.adjustSizes) {
distance = Math.sqrt(
(Math.pow(xDist, 2) + Math.pow(yDist, 2)) -
NodeMatrix[n1 + NODE_SIZE] -
NodeMatrix[n2 + NODE_SIZE]
);
if (options.linLogMode) {
if (options.outboundAttractionDistribution) {
//-- LinLog Degree Distributed Anti-collision Attraction
if (distance > 0) {
factor = -coefficient * ewc * Math.log(1 + distance) /
distance /
NodeMatrix[n1 + NODE_MASS];
}
}
else {
//-- LinLog Anti-collision Attraction
if (distance > 0) {
factor = -coefficient * ewc * Math.log(1 + distance) / distance;
}
}
}
else {
if (options.outboundAttractionDistribution) {
//-- Linear Degree Distributed Anti-collision Attraction
if (distance > 0) {
factor = -coefficient * ewc / NodeMatrix[n1 + NODE_MASS];
}
}
else {
//-- Linear Anti-collision Attraction
if (distance > 0) {
factor = -coefficient * ewc;
}
}
}
}
else {
distance = Math.sqrt(
Math.pow(xDist, 2) + Math.pow(yDist, 2)
);
if (options.linLogMode) {
if (options.outboundAttractionDistribution) {
//-- LinLog Degree Distributed Attraction
if (distance > 0) {
factor = -coefficient * ewc * Math.log(1 + distance) /
distance /
NodeMatrix[n1 + NODE_MASS];
}
}
else {
//-- LinLog Attraction
if (distance > 0)
factor = -coefficient * ewc * Math.log(1 + distance) / distance;
}
}
else {
if (options.outboundAttractionDistribution) {
//-- Linear Attraction Mass Distributed
// NOTE: Distance is set to 1 to override next condition
distance = 1;
factor = -coefficient * ewc / NodeMatrix[n1 + NODE_MASS];
}
else {
//-- Linear Attraction
// NOTE: Distance is set to 1 to override next condition
distance = 1;
factor = -coefficient * ewc;
}
}
}
// Updating nodes' dx and dy
// TODO: if condition or factor = 1?
if (distance > 0) {
// Updating nodes' dx and dy
NodeMatrix[n1 + NODE_DX] += xDist * factor;
NodeMatrix[n1 + NODE_DY] += yDist * factor;
NodeMatrix[n2 + NODE_DX] -= xDist * factor;
NodeMatrix[n2 + NODE_DY] -= yDist * factor;
}
}
// 5) Apply Forces
//-----------------
var force,
swinging,
traction,
nodespeed;
// MATH: sqrt and square distances
if (options.adjustSizes) {
for (n = 0; n < order; n += PPN) {
if (!NodeMatrix[n + NODE_FIXED]) {
force = Math.sqrt(
Math.pow(NodeMatrix[n + NODE_DX], 2) +
Math.pow(NodeMatrix[n + NODE_DY], 2)
);
if (force > MAX_FORCE) {
NodeMatrix[n + NODE_DX] =
NodeMatrix[n + NODE_DX] * MAX_FORCE / force;
NodeMatrix[n + NODE_DY] =
NodeMatrix[n + NODE_DY] * MAX_FORCE / force;
}
swinging = NodeMatrix[n + NODE_MASS] *
Math.sqrt(
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) *
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) +
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY]) *
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY])
);
traction = Math.sqrt(
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) *
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) +
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY]) *
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY])
) / 2;
nodespeed =
0.1 * Math.log(1 + traction) / (1 + Math.sqrt(swinging));
// Updating node's positon
NodeMatrix[n + NODE_X] =
NodeMatrix[n + NODE_X] + NodeMatrix[n + NODE_DX] *
(nodespeed / options.slowDown);
NodeMatrix[n + NODE_Y] =
NodeMatrix[n + NODE_Y] + NodeMatrix[n + NODE_DY] *
(nodespeed / options.slowDown);
}
}
}
else {
for (n = 0; n < order; n += PPN) {
if (!NodeMatrix[n + NODE_FIXED]) {
swinging = NodeMatrix[n + NODE_MASS] *
Math.sqrt(
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) *
(NodeMatrix[n + NODE_OLD_DX] - NodeMatrix[n + NODE_DX]) +
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY]) *
(NodeMatrix[n + NODE_OLD_DY] - NodeMatrix[n + NODE_DY])
);
traction = Math.sqrt(
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) *
(NodeMatrix[n + NODE_OLD_DX] + NodeMatrix[n + NODE_DX]) +
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY]) *
(NodeMatrix[n + NODE_OLD_DY] + NodeMatrix[n + NODE_DY])
) / 2;
nodespeed = NodeMatrix[n + NODE_CONVERGENCE] *
Math.log(1 + traction) / (1 + Math.sqrt(swinging));
// Updating node convergence
NodeMatrix[n + NODE_CONVERGENCE] =
Math.min(1, Math.sqrt(
nodespeed *
(Math.pow(NodeMatrix[n + NODE_DX], 2) +
Math.pow(NodeMatrix[n + NODE_DY], 2)) /
(1 + Math.sqrt(swinging))
));
// Updating node's positon
NodeMatrix[n + NODE_X] =
NodeMatrix[n + NODE_X] + NodeMatrix[n + NODE_DX] *
(nodespeed / options.slowDown);
NodeMatrix[n + NODE_Y] =
NodeMatrix[n + NODE_Y] + NodeMatrix[n + NODE_DY] *
(nodespeed / options.slowDown);
}
}
}
// We return the information about the layout (no need to return the matrices)
return {};
};
/***/ }),
/* 3 */
/***/ (function(module, exports) {
/**
* Graphology ForceAtlas2 Helpers
* ===============================
*
* Miscellaneous helper functions.
*/
/**
* Constants.
*/
var PPN = 10,
PPE = 3;
/**
* Very simple Object.assign-like function.
*
* @param {object} target - First object.
* @param {object} [...objects] - Objects to merge.
* @return {object}
*/
exports.assign = function(target) {
target = target || {};
var objects = Array.prototype.slice.call(arguments).slice(1),
i,
k,
l;
for (i = 0, l = objects.length; i < l; i++) {
if (!objects[i])
continue;
for (k in objects[i])
target[k] = objects[i][k];
}
return target;
};
/**
* Function used to validate the given settings.
*
* @param {object} settings - Settings to validate.
* @return {object|null}
*/
exports.validateSettings = function(settings) {
if ('linLogMode' in settings &&
typeof settings.linLogMode !== 'boolean')
return {message: 'the `linLogMode` setting should be a boolean.'};
if ('outboundAttractionDistribution' in settings &&
typeof settings.outboundAttractionDistribution !== 'boolean')
return {message: 'the `outboundAttractionDistribution` setting should be a boolean.'};
if ('adjustSizes' in settings &&
typeof settings.adjustSizes !== 'boolean')
return {message: 'the `adjustSizes` setting should be a boolean.'};
if ('edgeWeightInfluence' in settings &&
typeof settings.edgeWeightInfluence !== 'number' &&
settings.edgeWeightInfluence < 0)
return {message: 'the `edgeWeightInfluence` setting should be a number >= 0.'};
if ('scalingRatio' in settings &&
typeof settings.scalingRatio !== 'number' &&
settings.scalingRatio < 0)
return {message: 'the `scalingRatio` setting should be a number >= 0.'};
if ('strongGravityMode' in settings &&
typeof settings.strongGravityMode !== 'boolean')
return {message: 'the `strongGravityMode` setting should be a boolean.'};
if ('gravity' in settings &&
typeof settings.gravity !== 'number' &&
settings.gravity < 0)
return {message: 'the `gravity` setting should be a number >= 0.'};
if ('slowDown' in settings &&
typeof settings.slowDown !== 'number' &&
settings.slowDown < 0)
return {message: 'the `slowDown` setting should be a number >= 0.'};
if ('barnesHutOptimize' in settings &&
typeof settings.barnesHutOptimize !== 'boolean')
return {message: 'the `barnesHutOptimize` setting should be a boolean.'};
if ('barnesHutTheta' in settings &&
typeof settings.barnesHutTheta !== 'number' &&
settings.barnesHutTheta < 0)
return {message: 'the `barnesHutTheta` setting should be a number >= 0.'};
return null;
};
/**
* Function generating a flat matrix for both nodes & edges of the given graph.
*
* @param {Graph} graph - Target graph.
* @return {object} - Both matrices.
*/
exports.graphToByteArrays = function(graph) {
var nodes = graph.nodes(),
edges = graph.edges(),
order = nodes.length,
size = edges.length,
index = {},
i,
j;
var NodeMatrix = new Float32Array(order * PPN),
EdgeMatrix = new Float32Array(size * PPE);
// Iterate through nodes
for (i = j = 0; i < order; i++) {
// Node index
index[nodes[i]] = j;
// Populating byte array
NodeMatrix[j] = graph.getNodeAttribute(nodes[i], 'x');
NodeMatrix[j + 1] = graph.getNodeAttribute(nodes[i], 'y');
NodeMatrix[j + 2] = 0;
NodeMatrix[j + 3] = 0;
NodeMatrix[j + 4] = 0;
NodeMatrix[j + 5] = 0;
NodeMatrix[j + 6] = 1 + graph.degree(nodes[i]);
NodeMatrix[j + 7] = 1;
NodeMatrix[j + 8] = graph.getNodeAttribute(nodes[i], 'size') || 1;
NodeMatrix[j + 9] = 0;
j += PPN;
}
// Iterate through edges
for (i = j = 0; i < size; i++) {
// Populating byte array
EdgeMatrix[j] = index[graph.source(edges[i])];
EdgeMatrix[j + 1] = index[graph.target(edges[i])];
EdgeMatrix[j + 2] = graph.getEdgeAttribute(edges[i], 'weight') || 0;
j += PPE;
}
return {
nodes: NodeMatrix,
edges: EdgeMatrix
};
};
/**
* Function applying the layout back to the graph.
*
* @param {Graph} graph - Target graph.
* @param {Float32Array} NodeMatrix - Node matrix.
*/
exports.applyLayoutChanges = function(graph, NodeMatrix) {
var nodes = graph.nodes();
for (var i = 0, j = 0, l = NodeMatrix.length; i < l; i += PPN) {
graph.setNodeAttribute(nodes[j], 'x', NodeMatrix[i]);
graph.setNodeAttribute(nodes[j], 'y', NodeMatrix[i + 1]);
j++;
}
};
/**
* Function collecting the layout positions.
*
* @param {Graph} graph - Target graph.
* @param {Float32Array} NodeMatrix - Node matrix.
* @return {object} - Map to node positions.
*/
exports.collectLayoutChanges = function(graph, NodeMatrix) {
var nodes = graph.nodes(),
positions = Object.create(null);
for (var i = 0, j = 0, l = NodeMatrix.length; i < l; i += PPN) {
positions[nodes[j]] = {
x: NodeMatrix[i],
y: NodeMatrix[i + 1]
};
j++;
}
return positions;
};
/***/ }),
/* 4 */
/***/ (function(module, exports) {
/**
* Graphology ForceAtlas2 Layout Default Settings
* ===============================================
*/
module.exports = {
linLogMode: false,
outboundAttractionDistribution: false,
adjustSizes: false,
edgeWeightInfluence: 0,
scalingRatio: 1,
strongGravityMode: false,
gravity: 1,
slowDown: 1,
barnesHutOptimize: false,
barnesHutTheta: 0.5
};
/***/ })
/******/ ]);
});