Browse Source

added another solver based on barnesHut but with some of the forceAtlas2 equations

flowchartTest
Alex de Mulder 9 years ago
parent
commit
cc3e12ad66
12 changed files with 3438 additions and 3146 deletions
  1. +3255
    -3062
      dist/vis.js
  2. +0
    -0
      docs/graph2d/graph2d.html
  3. +5
    -6
      docs/network/new_network.html
  4. +0
    -0
      docs/old_network.html
  5. +1
    -3
      examples/network/28_world_cup_network_performance.html
  6. +36
    -13
      lib/network/modules/PhysicsEngine.js
  7. +36
    -34
      lib/network/modules/components/physics/BarnesHutSolver.js
  8. +16
    -12
      lib/network/modules/components/physics/CentralGravitySolver.js
  9. +23
    -0
      lib/network/modules/components/physics/FA2BasedCentralGravitySolver.js
  10. +35
    -0
      lib/network/modules/components/physics/FA2BasedRepulsionSolver.js
  11. +13
    -14
      lib/network/modules/components/physics/SpringSolver.js
  12. +18
    -2
      lib/network/options.js

+ 3255
- 3062
dist/vis.js
File diff suppressed because it is too large
View File


docs/graph2d.html → docs/graph2d/graph2d.html View File


+ 5
- 6
docs/network/new_network.html View File

@ -212,8 +212,7 @@
<tr>
<td><a href="./interaction.html">interaction</a></td>
<td>Used for all user interaction with the network. Handles mouse and touch events and selection as well as
the navigation
buttons and the popups.
the navigation buttons and the popups.
</td>
</tr>
<tr>
@ -798,11 +797,11 @@ var locales = {
</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','stabilize', this);">
<td colspan="2"><span parent="stabilize" class="right-caret"></span> stabilize()</td>
<td colspan="2"><span parent="stabilize" class="right-caret"></span> stabilize([iterations])</td>
</tr>
<tr class="hidden" parent="stabilize">
<td class="midMethods">Returns: none</td>
<td>You can manually call stabilize at any time. All the stabilization options above are used.</td>
<td>You can manually call stabilize at any time. All the stabilization options above are used. You can optionally supply the number of iterations it should do.</td>
</tr>
@ -1253,12 +1252,12 @@ var options = {
<tr class="">
<td>showPopup</td>
<td class="mid"><code>id of item corresponding to popup</code></td>
<td>Fired when the popup is shown.</td>
<td>Fired when the popup (tooltip) is shown.</td>
</tr>
<tr class="">
<td>hidePopup</td>
<td class="mid">none</td>
<td>Fired when the popup is hidden.</td>
<td>Fired when the popup (tooltip) is hidden.</td>
</tr>
<tr class="subHeader ">
<td colspan="3">Events triggered the physics simulation. Can be used to trigger GUI updates.</td>

docs/network.html → docs/old_network.html View File


+ 1
- 3
examples/network/28_world_cup_network_performance.html View File

@ -80,9 +80,7 @@
}
},
interaction: {
tooltipDelay: 200
},
rendering: {
tooltipDelay: 200,
hideEdgesOnDrag: true
}
};

+ 36
- 13
lib/network/modules/PhysicsEngine.js View File

