Browse Source

new hierarchical solver

v3_develop
Alex de Mulder 10 years ago
parent
commit
9377a6d2c9
5 changed files with 136 additions and 67 deletions
  1. +1
    -0
      HISTORY.md
  2. +68
    -34
      dist/vis.js
  3. +4
    -4
      lib/network/Network.js
  4. +58
    -29
      lib/network/mixins/physics/HierarchialRepulsionMixin.js
  5. +5
    -0
      lib/network/mixins/physics/PhysicsMixin.js

+ 1
- 0
HISTORY.md View File

@ -30,6 +30,7 @@ http://visjs.org
- Fixed drift in dragging nodes while zooming. - Fixed drift in dragging nodes while zooming.
- Fixed recursively constructing of hierarchical layouts. - Fixed recursively constructing of hierarchical layouts.
- Added borderWidth option for nodes. - Added borderWidth option for nodes.
- Implemented new Hierarchical view solver.

+ 68
- 34
dist/vis.js View File

@ -5,7 +5,7 @@
* A dynamic, browser-based visualization library. * A dynamic, browser-based visualization library.
* *
* @version 3.0.1-SNAPSHOT * @version 3.0.1-SNAPSHOT
* @date 2014-07-15
* @date 2014-07-16
* *
* @license * @license
* Copyright (C) 2011-2014 Almende B.V, http://almende.com * Copyright (C) 2011-2014 Almende B.V, http://almende.com
@ -19256,7 +19256,7 @@ return /******/ (function(modules) { // webpackBootstrap
damping: 0.09 damping: 0.09
}, },
repulsion: { repulsion: {
centralGravity: 0.1,
centralGravity: 0.0,
springLength: 200, springLength: 200,
springConstant: 0.05, springConstant: 0.05,
nodeDistance: 100, nodeDistance: 100,
@ -19264,10 +19264,10 @@ return /******/ (function(modules) { // webpackBootstrap
}, },
hierarchicalRepulsion: { hierarchicalRepulsion: {
enabled: false, enabled: false,
centralGravity: 0.5,
springLength: 150,
centralGravity: 0.0,
springLength: 100,
springConstant: 0.01, springConstant: 0.01,
nodeDistance: 60,
nodeDistance: 150,
damping: 0.09 damping: 0.09
}, },
damping: null, damping: null,
@ -26348,6 +26348,11 @@ return /******/ (function(modules) { // webpackBootstrap
} }
if (this.constants.hierarchicalLayout.enabled == true) { if (this.constants.hierarchicalLayout.enabled == true) {
this._setupHierarchicalLayout(); this._setupHierarchicalLayout();
showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
} }
else { else {
this.repositionNodes(); this.repositionNodes();
@ -26605,29 +26610,23 @@ return /******/ (function(modules) { // webpackBootstrap
* @private * @private
*/ */
exports._calculateNodeForces = function () { exports._calculateNodeForces = function () {
var dx, dy, distance, fx, fy, combinedClusterSize,
var dx, dy, distance, fx, fy,
repulsingForce, node1, node2, i, j; repulsingForce, node1, node2, i, j;
var nodes = this.calculationNodes; var nodes = this.calculationNodes;
var nodeIndices = this.calculationNodeIndices; var nodeIndices = this.calculationNodeIndices;
// approximation constants
var b = 5;
var a_base = 0.5 * -b;
// repulsing forces between nodes // repulsing forces between nodes
var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance; var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance;
var minimumDistance = nodeDistance;
var a = a_base / minimumDistance;
// we loop from i over all but the last entree in the array // 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 // 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++) { for (i = 0; i < nodeIndices.length - 1; i++) {
node1 = nodes[nodeIndices[i]]; node1 = nodes[nodeIndices[i]];
for (j = i + 1; j < nodeIndices.length; j++) { for (j = i + 1; j < nodeIndices.length; j++) {
node2 = nodes[nodeIndices[j]]; node2 = nodes[nodeIndices[j]];
// nodes only affect nodes on their level
if (node1.level == node2.level) { if (node1.level == node2.level) {
dx = node2.x - node1.x; dx = node2.x - node1.x;
@ -26635,12 +26634,13 @@ return /******/ (function(modules) { // webpackBootstrap
distance = Math.sqrt(dx * dx + dy * dy); distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 2 * minimumDistance) {
repulsingForce = a * distance + b;
var c = 0.05;
var d = 2 * minimumDistance * 2 * c;
repulsingForce = c * Math.pow(distance,2) - d * distance + d*d/(4*c);
var steepness = 0.05;
if (distance < nodeDistance) {
repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2);
}
else {
repulsingForce = 0;
}
// normalize force with // normalize force with
if (distance == 0) { if (distance == 0) {
distance = 0.01; distance = 0.01;
@ -26655,7 +26655,6 @@ return /******/ (function(modules) { // webpackBootstrap
node1.fy -= fy; node1.fy -= fy;
node2.fx += fx; node2.fx += fx;
node2.fy += fy; node2.fy += fy;
}
} }
} }
} }
@ -26672,6 +26671,17 @@ return /******/ (function(modules) { // webpackBootstrap
var dx, dy, fx, fy, springForce, distance; var dx, dy, fx, fy, springForce, distance;
var edges = this.edges; var edges = this.edges;
var nodes = this.calculationNodes;
var nodeIndices = this.calculationNodeIndices;
for (var i = 0; i < nodeIndices.length; i++) {
var node1 = nodes[nodeIndices[i]];
node1.springFx = 0;
node1.springFy = 0;
}
// forces caused by the edges, modelled as springs // forces caused by the edges, modelled as springs
for (edgeId in edges) { for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) { if (edges.hasOwnProperty(edgeId)) {
@ -26691,30 +26701,24 @@ return /******/ (function(modules) { // webpackBootstrap
distance = 0.01; distance = 0.01;
} }
distance = Math.max(0.8*edgeLength,Math.min(5*edgeLength, distance));
// the 1/distance is so the fx and fy can be calculated without sine or cosine. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance; springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce; fx = dx * springForce;
fy = dy * springForce; fy = dy * springForce;
edge.to.fx -= fx;
edge.to.fy -= fy;
edge.from.fx += fx;
edge.from.fy += fy;
var factor = 5;
if (distance > edgeLength) {
factor = 25;
if (edge.to.level != edge.from.level) {
edge.to.springFx -= fx;
edge.to.springFy -= fy;
edge.from.springFx += fx;
edge.from.springFy += fy;
} }
if (edge.from.level > edge.to.level) {
else {
var factor = 0.5;
edge.to.fx -= factor*fx; edge.to.fx -= factor*fx;
edge.to.fy -= factor*fy; edge.to.fy -= factor*fy;
}
else if (edge.from.level < edge.to.level) {
edge.from.fx += factor*fx; edge.from.fx += factor*fx;
edge.from.fy += factor*fy; edge.from.fy += factor*fy;
} }
@ -26722,6 +26726,36 @@ return /******/ (function(modules) { // webpackBootstrap
} }
} }
} }
// normalize spring forces
var springForce = 1;
var springFx, springFy;
for (i = 0; i < nodeIndices.length; i++) {
var node = nodes[nodeIndices[i]];
springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
node.fx += springFx;
node.fy += springFy;
}
// retain energy balance
var totalFx = 0;
var totalFy = 0;
for (i = 0; i < nodeIndices.length; i++) {
var node = nodes[nodeIndices[i]];
totalFx += node.fx;
totalFy += node.fy;
}
var correctionFx = totalFx / nodeIndices.length;
var correctionFy = totalFy / nodeIndices.length;
for (i = 0; i < nodeIndices.length; i++) {
var node = nodes[nodeIndices[i]];
node.fx -= correctionFx;
node.fy -= correctionFy;
}
}; };
/***/ }, /***/ },

+ 4
- 4
lib/network/Network.js View File

@ -122,7 +122,7 @@ function Network (container, data, options) {
damping: 0.09 damping: 0.09
}, },
repulsion: { repulsion: {
centralGravity: 0.1,
centralGravity: 0.0,
springLength: 200, springLength: 200,
springConstant: 0.05, springConstant: 0.05,
nodeDistance: 100, nodeDistance: 100,
@ -130,10 +130,10 @@ function Network (container, data, options) {
}, },
hierarchicalRepulsion: { hierarchicalRepulsion: {
enabled: false, enabled: false,
centralGravity: 0.5,
springLength: 150,
centralGravity: 0.0,
springLength: 100,
springConstant: 0.01, springConstant: 0.01,
nodeDistance: 60,
nodeDistance: 150,
damping: 0.09 damping: 0.09
}, },
damping: null, damping: null,

+ 58
- 29
lib/network/mixins/physics/HierarchialRepulsionMixin.js View File

@ -5,29 +5,23 @@
* @private * @private
*/ */
exports._calculateNodeForces = function () { exports._calculateNodeForces = function () {
var dx, dy, distance, fx, fy, combinedClusterSize,
var dx, dy, distance, fx, fy,
repulsingForce, node1, node2, i, j; repulsingForce, node1, node2, i, j;
var nodes = this.calculationNodes; var nodes = this.calculationNodes;
var nodeIndices = this.calculationNodeIndices; var nodeIndices = this.calculationNodeIndices;
// approximation constants
var b = 5;
var a_base = 0.5 * -b;
// repulsing forces between nodes // repulsing forces between nodes
var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance; var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance;
var minimumDistance = nodeDistance;
var a = a_base / minimumDistance;
// we loop from i over all but the last entree in the array // 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 // 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++) { for (i = 0; i < nodeIndices.length - 1; i++) {
node1 = nodes[nodeIndices[i]]; node1 = nodes[nodeIndices[i]];
for (j = i + 1; j < nodeIndices.length; j++) { for (j = i + 1; j < nodeIndices.length; j++) {
node2 = nodes[nodeIndices[j]]; node2 = nodes[nodeIndices[j]];
// nodes only affect nodes on their level
if (node1.level == node2.level) { if (node1.level == node2.level) {
dx = node2.x - node1.x; dx = node2.x - node1.x;
@ -35,12 +29,13 @@ exports._calculateNodeForces = function () {
distance = Math.sqrt(dx * dx + dy * dy); distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 2 * minimumDistance) {
repulsingForce = a * distance + b;
var c = 0.05;
var d = 2 * minimumDistance * 2 * c;
repulsingForce = c * Math.pow(distance,2) - d * distance + d*d/(4*c);
var steepness = 0.05;
if (distance < nodeDistance) {
repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2);
}
else {
repulsingForce = 0;
}
// normalize force with // normalize force with
if (distance == 0) { if (distance == 0) {
distance = 0.01; distance = 0.01;
@ -55,7 +50,6 @@ exports._calculateNodeForces = function () {
node1.fy -= fy; node1.fy -= fy;
node2.fx += fx; node2.fx += fx;
node2.fy += fy; node2.fy += fy;
}
} }
} }
} }
@ -72,6 +66,17 @@ exports._calculateHierarchicalSpringForces = function () {
var dx, dy, fx, fy, springForce, distance; var dx, dy, fx, fy, springForce, distance;
var edges = this.edges; var edges = this.edges;
var nodes = this.calculationNodes;
var nodeIndices = this.calculationNodeIndices;
for (var i = 0; i < nodeIndices.length; i++) {
var node1 = nodes[nodeIndices[i]];
node1.springFx = 0;
node1.springFy = 0;
}
// forces caused by the edges, modelled as springs // forces caused by the edges, modelled as springs
for (edgeId in edges) { for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) { if (edges.hasOwnProperty(edgeId)) {
@ -91,30 +96,24 @@ exports._calculateHierarchicalSpringForces = function () {
distance = 0.01; distance = 0.01;
} }
distance = Math.max(0.8*edgeLength,Math.min(5*edgeLength, distance));
// the 1/distance is so the fx and fy can be calculated without sine or cosine. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance; springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
fx = dx * springForce; fx = dx * springForce;
fy = dy * springForce; fy = dy * springForce;
edge.to.fx -= fx;
edge.to.fy -= fy;
edge.from.fx += fx;
edge.from.fy += fy;
var factor = 5;
if (distance > edgeLength) {
factor = 25;
if (edge.to.level != edge.from.level) {
edge.to.springFx -= fx;
edge.to.springFy -= fy;
edge.from.springFx += fx;
edge.from.springFy += fy;
} }
if (edge.from.level > edge.to.level) {
else {
var factor = 0.5;
edge.to.fx -= factor*fx; edge.to.fx -= factor*fx;
edge.to.fy -= factor*fy; edge.to.fy -= factor*fy;
}
else if (edge.from.level < edge.to.level) {
edge.from.fx += factor*fx; edge.from.fx += factor*fx;
edge.from.fy += factor*fy; edge.from.fy += factor*fy;
} }
@ -122,4 +121,34 @@ exports._calculateHierarchicalSpringForces = function () {
} }
} }
} }
// normalize spring forces
var springForce = 1;
var springFx, springFy;
for (i = 0; i < nodeIndices.length; i++) {
var node = nodes[nodeIndices[i]];
springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
node.fx += springFx;
node.fy += springFy;
}
// retain energy balance
var totalFx = 0;
var totalFy = 0;
for (i = 0; i < nodeIndices.length; i++) {
var node = nodes[nodeIndices[i]];
totalFx += node.fx;
totalFy += node.fy;
}
var correctionFx = totalFx / nodeIndices.length;
var correctionFy = totalFy / nodeIndices.length;
for (i = 0; i < nodeIndices.length; i++) {
var node = nodes[nodeIndices[i]];
node.fx -= correctionFx;
node.fy -= correctionFy;
}
}; };

+ 5
- 0
lib/network/mixins/physics/PhysicsMixin.js View File

@ -521,6 +521,11 @@ function graphRepositionNodes () {
} }
if (this.constants.hierarchicalLayout.enabled == true) { if (this.constants.hierarchicalLayout.enabled == true) {
this._setupHierarchicalLayout(); this._setupHierarchicalLayout();
showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
} }
else { else {
this.repositionNodes(); this.repositionNodes();

Loading…
Cancel
Save