Browse Source

finalized smooth edges and force calculation

css_transitions
Alex de Mulder 10 years ago
parent
commit
0bd417bdec
5 changed files with 55 additions and 147 deletions
  1. +39
    -126
      src/graph/Graph.js
  2. +8
    -10
      src/graph/Node.js
  3. +1
    -0
      src/graph/graphMixins/ClusterMixin.js
  4. +7
    -9
      src/graph/graphMixins/physics/PhysicsMixin.js
  5. +0
    -2
      src/graph/graphMixins/physics/repulsion.js

+ 39
- 126
src/graph/Graph.js View File

@ -56,7 +56,7 @@ function Graph (container, data, options) {
widthMax: 15,
width: 1,
style: 'line',
color: '#343434',
color: '#848484',
fontColor: '#343434',
fontSize: 14, // px
fontFace: 'arial',
@ -71,16 +71,17 @@ function Graph (container, data, options) {
physics: {
barnesHut: {
enabled: true,
theta: 1 / 0.3, // inverted to save time during calculation
gravitationalConstant: -7500,
theta: 1 / 0.5, // inverted to save time during calculation
gravitationalConstant: -3000,
centralGravity: 0.9,
springLength: 25,
springConstant: 0.05
springLength: 40,
springConstant: 0.04
},
repulsion: {
centralGravity: 0.01,
springLength: 60,
springConstant: 0.05
springLength: 80,
springConstant: 0.05,
nodeDistance: 100
},
centralGravity: null,
springLength: null,
@ -99,7 +100,7 @@ function Graph (container, data, options) {
forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
maxFontSize: 1000,
distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
edgeGrowth: 1, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
height: 1, // (px PNiC) | growth of the height per node in cluster.
radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
@ -119,7 +120,7 @@ function Graph (container, data, options) {
enabled: false
},
smoothCurves: true,
maxVelocity: 35,
maxVelocity: 25,
minVelocity: 0.1, // px/s
maxIterations: 1000 // maximum number of iteration to stabilize
};
@ -172,9 +173,6 @@ function Graph (container, data, options) {
this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
this.scale = 1; // defining the global scale variable in the constructor
this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
// TODO: create a counter to keep track on the number of nodes having values
// TODO: create a counter to keep track on the number of nodes currently moving
// TODO: create a counter to keep track on the number of edges having values
this.nodesData = null; // A DataSet or DataView
this.edgesData = null; // A DataSet or DataView
@ -218,7 +216,7 @@ function Graph (container, data, options) {
// load data (the disable start variable will be the same as the enabled clustering)
this.setData(data,this.constants.clustering.enabled);
// zoom so all data will fit on the screen
// zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
this.zoomToFit(true,this.constants.clustering.enabled);
// if clustering is disabled, the simulation will have started in the setData function
@ -274,9 +272,8 @@ Graph.prototype._getRange = function() {
* @private
*/
Graph.prototype._findCenter = function(range) {
var center = {x: (0.5 * (range.maxX + range.minX)),
y: (0.5 * (range.maxY + range.minY))};
return center;
return {x: (0.5 * (range.maxX + range.minX)),
y: (0.5 * (range.maxY + range.minY))};
};
@ -309,14 +306,15 @@ Graph.prototype.zoomToFit = function(initialZoom, doNotStart) {
var numberOfNodes = this.nodeIndices.length;
var range = this._getRange();
var zoomLevel;
if (initialZoom == true) {
if (this.constants.clustering.enabled == true &&
numberOfNodes >= this.constants.clustering.initialMaxNodes) {
var zoomLevel = 38.8467 / (numberOfNodes - 14.50184) + 0.0116; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
zoomLevel = 38.8467 / (numberOfNodes - 14.50184) + 0.0116; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
}
else {
var zoomLevel = 42.54117319 / (numberOfNodes + 39.31966387) + 0.1944405; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
zoomLevel = 42.54117319 / (numberOfNodes + 39.31966387) + 0.1944405; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
}
}
else {
@ -411,6 +409,7 @@ Graph.prototype.setData = function(data, disableStart) {
*/
Graph.prototype.setOptions = function (options) {
if (options) {
var prop;
// retrieve parameter values
if (options.width !== undefined) {this.width = options.width;}
if (options.height !== undefined) {this.height = options.height;}
@ -420,7 +419,7 @@ Graph.prototype.setOptions = function (options) {
if (options.physics) {
if (options.physics.barnesHut) {
this.constants.physics.barnesHut.enabled = true;
for (var prop in options.physics.barnesHut) {
for (prop in options.physics.barnesHut) {
if (options.physics.barnesHut.hasOwnProperty(prop)) {
this.constants.physics.barnesHut[prop] = options.physics.barnesHut[prop];
}
@ -429,7 +428,7 @@ Graph.prototype.setOptions = function (options) {
if (options.physics.repulsion) {
this.constants.physics.barnesHut.enabled = false;
for (var prop in options.physics.repulsion) {
for (prop in options.physics.repulsion) {
if (options.physics.repulsion.hasOwnProperty(prop)) {
this.constants.physics.repulsion[prop] = options.physics.repulsion[prop];
}
@ -439,7 +438,7 @@ Graph.prototype.setOptions = function (options) {
if (options.clustering) {
this.constants.clustering.enabled = true;
for (var prop in options.clustering) {
for (prop in options.clustering) {
if (options.clustering.hasOwnProperty(prop)) {
this.constants.clustering[prop] = options.clustering[prop];
}
@ -451,7 +450,7 @@ Graph.prototype.setOptions = function (options) {
if (options.navigation) {
this.constants.navigation.enabled = true;
for (var prop in options.navigation) {
for (prop in options.navigation) {
if (options.navigation.hasOwnProperty(prop)) {
this.constants.navigation[prop] = options.navigation[prop];
}
@ -463,7 +462,7 @@ Graph.prototype.setOptions = function (options) {
if (options.keyboard) {
this.constants.keyboard.enabled = true;
for (var prop in options.keyboard) {
for (prop in options.keyboard) {
if (options.keyboard.hasOwnProperty(prop)) {
this.constants.keyboard[prop] = options.keyboard[prop];
}
@ -475,7 +474,7 @@ Graph.prototype.setOptions = function (options) {
if (options.dataManipulationToolbar) {
this.constants.dataManipulationToolbar.enabled = true;
for (var prop in options.dataManipulationToolbar) {
for (prop in options.dataManipulationToolbar) {
if (options.dataManipulationToolbar.hasOwnProperty(prop)) {
this.constants.dataManipulationToolbar[prop] = options.dataManipulationToolbar[prop];
}
@ -493,12 +492,6 @@ Graph.prototype.setOptions = function (options) {
}
}
if (options.edges.length !== undefined &&
options.nodes && options.nodes.distance === undefined) {
this.constants.edges.length = options.edges.length;
this.constants.nodes.distance = options.edges.length * 1.25;
}
if (!options.edges.fontColor) {
this.constants.edges.fontColor = options.edges.color;
}
@ -873,7 +866,6 @@ Graph.prototype._onHold = function (event) {
/**
* handle the release of the screen
*
* @param event
* @private
*/
Graph.prototype._onRelease = function (event) {
@ -973,7 +965,7 @@ Graph.prototype._onMouseWheel = function(event) {
var pointer = this._getPointer(gesture.center);
// apply the new scale
scale = this._zoom(scale, pointer);
this._zoom(scale, pointer);
// store the new, applied scale -- this is now done in _zoom
// this.pinch.mousewheelScale = scale;
@ -1099,85 +1091,6 @@ Graph.prototype._checkHidePopup = function (pointer) {
};
/**
* Temporary method to test calculating a hub value for the nodes
* @param {number} level Maximum number edges between two nodes in order
* to call them connected. Optional, 1 by default
* @return {Number[]} connectioncount array with the connection count
* for each node
* @private
*/
Graph.prototype._getConnectionCount = function(level) {
if (level == undefined) {
level = 1;
}
// get the nodes connected to given nodes
function getConnectedNodes(nodes) {
var connectedNodes = [];
for (var j = 0, jMax = nodes.length; j < jMax; j++) {
var node = nodes[j];
// find all nodes connected to this node
var edges = node.edges;
for (var i = 0, iMax = edges.length; i < iMax; i++) {
var edge = edges[i];
var other = null;
// check if connected
if (edge.from == node)
other = edge.to;
else if (edge.to == node)
other = edge.from;
// check if the other node is not already in the list with nodes
var k, kMax;
if (other) {
for (k = 0, kMax = nodes.length; k < kMax; k++) {
if (nodes[k] == other) {
other = null;
break;
}
}
}
if (other) {
for (k = 0, kMax = connectedNodes.length; k < kMax; k++) {
if (connectedNodes[k] == other) {
other = null;
break;
}
}
}
if (other)
connectedNodes.push(other);
}
}
return connectedNodes;
}
var connections = [];
var nodes = this.nodes;
for (var id in nodes) {
if (nodes.hasOwnProperty(id)) {
var c = [nodes[id]];
for (var l = 0; l < level; l++) {
c = c.concat(getConnectedNodes(c));
}
connections.push(c);
}
}
var hubs = [];
for (var i = 0, len = connections.length; i < len; i++) {
hubs.push(connections[i].length);
}
return hubs;
};
/**
* Set a new size for the graph
* @param {string} width Width in pixels or percentage (for example '800px'
@ -1264,7 +1177,7 @@ Graph.prototype._addNodes = function(ids) {
this.nodes[id] = node; // note: this may replace an existing node
if ((node.xFixed == false || node.yFixed == false) && this.createNodeOnClick != true) {
var radius = this.constants.physics.springLength * 0.1*ids.length;
var radius = this.constants.physics.springLength * 0.2*ids.length;
var angle = 2 * Math.PI * Math.random();
if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
@ -1400,6 +1313,7 @@ Graph.prototype._addEdges = function (ids) {
this.moving = true;
this._updateValueRange(edges);
this._createBezierNodes();
this._setCalculationNodes();
};
/**
@ -1757,24 +1671,24 @@ Graph.prototype._isMoving = function(vmin) {
* /**
* Perform one discrete step for all nodes
*
* @param interval
* @private
*/
Graph.prototype._discreteStepNodes = function() {
var interval = 1.0;
var nodes = this.nodes;
var nodeId;
if (this.constants.maxVelocity > 0) {
for (var id in nodes) {
if (nodes.hasOwnProperty(id)) {
nodes[id].discreteStepLimited(interval, this.constants.maxVelocity);
for (nodeId in nodes) {
if (nodes.hasOwnProperty(nodeId)) {
nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
}
}
}
else {
for (var id in nodes) {
if (nodes.hasOwnProperty(id)) {
nodes[id].discreteStep(interval);
for (nodeId in nodes) {
if (nodes.hasOwnProperty(nodeId)) {
nodes[nodeId].discreteStep(interval);
}
}
}
@ -1799,10 +1713,10 @@ Graph.prototype.start = function() {
if (this.moving) {
this._doInAllActiveSectors("_initializeForceCalculation");
this._doInAllActiveSectors("_discreteStepNodes");
if (this.constants.smoothCurves) {
this._doInSupportSector("_discreteStepNodes");
}
this._doInAllActiveSectors("_discreteStepNodes");
this._findCenter(this._getRange())
}
@ -1830,16 +1744,15 @@ Graph.prototype.start = function() {
graph.start();
graph.start();
// var calctime = Date.now() - calctimeStart;
// var rendertimeStart = Date.now();
graph._redraw();
// var rendertime = Date.now() - rendertimeStart;
//this.end = window.performance.now();
//this.time = this.end - this.startTime;
//console.log('refresh time: ' + this.time);
//this.startTime = window.performance.now();
// this.end = window.performance.now();
// this.time = this.end - this.startTime;
// console.log('refresh time: ' + this.time);
// this.startTime = window.performance.now();
// var DOMelement = document.getElementById("calctimereporter");
// if (DOMelement !== undefined) {
// DOMelement.innerHTML = calctime;
@ -1900,7 +1813,7 @@ Graph.prototype._createBezierNodes = function() {
mass:1,
shape:'circle',
internalMultiplier:1,
damping: 1},{},{},this.constants);
damping: 1.2},{},{},this.constants);
edge.via = this.sectors['support']['nodes'][nodeId];
edge.via.parentEdgeId = edge.id;
edge.positionBezierNode();

+ 8
- 10
src/graph/Node.js View File

@ -52,7 +52,6 @@ function Node(properties, imagelist, grouplist, constants) {
this.radiusFixed = false;
this.radiusMin = constants.nodes.radiusMin;
this.radiusMax = constants.nodes.radiusMax;
this.internalMultiplier = 1;
this.imagelist = imagelist;
@ -376,15 +375,15 @@ Node.prototype._addForce = function(fx, fy) {
*/
Node.prototype.discreteStep = function(interval) {
if (!this.xFixed) {
var dx = -this.damping * this.vx; // damping force
var ax = (this.fx + dx) / this.mass; // acceleration
var dx = this.damping * this.vx; // damping force
var ax = (this.fx - dx) / this.mass; // acceleration
this.vx += ax * interval; // velocity
this.x += this.vx * interval; // position
}
if (!this.yFixed) {
var dy = -this.damping * this.vy; // damping force
var ay = (this.fy + dy) / this.mass; // acceleration
var dy = this.damping * this.vy; // damping force
var ay = (this.fy - dy) / this.mass; // acceleration
this.vy += ay * interval; // velocity
this.y += this.vy * interval; // position
}
@ -398,16 +397,16 @@ Node.prototype.discreteStep = function(interval) {
*/
Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
if (!this.xFixed) {
var dx = -this.damping * this.vx; // damping force
var ax = (this.fx + dx) / this.mass; // acceleration
var dx = this.damping * this.vx; // damping force
var ax = (this.fx - dx) / this.mass; // acceleration
this.vx += ax * interval; // velocity
this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
this.x += this.vx * interval; // position
}
if (!this.yFixed) {
var dy = -this.damping * this.vy; // damping force
var ay = (this.fy + dy) / this.mass; // acceleration
var dy = this.damping * this.vy; // damping force
var ay = (this.fy - dy) / this.mass; // acceleration
this.vy += ay * interval; // velocity
this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
this.y += this.vy * interval; // position
@ -435,7 +434,6 @@ Node.prototype.isMoving = function(vmin) {
return true;
}
else {
this.vx = 0; this.vy = 0;
return false;
}
//return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin);

+ 1
- 0
src/graph/graphMixins/ClusterMixin.js View File

@ -1048,6 +1048,7 @@ var ClusterMixin = {
var angle = 2 * Math.PI * Math.random();
if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
this._repositionBezierNodes(node);
}
}
},

+ 7
- 9
src/graph/graphMixins/physics/PhysicsMixin.js View File

@ -50,10 +50,8 @@ var physicsMixin = {
// 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._setCalculationNodes();
this._calculateGravitationalForces();
this._calculateNodeForces();
@ -158,7 +156,7 @@ var physicsMixin = {
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
edgeLength = edge.length;
// this implies that the edges between big clusters are longer
edgeLength += (edge.to.growthIndicator + edge.from.growthIndicator) * this.constants.clustering.edgeGrowth;
edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
this._calculateSpringForce(edge.from,edge.to,edgeLength);
}
}
@ -173,7 +171,7 @@ var physicsMixin = {
* @private
*/
_calculateSpringForcesWithSupport : function() {
var edgeLength, edge, edgeId, growthIndicator;
var edgeLength, edge, edgeId, combinedClusterSize;
var edges = this.edges;
// forces caused by the edges, modelled as springs
@ -188,14 +186,14 @@ var physicsMixin = {
var node2 = edge.via;
var node3 = edge.from;
edgeLength = 0.5*edge.length;
edgeLength = edge.length;
growthIndicator = 0.5*(node1.growthIndicator + node3.growthIndicator);
combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
// this implies that the edges between big clusters are longer
edgeLength += growthIndicator * this.constants.clustering.edgeGrowth;
this._calculateSpringForce(node1,node2,edgeLength);
this._calculateSpringForce(node2,node3,edgeLength);
edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
this._calculateSpringForce(node1,node2,0.5*edgeLength);
this._calculateSpringForce(node2,node3,0.5*edgeLength);
}
}
}

+ 0
- 2
src/graph/graphMixins/physics/repulsion.js View File

@ -49,12 +49,10 @@ var repulsionMixin = {
}
if (this.sectors['support']['nodes'].hasOwnProperty(node1.id)) {
// console.log(combinedClusterSize, repulsingForce, minimumDistance);
}
// amplify the repulsion for clusters.
repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
repulsingForce *= node1.internalMultiplier * node2.internalMultiplier;
fx = Math.cos(angle) * repulsingForce;
fy = Math.sin(angle) * repulsingForce;

Loading…
Cancel
Save