/**
|
|
* vis.js
|
|
* https://github.com/almende/vis
|
|
*
|
|
* A dynamic, browser-based visualization library.
|
|
*
|
|
* @version 4.9.1-SNAPSHOT
|
|
* @date 2015-10-05
|
|
*
|
|
* @license
|
|
* Copyright (C) 2011-2015 Almende B.V, http://almende.com
|
|
*
|
|
* Vis.js is dual licensed under both
|
|
*
|
|
* * The Apache 2.0 License
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* and
|
|
*
|
|
* * The MIT License
|
|
* http://opensource.org/licenses/MIT
|
|
*
|
|
* Vis.js may be distributed under either license.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
/******/ (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] = {
|
|
/******/ exports: {},
|
|
/******/ id: moduleId,
|
|
/******/ loaded: false
|
|
/******/ };
|
|
|
|
/******/ // Execute the module function
|
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
|
|
/******/ // Flag the module as loaded
|
|
/******/ module.loaded = 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;
|
|
|
|
/******/ // __webpack_public_path__
|
|
/******/ __webpack_require__.p = "";
|
|
|
|
/******/ // Load entry module and return exports
|
|
/******/ return __webpack_require__(0);
|
|
/******/ })
|
|
/************************************************************************/
|
|
/******/ ([
|
|
/* 0 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
'use strict';
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
|
|
|
var _PhysicsWorkerJs = __webpack_require__(1);
|
|
|
|
var _PhysicsWorkerJs2 = _interopRequireDefault(_PhysicsWorkerJs);
|
|
|
|
var physicsWorker = new _PhysicsWorkerJs2['default'](function (data) {
|
|
return postMessage(data);
|
|
});
|
|
self.addEventListener('message', function (event) {
|
|
return physicsWorker.handleMessage(event);
|
|
}, false);
|
|
|
|
/***/ },
|
|
/* 1 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
|
|
|
|
var _componentsPhysicsBarnesHutSolver = __webpack_require__(2);
|
|
|
|
var _componentsPhysicsBarnesHutSolver2 = _interopRequireDefault(_componentsPhysicsBarnesHutSolver);
|
|
|
|
var _componentsPhysicsRepulsionSolver = __webpack_require__(3);
|
|
|
|
var _componentsPhysicsRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsRepulsionSolver);
|
|
|
|
var _componentsPhysicsHierarchicalRepulsionSolver = __webpack_require__(4);
|
|
|
|
var _componentsPhysicsHierarchicalRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalRepulsionSolver);
|
|
|
|
var _componentsPhysicsSpringSolver = __webpack_require__(5);
|
|
|
|
var _componentsPhysicsSpringSolver2 = _interopRequireDefault(_componentsPhysicsSpringSolver);
|
|
|
|
var _componentsPhysicsHierarchicalSpringSolver = __webpack_require__(6);
|
|
|
|
var _componentsPhysicsHierarchicalSpringSolver2 = _interopRequireDefault(_componentsPhysicsHierarchicalSpringSolver);
|
|
|
|
var _componentsPhysicsCentralGravitySolver = __webpack_require__(7);
|
|
|
|
var _componentsPhysicsCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsCentralGravitySolver);
|
|
|
|
var _componentsPhysicsFA2BasedRepulsionSolver = __webpack_require__(8);
|
|
|
|
var _componentsPhysicsFA2BasedRepulsionSolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedRepulsionSolver);
|
|
|
|
var _componentsPhysicsFA2BasedCentralGravitySolver = __webpack_require__(9);
|
|
|
|
var _componentsPhysicsFA2BasedCentralGravitySolver2 = _interopRequireDefault(_componentsPhysicsFA2BasedCentralGravitySolver);
|
|
|
|
var PhysicsWorker = (function () {
|
|
function PhysicsWorker(postMessage) {
|
|
_classCallCheck(this, PhysicsWorker);
|
|
|
|
this.body = {};
|
|
this.physicsBody = { physicsNodeIndices: [], physicsEdgeIndices: [], forces: {}, velocities: {} };
|
|
this.postMessage = postMessage;
|
|
this.options = {};
|
|
this.stabilized = false;
|
|
this.previousStates = {};
|
|
this.positions = {};
|
|
this.timestep = 0.5;
|
|
}
|
|
|
|
_createClass(PhysicsWorker, [{
|
|
key: 'handleMessage',
|
|
value: function handleMessage(event) {
|
|
var msg = event.data;
|
|
switch (msg.type) {
|
|
case 'calculateForces':
|
|
this.calculateForces();
|
|
this.moveNodes();
|
|
this.postMessage({
|
|
type: 'positions',
|
|
data: {
|
|
positions: this.positions,
|
|
stabilized: this.stabilized
|
|
}
|
|
});
|
|
break;
|
|
case 'update':
|
|
var node = this.body.nodes[msg.data.id];
|
|
node.x = msg.data.x;
|
|
node.y = msg.data.y;
|
|
break;
|
|
case 'options':
|
|
this.options = msg.data;
|
|
this.timestep = this.options.timestep;
|
|
this.init();
|
|
break;
|
|
case 'physicsObjects':
|
|
this.body.nodes = msg.data.nodes;
|
|
this.body.edges = msg.data.edges;
|
|
this.updatePhysicsData();
|
|
break;
|
|
default:
|
|
console.warn('unknown message from PhysicsEngine', msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* configure the engine.
|
|
*/
|
|
}, {
|
|
key: 'init',
|
|
value: function init() {
|
|
var options;
|
|
if (this.options.solver === 'forceAtlas2Based') {
|
|
options = this.options.forceAtlas2Based;
|
|
this.nodesSolver = new _componentsPhysicsFA2BasedRepulsionSolver2['default'](this.body, this.physicsBody, options);
|
|
this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options);
|
|
this.gravitySolver = new _componentsPhysicsFA2BasedCentralGravitySolver2['default'](this.body, this.physicsBody, options);
|
|
} else if (this.options.solver === 'repulsion') {
|
|
options = this.options.repulsion;
|
|
this.nodesSolver = new _componentsPhysicsRepulsionSolver2['default'](this.body, this.physicsBody, options);
|
|
this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options);
|
|
this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options);
|
|
} else if (this.options.solver === 'hierarchicalRepulsion') {
|
|
options = this.options.hierarchicalRepulsion;
|
|
this.nodesSolver = new _componentsPhysicsHierarchicalRepulsionSolver2['default'](this.body, this.physicsBody, options);
|
|
this.edgesSolver = new _componentsPhysicsHierarchicalSpringSolver2['default'](this.body, this.physicsBody, options);
|
|
this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options);
|
|
} else {
|
|
// barnesHut
|
|
options = this.options.barnesHut;
|
|
this.nodesSolver = new _componentsPhysicsBarnesHutSolver2['default'](this.body, this.physicsBody, options);
|
|
this.edgesSolver = new _componentsPhysicsSpringSolver2['default'](this.body, this.physicsBody, options);
|
|
this.gravitySolver = new _componentsPhysicsCentralGravitySolver2['default'](this.body, this.physicsBody, options);
|
|
}
|
|
|
|
this.modelOptions = options;
|
|
}
|
|
|
|
/**
|
|
* Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time.
|
|
*
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: 'updatePhysicsData',
|
|
value: function updatePhysicsData() {
|
|
this.physicsBody.forces = {};
|
|
this.physicsBody.physicsNodeIndices = [];
|
|
this.physicsBody.physicsEdgeIndices = [];
|
|
var nodes = this.body.nodes;
|
|
var edges = this.body.edges;
|
|
|
|
// get node indices for physics
|
|
for (var nodeId in nodes) {
|
|
if (nodes.hasOwnProperty(nodeId)) {
|
|
this.physicsBody.physicsNodeIndices.push(nodeId);
|
|
this.positions[nodeId] = {
|
|
x: nodes[nodeId].x,
|
|
y: nodes[nodeId].y
|
|
};
|
|
}
|
|
}
|
|
|
|
// get edge indices for physics
|
|
for (var edgeId in edges) {
|
|
if (edges.hasOwnProperty(edgeId)) {
|
|
this.physicsBody.physicsEdgeIndices.push(edgeId);
|
|
}
|
|
}
|
|
|
|
// get the velocity and the forces vector
|
|
for (var i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) {
|
|
var nodeId = this.physicsBody.physicsNodeIndices[i];
|
|
this.physicsBody.forces[nodeId] = { x: 0, y: 0 };
|
|
|
|
// forces can be reset because they are recalculated. Velocities have to persist.
|
|
if (this.physicsBody.velocities[nodeId] === undefined) {
|
|
this.physicsBody.velocities[nodeId] = { x: 0, y: 0 };
|
|
}
|
|
}
|
|
|
|
// clean deleted nodes from the velocity vector
|
|
for (var nodeId in this.physicsBody.velocities) {
|
|
if (nodes[nodeId] === undefined) {
|
|
delete this.physicsBody.velocities[nodeId];
|
|
}
|
|
}
|
|
// console.log(this.physicsBody);
|
|
}
|
|
|
|
/**
|
|
* move the nodes one timestap and check if they are stabilized
|
|
* @returns {boolean}
|
|
*/
|
|
}, {
|
|
key: 'moveNodes',
|
|
value: function moveNodes() {
|
|
var nodeIndices = this.physicsBody.physicsNodeIndices;
|
|
var maxVelocity = this.options.maxVelocity ? this.options.maxVelocity : 1e9;
|
|
var maxNodeVelocity = 0;
|
|
|
|
for (var i = 0; i < nodeIndices.length; i++) {
|
|
var nodeId = nodeIndices[i];
|
|
var nodeVelocity = this._performStep(nodeId, maxVelocity);
|
|
// stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized
|
|
maxNodeVelocity = Math.max(maxNodeVelocity, nodeVelocity);
|
|
}
|
|
|
|
// evaluating the stabilized and adaptiveTimestepEnabled conditions
|
|
this.stabilized = maxNodeVelocity < this.options.minVelocity;
|
|
}
|
|
|
|
/**
|
|
* Perform the actual step
|
|
*
|
|
* @param nodeId
|
|
* @param maxVelocity
|
|
* @returns {number}
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: '_performStep',
|
|
value: function _performStep(nodeId, maxVelocity) {
|
|
var node = this.body.nodes[nodeId];
|
|
var timestep = this.timestep;
|
|
var forces = this.physicsBody.forces;
|
|
var velocities = this.physicsBody.velocities;
|
|
|
|
// store the state so we can revert
|
|
this.previousStates[nodeId] = { x: node.x, y: node.y, vx: velocities[nodeId].x, vy: velocities[nodeId].y };
|
|
|
|
if (node.options.fixed.x === false) {
|
|
var dx = this.modelOptions.damping * velocities[nodeId].x; // damping force
|
|
var ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration
|
|
velocities[nodeId].x += ax * timestep; // velocity
|
|
velocities[nodeId].x = Math.abs(velocities[nodeId].x) > maxVelocity ? velocities[nodeId].x > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].x;
|
|
node.x += velocities[nodeId].x * timestep; // position
|
|
this.positions[nodeId].x = node.x;
|
|
} else {
|
|
forces[nodeId].x = 0;
|
|
velocities[nodeId].x = 0;
|
|
}
|
|
|
|
if (node.options.fixed.y === false) {
|
|
var dy = this.modelOptions.damping * velocities[nodeId].y; // damping force
|
|
var ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration
|
|
velocities[nodeId].y += ay * timestep; // velocity
|
|
velocities[nodeId].y = Math.abs(velocities[nodeId].y) > maxVelocity ? velocities[nodeId].y > 0 ? maxVelocity : -maxVelocity : velocities[nodeId].y;
|
|
node.y += velocities[nodeId].y * timestep; // position
|
|
this.positions[nodeId].y = node.y;
|
|
} else {
|
|
forces[nodeId].y = 0;
|
|
velocities[nodeId].y = 0;
|
|
}
|
|
|
|
var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x, 2) + Math.pow(velocities[nodeId].y, 2));
|
|
return totalVelocity;
|
|
}
|
|
|
|
/**
|
|
* calculate the forces for one physics iteration.
|
|
*/
|
|
}, {
|
|
key: 'calculateForces',
|
|
value: function calculateForces() {
|
|
this.gravitySolver.solve();
|
|
this.nodesSolver.solve();
|
|
this.edgesSolver.solve();
|
|
}
|
|
}]);
|
|
|
|
return PhysicsWorker;
|
|
})();
|
|
|
|
exports['default'] = PhysicsWorker;
|
|
module.exports = exports['default'];
|
|
|
|
/***/ },
|
|
/* 2 */
|
|
/***/ function(module, exports) {
|
|
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var BarnesHutSolver = (function () {
|
|
function BarnesHutSolver(body, physicsBody, options) {
|
|
_classCallCheck(this, BarnesHutSolver);
|
|
|
|
this.body = body;
|
|
this.physicsBody = physicsBody;
|
|
this.barnesHutTree;
|
|
this.setOptions(options);
|
|
this.randomSeed = 5;
|
|
}
|
|
|
|
_createClass(BarnesHutSolver, [{
|
|
key: "setOptions",
|
|
value: function setOptions(options) {
|
|
this.options = options;
|
|
this.thetaInversed = 1 / this.options.theta;
|
|
this.overlapAvoidanceFactor = 1 - Math.max(0, Math.min(1, this.options.avoidOverlap)); // if 1 then min distance = 0.5, if 0.5 then min distance = 0.5 + 0.5*node.shape.radius
|
|
}
|
|
}, {
|
|
key: "seededRandom",
|
|
value: function seededRandom() {
|
|
var x = Math.sin(this.randomSeed++) * 10000;
|
|
return x - Math.floor(x);
|
|
}
|
|
|
|
/**
|
|
* This function calculates the forces the nodes apply on eachother based on a gravitational model.
|
|
* The Barnes Hut method is used to speed up this N-body simulation.
|
|
*
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "solve",
|
|
value: function solve() {
|
|
if (this.options.gravitationalConstant !== 0 && this.physicsBody.physicsNodeIndices.length > 0) {
|
|
var node = undefined;
|
|
var nodes = this.body.nodes;
|
|
var nodeIndices = this.physicsBody.physicsNodeIndices;
|
|
var nodeCount = nodeIndices.length;
|
|
|
|
// create the tree
|
|
var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices);
|
|
|
|
// for debugging
|
|
this.barnesHutTree = barnesHutTree;
|
|
|
|
// place the nodes one by one recursively
|
|
for (var i = 0; i < nodeCount; i++) {
|
|
node = nodes[nodeIndices[i]];
|
|
if (node.options.mass > 0) {
|
|
// starting with root is irrelevant, it never passes the BarnesHutSolver condition
|
|
this._getForceContribution(barnesHutTree.root.children.NW, node);
|
|
this._getForceContribution(barnesHutTree.root.children.NE, node);
|
|
this._getForceContribution(barnesHutTree.root.children.SW, node);
|
|
this._getForceContribution(barnesHutTree.root.children.SE, node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
|
|
* If a region contains a single node, we check if it is not itself, then we apply the force.
|
|
*
|
|
* @param parentBranch
|
|
* @param node
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_getForceContribution",
|
|
value: function _getForceContribution(parentBranch, node) {
|
|
// we get no force contribution from an empty region
|
|
if (parentBranch.childrenCount > 0) {
|
|
var dx = undefined,
|
|
dy = undefined,
|
|
distance = undefined;
|
|
|
|
// get the distance from the center of mass to the node.
|
|
dx = parentBranch.centerOfMass.x - node.x;
|
|
dy = parentBranch.centerOfMass.y - node.y;
|
|
distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
// BarnesHutSolver condition
|
|
// original condition : s/d < theta = passed === d/s > 1/theta = passed
|
|
// calcSize = 1/s --> d * 1/s > 1/theta = passed
|
|
if (distance * parentBranch.calcSize > this.thetaInversed) {
|
|
this._calculateForces(distance, dx, dy, node, parentBranch);
|
|
} else {
|
|
// Did not pass the condition, go into children if available
|
|
if (parentBranch.childrenCount === 4) {
|
|
this._getForceContribution(parentBranch.children.NW, node);
|
|
this._getForceContribution(parentBranch.children.NE, node);
|
|
this._getForceContribution(parentBranch.children.SW, node);
|
|
this._getForceContribution(parentBranch.children.SE, node);
|
|
} else {
|
|
// parentBranch must have only one node, if it was empty we wouldnt be here
|
|
if (parentBranch.children.data.id != node.id) {
|
|
// if it is not self
|
|
this._calculateForces(distance, dx, dy, node, parentBranch);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the forces based on the distance.
|
|
*
|
|
* @param distance
|
|
* @param dx
|
|
* @param dy
|
|
* @param node
|
|
* @param parentBranch
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_calculateForces",
|
|
value: function _calculateForces(distance, dx, dy, node, parentBranch) {
|
|
if (distance === 0) {
|
|
distance = 0.1;
|
|
dx = distance;
|
|
}
|
|
|
|
if (this.overlapAvoidanceFactor < 1) {
|
|
distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius);
|
|
}
|
|
|
|
// the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines
|
|
// it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce
|
|
var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / Math.pow(distance, 3);
|
|
var fx = dx * gravityForce;
|
|
var fy = dy * gravityForce;
|
|
|
|
this.physicsBody.forces[node.id].x += fx;
|
|
this.physicsBody.forces[node.id].y += fy;
|
|
}
|
|
|
|
/**
|
|
* This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
|
|
*
|
|
* @param nodes
|
|
* @param nodeIndices
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_formBarnesHutTree",
|
|
value: function _formBarnesHutTree(nodes, nodeIndices) {
|
|
var node = undefined;
|
|
var nodeCount = nodeIndices.length;
|
|
|
|
var minX = nodes[nodeIndices[0]].x;
|
|
var minY = nodes[nodeIndices[0]].y;
|
|
var maxX = nodes[nodeIndices[0]].x;
|
|
var maxY = nodes[nodeIndices[0]].y;
|
|
|
|
// get the range of the nodes
|
|
for (var i = 1; i < nodeCount; i++) {
|
|
var x = nodes[nodeIndices[i]].x;
|
|
var y = nodes[nodeIndices[i]].y;
|
|
if (nodes[nodeIndices[i]].options.mass > 0) {
|
|
if (x < minX) {
|
|
minX = x;
|
|
}
|
|
if (x > maxX) {
|
|
maxX = x;
|
|
}
|
|
if (y < minY) {
|
|
minY = y;
|
|
}
|
|
if (y > maxY) {
|
|
maxY = y;
|
|
}
|
|
}
|
|
}
|
|
// make the range a square
|
|
var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
|
|
if (sizeDiff > 0) {
|
|
minY -= 0.5 * sizeDiff;
|
|
maxY += 0.5 * sizeDiff;
|
|
} // xSize > ySize
|
|
else {
|
|
minX += 0.5 * sizeDiff;
|
|
maxX -= 0.5 * sizeDiff;
|
|
} // xSize < ySize
|
|
|
|
var minimumTreeSize = 1e-5;
|
|
var rootSize = Math.max(minimumTreeSize, Math.abs(maxX - minX));
|
|
var halfRootSize = 0.5 * rootSize;
|
|
var centerX = 0.5 * (minX + maxX),
|
|
centerY = 0.5 * (minY + maxY);
|
|
|
|
// construct the barnesHutTree
|
|
var barnesHutTree = {
|
|
root: {
|
|
centerOfMass: { x: 0, y: 0 },
|
|
mass: 0,
|
|
range: {
|
|
minX: centerX - halfRootSize, maxX: centerX + halfRootSize,
|
|
minY: centerY - halfRootSize, maxY: centerY + halfRootSize
|
|
},
|
|
size: rootSize,
|
|
calcSize: 1 / rootSize,
|
|
children: { data: null },
|
|
maxWidth: 0,
|
|
level: 0,
|
|
childrenCount: 4
|
|
}
|
|
};
|
|
this._splitBranch(barnesHutTree.root);
|
|
|
|
// place the nodes one by one recursively
|
|
for (var i = 0; i < nodeCount; i++) {
|
|
node = nodes[nodeIndices[i]];
|
|
if (node.options.mass > 0) {
|
|
this._placeInTree(barnesHutTree.root, node);
|
|
}
|
|
}
|
|
|
|
// make global
|
|
return barnesHutTree;
|
|
}
|
|
|
|
/**
|
|
* this updates the mass of a branch. this is increased by adding a node.
|
|
*
|
|
* @param parentBranch
|
|
* @param node
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_updateBranchMass",
|
|
value: function _updateBranchMass(parentBranch, node) {
|
|
var totalMass = parentBranch.mass + node.options.mass;
|
|
var totalMassInv = 1 / totalMass;
|
|
|
|
parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
|
|
parentBranch.centerOfMass.x *= totalMassInv;
|
|
|
|
parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
|
|
parentBranch.centerOfMass.y *= totalMassInv;
|
|
|
|
parentBranch.mass = totalMass;
|
|
var biggestSize = Math.max(Math.max(node.height, node.radius), node.width);
|
|
parentBranch.maxWidth = parentBranch.maxWidth < biggestSize ? biggestSize : parentBranch.maxWidth;
|
|
}
|
|
|
|
/**
|
|
* determine in which branch the node will be placed.
|
|
*
|
|
* @param parentBranch
|
|
* @param node
|
|
* @param skipMassUpdate
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_placeInTree",
|
|
value: function _placeInTree(parentBranch, node, skipMassUpdate) {
|
|
if (skipMassUpdate != true || skipMassUpdate === undefined) {
|
|
// update the mass of the branch.
|
|
this._updateBranchMass(parentBranch, node);
|
|
}
|
|
|
|
if (parentBranch.children.NW.range.maxX > node.x) {
|
|
// in NW or SW
|
|
if (parentBranch.children.NW.range.maxY > node.y) {
|
|
// in NW
|
|
this._placeInRegion(parentBranch, node, "NW");
|
|
} else {
|
|
// in SW
|
|
this._placeInRegion(parentBranch, node, "SW");
|
|
}
|
|
} else {
|
|
// in NE or SE
|
|
if (parentBranch.children.NW.range.maxY > node.y) {
|
|
// in NE
|
|
this._placeInRegion(parentBranch, node, "NE");
|
|
} else {
|
|
// in SE
|
|
this._placeInRegion(parentBranch, node, "SE");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* actually place the node in a region (or branch)
|
|
*
|
|
* @param parentBranch
|
|
* @param node
|
|
* @param region
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_placeInRegion",
|
|
value: function _placeInRegion(parentBranch, node, region) {
|
|
switch (parentBranch.children[region].childrenCount) {
|
|
case 0:
|
|
// place node here
|
|
parentBranch.children[region].children.data = node;
|
|
parentBranch.children[region].childrenCount = 1;
|
|
this._updateBranchMass(parentBranch.children[region], node);
|
|
break;
|
|
case 1:
|
|
// convert into children
|
|
// if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
|
|
// we move one node a pixel and we do not put it in the tree.
|
|
if (parentBranch.children[region].children.data.x === node.x && parentBranch.children[region].children.data.y === node.y) {
|
|
node.x += this.seededRandom();
|
|
node.y += this.seededRandom();
|
|
} else {
|
|
this._splitBranch(parentBranch.children[region]);
|
|
this._placeInTree(parentBranch.children[region], node);
|
|
}
|
|
break;
|
|
case 4:
|
|
// place in branch
|
|
this._placeInTree(parentBranch.children[region], node);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
|
|
* after the split is complete.
|
|
*
|
|
* @param parentBranch
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_splitBranch",
|
|
value: function _splitBranch(parentBranch) {
|
|
// if the branch is shaded with a node, replace the node in the new subset.
|
|
var containedNode = null;
|
|
if (parentBranch.childrenCount === 1) {
|
|
containedNode = parentBranch.children.data;
|
|
parentBranch.mass = 0;
|
|
parentBranch.centerOfMass.x = 0;
|
|
parentBranch.centerOfMass.y = 0;
|
|
}
|
|
parentBranch.childrenCount = 4;
|
|
parentBranch.children.data = null;
|
|
this._insertRegion(parentBranch, "NW");
|
|
this._insertRegion(parentBranch, "NE");
|
|
this._insertRegion(parentBranch, "SW");
|
|
this._insertRegion(parentBranch, "SE");
|
|
|
|
if (containedNode != null) {
|
|
this._placeInTree(parentBranch, containedNode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function subdivides the region into four new segments.
|
|
* Specifically, this inserts a single new segment.
|
|
* It fills the children section of the parentBranch
|
|
*
|
|
* @param parentBranch
|
|
* @param region
|
|
* @param parentRange
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_insertRegion",
|
|
value: function _insertRegion(parentBranch, region) {
|
|
var minX = undefined,
|
|
maxX = undefined,
|
|
minY = undefined,
|
|
maxY = undefined;
|
|
var childSize = 0.5 * parentBranch.size;
|
|
switch (region) {
|
|
case "NW":
|
|
minX = parentBranch.range.minX;
|
|
maxX = parentBranch.range.minX + childSize;
|
|
minY = parentBranch.range.minY;
|
|
maxY = parentBranch.range.minY + childSize;
|
|
break;
|
|
case "NE":
|
|
minX = parentBranch.range.minX + childSize;
|
|
maxX = parentBranch.range.maxX;
|
|
minY = parentBranch.range.minY;
|
|
maxY = parentBranch.range.minY + childSize;
|
|
break;
|
|
case "SW":
|
|
minX = parentBranch.range.minX;
|
|
maxX = parentBranch.range.minX + childSize;
|
|
minY = parentBranch.range.minY + childSize;
|
|
maxY = parentBranch.range.maxY;
|
|
break;
|
|
case "SE":
|
|
minX = parentBranch.range.minX + childSize;
|
|
maxX = parentBranch.range.maxX;
|
|
minY = parentBranch.range.minY + childSize;
|
|
maxY = parentBranch.range.maxY;
|
|
break;
|
|
}
|
|
|
|
parentBranch.children[region] = {
|
|
centerOfMass: { x: 0, y: 0 },
|
|
mass: 0,
|
|
range: { minX: minX, maxX: maxX, minY: minY, maxY: maxY },
|
|
size: 0.5 * parentBranch.size,
|
|
calcSize: 2 * parentBranch.calcSize,
|
|
children: { data: null },
|
|
maxWidth: 0,
|
|
level: parentBranch.level + 1,
|
|
childrenCount: 0
|
|
};
|
|
}
|
|
|
|
//--------------------------- DEBUGGING BELOW ---------------------------//
|
|
|
|
/**
|
|
* This function is for debugging purposed, it draws the tree.
|
|
*
|
|
* @param ctx
|
|
* @param color
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_debug",
|
|
value: function _debug(ctx, color) {
|
|
if (this.barnesHutTree !== undefined) {
|
|
|
|
ctx.lineWidth = 1;
|
|
|
|
this._drawBranch(this.barnesHutTree.root, ctx, color);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function is for debugging purposes. It draws the branches recursively.
|
|
*
|
|
* @param branch
|
|
* @param ctx
|
|
* @param color
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_drawBranch",
|
|
value: function _drawBranch(branch, ctx, color) {
|
|
if (color === undefined) {
|
|
color = "#FF0000";
|
|
}
|
|
|
|
if (branch.childrenCount === 4) {
|
|
this._drawBranch(branch.children.NW, ctx);
|
|
this._drawBranch(branch.children.NE, ctx);
|
|
this._drawBranch(branch.children.SE, ctx);
|
|
this._drawBranch(branch.children.SW, ctx);
|
|
}
|
|
ctx.strokeStyle = color;
|
|
ctx.beginPath();
|
|
ctx.moveTo(branch.range.minX, branch.range.minY);
|
|
ctx.lineTo(branch.range.maxX, branch.range.minY);
|
|
ctx.stroke();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(branch.range.maxX, branch.range.minY);
|
|
ctx.lineTo(branch.range.maxX, branch.range.maxY);
|
|
ctx.stroke();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(branch.range.maxX, branch.range.maxY);
|
|
ctx.lineTo(branch.range.minX, branch.range.maxY);
|
|
ctx.stroke();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(branch.range.minX, branch.range.maxY);
|
|
ctx.lineTo(branch.range.minX, branch.range.minY);
|
|
ctx.stroke();
|
|
|
|
/*
|
|
if (branch.mass > 0) {
|
|
ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
|
|
ctx.stroke();
|
|
}
|
|
*/
|
|
}
|
|
}]);
|
|
|
|
return BarnesHutSolver;
|
|
})();
|
|
|
|
exports["default"] = BarnesHutSolver;
|
|
module.exports = exports["default"];
|
|
|
|
/***/ },
|
|
/* 3 */
|
|
/***/ function(module, exports) {
|
|
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var RepulsionSolver = (function () {
|
|
function RepulsionSolver(body, physicsBody, options) {
|
|
_classCallCheck(this, RepulsionSolver);
|
|
|
|
this.body = body;
|
|
this.physicsBody = physicsBody;
|
|
this.setOptions(options);
|
|
}
|
|
|
|
_createClass(RepulsionSolver, [{
|
|
key: "setOptions",
|
|
value: function setOptions(options) {
|
|
this.options = options;
|
|
}
|
|
|
|
/**
|
|
* Calculate the forces the nodes apply on each other based on a repulsion field.
|
|
* This field is linearly approximated.
|
|
*
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "solve",
|
|
value: function solve() {
|
|
var dx, dy, distance, fx, fy, repulsingForce, node1, node2;
|
|
|
|
var nodes = this.body.nodes;
|
|
var nodeIndices = this.physicsBody.physicsNodeIndices;
|
|
var forces = this.physicsBody.forces;
|
|
|
|
// repulsing forces between nodes
|
|
var nodeDistance = this.options.nodeDistance;
|
|
|
|
// approximation constants
|
|
var a = -2 / 3 / nodeDistance;
|
|
var b = 4 / 3;
|
|
|
|
// we loop from i over all but the last entree in the array
|
|
// j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j
|
|
for (var i = 0; i < nodeIndices.length - 1; i++) {
|
|
node1 = nodes[nodeIndices[i]];
|
|
for (var j = i + 1; j < nodeIndices.length; j++) {
|
|
node2 = nodes[nodeIndices[j]];
|
|
|
|
dx = node2.x - node1.x;
|
|
dy = node2.y - node1.y;
|
|
distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
// same condition as BarnesHutSolver, making sure nodes are never 100% overlapping.
|
|
if (distance === 0) {
|
|
distance = 0.1 * Math.random();
|
|
dx = distance;
|
|
}
|
|
|
|
if (distance < 2 * nodeDistance) {
|
|
if (distance < 0.5 * nodeDistance) {
|
|
repulsingForce = 1.0;
|
|
} else {
|
|
repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / nodeDistance - 1) * steepness))
|
|
}
|
|
repulsingForce = repulsingForce / distance;
|
|
|
|
fx = dx * repulsingForce;
|
|
fy = dy * repulsingForce;
|
|
|
|
forces[node1.id].x -= fx;
|
|
forces[node1.id].y -= fy;
|
|
forces[node2.id].x += fx;
|
|
forces[node2.id].y += fy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}]);
|
|
|
|
return RepulsionSolver;
|
|
})();
|
|
|
|
exports["default"] = RepulsionSolver;
|
|
module.exports = exports["default"];
|
|
|
|
/***/ },
|
|
/* 4 */
|
|
/***/ function(module, exports) {
|
|
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var HierarchicalRepulsionSolver = (function () {
|
|
function HierarchicalRepulsionSolver(body, physicsBody, options) {
|
|
_classCallCheck(this, HierarchicalRepulsionSolver);
|
|
|
|
this.body = body;
|
|
this.physicsBody = physicsBody;
|
|
this.setOptions(options);
|
|
}
|
|
|
|
_createClass(HierarchicalRepulsionSolver, [{
|
|
key: "setOptions",
|
|
value: function setOptions(options) {
|
|
this.options = options;
|
|
}
|
|
|
|
/**
|
|
* Calculate the forces the nodes apply on each other based on a repulsion field.
|
|
* This field is linearly approximated.
|
|
*
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "solve",
|
|
value: function solve() {
|
|
var dx, dy, distance, fx, fy, repulsingForce, node1, node2, i, j;
|
|
|
|
var nodes = this.body.nodes;
|
|
var nodeIndices = this.physicsBody.physicsNodeIndices;
|
|
var forces = this.physicsBody.forces;
|
|
|
|
// repulsing forces between nodes
|
|
var nodeDistance = this.options.nodeDistance;
|
|
|
|
// we loop from i over all but the last entree in the array
|
|
// j loops from i+1 to the last. This way we do not double count any of the indices, nor i === j
|
|
for (i = 0; i < nodeIndices.length - 1; i++) {
|
|
node1 = nodes[nodeIndices[i]];
|
|
for (j = i + 1; j < nodeIndices.length; j++) {
|
|
node2 = nodes[nodeIndices[j]];
|
|
|
|
// nodes only affect nodes on their level
|
|
if (node1.level === node2.level) {
|
|
dx = node2.x - node1.x;
|
|
dy = node2.y - node1.y;
|
|
distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
var steepness = 0.05;
|
|
if (distance < nodeDistance) {
|
|
repulsingForce = -Math.pow(steepness * distance, 2) + Math.pow(steepness * nodeDistance, 2);
|
|
} else {
|
|
repulsingForce = 0;
|
|
}
|
|
// normalize force with
|
|
if (distance === 0) {
|
|
distance = 0.01;
|
|
} else {
|
|
repulsingForce = repulsingForce / distance;
|
|
}
|
|
fx = dx * repulsingForce;
|
|
fy = dy * repulsingForce;
|
|
|
|
forces[node1.id].x -= fx;
|
|
forces[node1.id].y -= fy;
|
|
forces[node2.id].x += fx;
|
|
forces[node2.id].y += fy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}]);
|
|
|
|
return HierarchicalRepulsionSolver;
|
|
})();
|
|
|
|
exports["default"] = HierarchicalRepulsionSolver;
|
|
module.exports = exports["default"];
|
|
|
|
/***/ },
|
|
/* 5 */
|
|
/***/ function(module, exports) {
|
|
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var SpringSolver = (function () {
|
|
function SpringSolver(body, physicsBody, options) {
|
|
_classCallCheck(this, SpringSolver);
|
|
|
|
this.body = body;
|
|
this.physicsBody = physicsBody;
|
|
this.setOptions(options);
|
|
}
|
|
|
|
_createClass(SpringSolver, [{
|
|
key: "setOptions",
|
|
value: function setOptions(options) {
|
|
this.options = options;
|
|
}
|
|
|
|
/**
|
|
* This function calculates the springforces on the nodes, accounting for the support nodes.
|
|
*
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "solve",
|
|
value: function solve() {
|
|
var edgeLength = undefined,
|
|
edge = undefined;
|
|
var edgeIndices = this.physicsBody.physicsEdgeIndices;
|
|
var edges = this.body.edges;
|
|
var nodes = this.body.nodes;
|
|
var node1 = undefined,
|
|
node2 = undefined,
|
|
node3 = undefined;
|
|
|
|
// forces caused by the edges, modelled as springs
|
|
for (var i = 0; i < edgeIndices.length; i++) {
|
|
edge = edges[edgeIndices[i]];
|
|
if (edge.connected === true && edge.toId !== edge.fromId) {
|
|
// only calculate forces if nodes are in the same sector
|
|
if (this.body.nodes[edge.toId] !== undefined && this.body.nodes[edge.fromId] !== undefined) {
|
|
if (edge.edgeType.via !== undefined) {
|
|
edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length;
|
|
node1 = nodes[edge.to.id];
|
|
node2 = nodes[edge.edgeType.via.id];
|
|
node3 = nodes[edge.from.id];
|
|
|
|
this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
|
|
this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
|
|
} else {
|
|
// the * 1.5 is here so the edge looks as large as a smooth edge. It does not initially because the smooth edges use
|
|
// the support nodes which exert a repulsive force on the to and from nodes, making the edge appear larger.
|
|
edgeLength = edge.options.length === undefined ? this.options.springLength * 1.5 : edge.options.length;
|
|
this._calculateSpringForce(nodes[edge.from.id], nodes[edge.to.id], edgeLength);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is the code actually performing the calculation for the function above.
|
|
*
|
|
* @param node1
|
|
* @param node2
|
|
* @param edgeLength
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_calculateSpringForce",
|
|
value: function _calculateSpringForce(node1, node2, edgeLength) {
|
|
var dx = node1.x - node2.x;
|
|
var dy = node1.y - node2.y;
|
|
var distance = Math.max(Math.sqrt(dx * dx + dy * dy), 0.01);
|
|
|
|
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
|
|
var springForce = this.options.springConstant * (edgeLength - distance) / distance;
|
|
|
|
var fx = dx * springForce;
|
|
var fy = dy * springForce;
|
|
|
|
// handle the case where one node is not part of the physcis
|
|
if (this.physicsBody.forces[node1.id] !== undefined) {
|
|
this.physicsBody.forces[node1.id].x += fx;
|
|
this.physicsBody.forces[node1.id].y += fy;
|
|
}
|
|
|
|
if (this.physicsBody.forces[node2.id] !== undefined) {
|
|
this.physicsBody.forces[node2.id].x -= fx;
|
|
this.physicsBody.forces[node2.id].y -= fy;
|
|
}
|
|
}
|
|
}]);
|
|
|
|
return SpringSolver;
|
|
})();
|
|
|
|
exports["default"] = SpringSolver;
|
|
module.exports = exports["default"];
|
|
|
|
/***/ },
|
|
/* 6 */
|
|
/***/ function(module, exports) {
|
|
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var HierarchicalSpringSolver = (function () {
|
|
function HierarchicalSpringSolver(body, physicsBody, options) {
|
|
_classCallCheck(this, HierarchicalSpringSolver);
|
|
|
|
this.body = body;
|
|
this.physicsBody = physicsBody;
|
|
this.setOptions(options);
|
|
}
|
|
|
|
_createClass(HierarchicalSpringSolver, [{
|
|
key: "setOptions",
|
|
value: function setOptions(options) {
|
|
this.options = options;
|
|
}
|
|
|
|
/**
|
|
* This function calculates the springforces on the nodes, accounting for the support nodes.
|
|
*
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "solve",
|
|
value: function solve() {
|
|
var edgeLength, edge;
|
|
var dx, dy, fx, fy, springForce, distance;
|
|
var edges = this.body.edges;
|
|
var factor = 0.5;
|
|
|
|
var edgeIndices = this.physicsBody.physicsEdgeIndices;
|
|
var nodeIndices = this.physicsBody.physicsNodeIndices;
|
|
var forces = this.physicsBody.forces;
|
|
|
|
// initialize the spring force counters
|
|
for (var i = 0; i < nodeIndices.length; i++) {
|
|
var nodeId = nodeIndices[i];
|
|
forces[nodeId].springFx = 0;
|
|
forces[nodeId].springFy = 0;
|
|
}
|
|
|
|
// forces caused by the edges, modelled as springs
|
|
for (var i = 0; i < edgeIndices.length; i++) {
|
|
edge = edges[edgeIndices[i]];
|
|
if (edge.connected === true) {
|
|
edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length;
|
|
|
|
dx = edge.from.x - edge.to.x;
|
|
dy = edge.from.y - edge.to.y;
|
|
distance = Math.sqrt(dx * dx + dy * dy);
|
|
distance = distance === 0 ? 0.01 : distance;
|
|
|
|
// the 1/distance is so the fx and fy can be calculated without sine or cosine.
|
|
springForce = this.options.springConstant * (edgeLength - distance) / distance;
|
|
|
|
fx = dx * springForce;
|
|
fy = dy * springForce;
|
|
|
|
if (edge.to.level != edge.from.level) {
|
|
if (forces[edge.toId] !== undefined) {
|
|
forces[edge.toId].springFx -= fx;
|
|
forces[edge.toId].springFy -= fy;
|
|
}
|
|
if (forces[edge.fromId] !== undefined) {
|
|
forces[edge.fromId].springFx += fx;
|
|
forces[edge.fromId].springFy += fy;
|
|
}
|
|
} else {
|
|
if (forces[edge.toId] !== undefined) {
|
|
forces[edge.toId].x -= factor * fx;
|
|
forces[edge.toId].y -= factor * fy;
|
|
}
|
|
if (forces[edge.fromId] !== undefined) {
|
|
forces[edge.fromId].x += factor * fx;
|
|
forces[edge.fromId].y += factor * fy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// normalize spring forces
|
|
var springForce = 1;
|
|
var springFx, springFy;
|
|
for (var i = 0; i < nodeIndices.length; i++) {
|
|
var nodeId = nodeIndices[i];
|
|
springFx = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFx));
|
|
springFy = Math.min(springForce, Math.max(-springForce, forces[nodeId].springFy));
|
|
|
|
forces[nodeId].x += springFx;
|
|
forces[nodeId].y += springFy;
|
|
}
|
|
|
|
// retain energy balance
|
|
var totalFx = 0;
|
|
var totalFy = 0;
|
|
for (var i = 0; i < nodeIndices.length; i++) {
|
|
var nodeId = nodeIndices[i];
|
|
totalFx += forces[nodeId].x;
|
|
totalFy += forces[nodeId].y;
|
|
}
|
|
var correctionFx = totalFx / nodeIndices.length;
|
|
var correctionFy = totalFy / nodeIndices.length;
|
|
|
|
for (var i = 0; i < nodeIndices.length; i++) {
|
|
var nodeId = nodeIndices[i];
|
|
forces[nodeId].x -= correctionFx;
|
|
forces[nodeId].y -= correctionFy;
|
|
}
|
|
}
|
|
}]);
|
|
|
|
return HierarchicalSpringSolver;
|
|
})();
|
|
|
|
exports["default"] = HierarchicalSpringSolver;
|
|
module.exports = exports["default"];
|
|
|
|
/***/ },
|
|
/* 7 */
|
|
/***/ function(module, exports) {
|
|
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var CentralGravitySolver = (function () {
|
|
function CentralGravitySolver(body, physicsBody, options) {
|
|
_classCallCheck(this, CentralGravitySolver);
|
|
|
|
this.body = body;
|
|
this.physicsBody = physicsBody;
|
|
this.setOptions(options);
|
|
}
|
|
|
|
_createClass(CentralGravitySolver, [{
|
|
key: "setOptions",
|
|
value: function setOptions(options) {
|
|
this.options = options;
|
|
}
|
|
}, {
|
|
key: "solve",
|
|
value: function solve() {
|
|
var dx = undefined,
|
|
dy = undefined,
|
|
distance = undefined,
|
|
node = undefined;
|
|
var nodes = this.body.nodes;
|
|
var nodeIndices = this.physicsBody.physicsNodeIndices;
|
|
var forces = this.physicsBody.forces;
|
|
|
|
for (var i = 0; i < nodeIndices.length; i++) {
|
|
var nodeId = nodeIndices[i];
|
|
node = nodes[nodeId];
|
|
dx = -node.x;
|
|
dy = -node.y;
|
|
distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
this._calculateForces(distance, dx, dy, forces, node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the forces based on the distance.
|
|
* @private
|
|
*/
|
|
}, {
|
|
key: "_calculateForces",
|
|
value: function _calculateForces(distance, dx, dy, forces, node) {
|
|
var gravityForce = distance === 0 ? 0 : this.options.centralGravity / distance;
|
|
forces[node.id].x = dx * gravityForce;
|
|
forces[node.id].y = dy * gravityForce;
|
|
}
|
|
}]);
|
|
|
|
return CentralGravitySolver;
|
|
})();
|
|
|
|
exports["default"] = CentralGravitySolver;
|
|
module.exports = exports["default"];
|
|
|
|
/***/ },
|
|
/* 8 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
|
|
var _BarnesHutSolver2 = __webpack_require__(2);
|
|
|
|
var _BarnesHutSolver3 = _interopRequireDefault(_BarnesHutSolver2);
|
|
|
|
var ForceAtlas2BasedRepulsionSolver = (function (_BarnesHutSolver) {
|
|
_inherits(ForceAtlas2BasedRepulsionSolver, _BarnesHutSolver);
|
|
|
|
function ForceAtlas2BasedRepulsionSolver(body, physicsBody, options) {
|
|
_classCallCheck(this, ForceAtlas2BasedRepulsionSolver);
|
|
|
|
_get(Object.getPrototypeOf(ForceAtlas2BasedRepulsionSolver.prototype), "constructor", this).call(this, body, physicsBody, options);
|
|
}
|
|
|
|
/**
|
|
* Calculate the forces based on the distance.
|
|
*
|
|
* @param distance
|
|
* @param dx
|
|
* @param dy
|
|
* @param node
|
|
* @param parentBranch
|
|
* @private
|
|
*/
|
|
|
|
_createClass(ForceAtlas2BasedRepulsionSolver, [{
|
|
key: "_calculateForces",
|
|
value: function _calculateForces(distance, dx, dy, node, parentBranch) {
|
|
if (distance === 0) {
|
|
distance = 0.1 * Math.random();
|
|
dx = distance;
|
|
}
|
|
|
|
if (this.overlapAvoidanceFactor < 1) {
|
|
distance = Math.max(0.1 + this.overlapAvoidanceFactor * node.shape.radius, distance - node.shape.radius);
|
|
}
|
|
|
|
var degree = node.edges.length + 1;
|
|
// the dividing by the distance cubed instead of squared allows us to get the fx and fy components without sines and cosines
|
|
// it is shorthand for gravityforce with distance squared and fx = dx/distance * gravityForce
|
|
var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass * degree / Math.pow(distance, 2);
|
|
var fx = dx * gravityForce;
|
|
var fy = dy * gravityForce;
|
|
|
|
this.physicsBody.forces[node.id].x += fx;
|
|
this.physicsBody.forces[node.id].y += fy;
|
|
}
|
|
}]);
|
|
|
|
return ForceAtlas2BasedRepulsionSolver;
|
|
})(_BarnesHutSolver3["default"]);
|
|
|
|
exports["default"] = ForceAtlas2BasedRepulsionSolver;
|
|
module.exports = exports["default"];
|
|
|
|
/***/ },
|
|
/* 9 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = (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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
|
|
|
var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
|
|
var _CentralGravitySolver2 = __webpack_require__(7);
|
|
|
|
var _CentralGravitySolver3 = _interopRequireDefault(_CentralGravitySolver2);
|
|
|
|
var ForceAtlas2BasedCentralGravitySolver = (function (_CentralGravitySolver) {
|
|
_inherits(ForceAtlas2BasedCentralGravitySolver, _CentralGravitySolver);
|
|
|
|
function ForceAtlas2BasedCentralGravitySolver(body, physicsBody, options) {
|
|
_classCallCheck(this, ForceAtlas2BasedCentralGravitySolver);
|
|
|
|
_get(Object.getPrototypeOf(ForceAtlas2BasedCentralGravitySolver.prototype), "constructor", this).call(this, body, physicsBody, options);
|
|
}
|
|
|
|
/**
|
|
* Calculate the forces based on the distance.
|
|
* @private
|
|
*/
|
|
|
|
_createClass(ForceAtlas2BasedCentralGravitySolver, [{
|
|
key: "_calculateForces",
|
|
value: function _calculateForces(distance, dx, dy, forces, node) {
|
|
if (distance > 0) {
|
|
var degree = node.edges.length + 1;
|
|
var gravityForce = this.options.centralGravity * degree * node.options.mass;
|
|
forces[node.id].x = dx * gravityForce;
|
|
forces[node.id].y = dy * gravityForce;
|
|
}
|
|
}
|
|
}]);
|
|
|
|
return ForceAtlas2BasedCentralGravitySolver;
|
|
})(_CentralGravitySolver3["default"]);
|
|
|
|
exports["default"] = ForceAtlas2BasedCentralGravitySolver;
|
|
module.exports = exports["default"];
|
|
|
|
/***/ }
|
|
/******/ ]);
|