vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

530 lines
23 KiB

/**
* Created by Alex on 2/6/14.
*/
var physicsMixin = {
/**
* Toggling barnes Hut calculation on and off.
*
* @private
*/
_toggleBarnesHut : function() {
this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
this._loadSelectedForceSolver();
this.moving = true;
this.start();
},
/**
* This loads the node force solver based on the barnes hut or repulsion algorithm
*
* @private
*/
_loadSelectedForceSolver : function() {
// this overloads the this._calculateNodeForces
if (this.constants.physics.barnesHut.enabled == true) {
this._clearMixin(repulsionMixin);
this._clearMixin(hierarchalRepulsionMixin);
this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
this.constants.physics.damping = this.constants.physics.barnesHut.damping;
this._loadMixin(barnesHutMixin);
}
else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
this._clearMixin(barnesHutMixin);
this._clearMixin(repulsionMixin);
this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
this._loadMixin(hierarchalRepulsionMixin);
}
else {
this._clearMixin(barnesHutMixin);
this._clearMixin(hierarchalRepulsionMixin);
this.barnesHutTree = undefined;
this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
this.constants.physics.damping = this.constants.physics.repulsion.damping;
this._loadMixin(repulsionMixin);
}
},
/**
* Before calculating the forces, we check if we need to cluster to keep up performance and we check
* if there is more than one node. If it is just one node, we dont calculate anything.
*
* @private
*/
_initializeForceCalculation : function() {
// stop calculation if there is only one node
if (this.nodeIndices.length == 1) {
this.nodes[this.nodeIndices[0]]._setForce(0,0);
}
else {
// if there are too many nodes on screen, we cluster without repositioning
if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
this.clusterToFit(this.constants.clustering.reduceToNodes, false);
}
// we now start the force calculation
this._calculateForces();
}
},
/**
* Calculate the external forces acting on the nodes
* Forces are caused by: edges, repulsing forces between nodes, gravity
* @private
*/
_calculateForces : function() {
// Gravity is required to keep separated groups from floating off
// the forces are reset to zero in this loop by using _setForce instead
// of _addForce
this._calculateGravitationalForces();
this._calculateNodeForces();
if (this.constants.smoothCurves == true) {
this._calculateSpringForcesWithSupport();
}
else {
this._calculateSpringForces();
}
},
/**
* Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
* handled in the calculateForces function. We then use a quadratic curve with the center node as control.
* This function joins the datanodes and invisible (called support) nodes into one object.
* We do this so we do not contaminate this.nodes with the support nodes.
*
* @private
*/
_updateCalculationNodes : function() {
if (this.constants.smoothCurves == true) {
this.calculationNodes = {};
this.calculationNodeIndices = [];
for (var nodeId in this.nodes) {
if (this.nodes.hasOwnProperty(nodeId)) {
this.calculationNodes[nodeId] = this.nodes[nodeId];
}
}
var supportNodes = this.sectors['support']['nodes'];
for (var supportNodeId in supportNodes) {
if (supportNodes.hasOwnProperty(supportNodeId)) {
if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
}
else {
supportNodes[supportNodeId]._setForce(0,0);
}
}
}
for (var idx in this.calculationNodes) {
if (this.calculationNodes.hasOwnProperty(idx)) {
this.calculationNodeIndices.push(idx);
}
}
}
else {
this.calculationNodes = this.nodes;
this.calculationNodeIndices = this.nodeIndices;
}
},
/**
* this function applies the central gravity effect to keep groups from floating off
*
* @private
*/
_calculateGravitationalForces : function() {
var dx, dy, distance, node, i;
var nodes = this.calculationNodes;
var gravity = this.constants.physics.centralGravity;
var gravityForce = 0;
for (i = 0; i < this.calculationNodeIndices.length; i++) {
node = nodes[this.calculationNodeIndices[i]];
node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
// gravity does not apply when we are in a pocket sector
if (this._sector() == "default" && gravity != 0) {
dx = -node.x;
dy = -node.y;
distance = Math.sqrt(dx*dx + dy*dy);
gravityForce = gravity / distance;
node.fx = dx * gravityForce;
node.fy = dy * gravityForce;
}
else {
node.fx = 0;
node.fy = 0;
}
}
},
/**
* this function calculates the effects of the springs in the case of unsmooth curves.
*
* @private
*/
_calculateSpringForces : function() {
var edgeLength, edge, edgeId;
var dx, dy, fx, fy, springForce, length;
var edges = this.edges;
// forces caused by the edges, modelled as springs
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
edgeLength = edge.customLength ? edge.length : this.constants.physics.springLength;
// this implies that the edges between big clusters are longer
edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
dx = (edge.from.x - edge.to.x);
dy = (edge.from.y - edge.to.y);
length = Math.sqrt(dx * dx + dy * dy);
if (length == 0) {
length = 0.01;
}
springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
fx = dx * springForce;
fy = dy * springForce;
edge.from.fx += fx;
edge.from.fy += fy;
edge.to.fx -= fx;
edge.to.fy -= fy;
}
}
}
}
},
/**
* This function calculates the springforces on the nodes, accounting for the support nodes.
*
* @private
*/
_calculateSpringForcesWithSupport : function() {
var edgeLength, edge, edgeId, combinedClusterSize;
var edges = this.edges;
// forces caused by the edges, modelled as springs
for (edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
edge = edges[edgeId];
if (edge.connected) {
// only calculate forces if nodes are in the same sector
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
if (edge.via != null) {
var node1 = edge.to;
var node2 = edge.via;
var node3 = edge.from;
edgeLength = edge.customLength ? edge.length : this.constants.physics.springLength;
combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
// this implies that the edges between big clusters are longer
edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
this._calculateSpringForce(node1,node2,0.5*edgeLength);
this._calculateSpringForce(node2,node3,0.5*edgeLength);
}
}
}
}
}
},
/**
* This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
*
* @param node1
* @param node2
* @param edgeLength
* @private
*/
_calculateSpringForce : function(node1,node2,edgeLength) {
var dx, dy, fx, fy, springForce, length;
dx = (node1.x - node2.x);
dy = (node1.y - node2.y);
length = Math.sqrt(dx * dx + dy * dy);
springForce = this.constants.physics.springConstant * (edgeLength - length) / length;
if (length == 0) {
length = 0.01;
}
fx = dx * springForce;
fy = dy * springForce;
node1.fx += fx;
node1.fy += fy;
node2.fx -= fx;
node2.fy -= fy;
},
/**
* Load the HTML for the physics config and bind it
* @private
*/
_loadPhysicsConfiguration : function() {
if (this.physicsConfiguration === undefined) {
this.physicsConfiguration = document.createElement('div');
this.physicsConfiguration.className = "PhysicsConfiguration";
this.physicsConfiguration.innerHTML = '' +
'<table><tr><td><b>Simulation Mode:</b></td></tr>' +
'<tr>' +
'<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
'<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>'+
'<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
'</tr>'+
'</table>' +
'<table id="graph_BH_table" style="display:none">'+
'<tr><td><b>Barnes Hut</b></td></tr>'+
'<tr>'+
'<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="500" max="20000" value="2000" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-20000</td><td><input value="-2000" id="graph_BH_gc_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="0.3" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="0.03" id="graph_BH_cg_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="100" step="1" style="width:300px" id="graph_BH_sl"></td><td>500</td><td><input value="100" id="graph_BH_sl_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="0.05" step="0.005" style="width:300px" id="graph_BH_sc"></td><td>0.5</td><td><input value="0.05" id="graph_BH_sc_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="0.09" step="0.005" style="width:300px" id="graph_BH_damp"></td><td>0.3</td><td><input value="0.09" id="graph_BH_damp_value" style="width:60px"></td>'+
'</tr>'+
'</table>'+
'<table id="graph_R_table" style="display:none">'+
'<tr><td><b>Repulsion</b></td></tr>'+
'<tr>'+
'<td width="150px">nodeDistance</td><td>0</td><td><input type="range" min="0" max="300" value="100" step="1" style="width:300px" id="graph_R_nd"></td><td width="50px">300</td><td><input value="100" id="graph_R_nd_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="0.1" step="0.05" style="width:300px" id="graph_R_cg"></td><td>3</td><td><input value="0.01" id="graph_R_cg_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="200" step="1" style="width:300px" id="graph_R_sl"></td><td>500</td><td><input value="200" id="graph_R_sl_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="0.05" step="0.005" style="width:300px" id="graph_R_sc"></td><td>0.5</td><td><input value="0.05" id="graph_R_sc_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="0.09" step="0.005" style="width:300px" id="graph_R_damp"></td><td>0.3</td><td><input value="0.09" id="graph_R_damp_value" style="width:60px"></td>'+
'</tr>'+
'</table>'+
'<table id="graph_H_table" style="display:none">'+
'<tr><td width="150"><b>Hierarchical</b></td></tr>'+
'<tr>'+
'<td width="150px">nodeDistance</td><td>0</td><td><input type="range" min="0" max="300" value="60" step="1" style="width:300px" id="graph_H_nd"></td><td width="50px">300</td><td><input value="60" id="graph_H_nd_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="0" step="0.05" style="width:300px" id="graph_H_cg"></td><td>3</td><td><input value="0" id="graph_H_cg_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="100" step="1" style="width:300px" id="graph_H_sl"></td><td>500</td><td><input value="100" id="graph_H_sl_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="0.01" step="0.005" style="width:300px" id="graph_H_sc"></td><td>0.5</td><td><input value="0.01" id="graph_H_sc_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="0.09" step="0.005" style="width:300px" id="graph_H_damp"></td><td>0.3</td><td><input value="0.09" id="graph_H_damp_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">direction</td><td>1</td><td><input type="range" min="0" max="3" value="0" step="1" style="width:300px" id="graph_H_direction"></td><td>4</td><td><input value="LR" id="graph_H_direction_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">levelSeparation</td><td>1</td><td><input type="range" min="0" max="500" value="150" step="1" style="width:300px" id="graph_H_levsep"></td><td>500</td><td><input value="150" id="graph_H_levsep_value" style="width:60px"></td>'+
'</tr>'+
'<tr>'+
'<td width="150px">nodeSpacing</td><td>1</td><td><input type="range" min="0" max="500" value="100" step="1" style="width:300px" id="graph_H_nspac"></td><td>500</td><td><input value="100" id="graph_H_nspac_value" style="width:60px"></td>'+
'</tr>'+
'</table>'
this.containerElement.parentElement.insertBefore(this.physicsConfiguration,this.containerElement);
var hierarchicalLayoutDirections = ["LR","RL","UD","DU"];
var rangeElement;
rangeElement = document.getElementById('graph_BH_gc');
rangeElement.innerHTML = this.constants.physics.barnesHut.gravitationalConstant;
rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_gc',-1,"physics_barnesHut_gravitationalConstant");
rangeElement = document.getElementById('graph_BH_cg');
rangeElement.innerHTML = this.constants.physics.barnesHut.centralGravity;
rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_cg',1,"physics_centralGravity");
rangeElement = document.getElementById('graph_BH_sc');
rangeElement.innerHTML = this.constants.physics.barnesHut.springConstant;
rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_sc',1,"physics_springConstant");
rangeElement = document.getElementById('graph_BH_sl');
rangeElement.innerHTML = this.constants.physics.barnesHut.springLength;
rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_sl',1,"physics_springLength");
rangeElement = document.getElementById('graph_BH_damp');
rangeElement.innerHTML = this.constants.physics.barnesHut.damping;
rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_damp',1,"physics_damping");
rangeElement = document.getElementById('graph_R_nd');
rangeElement.innerHTML = this.constants.physics.repulsion.nodeDistance;
rangeElement.onchange = showValueOfRange.bind(this,'graph_R_nd',1,"physics_repulsion_nodeDistance");
rangeElement = document.getElementById('graph_R_cg');
rangeElement.innerHTML = this.constants.physics.repulsion.centralGravity;
rangeElement.onchange = showValueOfRange.bind(this,'graph_R_cg',1,"physics_centralGravity");
rangeElement = document.getElementById('graph_R_sc');
rangeElement.innerHTML = this.constants.physics.repulsion.springConstant;
rangeElement.onchange = showValueOfRange.bind(this,'graph_R_sc',1,"physics_springConstant");
rangeElement = document.getElementById('graph_R_sl');
rangeElement.innerHTML = this.constants.physics.repulsion.springLength;
rangeElement.onchange = showValueOfRange.bind(this,'graph_R_sl',1,"physics_springLength");
rangeElement = document.getElementById('graph_R_damp');
rangeElement.innerHTML = this.constants.physics.repulsion.damping;
rangeElement.onchange = showValueOfRange.bind(this,'graph_R_damp',1,"physics_damping");
rangeElement = document.getElementById('graph_H_nd');
rangeElement.innerHTML = this.constants.physics.hierarchicalRepulsion.nodeDistance;
rangeElement.onchange = showValueOfRange.bind(this,'graph_H_nd',1,"physics_hierarchicalRepulsion_nodeDistance");
rangeElement = document.getElementById('graph_H_cg');
rangeElement.innerHTML = this.constants.physics.hierarchicalRepulsion.centralGravity;
rangeElement.onchange = showValueOfRange.bind(this,'graph_H_cg',1,"physics_centralGravity");
rangeElement = document.getElementById('graph_H_sc');
rangeElement.innerHTML = this.constants.physics.hierarchicalRepulsion.springConstant;
rangeElement.onchange = showValueOfRange.bind(this,'graph_H_sc',1,"physics_springConstant");
rangeElement = document.getElementById('graph_H_sl');
rangeElement.innerHTML = this.constants.physics.hierarchicalRepulsion.springLength;
rangeElement.onchange = showValueOfRange.bind(this,'graph_H_sl',1,"physics_springLength");
rangeElement = document.getElementById('graph_H_damp');
rangeElement.innerHTML = this.constants.physics.hierarchicalRepulsion.damping;
rangeElement.onchange = showValueOfRange.bind(this,'graph_H_damp',1,"physics_damping");
rangeElement = document.getElementById('graph_H_direction');
rangeElement.innerHTML = hierarchicalLayoutDirections.indexOf(this.constants.hierarchicalLayout.direction);
rangeElement.onchange = showValueOfRange.bind(this,'graph_H_direction',hierarchicalLayoutDirections,"hierarchicalLayout_direction");
rangeElement = document.getElementById('graph_H_levsep');
rangeElement.innerHTML = this.constants.hierarchicalLayout.levelSeparation;
rangeElement.onchange = showValueOfRange.bind(this,'graph_H_levsep',1,"hierarchicalLayout_levelSeparation");
rangeElement = document.getElementById('graph_H_nspac');
rangeElement.innerHTML = this.constants.hierarchicalLayout.nodeSpacing;
rangeElement.onchange = showValueOfRange.bind(this,'graph_H_nspac',1,"hierarchicalLayout_nodeSpacing");
var radioButton1 = document.getElementById("graph_physicsMethod1");
var radioButton2 = document.getElementById("graph_physicsMethod2");
var radioButton3 = document.getElementById("graph_physicsMethod3");
radioButton2.checked = true;
if (this.constants.physics.barnesHut.enabled) {
radioButton1.checked = true;
}
if (this.constants.hierarchicalLayout.enabled) {
radioButton3.checked = true;
}
switchConfigurations.apply(this);
radioButton1.onchange = switchConfigurations.bind(this);
radioButton2.onchange = switchConfigurations.bind(this);
radioButton3.onchange = switchConfigurations.bind(this);
}
},
_overWriteGraphConstants : function(constantsVariableName, value) {
var nameArray = constantsVariableName.split("_");
if (nameArray.length == 1) {
this.constants[nameArray[0]] = value;
}
else if (nameArray.length == 2) {
this.constants[nameArray[0]][nameArray[1]] = value;
}
else if (nameArray.length == 3) {
this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
}
}
}
function switchConfigurations () {
var ids = ["graph_BH_table","graph_R_table","graph_H_table"]
var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
var tableId = "graph_" + radioButton + "_table";
var table = document.getElementById(tableId);
table.style.display = "block";
for (var i = 0; i < ids.length; i++) {
if (ids[i] != tableId) {
table = document.getElementById(ids[i]);
table.style.display = "none";
}
}
this._restoreNodes();
if (radioButton == "R") {
this.constants.hierarchicalLayout.enabled = false;
this.constants.physics.hierarchicalRepulsion.enabeled = false;
this.constants.physics.barnesHut.enabled = false;
}
else if (radioButton == "H") {
this.constants.hierarchicalLayout.enabled = true;
this.constants.physics.hierarchicalRepulsion.enabeled = true;
this.constants.physics.barnesHut.enabled = false;
this._setupHierarchicalLayout();
}
else {
this.constants.hierarchicalLayout.enabled = false;
this.constants.physics.hierarchicalRepulsion.enabeled = false;
this.constants.physics.barnesHut.enabled = true;
}
this._loadSelectedForceSolver();
this.moving = true;
this.start();
}
function showValueOfRange (id,map,constantsVariableName) {
var valueId = id + "_value";
var rangeValue = document.getElementById(id).value;
if (constantsVariableName == "hierarchicalLayout_direction" ||
constantsVariableName == "hierarchicalLayout_levelSeparation" ||
constantsVariableName == "hierarchicalLayout_nodeSpacing") {
this._setupHierarchicalLayout();
}
if (map instanceof Array) {
document.getElementById(valueId).value = map[parseInt(rangeValue)];
this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
}
else {
document.getElementById(valueId).value = map * parseFloat(rangeValue);
this._overWriteGraphConstants(constantsVariableName,map * parseFloat(rangeValue));
}
this.moving = true;
this.start();
};