Browse Source

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

flowchartTest
Alex de Mulder 10 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