diff --git a/dist/vis.js b/dist/vis.js
index 2bc2924a..afd722b9 100644
--- a/dist/vis.js
+++ b/dist/vis.js
@@ -8794,7 +8794,6 @@ function Node(properties, imagelist, grouplist, constants) {
this.grouplist = grouplist;
- this.nodeProperties = properties;
this.setProperties(properties, constants);
// creating the variables for clustering
@@ -8879,10 +8878,10 @@ Node.prototype.setProperties = function(properties, constants) {
if (!properties) {
return;
}
-
+ this.originalLabel = undefined;
// basic properties
if (properties.id !== undefined) {this.id = properties.id;}
- if (properties.label !== undefined) {this.label = properties.label;}
+ if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
if (properties.title !== undefined) {this.title = properties.title;}
if (properties.group !== undefined) {this.group = properties.group;}
if (properties.x !== undefined) {this.x = properties.x;}
@@ -9190,6 +9189,7 @@ Node.prototype.setValueRange = function(min, max) {
this.radius = (this.value - min) * scale + this.radiusMin;
}
}
+ this.baseRadiusValue = this.radius;
};
/**
@@ -9224,12 +9224,19 @@ Node.prototype.isOverlappingWith = function(obj) {
Node.prototype._resizeImage = function (ctx) {
// TODO: pre calculate the image size
- if (!this.width) { // undefined or 0
+ if (!this.width || !this.height) { // undefined or 0
var width, height;
if (this.value) {
+ this.radius = this.baseRadiusValue;
var scale = this.imageObj.height / this.imageObj.width;
- width = this.radius || this.imageObj.width;
- height = this.radius * scale || this.imageObj.height;
+ if (scale !== undefined) {
+ width = this.radius || this.imageObj.width;
+ height = this.radius * scale || this.imageObj.height;
+ }
+ else {
+ width = 0;
+ height = 0;
+ }
}
else {
width = this.imageObj.width;
@@ -9238,10 +9245,13 @@ Node.prototype._resizeImage = function (ctx) {
this.width = width;
this.height = height;
- this.width += this.clusterSize * this.clusterSizeWidthFactor;
- this.height += this.clusterSize * this.clusterSizeHeightFactor;
- this.radius += this.clusterSize * this.clusterSizeRadiusFactor;
+ if (this.width && this.height) {
+ this.width += this.clusterSize * this.clusterSizeWidthFactor;
+ this.height += this.clusterSize * this.clusterSizeHeightFactor;
+ this.radius += this.clusterSize * this.clusterSizeRadiusFactor;
+ }
}
+
};
Node.prototype._drawImage = function (ctx) {
@@ -9251,7 +9261,7 @@ Node.prototype._drawImage = function (ctx) {
this.top = this.y - this.height / 2;
var yLabel;
- if (this.imageObj) {
+ if (this.imageObj.width != 0 ) {
// draw the shade
if (this.clusterSize > 1) {
var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
@@ -9634,12 +9644,13 @@ Node.prototype.inView = function() {
this.y < this.canvasBottomRight.y);
}
-
/**
* 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 canvasTopLeft
+ * @param canvasBottomRight
*/
Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
this.graphScaleInv = 1.0/scale;
@@ -9647,6 +9658,16 @@ Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight)
this.canvasBottomRight = canvasBottomRight;
};
+
+/**
+ * This allows the zoom level of the graph to influence the rendering
+ *
+ * @param scale
+ */
+Node.prototype.setScale = function(scale) {
+ this.graphScaleInv = 1.0/scale;
+};
+
/**
* This function updates the damping parameter for clusters, based ont he
*
@@ -10544,27 +10565,60 @@ var SectorMixin = {
},
+ /**
+ * /**
+ * This function sets the global references to nodes, edges and nodeIndices back to
+ * those of the supplied (active) sector. If a type is defined, do the specific type
+ *
+ * @param {String} sectorID
+ * @param {String} [sectorType] | "active" or "frozen"
+ * @private
+ */
+ _switchToSector : function(sectorID, sectorType) {
+ if (sectorType === undefined || sectorType == "active") {
+ this._switchToActiveSector(sectorID);
+ }
+ else {
+ this._switchToFrozenSector(sectorID);
+ }
+ },
+
+
/**
* This function sets the global references to nodes, edges and nodeIndices back to
- * those of the supplied (active) sector.
+ * those of the supplied active sector.
*
* @param sectorID
* @private
*/
- _switchToSector : function(sectorID) {
+ _switchToActiveSector : function(sectorID) {
this.nodeIndices = this.sectors["active"][sectorID]["nodeIndices"];
this.nodes = this.sectors["active"][sectorID]["nodes"];
this.edges = this.sectors["active"][sectorID]["edges"];
},
+ /**
+ * This function sets the global references to nodes, edges and nodeIndices back to
+ * those of the supplied frozen sector.
+ *
+ * @param sectorID
+ * @private
+ */
+ _switchToFrozenSector : function(sectorID) {
+ this.nodeIndices = this.sectors["frozen"][sectorID]["nodeIndices"];
+ this.nodes = this.sectors["frozen"][sectorID]["nodes"];
+ this.edges = this.sectors["frozen"][sectorID]["edges"];
+ },
+
+
/**
* This function sets the global references to nodes, edges and nodeIndices back to
* those of the currently active sector.
*
* @private
*/
- _loadActiveSector : function() {
+ _loadLatestSector : function() {
this._switchToSector(this._sector());
},
@@ -10629,7 +10683,22 @@ var SectorMixin = {
* @private
*/
_createNewSector : function(newID) {
- this.sectors["active"][newID] = {"nodes":{ },"edges":{ },"nodeIndices":[]}
+ // create the new sector
+ this.sectors["active"][newID] = {"nodes":{},
+ "edges":{},
+ "nodeIndices":[],
+ "formationScale": this.scale,
+ "drawingNode": undefined};
+
+ // create the new sector render node. This gives visual feedback that you are in a new sector.
+ this.sectors["active"][newID]['drawingNode'] = new Node(
+ {id:newID,
+ color: {
+ background: "#eaefef",
+ border: "495c5e"
+ }
+ },{},{},this.constants);
+ this.sectors["active"][newID]['drawingNode'].clusterSize = 2;
},
@@ -10753,16 +10822,18 @@ var SectorMixin = {
// when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
delete this.nodes[node.id];
+ var unqiueIdentifier = util.randomUUID();
+
// we fully freeze the currently active sector
this._freezeSector(sector);
// we create a new active sector. This sector has the ID of the node to ensure uniqueness
- this._createNewSector(node.id);
+ this._createNewSector(unqiueIdentifier);
// we add the active sector to the sectors array to be able to revert these steps later on
- this._setActiveSector(node.id);
+ this._setActiveSector(unqiueIdentifier);
- // we redirect the global references to the new sector's references.
+ // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier
this._switchToSector(this._sector());
// finally we add the node we removed from our previous active sector to the new active sector
@@ -10782,30 +10853,34 @@ var SectorMixin = {
// we cannot collapse the default sector
if (sector != "default") {
- var previousSector = this._previousSector();
+ if ((this.nodeIndices.length == 1) ||
+ (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
+ (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
+ var previousSector = this._previousSector();
- // we collapse the sector back to a single cluster
- this._collapseThisToSingleCluster();
+ // we collapse the sector back to a single cluster
+ this._collapseThisToSingleCluster();
- // we move the remaining nodes, edges and nodeIndices to the previous sector.
- // This previous sector is the one we will reactivate
- this._mergeThisWithFrozen(previousSector);
+ // we move the remaining nodes, edges and nodeIndices to the previous sector.
+ // This previous sector is the one we will reactivate
+ this._mergeThisWithFrozen(previousSector);
- // the previously active (frozen) sector now has all the data from the currently active sector.
- // we can now delete the active sector.
- this._deleteActiveSector(sector);
+ // the previously active (frozen) sector now has all the data from the currently active sector.
+ // we can now delete the active sector.
+ this._deleteActiveSector(sector);
- // we activate the previously active (and currently frozen) sector.
- this._activateSector(previousSector);
+ // we activate the previously active (and currently frozen) sector.
+ this._activateSector(previousSector);
- // we load the references from the newly active sector into the global references
- this._switchToSector(previousSector);
+ // we load the references from the newly active sector into the global references
+ this._switchToSector(previousSector);
- // we forget the previously active sector because we reverted to the one before
- this._forgetLastSector();
+ // we forget the previously active sector because we reverted to the one before
+ this._forgetLastSector();
- // finally, we update the node index list.
- this._updateNodeIndexList();
+ // finally, we update the node index list.
+ this._updateNodeIndexList();
+ }
}
},
@@ -10824,7 +10899,7 @@ var SectorMixin = {
for (var sector in this.sectors["active"]) {
if (this.sectors["active"].hasOwnProperty(sector)) {
// switch the global references to those of this sector
- this._switchToSector(sector);
+ this._switchToActiveSector(sector);
this[runFunction]();
}
}
@@ -10833,14 +10908,13 @@ var SectorMixin = {
for (var sector in this.sectors["active"]) {
if (this.sectors["active"].hasOwnProperty(sector)) {
// switch the global references to those of this sector
- this._switchToSector(sector);
+ this._switchToActiveSector(sector);
this[runFunction](args);
}
}
}
-
// we revert the global references back to our active sector
- this._loadActiveSector();
+ this._loadLatestSector();
},
@@ -10857,7 +10931,8 @@ var SectorMixin = {
if (args === undefined) {
for (var sector in this.sectors["frozen"]) {
if (this.sectors["frozen"].hasOwnProperty(sector)) {
- this._switchToSector(sector);
+ // switch the global references to those of this sector
+ this._switchToFrozenSector(sector);
this[runFunction]();
}
}
@@ -10865,12 +10940,13 @@ var SectorMixin = {
else {
for (var sector in this.sectors["frozen"]) {
if (this.sectors["frozen"].hasOwnProperty(sector)) {
- this._switchToSector(sector);
+ // switch the global references to those of this sector
+ this._switchToFrozenSector(sector);
this[runFunction](args);
}
}
}
- this._loadActiveSector();
+ this._loadLatestSector();
},
@@ -10899,6 +10975,52 @@ var SectorMixin = {
var sector = this._sector();
this.sectors["active"][sector]["nodeIndices"] = [];
this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
+ },
+
+
+ /**
+ * Draw the encompassing sector node
+ *
+ * @param ctx
+ * @param sectorType
+ * @private
+ */
+ _drawSectorNodes : function(ctx,sectorType) {
+ var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
+ for (var sector in this.sectors[sectorType]) {
+ if (this.sectors[sectorType].hasOwnProperty(sector)) {
+ minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9;
+ if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
+
+ this._switchToSector(sector,sectorType);
+
+ for (var nodeID in this.nodes) {
+ if (this.nodes.hasOwnProperty(nodeID)) {
+ node = this.nodes[nodeID];
+ node.resize(ctx);
+ if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
+ if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
+ if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
+ if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
+ }
+ }
+ node = this.sectors[sectorType][sector]["drawingNode"];
+ node.x = 0.5 * (maxX + minX);
+ node.y = 0.5 * (maxY + minY);
+ node.width = node.x - minX;
+ node.height = node.y - minY;
+ node.radius = Math.sqrt(Math.pow(node.width,2) + Math.pow(node.height,2));
+ node.setScale(this.scale);
+ node._drawCircle(ctx);
+ }
+ }
+ }
+ },
+
+ _drawAllSectorNodes : function(ctx) {
+ this._drawSectorNodes(ctx,"frozen");
+ this._drawSectorNodes(ctx,"active");
+ this._loadLatestSector();
}
};
/**
@@ -10920,12 +11042,12 @@ function Cluster() {
Cluster.prototype.clusterToFit = function(maxNumberOfNodes, reposition) {
var numberOfNodes = this.nodeIndices.length;
- var maxLevels = 15;
+ var maxLevels = 50;
var level = 0;
// we first cluster the hubs, then we pull in the outliers, repeat
while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
- if (level % 5 == 0) {
+ if (level % 3 == 0) {
console.log("Aggregating Hubs @ level: ",level,". Threshold:", this.hubThreshold,"clusterSession",this.clusterSession);
this.forceAggregateHubs();
}
@@ -10952,16 +11074,23 @@ Cluster.prototype.clusterToFit = function(maxNumberOfNodes, reposition) {
Cluster.prototype.openCluster = function(node) {
var isMovingBeforeClustering = this.moving;
- if (node.clusterSize > 15) {
+ if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node)) {
this._addSector(node);
+ var level = 0;
+ while ((this.nodeIndices.length < this.constants.clustering.maxNumberOfNodes) &&
+ (level < 5)) {
+ this.decreaseClusterLevel();
+ level += 1;
+ }
}
+ else {
+ this._expandClusterNode(node,false,true);
- this._expandClusterNode(node,false,true);
-
- // housekeeping
- this._updateNodeIndexList();
- this._updateDynamicEdges();
- this.updateLabels();
+ // housekeeping
+ this._updateNodeIndexList();
+ this._updateDynamicEdges();
+ this.updateLabels();
+ }
// if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
if (this.moving != isMovingBeforeClustering) {
@@ -11014,23 +11143,29 @@ Cluster.prototype.updateClusters = function(zoomDirection,recursive,force) {
var amountOfNodes = this.nodeIndices.length;
// on zoom out collapse the sector back to default
-// if (this.previousScale > this.scale && zoomDirection == 0) {
-// this._collapseUniverse();
-// }
+ if (this.previousScale > this.scale && zoomDirection == 0) {
+ this._collapseSector();
+ }
// check if we zoom in or out
if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
this._formClusters(force);
}
- else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom out
- this._openClusters(recursive,force);
- this._openClustersBySize();
+ else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in
+
+ if (force == false) {
+ this._openClustersBySize();
+ }
+ else {
+ this._openClusters(recursive,force);
+ }
}
this._updateNodeIndexList();
// if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
if (this.nodeIndices.length == amountOfNodes && (this.previousScale > this.scale || zoomDirection == -1)) {
this._aggregateHubs(force);
+ this._updateNodeIndexList();
}
// we now reduce snakes.
@@ -11078,7 +11213,7 @@ Cluster.prototype.handleSnakes = function() {
*/
Cluster.prototype._aggregateHubs = function(force) {
this._getHubSize();
- this._formClustersByHub(force);
+ this._formClustersByHub(force,false);
};
@@ -11116,11 +11251,10 @@ Cluster.prototype.forceAggregateHubs = function() {
Cluster.prototype._openClustersBySize = function() {
for (nodeID in this.nodes) {
if (this.nodes.hasOwnProperty(nodeID)) {
- node = this.nodes[nodeID];
+ var 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);
+ if ((node.width*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
+ (node.height*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
this.openCluster(node);
}
}
@@ -11129,9 +11263,6 @@ Cluster.prototype._openClustersBySize = function() {
};
-
-
-
/**
* This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it
* has to be opened based on the current zoom level.
@@ -11160,7 +11291,7 @@ Cluster.prototype._expandClusterNode = function(parentNode, recursive, force, op
// first check if node is a cluster
if (parentNode.clusterSize > 1) {
// this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
- if (parentNode.clusterSize < 20) {
+ if (parentNode.clusterSize < this.constants.clustering.sectorThreshold) {
openAll = true;
}
recursive = openAll ? true : recursive;
@@ -11180,7 +11311,7 @@ Cluster.prototype._expandClusterNode = function(parentNode, recursive, force, op
}
}
else {
- if (this._parentNodeInActiveArea(parentNode)) {
+ if (this._nodeInActiveArea(parentNode)) {
this._expelChildFromParent(parentNode,containedNodeID,recursive,force,openAll);
}
}
@@ -11292,7 +11423,7 @@ Cluster.prototype._formClusters = function(force) {
*/
Cluster.prototype._formClustersByZoom = function() {
var dx,dy,length,
- minLength = this.constants.clustering.clusterEdgeLength/this.scale;
+ minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
// check if any edges are shorter than minLength and start the clustering
// the clustering favours the node with the larger mass
@@ -11300,25 +11431,27 @@ Cluster.prototype._formClustersByZoom = function() {
if (this.edges.hasOwnProperty(edgeID)) {
var edge = this.edges[edgeID];
if (edge.connected) {
- dx = (edge.to.x - edge.from.x);
- dy = (edge.to.y - edge.from.y);
- length = Math.sqrt(dx * dx + dy * dy);
-
-
- if (length < minLength) {
- // first check which node is larger
- var parentNode = edge.from;
- var childNode = edge.to;
- if (edge.to.mass > edge.from.mass) {
- parentNode = edge.to;
- childNode = edge.from;
- }
+ if (edge.toId != edge.fromId) {
+ dx = (edge.to.x - edge.from.x);
+ dy = (edge.to.y - edge.from.y);
+ length = Math.sqrt(dx * dx + dy * dy);
+
+
+ if (length < minLength) {
+ // first check which node is larger
+ var parentNode = edge.from;
+ var childNode = edge.to;
+ if (edge.to.mass > edge.from.mass) {
+ parentNode = edge.to;
+ childNode = edge.from;
+ }
- if (childNode.dynamicEdgesLength == 1) {
- this._addToCluster(parentNode,childNode,false);
- }
- else if (parentNode.dynamicEdgesLength == 1) {
- this._addToCluster(childNode,parentNode,false);
+ if (childNode.dynamicEdgesLength == 1) {
+ this._addToCluster(parentNode,childNode,false);
+ }
+ else if (parentNode.dynamicEdgesLength == 1) {
+ this._addToCluster(childNode,parentNode,false);
+ }
}
}
}
@@ -11344,11 +11477,13 @@ Cluster.prototype._forceClustersByZoom = function() {
var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
// group to the largest node
- if (parentNode.mass > childNode.mass) {
- this._addToCluster(parentNode,childNode,true);
- }
- else {
- this._addToCluster(childNode,parentNode,true);
+ if (childNode.id != parentNode.id) {
+ if (parentNode.mass > childNode.mass) {
+ this._addToCluster(parentNode,childNode,true);
+ }
+ else {
+ this._addToCluster(childNode,parentNode,true);
+ }
}
}
}
@@ -11387,14 +11522,12 @@ Cluster.prototype._formClusterFromHub = function(hubNode, force, onlyEqual, abso
if (absorptionSizeOffset === undefined) {
absorptionSizeOffset = 0;
}
-
// we decide if the node is a hub
if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) ||
(hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) {
-
// initialize variables
var dx,dy,length;
- var minLength = this.constants.clustering.clusterEdgeLength/this.scale;
+ var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
var allowCluster = false;
// we create a list of edges because the dynamicEdges change over the course of this loop
@@ -11405,20 +11538,22 @@ Cluster.prototype._formClusterFromHub = function(hubNode, force, onlyEqual, abso
}
// if the hub clustering is not forces, we check if one of the edges connected
- // to a cluster is small enough based on the constants.clustering.clusterEdgeLength
+ // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
if (force == false) {
allowCluster = false;
for (j = 0; j < amountOfInitialEdges; j++) {
var edge = this.edges[edgesIDarray[j]];
if (edge !== undefined) {
if (edge.connected) {
- dx = (edge.to.x - edge.from.x);
- dy = (edge.to.y - edge.from.y);
- length = Math.sqrt(dx * dx + dy * dy);
+ if (edge.toId != edge.fromId) {
+ dx = (edge.to.x - edge.from.x);
+ dy = (edge.to.y - edge.from.y);
+ length = Math.sqrt(dx * dx + dy * dy);
- if (length < minLength) {
- allowCluster = true;
- break;
+ if (length < minLength) {
+ allowCluster = true;
+ break;
+ }
}
}
}
@@ -11430,13 +11565,12 @@ Cluster.prototype._formClusterFromHub = function(hubNode, force, onlyEqual, abso
// we loop over all edges INITIALLY connected to this hub
for (j = 0; j < amountOfInitialEdges; j++) {
edge = this.edges[edgesIDarray[j]];
-
// the edge can be clustered by this function in a previous loop
if (edge !== undefined) {
var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
-
- // we do not want hubs to merge with other hubs.
- if (childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) {
+ // we do not want hubs to merge with other hubs nor do we want to cluster itself.
+ if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
+ (childNode.id != hubNode.id)) {
this._addToCluster(hubNode,childNode,force);
}
}
@@ -11449,7 +11583,6 @@ Cluster.prototype._formClusterFromHub = function(hubNode, force, onlyEqual, abso
/**
* This function adds the child node to the parent node, creating a cluster if it is not already.
- * This function is called only from updateClusters()
*
* @param {Node} parentNode | this is the node that will house the child node
* @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node
@@ -11470,13 +11603,18 @@ Cluster.prototype._addToCluster = function(parentNode, childNode, force) {
this._connectEdgeToCluster(parentNode,childNode,edge);
}
}
+ // a contained node has no dynamic edges.
childNode.dynamicEdges = [];
+ // remove circular edges from clusters
+ this._containCircularEdgesFromNode(parentNode,childNode);
+
+
// remove the childNode from the global nodes object
delete this.nodes[childNode.id];
+ // update the properties of the child and parent
var massBefore = parentNode.mass;
-
childNode.clusterSession = this.clusterSession;
parentNode.mass += this.constants.clustering.massTransferCoefficient * childNode.mass;
parentNode.clusterSize += childNode.clusterSize;
@@ -11487,9 +11625,10 @@ Cluster.prototype._addToCluster = function(parentNode, childNode, force) {
parentNode.clusterSessions.push(this.clusterSession);
}
- // giving the clusters a dynamic formationScale to ensure not all clusters open up when zoomed
+ // forced clusters only open from screen size and double tap
if (force == true) {
- parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3);
+ // parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3);
+ parentNode.formationScale = 0;
}
else {
parentNode.formationScale = this.scale; // The latest child has been added on this scale
@@ -11507,7 +11646,6 @@ Cluster.prototype._addToCluster = function(parentNode, childNode, force) {
// the mass has altered, preservation of energy dictates the velocity to be updated
parentNode.updateVelocity(massBefore);
-
// restart the simulation to reorganise all nodes
this.moving = true;
};
@@ -11538,7 +11676,6 @@ Cluster.prototype._updateDynamicEdges = function() {
}
}
}
-
node.dynamicEdgesLength -= correction;
}
};
@@ -11582,21 +11719,39 @@ Cluster.prototype._addToContainedEdges = function(parentNode, childNode, edge) {
* @private
*/
Cluster.prototype._connectEdgeToCluster = function(parentNode, childNode, edge) {
- if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
- edge.originalToID.push(childNode.id);
- edge.to = parentNode;
- edge.toId = parentNode.id;
- }
- else { // edge connected to other node with the "from" side
- edge.originalFromID.push(childNode.id);
- edge.from = parentNode;
- edge.fromId = parentNode.id;
+ // handle circular edges
+ if (edge.toId == edge.fromId) {
+ this._addToContainedEdges(parentNode, childNode, edge);
}
+ else {
+ if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
+ edge.originalToID.push(childNode.id);
+ edge.to = parentNode;
+ edge.toId = parentNode.id;
+ }
+ else { // edge connected to other node with the "from" side
+ edge.originalFromID.push(childNode.id);
+ edge.from = parentNode;
+ edge.fromId = parentNode.id;
+ }
- this._addToReroutedEdges(parentNode,childNode,edge);
+ this._addToReroutedEdges(parentNode,childNode,edge);
+ }
};
+Cluster.prototype._containCircularEdgesFromNode = function(parentNode, childNode) {
+ // manage all the edges connected to the child and parent nodes
+ for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
+ var edge = parentNode.dynamicEdges[i];
+ // handle circular edges
+ if (edge.toId == edge.fromId) {
+ this._addToContainedEdges(parentNode, childNode, edge);
+ }
+ }
+}
+
+
/**
* This adds an edge from the childNode to the rerouted edges of the parent node
*
@@ -11726,7 +11881,12 @@ Cluster.prototype.updateLabels = function() {
if (this.nodes.hasOwnProperty(nodeID)) {
node = this.nodes[nodeID];
if (node.clusterSize == 1) {
- node.label = String(node.id);
+ if (node.originalLabel !== undefined) {
+ node.label = node.originalLabel;
+ }
+ else {
+ node.label = String(node.id);
+ }
}
}
}
@@ -11750,7 +11910,7 @@ Cluster.prototype.updateLabels = function() {
* @returns {boolean}
* @private
*/
-Cluster.prototype._parentNodeInActiveArea = function(node) {
+Cluster.prototype._nodeInActiveArea = function(node) {
return (
Math.abs(node.x - this.zoomCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
&&
@@ -11829,11 +11989,13 @@ Cluster.prototype._getHubSize = function() {
*/
Cluster.prototype._reduceAmountOfSnakes = function(fraction) {
this.hubThreshold = 2;
+ var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
for (nodeID in this.nodes) {
if (this.nodes.hasOwnProperty(nodeID)) {
if (this.nodes[nodeID].dynamicEdgesLength == 2 && this.nodes[nodeID].dynamicEdges.length >= 2) {
- if (Math.random() <= fraction) {
- this._formClusterFromHub(this.nodes[nodeID],true,true,1)
+ if (reduceAmount > 0) {
+ this._formClusterFromHub(this.nodes[nodeID],true,true,1);
+ reduceAmount -= 1;
}
}
}
@@ -11896,8 +12058,8 @@ function Graph (container, data, options) {
//fontFace: verdana,
fontFace: 'arial',
color: {
- border: '#2B7CE9',
- background: '#97C2FC',
+ border: '#2B7CE9',
+ background: '#97C2FC',
highlight: {
border: '#2B7CE9',
background: '#D2E5FF'
@@ -11925,15 +12087,16 @@ function Graph (container, data, options) {
altLength: undefined
}
},
- clustering: { // TODO: naming of variables
- enableClustering: true, // global on/off switch for clustering.
+ clustering: {
+ enableClustering: false, // global on/off switch for clustering.
maxNumberOfNodes: 100, // for automatic (initial) clustering
- snakeThreshold: 0.5, // maximum percentage of allowed snakenodes (long strings of connected nodes) within all nodes
- clusterEdgeLength: 25, // threshold edge length for clustering
- relativeOpenFactor: 0.2, // if the width or height of a cluster takes up this much of the screen, open the cluster
+ snakeThreshold: 0.7, // maximum percentage of allowed snakenodes (long strings of connected nodes) within all nodes
+ clusterEdgeThreshold: 15, // edge length threshold. if smaller, this node is clustered
+ sectorThreshold: 50, // cluster size threshold. If larger, expanding in own sector.
+ screenSizeThreshold: 0.2, // relative size threshold. If the width or height of a clusternode takes up this much of the screen, decluster node
fontSizeMultiplier: 4, // how much the cluster font size grows per node in cluster (in px)
- forceAmplification: 0.7, // factor of increase fo the repulsion force of a cluster (per node in cluster)
- distanceAmplification: 0.3, // factor how much the repulsion distance of a cluster increases (per node in cluster).
+ forceAmplification: 0.6, // factor of increase fo the repulsion force of a cluster (per node in cluster)
+ distanceAmplification: 0.2, // factor how much the repulsion distance of a cluster increases (per node in cluster).
edgeGrowth: 11, // amount of clusterSize connected to the edge is multiplied with this and added to edgeLength
clusterSizeWidthFactor: 10, // growth of the width per node in cluster
clusterSizeHeightFactor: 10, // growth of the height per node in cluster
@@ -12118,7 +12281,7 @@ Graph.prototype.setData = function(data, disableStart) {
this._setEdges(data && data.edges);
}
- this._putDataInUniverse();
+ this._putDataInSector();
if (!disableStart) {
// find a stable position or start animating to a stable position
@@ -12141,17 +12304,17 @@ Graph.prototype.setOptions = function (options) {
if (options.stabilize !== undefined) {this.stabilize = options.stabilize;}
if (options.selectable !== undefined) {this.selectable = options.selectable;}
- if (optiones.clustering) {
+ if (options.clustering) {
for (var prop in optiones.clustering) {
if (options.clustering.hasOwnProperty(prop)) {
- this.constants.clustering[prop] = options.edges[prop];
+ this.constants.clustering[prop] = options.clustering[prop];
}
}
}
// TODO: work out these options and document them
if (options.edges) {
- for (var prop in options.edges) {
+ for (prop in options.edges) {
if (options.edges.hasOwnProperty(prop)) {
this.constants.edges[prop] = options.edges[prop];
}
@@ -12278,7 +12441,7 @@ Graph.prototype._create = function () {
this.mouseTrap.bind("-",this.increaseClusterLevel.bind(me));
this.mouseTrap.bind("s",this.singleStep.bind(me));
this.mouseTrap.bind("h",this.updateClustersDefault.bind(me));
- this.mouseTrap.bind("c",this._collapseUniverse.bind(me));
+ this.mouseTrap.bind("c",this._collapseSector.bind(me));
this.mouseTrap.bind("f",this.toggleFreeze.bind(me));
// add the frame to the container element
@@ -12461,7 +12624,6 @@ Graph.prototype._onTap = function (event) {
if (node) {
if (node.isSelected() && elapsedTime < 300) {
this.openCluster(node);
- this.openCluster(node);
}
// select this node
this._selectNodes([nodeId]);
@@ -12839,10 +13001,10 @@ Graph.prototype._selectNodes = function(selection, append) {
*/
Graph.prototype._getNodesOverlappingWith = function (obj) {
var overlappingNodes = [];
- var nodes;
+ var nodes, sector;
// search in all sectors for nodes
- for (var sector in this.sectors["active"]) {
+ for (sector in this.sectors["active"]) {
if (this.sectors["active"].hasOwnProperty(sector)) {
nodes = this.sectors["active"][sector]["nodes"];
for (var id in nodes) {
@@ -12855,7 +13017,7 @@ Graph.prototype._getNodesOverlappingWith = function (obj) {
}
}
- for (var sector in this.sectors["frozen"]) {
+ for (sector in this.sectors["frozen"]) {
if (this.sectors["frozen"].hasOwnProperty(sector)) {
nodes = this.sectors["frozen"][sector]["nodes"];
for (var id in nodes) {
@@ -13361,8 +13523,9 @@ Graph.prototype._redraw = function() {
ctx.translate(this.translation.x, this.translation.y);
ctx.scale(this.scale, this.scale);
- this._doInAllUniverses("_drawEdges",ctx);
- this._doInAllUniverses("_drawNodes",ctx);
+ this._doInAllSectors("_drawAllSectorNodes",ctx);
+ this._doInAllSectors("_drawEdges",ctx);
+ this._doInAllSectors("_drawNodes",ctx);
// restore original scaling and translation
ctx.restore();
@@ -13790,8 +13953,8 @@ Graph.prototype._discreteStepNodes = function() {
Graph.prototype.start = function() {
if (!this.freezeSimulation) {
if (this.moving) {
- this._doInAllActiveUniverses("_calculateForces");
- this._doInAllActiveUniverses("_discreteStepNodes");
+ this._doInAllActiveSectors("_calculateForces");
+ this._doInAllActiveSectors("_discreteStepNodes");
}
if (this.moving) {
@@ -13851,18 +14014,19 @@ Graph.prototype._loadSectorSystem = function() {
this.sectors = {};
this.activeSector = ["default"];
this.sectors["active"] = {};
- this.sectors["active"][this.activeSector[this.activeSector.length-1]] = {"nodes":{},"edges":{},"nodeIndices":[]};
+ this.sectors["active"][this.activeSector[this.activeSector.length-1]] = {"nodes":{},
+ "edges":{},
+ "nodeIndices":[],
+ "formationScale": 1.0,
+ "drawingNode": undefined};
this.sectors["frozen"] = {};
- this.sectors["draw"] = {};
this.nodeIndices = this.sectors["active"][this.activeSector[this.activeSector.length-1]]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
- for (var mixinFunction in UniverseMixin) {
- if (UniverseMixin.hasOwnProperty(mixinFunction)) {
- Graph.prototype[mixinFunction] = UniverseMixin[mixinFunction];
+ for (var mixinFunction in SectorMixin) {
+ if (SectorMixin.hasOwnProperty(mixinFunction)) {
+ Graph.prototype[mixinFunction] = SectorMixin[mixinFunction];
}
}
-
-
};
/**
* vis.js module exports
diff --git a/examples/graph/17_network_info.html b/examples/graph/17_network_info.html
index ec4379e2..0b7e033f 100644
--- a/examples/graph/17_network_info.html
+++ b/examples/graph/17_network_info.html
@@ -17,7 +17,7 @@
}
-
+