diff --git a/.gitignore b/.gitignore index ec01a71f..df8fb4e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ .idea node_modules .project -dist .settings/.jsdtscope .settings/org.eclipse.wst.jsdt.ui.superType.container .settings/org.eclipse.wst.jsdt.ui.superType.name npm-debug.log +dist/ diff --git a/examples/graph/02_random_nodes.html b/examples/graph/02_random_nodes.html index 57ea0714..69ac60f8 100755 --- a/examples/graph/02_random_nodes.html +++ b/examples/graph/02_random_nodes.html @@ -90,8 +90,7 @@ edges: { length: 50 }, - stabilize: false, - clustering : true + stabilize: false }; graph = new vis.Graph(container, data, options); @@ -104,7 +103,8 @@ - +calculation time: ms +render time: ms
diff --git a/src/graph/ClusterMixin.js b/src/graph/ClusterMixin.js index bc8a8815..c4755a51 100644 --- a/src/graph/ClusterMixin.js +++ b/src/graph/ClusterMixin.js @@ -618,7 +618,7 @@ var ClusterMixin = { // update the properties of the child and parent var massBefore = parentNode.mass; childNode.clusterSession = this.clusterSession; - parentNode.mass += childNode.mass; + parentNode.mass += this.constants.clustering.massTransferCoefficient * childNode.mass; parentNode.clusterSize += childNode.clusterSize; parentNode.fontSize += this.constants.clustering.fontSizeMultiplier * childNode.clusterSize; @@ -931,7 +931,7 @@ var ClusterMixin = { for (var i = 0; i < this.nodeIndices.length; i++) { var node = this.nodes[this.nodeIndices[i]]; if (!node.isFixed()) { - var radius = this.constants.physics.springLength * (1 + 0.6*node.clusterSize); + var radius = this.constants.edges.length * (1 + 0.6*node.clusterSize); var angle = 2 * Math.PI * Math.random(); node.x = radius * Math.cos(angle); node.y = radius * Math.sin(angle); diff --git a/src/graph/Edge.js b/src/graph/Edge.js index 5b6959f0..c5192f90 100644 --- a/src/graph/Edge.js +++ b/src/graph/Edge.js @@ -31,7 +31,7 @@ function Edge (properties, graph, constants) { this.title = undefined; this.width = constants.edges.width; this.value = undefined; - this.length = constants.edges.length; + this.length = constants.physics.springLength; this.selected = false; this.from = null; // a node @@ -49,7 +49,6 @@ function Edge (properties, graph, constants) { // 2012-08-08 this.dash = util.extend({}, constants.edges.dash); // contains properties length, gap, altLength - this.springConstant = constants.physics.springConstant; this.color = constants.edges.color; this.widthFixed = false; this.lengthFixed = false; @@ -103,7 +102,6 @@ Edge.prototype.setProperties = function(properties, constants) { this.widthFixed = this.widthFixed || (properties.width !== undefined); this.lengthFixed = this.lengthFixed || (properties.length !== undefined); - this.stiffness = 1 / this.length; // set draw method based on style switch (this.style) { diff --git a/src/graph/Graph.js b/src/graph/Graph.js index e2949bb8..d7c446f1 100644 --- a/src/graph/Graph.js +++ b/src/graph/Graph.js @@ -66,11 +66,12 @@ function Graph (container, data, options) { } }, physics: { - springConstant:0.05, - springLength: 100, - centralGravity: 0.1, - nodeGravityConstant: -10000, - barnesHutTheta: 0.2 + enableBarnesHut: false, + barnesHutTheta: 1 / 0.4, // inverted to save time during calculation + barnesHutGravitationalConstant: -10000, + centralGravity: 0.08, + springLength: 50, + springConstant: 0.02 }, clustering: { // Per Node in Cluster = PNiC enabled: false, // (Boolean) | global on/off switch for clustering. @@ -88,7 +89,8 @@ function Graph (container, data, options) { nodeScaling: {width: 10, // (px PNiC) | growth of the width per node in cluster. height: 10, // (px PNiC) | growth of the height per node in cluster. radius: 10}, // (px PNiC) | growth of the radius per node in cluster. - activeAreaBoxSize: 100 // (px) | box area around the curser where clusters are popped open. + activeAreaBoxSize: 100, // (px) | box area around the curser where clusters are popped open. + massTransferCoefficient: 1 // (multiplier) | parent.mass += massTransferCoefficient * child.mass }, navigation: { enabled: false, @@ -101,7 +103,7 @@ function Graph (container, data, options) { dataManipulationToolbar: { enabled: false }, - minVelocity: 0.2, // px/s + minVelocity: 0.1, // px/s maxIterations: 1000 // maximum number of iteration to stabilize }; @@ -642,7 +644,7 @@ Graph.prototype._createKeyBinds = function() { this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown"); this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup"); } - this.mousetrap.bind("b",this._formBarnesHutTree.bind(me)); + this.mousetrap.bind("b",this._toggleBarnesHut.bind(me)); if (this.constants.dataManipulationToolbar.enabled == true) { this.mousetrap.bind("escape",this._createManipulatorBar.bind(me)); @@ -1502,7 +1504,7 @@ Graph.prototype._redraw = function() { this._doInAllSectors("_drawEdges",ctx); this._doInAllSectors("_drawNodes",ctx,true); - this._drawTree(ctx,"#F00F0F"); +// this._drawTree(ctx,"#F00F0F"); // restore original scaling and translation ctx.restore(); @@ -1710,7 +1712,7 @@ Graph.prototype._isMoving = function(vmin) { * @private */ Graph.prototype._discreteStepNodes = function() { - var interval = 0.5; + var interval = 1; var nodes = this.nodes; this.constants.maxVelocity = 30; @@ -1769,14 +1771,28 @@ Graph.prototype.start = function() { graph._zoom(graph.scale*(1 + graph.zoomIncrement), center); } + var calctimeStart = Date.now(); 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(); + var DOMelement = document.getElementById("calctimereporter"); + if (DOMelement !== undefined) { + DOMelement.innerHTML = calctime; + } + DOMelement = document.getElementById("rendertimereporter"); + if (DOMelement !== undefined) { + DOMelement.innerHTML = rendertime; + } }, this.renderTimestep); } } @@ -1823,6 +1839,7 @@ Graph.prototype.toggleFreeze = function() { * @private */ Graph.prototype._loadPhysicsSystem = function() { + this.forceCalculationTime = 0; for (var mixinFunction in physicsMixin) { if (physicsMixin.hasOwnProperty(mixinFunction)) { Graph.prototype[mixinFunction] = physicsMixin[mixinFunction]; diff --git a/src/graph/Node.js b/src/graph/Node.js index 044eed67..dd0c991f 100644 --- a/src/graph/Node.js +++ b/src/graph/Node.js @@ -993,8 +993,10 @@ Node.prototype.clearVelocity = function() { */ Node.prototype.updateVelocity = function(massBeforeClustering) { var energyBefore = this.vx * this.vx * massBeforeClustering; - this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass); + //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass); + this.vx = Math.sqrt(energyBefore/this.mass); energyBefore = this.vy * this.vy * massBeforeClustering; - this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass); + //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass); + this.vy = Math.sqrt(energyBefore/this.mass); }; diff --git a/src/graph/physicsMixin.js b/src/graph/physicsMixin.js index 83cb438a..357b256e 100644 --- a/src/graph/physicsMixin.js +++ b/src/graph/physicsMixin.js @@ -5,6 +5,12 @@ var physicsMixin = { + + _toggleBarnesHut : function() { + this.constants.physics.enableBarnesHut = !this.constants.physics.enableBarnesHut; + this.moving = true; + this.start(); + }, /** * 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. @@ -23,8 +29,13 @@ var physicsMixin = { } // we now start the force calculation - // this._calculateForcesBarnesHut(); - this._calculateForcesOriginal(); + if (this.constants.physics.enableBarnesHut == true) { + this._calculateForcesBarnesHut(); + } + else { + this.barnesHutTree = undefined; + this._calculateForcesRepulsion(); + } } }, @@ -34,28 +45,15 @@ var physicsMixin = { * Forces are caused by: edges, repulsing forces between nodes, gravity * @private */ - _calculateForcesOriginal : function() { + _calculateForcesRepulsion : 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(); -// var startTimeAll = Date.now(); - - this._calculateGravitationalForces(1); - -// var startTimeRepulsion = Date.now(); - // All nodes repel eachother. this._calculateRepulsionForces(); -// var endTimeRepulsion = Date.now(); - - // the edges are strings - this._calculateSpringForces(1); - -// var endTimeAll = Date.now(); - -// echo("Time repulsion part:", endTimeRepulsion - startTimeRepulsion); -// echo("Time total force calc:", endTimeAll - startTimeAll); + this._calculateSpringForces(); }, /** @@ -64,27 +62,11 @@ var physicsMixin = { * @private */ _calculateForcesBarnesHut : 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 - -// var startTimeAll = Date.now(); - - this._clearForces(); + this._calculateGravitationalForces(); -// var startTimeRepulsion = Date.now(); - // All nodes repel eachother. this._calculateBarnesHutForces(); -// var endTimeRepulsion = Date.now(); - - // the edges are strings - this._calculateSpringForces(1); - -// var endTimeAll = Date.now(); - -// echo("Time repulsion part:", endTimeRepulsion - startTimeRepulsion); -// echo("Time total force calc:", endTimeAll - startTimeAll); + this._calculateSpringForces(); }, @@ -99,7 +81,7 @@ var physicsMixin = { } }, - _calculateGravitationalForces : function(boost) { + _calculateGravitationalForces : function() { var dx, dy, angle, fx, fy, node, i; var nodes = this.nodes; var gravity = this.constants.physics.centralGravity; @@ -135,7 +117,6 @@ var physicsMixin = { // repulsing forces between nodes var minimumDistance = this.constants.nodes.distance; - //var steepness = 10; // 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 @@ -148,8 +129,6 @@ var physicsMixin = { dy = node2.y - node1.y; distance = Math.sqrt(dx * dx + dy * dy); - - // clusters have a larger region of influence minimumDistance = (clusterSize == 0) ? this.constants.nodes.distance : (this.constants.nodes.distance * (1 + clusterSize * this.constants.clustering.distanceAmplification)); var a = a_base / minimumDistance; if (distance < 2*minimumDistance) { // at 2.0 * the minimum distance, the force is 0.000045 @@ -174,7 +153,7 @@ var physicsMixin = { } }, - _calculateSpringForces : function(boost) { + _calculateSpringForces : function() { var dx, dy, angle, fx, fy, springForce, length, edgeLength, edge, edgeId, clusterSize; var edges = this.edges; @@ -196,7 +175,7 @@ var physicsMixin = { length = Math.sqrt(dx * dx + dy * dy); angle = Math.atan2(dy, dx); - springForce = edge.springConstant * (edgeLength - length); + springForce = this.constants.physics.springConstant * (edgeLength - length); fx = Math.cos(angle) * springForce; fy = Math.sin(angle) * springForce; @@ -241,11 +220,8 @@ var physicsMixin = { distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // distance is 0 if it looks to apply a force on itself. - // we invert it here because we need the inverted distance for the force calculation too. - var distanceInv = 1/distance; - // BarnesHut condition - if (parentBranch.size * distanceInv > this.constants.physics.barnesHutTheta) { + if (distance * parentBranch.calcSize < this.constants.physics.barnesHutTheta) { // Did not pass the condition, go into children if available if (parentBranch.childrenCount == 4) { this._getForceContribution(parentBranch.children.NW,node); @@ -255,20 +231,20 @@ var physicsMixin = { } else { // parentBranch must have only one node, if it was empty we wouldnt be here if (parentBranch.children.data.id != node.id) { // if it is not self - this._getForceOnNode(parentBranch, node, dx ,dy, distanceInv); + this._getForceOnNode(parentBranch, node, dx ,dy, distance); } } } else { - this._getForceOnNode(parentBranch, node, dx ,dy, distanceInv); + this._getForceOnNode(parentBranch, node, dx ,dy, distance); } } } }, - _getForceOnNode : function(parentBranch, node, dx ,dy, distanceInv) { + _getForceOnNode : function(parentBranch, node, dx ,dy, distance) { // even if the parentBranch only has one node, its Center of Mass is at the right place (the node in this case). - var gravityForce = this.constants.physics.nodeGravityConstant * parentBranch.mass * node.mass * distanceInv * distanceInv; + var gravityForce = this.constants.physics.barnesHutGravitationalConstant * parentBranch.mass * node.mass / (distance * distance); var angle = Math.atan2(dy, dx); var fx = Math.cos(angle) * gravityForce; var fy = Math.sin(angle) * gravityForce; @@ -308,6 +284,7 @@ var physicsMixin = { mass:0, range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY}, size: Math.abs(maxX - minX), + calcSize: 1 / Math.abs(maxX - minX), children: {data:null}, level: 0, childrenCount: 4 @@ -324,6 +301,7 @@ var physicsMixin = { this.barnesHutTree = barnesHutTree }, + _updateBranchMass : function(parentBranch, node) { var totalMass = parentBranch.mass + node.mass; var totalMassInv = 1/totalMass; @@ -337,6 +315,7 @@ var physicsMixin = { parentBranch.mass = totalMass; }, + _placeInTree : function(parentBranch,node) { // update the mass of the branch. this._updateBranchMass(parentBranch,node); @@ -359,6 +338,7 @@ var physicsMixin = { } }, + _placeInRegion : function(parentBranch,node,region) { switch (parentBranch.children[region].childrenCount) { case 0: // place node here @@ -376,6 +356,7 @@ var physicsMixin = { } }, + _splitBranch : function(parentBranch) { // if the branch is filled with a node, replace the node in the new subset. var containedNode = null; @@ -441,6 +422,7 @@ var physicsMixin = { mass:0, range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY}, size: 0.5 * parentBranch.size, + calcSize: 2 * parentBranch.calcSize, children: {data:null}, level: parentBranch.level +1, childrenCount: 0