@ -1,9 +1,11 @@
import BarnesHutSolver from './components/physics/BarnesHutSolver';
import Repulsion from './components/physics/RepulsionSolver';
import HierarchicalRepulsion from './components/physics/HierarchicalRepulsionSolver';
import SpringSolver from './components/physics/SpringSolver';
import HierarchicalSpringSolver from './components/physics/HierarchicalSpringSolver';
import CentralGravitySolver from './components/physics/CentralGravitySolver';
import BarnesHutSolver from './components/physics/BarnesHutSolver';
import Repulsion from './components/physics/RepulsionSolver';
import HierarchicalRepulsion from './components/physics/HierarchicalRepulsionSolver';
import SpringSolver from './components/physics/SpringSolver';
import HierarchicalSpringSolver from './components/physics/HierarchicalSpringSolver';
import CentralGravitySolver from './components/physics/CentralGravitySolver';
import ForceAtlas2BasedRepulsionSolver from './components/physics/FA2BasedRepulsionSolver';
import ForceAtlas2BasedCentralGravitySolver from './components/physics/FA2BasedCentralGravitySolver';
var util = require('../../util');
@ -35,6 +37,14 @@ class PhysicsEngine {
springConstant: 0.04,
damping: 0.09
},
forceAtlas2Based: {
theta: 0.5,
gravitationalConstant: -800,
centralGravity: 0.01,
springConstant: 0.08,
springLength: 100,
damping: 0.4
},
repulsion: {
centralGravity: 0.2,
springLength: 200,
@ -100,30 +110,37 @@ class PhysicsEngine {
util.mergeOptions(this.options, options, 'stabilization')
}
}
this.init();
}
init() {
var options;
if (this.options.solver === 'repulsion') {
if (this.options.solver === 'forceAtlas2Based') {
options = this.options.forceAtlas2Based;
this.nodesSolver = new ForceAtlas2BasedRepulsionSolver(this.body, this.physicsBody, options);
this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
this.gravitySolver = new ForceAtlas2BasedCentralGravitySolver(this.body, this.physicsBody, options);
}
else if (this.options.solver === 'repulsion') {
options = this.options.repulsion;
this.nodesSolver = new Repulsion(this.body, this.physicsBody, options);
this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
}
else if (this.options.solver === 'hierarchicalRepulsion') {
options = this.options.hierarchicalRepulsion;
this.nodesSolver = new HierarchicalRepulsion(this.body, this.physicsBody, options);
this.edgesSolver = new HierarchicalSpringSolver(this.body, this.physicsBody, options);
this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
}
else { // barnesHut
options = this.options.barnesHut;
this.nodesSolver = new BarnesHutSolver(this.body, this.physicsBody, options);
this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
}
this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
this.modelOptions = options;
}
@ -455,7 +472,12 @@ class PhysicsEngine {
* Find a stable position for all nodes
* @private
*/
stabilize() {
stabilize(iterations = this.options.stabilization.iterations) {
if (typeof iterations !== 'number') {
console.log('The stabilize method needs a numeric amount of iterations. Switching to default: ', this.options.stabilization.iterations);
iterations = this.options.stabilization.iterations;
}
// stop the render loop
this.stopSimulation();
@ -466,6 +488,7 @@ class PhysicsEngine {
this.body.emitter.emit('_blockRedrawRequests');
this.body.emitter.emit('startStabilizing');
this.startedStabilization = true;
this.targetIterations = iterations;
// start the stabilization
if (this.options.stabilization.onlyDynamicEdges === true) {
@ -478,14 +501,14 @@ class PhysicsEngine {
_stabilizationBatch() {
var count = 0;
while (this.stabilized === false && count < this.options.stabilization.updateInterval && this.stabilizationIterations < this.options.stabilization.iterations) {
while (this.stabilized === false && count < this.options.stabilization.updateInterval && this.stabilizationIterations < this.targetIterations) {
this.physicsTick();
this.stabilizationIterations++;
count++;
}
if (this.stabilized === false && this.stabilizationIterations < this.options.stabilization.iterations) {
this.body.emitter.emit('stabilizationProgress', {iterations: this.stabilizationIterations, total: this.options.stabilization.iterations});
if (this.stabilized === false && this.stabilizationIterations < this.targetIterations) {
this.body.emitter.emit('stabilizationProgress', {iterations: this.stabilizationIterations, total: this.targetIterations});
setTimeout(this._stabilizationBatch.bind(this),0);
}
else {

+ 36
- 34
lib/network/modules/components/physics/BarnesHutSolver.js View File

@ -1,3 +1,4 @@
class BarnesHutSolver {
constructor(body, physicsBody, options) {
this.body = body;
@ -19,20 +20,20 @@ class BarnesHutSolver {
* @private
*/
solve() {
if (this.options.gravitationalConstant != 0) {
var node;
var nodes = this.body.nodes;
var nodeIndices = this.physicsBody.physicsNodeIndices;
var nodeCount = nodeIndices.length;
if (this.options.gravitationalConstant !== 0 && this.physicsBody.physicsNodeIndices.length > 0) {
let node;
let nodes = this.body.nodes;
let nodeIndices = this.physicsBody.physicsNodeIndices;
let nodeCount = nodeIndices.length;
// create the tree
var barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices);
let barnesHutTree = this._formBarnesHutTree(nodes, nodeIndices);
// for debugging
this.barnesHutTree = barnesHutTree;
// place the nodes one by one recursively
for (var i = 0; i < nodeCount; i++) {
for (let 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
@ -57,7 +58,7 @@ class BarnesHutSolver {
_getForceContribution(parentBranch, node) {
// we get no force contribution from an empty region
if (parentBranch.childrenCount > 0) {
var dx, dy, distance;
let dx, dy, distance;
// get the distance from the center of mass to the node.
dx = parentBranch.centerOfMass.x - node.x;
@ -99,14 +100,15 @@ class BarnesHutSolver {
* @private
*/
_calculateForces(distance, dx, dy, node, parentBranch) {
// duplicate code to reduce function calls to speed up program
if (distance === 0) {
distance = 0.1 * Math.random();
dx = distance;
}
var gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
var fx = dx * gravityForce;
var fy = dy * gravityForce;
// 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
let gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass / Math.pow(distance,3);
let fx = dx * gravityForce;
let fy = dy * gravityForce;
this.physicsBody.forces[node.id].x += fx;
this.physicsBody.forces[node.id].y += fy;
@ -121,18 +123,18 @@ class BarnesHutSolver {
* @private
*/
_formBarnesHutTree(nodes, nodeIndices) {
var node;
var nodeCount = nodeIndices.length;
let node;
let nodeCount = nodeIndices.length;
var minX = Number.MAX_VALUE,
minY = Number.MAX_VALUE,
maxX = -Number.MAX_VALUE,
maxY = -Number.MAX_VALUE;
let minX = nodes[nodeIndices[0]].x;
let minY = nodes[nodeIndices[0]].y;
let maxX = nodes[nodeIndices[0]].x;
let maxY = nodes[nodeIndices[0]].y;
// get the range of the nodes
for (var i = 0; i < nodeCount; i++) {
var x = nodes[nodeIndices[i]].x;
var y = nodes[nodeIndices[i]].y;
for (let i = 1; i < nodeCount; i++) {
let x = nodes[nodeIndices[i]].x;
let y = nodes[nodeIndices[i]].y;
if (nodes[nodeIndices[i]].options.mass > 0) {
if (x < minX) {
minX = x;
@ -149,7 +151,7 @@ class BarnesHutSolver {
}
}
// make the range a square
var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
let 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;
@ -160,13 +162,13 @@ class BarnesHutSolver {
} // 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);
let minimumTreeSize = 1e-5;
let rootSize = Math.max(minimumTreeSize, Math.abs(maxX - minX));
let halfRootSize = 0.5 * rootSize;
let centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
// construct the barnesHutTree
var barnesHutTree = {
let barnesHutTree = {
root: {
centerOfMass: {x: 0, y: 0},
mass: 0,
@ -185,7 +187,7 @@ class BarnesHutSolver {
this._splitBranch(barnesHutTree.root);
// place the nodes one by one recursively
for (i = 0; i < nodeCount; i++) {
for (let i = 0; i < nodeCount; i++) {
node = nodes[nodeIndices[i]];
if (node.options.mass > 0) {
this._placeInTree(barnesHutTree.root, node);
@ -205,8 +207,8 @@ class BarnesHutSolver {
* @private
*/
_updateBranchMass(parentBranch, node) {
var totalMass = parentBranch.mass + node.options.mass;
var totalMassInv = 1 / totalMass;
let totalMass = parentBranch.mass + node.options.mass;
let totalMassInv = 1 / totalMass;
parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
parentBranch.centerOfMass.x *= totalMassInv;
@ -215,7 +217,7 @@ class BarnesHutSolver {
parentBranch.centerOfMass.y *= totalMassInv;
parentBranch.mass = totalMass;
var biggestSize = Math.max(Math.max(node.height, node.radius), node.width);
let biggestSize = Math.max(Math.max(node.height, node.radius), node.width);
parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
}
@ -298,7 +300,7 @@ class BarnesHutSolver {
*/
_splitBranch(parentBranch) {
// if the branch is shaded with a node, replace the node in the new subset.
var containedNode = null;
let containedNode = null;
if (parentBranch.childrenCount === 1) {
containedNode = parentBranch.children.data;
parentBranch.mass = 0;
@ -329,8 +331,8 @@ class BarnesHutSolver {
* @private
*/
_insertRegion(parentBranch, region) {
var minX, maxX, minY, maxY;
var childSize = 0.5 * parentBranch.size;
let minX, maxX, minY, maxY;
let childSize = 0.5 * parentBranch.size;
switch (region) {
case "NW":
minX = parentBranch.range.minX;

+ 16
- 12
lib/network/modules/components/physics/CentralGravitySolver.js View File

@ -10,27 +10,31 @@ class CentralGravitySolver {
}
solve() {
var dx, dy, distance, node, i;
var nodes = this.body.nodes;
var nodeIndices = this.physicsBody.physicsNodeIndices;
var forces = this.physicsBody.forces;
let dx, dy, distance, node;
let nodes = this.body.nodes;
let nodeIndices = this.physicsBody.physicsNodeIndices;
let forces = this.physicsBody.forces;
var gravity = this.options.centralGravity;
var gravityForce = 0;
for (i = 0; i < nodeIndices.length; i++) {
for (let i = 0; i < nodeIndices.length; i++) {
let nodeId = nodeIndices[i];
node = nodes[nodeId];
dx = -node.x;
dy = -node.y;
distance = Math.sqrt(dx * dx + dy * dy);
gravityForce = (distance === 0) ? 0 : (gravity / distance);
forces[nodeId].x = dx * gravityForce;
forces[nodeId].y = dy * gravityForce;
this._calculateForces(distance, dx, dy, forces, node);
}
}
/**
* Calculate the forces based on the distance.
* @private
*/
_calculateForces(distance, dx, dy, forces, node) {
let gravityForce = (distance === 0) ? 0 : (this.options.centralGravity / distance);
forces[node.id].x = dx * gravityForce;
forces[node.id].y = dy * gravityForce;
}
}

+ 23
- 0
lib/network/modules/components/physics/FA2BasedCentralGravitySolver.js View File

@ -0,0 +1,23 @@
import CentralGravitySolver from "./CentralGravitySolver"
class ForceAtlas2BasedCentralGravitySolver extends CentralGravitySolver {
constructor(body, physicsBody, options) {
super(body, physicsBody, options);
}
/**
* Calculate the forces based on the distance.
* @private
*/
_calculateForces(distance, dx, dy, forces, node) {
if (distance > 0) {
let degree = (node.edges.length + 1);
let gravityForce = this.options.centralGravity * degree * node.options.mass;
forces[node.id].x = dx * gravityForce;
forces[node.id].y = dy * gravityForce;
}
}
}
export default ForceAtlas2BasedCentralGravitySolver;

+ 35
- 0
lib/network/modules/components/physics/FA2BasedRepulsionSolver.js View File

@ -0,0 +1,35 @@
import BarnesHutSolver from "./BarnesHutSolver"
class ForceAtlas2BasedRepulsionSolver extends BarnesHutSolver {
constructor(body, physicsBody, options) {
super(body, physicsBody, options);
}
/**
* Calculate the forces based on the distance.
*
* @param distance
* @param dx
* @param dy
* @param node
* @param parentBranch
* @private
*/
_calculateForces(distance, dx, dy, node, parentBranch) {
if (distance === 0) {
distance = 0.1 * Math.random();
dx = distance;
}
let 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
let gravityForce = this.options.gravitationalConstant * parentBranch.mass * node.options.mass * degree / Math.pow(distance,2);
let fx = dx * gravityForce;
let fy = dy * gravityForce;
this.physicsBody.forces[node.id].x += fx;
this.physicsBody.forces[node.id].y += fy;
}
}
export default ForceAtlas2BasedRepulsionSolver;

+ 13
- 14
lib/network/modules/components/physics/SpringSolver.js View File

@ -15,9 +15,10 @@ class SpringSolver {
* @private
*/
solve() {
var edgeLength, edge;
var edgeIndices = this.physicsBody.physicsEdgeIndices;
var edges = this.body.edges;
let edgeLength, edge;
let edgeIndices = this.physicsBody.physicsEdgeIndices;
let edges = this.body.edges;
let node1, node2, node3;
// forces caused by the edges, modelled as springs
for (let i = 0; i < edgeIndices.length; i++) {
@ -27,9 +28,9 @@ class SpringSolver {
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;
var node1 = edge.to;
var node2 = edge.edgeType.via;
var node3 = edge.from;
node1 = edge.to;
node2 = edge.edgeType.via;
node3 = edge.from;
this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
@ -55,18 +56,16 @@ class SpringSolver {
* @private
*/
_calculateSpringForce(node1, node2, edgeLength) {
var dx, dy, fx, fy, springForce, distance;
dx = (node1.x - node2.x);
dy = (node1.y - node2.y);
distance = Math.sqrt(dx * dx + dy * dy);
let dx = (node1.x - node2.x);
let dy = (node1.y - node2.y);
let 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;
let springForce = this.options.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce;
fy = dy * springForce;
let fx = dx * springForce;
let fy = dy * springForce;
// handle the case where one node is not part of the physcis
if (this.physicsBody.forces[node1.id] !== undefined) {

+ 18
- 2
lib/network/options.js View File

@ -223,6 +223,14 @@ let allOptions = {
damping: {number},
__type__: {object}
},
forceAtlas2Based: {
gravitationalConstant: {number},
centralGravity: {number},
springLength: {number},
springConstant: {number},
damping: {number},
__type__: {object}
},
repulsion: {
centralGravity: {number},
springLength: {number},
@ -241,7 +249,7 @@ let allOptions = {
},
maxVelocity: {number},
minVelocity: {number}, // px/s
solver: {string:['barnesHut','repulsion','hierarchicalRepulsion']},
solver: {string:['barnesHut','repulsion','hierarchicalRepulsion','forceAtlas2Based']},
stabilization: {
enabled: {boolean},
iterations: {number}, // maximum number of iteration to stabilize
@ -422,6 +430,14 @@ let configureOptions = {
springConstant: [0.04, 0, 5, 0.005],
damping: [0.09, 0, 1, 0.01]
},
forceAtlas2Based: {
//theta: [0.5, 0.1, 1, 0.05],
gravitationalConstant: [-800, -3000, 0, 50],
centralGravity: [0.01, 0, 1, 0.005],
springLength: [95, 0, 500, 5],
springConstant: [0.08, 0, 5, 0.005],
damping: [0.4, 0, 1, 0.01]
},
repulsion: {
centralGravity: [0.2, 0, 10, 0.05],
springLength: [200, 0, 500, 5],
@ -438,7 +454,7 @@ let configureOptions = {
},
maxVelocity: [50, 0, 150, 1],
minVelocity: [0.1, 0.01, 0.5, 0.01],
solver: ['barnesHut', 'repulsion', 'hierarchicalRepulsion'],
solver: ['barnesHut', 'repulsion', 'hierarchicalRepulsion','forceAtlas2Based'],
timestep: [0.5, 0, 1, 0.05]
},
global: {

Loading…
Cancel
Save