(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 }; /***/ }) /******/ ]); });