Browse Source

improved decluster around zoom center. Added only render when visible check, speeding up drawing.

css_transitions
Alex de Mulder 11 years ago
parent
commit
d80de1b128
6 changed files with 27161 additions and 15027 deletions
  1. +8
    -0
      dist/vis.css
  2. +15109
    -14999
      dist/vis.js
  3. +24
    -19
      src/graph/Graph.js
  4. +29
    -3
      src/graph/Node.js
  5. +27
    -6
      src/graph/cluster.js
  6. +11964
    -0
      vis.js.tmp

+ 8
- 0
dist/vis.css View File

@ -109,6 +109,14 @@
z-index: 999; z-index: 999;
} }
.vis.timeline .item.point.selected {
background-color: #FFF785;
z-index: 999;
}
.vis.timeline .item.point.selected .dot {
border-color: #FFC200;
}
.vis.timeline .item.cluster { .vis.timeline .item.cluster {
/* TODO: use another color or pattern? */ /* TODO: use another color or pattern? */
background: #97B0F8 url('img/cluster_bg.png'); background: #97B0F8 url('img/cluster_bg.png');

+ 15109
- 14999
dist/vis.js
File diff suppressed because it is too large
View File


+ 24
- 19
src/graph/Graph.js View File

@ -64,10 +64,11 @@ function Graph (container, data, options) {
} }
}, },
clustering: { // TODO: naming of variables clustering: { // TODO: naming of variables
enableClustering: false,
enableClustering: true,
maxNumberOfNodes: 100, // for automatic (initial) clustering maxNumberOfNodes: 100, // for automatic (initial) clustering
snakeThreshold: 0.5, // maximum percentage of allowed snakes (long strings of connected nodes) snakeThreshold: 0.5, // maximum percentage of allowed snakes (long strings of connected nodes)
clusterLength: 30, // threshold edge length for clusteringl clusterLength: 30, // threshold edge length for clusteringl
relativeOpenFactor: 0.5, // if the width or height of a cluster takes up this much of the screen, open the cluster
fontSizeMultiplier: 4, // how much the cluster font size grows per node (in px) fontSizeMultiplier: 4, // how much the cluster font size grows per node (in px)
forceAmplification: 0.7, // amount of clusterSize between two nodes multiply this value (+1) with the repulsion force forceAmplification: 0.7, // amount of clusterSize between two nodes multiply this value (+1) with the repulsion force
distanceAmplification: 0.3, // amount of clusterSize between two nodes multiply this value (+1) with the repulsion force distanceAmplification: 0.3, // amount of clusterSize between two nodes multiply this value (+1) with the repulsion force
@ -709,7 +710,7 @@ Graph.prototype._zoom = function(scale, pointer) {
this.updateClustersDefault(); this.updateClustersDefault();
this._redraw(); this._redraw();
//console.log("current zoomscale:",this.scale)
console.log("current zoomscale:",this.scale);
return scale; return scale;
}; };
@ -1606,21 +1607,32 @@ Graph.prototype._drawNodes = function(ctx) {
// first draw the unselected nodes // first draw the unselected nodes
var nodes = this.nodes; var nodes = this.nodes;
var selected = []; var selected = [];
var canvasTopLeft = {"x": (0-this.translation.x)/this.scale,
"y": (0-this.translation.y)/this.scale};
var canvasBottomRight = {"x": (this.frame.canvas.clientWidth -this.translation.x)/this.scale,
"y": (this.frame.canvas.clientHeight-this.translation.y)/this.scale};
for (var id in nodes) { for (var id in nodes) {
if (nodes.hasOwnProperty(id)) { if (nodes.hasOwnProperty(id)) {
nodes[id].setScale(this.scale);
nodes[id].setScaleAndPos(this.scale,canvasTopLeft,canvasBottomRight);
if (nodes[id].isSelected()) { if (nodes[id].isSelected()) {
selected.push(id); selected.push(id);
} }
else { else {
nodes[id].draw(ctx);
if (nodes[id].inArea()) {
nodes[id].draw(ctx);
}
} }
} }
} }
// draw the selected nodes on top // draw the selected nodes on top
for (var s = 0, sMax = selected.length; s < sMax; s++) { for (var s = 0, sMax = selected.length; s < sMax; s++) {
nodes[selected[s]].draw(ctx);
if (nodes[selected[s]].inArea()) {
nodes[selected[s]].draw(ctx);
}
} }
}; };
@ -1677,7 +1689,7 @@ Graph.prototype._calculateForces = function() {
this.nodes[this.nodeIndices[0]]._setForce(0,0); this.nodes[this.nodeIndices[0]]._setForce(0,0);
} }
// if there are too many nodes on screen, we cluster without repositioning // if there are too many nodes on screen, we cluster without repositioning
else if (this.nodeIndices.length > this.constants.clustering.maxNumberOfNodes * 4) {
else if (this.nodeIndices.length > this.constants.clustering.maxNumberOfNodes * 4 && this.constants.clustering.enableClustering == true) {
this.clusterToFit(this.constants.clustering.maxNumberOfNodes * 2, false); this.clusterToFit(this.constants.clustering.maxNumberOfNodes * 2, false);
this._calculateForces(); this._calculateForces();
} }
@ -1914,24 +1926,18 @@ Graph.prototype.start = function() {
if (!this.timer) { if (!this.timer) {
var graph = this; var graph = this;
this.timer = window.setTimeout(function () { this.timer = window.setTimeout(function () {
var start,end,time;
graph.timer = undefined; graph.timer = undefined;
// benchmark the calculation
// var start = window.performance.now();
graph.start(); graph.start();
// Optionally call this twice for faster convergence
graph.start(); graph.start();
// var end = window.performance.now();
// var time = end - start;
// console.log('Simulation time: ' + time);
// start = window.performance.now();
graph._redraw(); graph._redraw();
// end = window.performance.now();
// time = end - start;
// console.log('Drawing time: ' + time);
// start = window.performance.now();
// graph._redraw();
// end = window.performance.now();
// time = end - start;
// console.log('Drawing time: ' + time);
}, this.refreshRate); }, this.refreshRate);
} }
} }
@ -1955,7 +1961,6 @@ Graph.prototype.singleStep = function() {
/** /**
* Freeze the animation * Freeze the animation
*/ */

+ 29
- 3
src/graph/Node.js View File

@ -75,6 +75,8 @@ function Node(properties, imagelist, grouplist, constants) {
this.damping = 0.9; // damping factor this.damping = 0.9; // damping factor
this.graphScaleInv = 1; this.graphScaleInv = 1;
this.canvasTopLeft = {"x": -300, "y": -300};
this.canvasBottomRight = {"x": 300, "y": 300};
} }
/** /**
@ -862,13 +864,37 @@ Node.prototype.getTextSize = function(ctx) {
} }
}; };
Node.prototype.inArea = function() {
if (this.width !== undefined) {
return (this.x + this.width*0.8 >= this.canvasTopLeft.x &&
this.x - this.width*0.8 < this.canvasBottomRight.x &&
this.y + this.height*0.8 >= this.canvasTopLeft.y &&
this.y - this.height*0.8 < this.canvasBottomRight.y);
}
else {
return true;
}
}
Node.prototype.inView = function() {
return (this.x >= this.canvasTopLeft.x &&
this.x < this.canvasBottomRight.x &&
this.y >= this.canvasTopLeft.y &&
this.y < this.canvasBottomRight.y);
}
/** /**
* This allows the zoom level of the graph to influence the rendering * This allows the zoom level of the graph to influence the rendering
* We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas
* *
* @param scale * @param scale
*/ */
Node.prototype.setScale = function(scale) {
Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
this.graphScaleInv = 1.0/scale; this.graphScaleInv = 1.0/scale;
this.canvasTopLeft = canvasTopLeft;
this.canvasBottomRight = canvasBottomRight;
}; };
/** /**
@ -890,7 +916,6 @@ Node.prototype.clearVelocity = function() {
}; };
/** /**
* Basic preservation of (kinectic) energy * Basic preservation of (kinectic) energy
* *
@ -901,4 +926,5 @@ Node.prototype.updateVelocity = function(massBeforeClustering) {
this.vx = Math.sqrt(energyBefore/this.mass); this.vx = Math.sqrt(energyBefore/this.mass);
energyBefore = this.vy * this.vy * massBeforeClustering; energyBefore = this.vy * this.vy * massBeforeClustering;
this.vy = Math.sqrt(energyBefore/this.mass); this.vy = Math.sqrt(energyBefore/this.mass);
};
};

+ 27
- 6
src/graph/cluster.js View File

@ -80,6 +80,7 @@ Cluster.prototype.updateClusters = function(zoomDirection,recursive,force) {
} }
else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom out else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom out
this._openClusters(recursive,force); this._openClusters(recursive,force);
this._openClustersBySize();
} }
this._updateNodeIndexList(); this._updateNodeIndexList();
@ -163,6 +164,26 @@ Cluster.prototype.forceAggregateHubs = function() {
} }
}; };
/**
* If a cluster takes up more than a set percentage of the screen, open the cluster
*
* @private
*/
Cluster.prototype._openClustersBySize = function() {
for (nodeID in this.nodes) {
if (this.nodes.hasOwnProperty(nodeID)) {
node = this.nodes[nodeID];
if (node.inView() == true) {
if ((node.width*this.scale > this.constants.clustering.relativeOpenFactor * this.frame.canvas.clientWidth) ||
(node.height*this.scale > this.constants.clustering.relativeOpenFactor * this.frame.canvas.clientHeight)) {
this.openCluster(node);
this.openCluster(node);
}
}
}
}
};
@ -767,12 +788,12 @@ Cluster.prototype.updateLabels = function() {
} }
/* Debug Override */ /* Debug Override */
for (nodeID in this.nodes) {
if (this.nodes.hasOwnProperty(nodeID)) {
node = this.nodes[nodeID];
node.label = String(node.clusterSize).concat(":",String(node.id));
}
}
// for (nodeID in this.nodes) {
// if (this.nodes.hasOwnProperty(nodeID)) {
// node = this.nodes[nodeID];
// node.label = String(Math.round(node.width)).concat(":",Math.round(node.width*this.scale));
// }
// }
}; };

+ 11964
- 0
vis.js.tmp
File diff suppressed because it is too large
View File


Loading…
Cancel
Save