| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * Creation of the ClusterMixin var.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * This contains all the functions the Network object can use to employ clustering
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								* This is only called in the constructor of the network object
							 | 
						
						
						
							| 
								
							 | 
							
								*
							 | 
						
						
						
							| 
								
							 | 
							
								*/
							 | 
						
						
						
							| 
								
							 | 
							
								exports.startWithClustering = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								 // cluster if the data set is big
							 | 
						
						
						
							| 
								
							 | 
							
								 this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								 // updates the lables after clustering
							 | 
						
						
						
							| 
								
							 | 
							
								 this.updateLabels();
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								 // this is called here because if clusterin is disabled, the start and stabilize are called in
							 | 
						
						
						
							| 
								
							 | 
							
								 // the setData function.
							 | 
						
						
						
							| 
								
							 | 
							
								 if (this.stabilize) {
							 | 
						
						
						
							| 
								
							 | 
							
								   this._stabilize();
							 | 
						
						
						
							| 
								
							 | 
							
								 }
							 | 
						
						
						
							| 
								
							 | 
							
								 this.start();
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function clusters until the initialMaxNodes has been reached
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Number}  maxNumberOfNodes
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} reposition
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.clusterToFit = function(maxNumberOfNodes, reposition) {
							 | 
						
						
						
							| 
								
							 | 
							
								  var numberOfNodes = this.nodeIndices.length;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  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 % 3 == 0) {
							 | 
						
						
						
							| 
								
							 | 
							
								      this.forceAggregateHubs(true);
							 | 
						
						
						
							| 
								
							 | 
							
								      this.normalizeClusterLevels();
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								    else {
							 | 
						
						
						
							| 
								
							 | 
							
								      this.increaseClusterLevel(); // this also includes a cluster normalization
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    numberOfNodes = this.nodeIndices.length;
							 | 
						
						
						
							| 
								
							 | 
							
								    level += 1;
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // after the clustering we reposition the nodes to reduce the initial chaos
							 | 
						
						
						
							| 
								
							 | 
							
								  if (level > 0 && reposition == true) {
							 | 
						
						
						
							| 
								
							 | 
							
								    this.repositionNodes();
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  this._updateCalculationNodes();
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function can be called to open up a specific cluster. It is only called by
							 | 
						
						
						
							| 
								
							 | 
							
								 * It will unpack the cluster back one level.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param node    | Node object: cluster to open.
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.openCluster = function(node) {
							 | 
						
						
						
							| 
								
							 | 
							
								  var isMovingBeforeClustering = this.moving;
							 | 
						
						
						
							| 
								
							 | 
							
								  if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
							 | 
						
						
						
							| 
								
							 | 
							
								    !(this._sector() == "default" && this.nodeIndices.length == 1)) {
							 | 
						
						
						
							| 
								
							 | 
							
								    // this loads a new sector, loads the nodes and edges and nodeIndices of it.
							 | 
						
						
						
							| 
								
							 | 
							
								    this._addSector(node);
							 | 
						
						
						
							| 
								
							 | 
							
								    var level = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // we decluster until we reach a decent number of nodes
							 | 
						
						
						
							| 
								
							 | 
							
								    while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      this.decreaseClusterLevel();
							 | 
						
						
						
							| 
								
							 | 
							
								      level += 1;
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  else {
							 | 
						
						
						
							| 
								
							 | 
							
								    this._expandClusterNode(node,false,true);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // update the index list, dynamic edges and labels
							 | 
						
						
						
							| 
								
							 | 
							
								    this._updateNodeIndexList();
							 | 
						
						
						
							| 
								
							 | 
							
								    this._updateDynamicEdges();
							 | 
						
						
						
							| 
								
							 | 
							
								    this._updateCalculationNodes();
							 | 
						
						
						
							| 
								
							 | 
							
								    this.updateLabels();
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
							 | 
						
						
						
							| 
								
							 | 
							
								  if (this.moving != isMovingBeforeClustering) {
							 | 
						
						
						
							| 
								
							 | 
							
								    this.start();
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This calls the updateClustes with default arguments
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.updateClustersDefault = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  if (this.constants.clustering.enabled == true) {
							 | 
						
						
						
							| 
								
							 | 
							
								    this.updateClusters(0,false,false);
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
							 | 
						
						
						
							| 
								
							 | 
							
								 * be clustered with their connected node. This can be repeated as many times as needed.
							 | 
						
						
						
							| 
								
							 | 
							
								 * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets.
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.increaseClusterLevel = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  this.updateClusters(-1,false,true);
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
							 | 
						
						
						
							| 
								
							 | 
							
								 * be unpacked if they are a cluster. This can be repeated as many times as needed.
							 | 
						
						
						
							| 
								
							 | 
							
								 * This can be called externally (by a key-bind for instance) to look into clusters without zooming.
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.decreaseClusterLevel = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  this.updateClusters(1,false,true);
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This is the main clustering function. It clusters and declusters on zoom or forced
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function clusters on zoom, it can be called with a predefined zoom direction
							 | 
						
						
						
							| 
								
							 | 
							
								 * If out, check if we can form clusters, if in, check if we can open clusters.
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function is only called from _zoom()
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Number} zoomDirection  | -1 / 0 / +1   for  zoomOut / determineByZoom / zoomIn
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} recursive     | enabled or disable recursive calling of the opening of clusters
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} force         | enabled or disable forcing
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} doNotStart    | if true do not call start
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
							 | 
						
						
						
							| 
								
							 | 
							
								  var isMovingBeforeClustering = this.moving;
							 | 
						
						
						
							| 
								
							 | 
							
								  var amountOfNodes = this.nodeIndices.length;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // on zoom out collapse the sector if the scale is at the level the sector was made
							 | 
						
						
						
							| 
								
							 | 
							
								  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
							 | 
						
						
						
							| 
								
							 | 
							
								    // forming clusters when forced pulls outliers in. When not forced, the edge length of the
							 | 
						
						
						
							| 
								
							 | 
							
								    // outer nodes determines if it is being clustered
							 | 
						
						
						
							| 
								
							 | 
							
								    this._formClusters(force);
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in
							 | 
						
						
						
							| 
								
							 | 
							
								    if (force == true) {
							 | 
						
						
						
							| 
								
							 | 
							
								      // _openClusters checks for each node if the formationScale of the cluster is smaller than
							 | 
						
						
						
							| 
								
							 | 
							
								      // the current scale and if so, declusters. When forced, all clusters are reduced by one step
							 | 
						
						
						
							| 
								
							 | 
							
								      this._openClusters(recursive,force);
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								    else {
							 | 
						
						
						
							| 
								
							 | 
							
								      // if a cluster takes up a set percentage of the active window
							 | 
						
						
						
							| 
								
							 | 
							
								      this._openClustersBySize();
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  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 chains.
							 | 
						
						
						
							| 
								
							 | 
							
								  if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
							 | 
						
						
						
							| 
								
							 | 
							
								    this.handleChains();
							 | 
						
						
						
							| 
								
							 | 
							
								    this._updateNodeIndexList();
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  this.previousScale = this.scale;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // rest of the update the index list, dynamic edges and labels
							 | 
						
						
						
							| 
								
							 | 
							
								  this._updateDynamicEdges();
							 | 
						
						
						
							| 
								
							 | 
							
								  this.updateLabels();
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // if a cluster was formed, we increase the clusterSession
							 | 
						
						
						
							| 
								
							 | 
							
								  if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
							 | 
						
						
						
							| 
								
							 | 
							
								    this.clusterSession += 1;
							 | 
						
						
						
							| 
								
							 | 
							
								    // if clusters have been made, we normalize the cluster level
							 | 
						
						
						
							| 
								
							 | 
							
								    this.normalizeClusterLevels();
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  if (doNotStart == false || doNotStart === undefined) {
							 | 
						
						
						
							| 
								
							 | 
							
								    // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.moving != isMovingBeforeClustering) {
							 | 
						
						
						
							| 
								
							 | 
							
								      this.start();
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  this._updateCalculationNodes();
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function handles the chains. It is called on every updateClusters().
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.handleChains = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  // after clustering we check how many chains there are
							 | 
						
						
						
							| 
								
							 | 
							
								  var chainPercentage = this._getChainFraction();
							 | 
						
						
						
							| 
								
							 | 
							
								  if (chainPercentage > this.constants.clustering.chainThreshold) {
							 | 
						
						
						
							| 
								
							 | 
							
								    this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * this functions starts clustering by hubs
							 | 
						
						
						
							| 
								
							 | 
							
								 * The minimum hub threshold is set globally
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._aggregateHubs = function(force) {
							 | 
						
						
						
							| 
								
							 | 
							
								  this._getHubSize();
							 | 
						
						
						
							| 
								
							 | 
							
								  this._formClustersByHub(force,false);
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function is fired by keypress. It forces hubs to form.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.forceAggregateHubs = function(doNotStart) {
							 | 
						
						
						
							| 
								
							 | 
							
								  var isMovingBeforeClustering = this.moving;
							 | 
						
						
						
							| 
								
							 | 
							
								  var amountOfNodes = this.nodeIndices.length;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  this._aggregateHubs(true);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // update the index list, dynamic edges and labels
							 | 
						
						
						
							| 
								
							 | 
							
								  this._updateNodeIndexList();
							 | 
						
						
						
							| 
								
							 | 
							
								  this._updateDynamicEdges();
							 | 
						
						
						
							| 
								
							 | 
							
								  this.updateLabels();
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // if a cluster was formed, we increase the clusterSession
							 | 
						
						
						
							| 
								
							 | 
							
								  if (this.nodeIndices.length != amountOfNodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								    this.clusterSession += 1;
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  if (doNotStart == false || doNotStart === undefined) {
							 | 
						
						
						
							| 
								
							 | 
							
								    // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.moving != isMovingBeforeClustering) {
							 | 
						
						
						
							| 
								
							 | 
							
								      this.start();
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * If a cluster takes up more than a set percentage of the screen, open the cluster
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._openClustersBySize = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      var node = this.nodes[nodeId];
							 | 
						
						
						
							| 
								
							 | 
							
								      if (node.inView() == true) {
							 | 
						
						
						
							| 
								
							 | 
							
								        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);
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * 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.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._openClusters = function(recursive,force) {
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < this.nodeIndices.length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								    var node = this.nodes[this.nodeIndices[i]];
							 | 
						
						
						
							| 
								
							 | 
							
								    this._expandClusterNode(node,recursive,force);
							 | 
						
						
						
							| 
								
							 | 
							
								    this._updateCalculationNodes();
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function checks if a node has to be opened. This is done by checking the zoom level.
							 | 
						
						
						
							| 
								
							 | 
							
								 * If the node contains child nodes, this function is recursively called on the child nodes as well.
							 | 
						
						
						
							| 
								
							 | 
							
								 * This recursive behaviour is optional and can be set by the recursive argument.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Node}    parentNode    | to check for cluster and expand
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} recursive     | enabled or disable recursive calling
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} force         | enabled or disable forcing
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} [openAll]     | This will recursively force all nodes in the parent to be released
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
							 | 
						
						
						
							| 
								
							 | 
							
								  // 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 < this.constants.clustering.sectorThreshold) {
							 | 
						
						
						
							| 
								
							 | 
							
								      openAll = true;
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								    recursive = openAll ? true : recursive;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // if the last child has been added on a smaller scale than current scale decluster
							 | 
						
						
						
							| 
								
							 | 
							
								    if (parentNode.formationScale < this.scale || force == true) {
							 | 
						
						
						
							| 
								
							 | 
							
								      // we will check if any of the contained child nodes should be removed from the cluster
							 | 
						
						
						
							| 
								
							 | 
							
								      for (var containedNodeId in parentNode.containedNodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								        if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								          var childNode = parentNode.containedNodes[containedNodeId];
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								          // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that
							 | 
						
						
						
							| 
								
							 | 
							
								          // the largest cluster is the one that comes from outside
							 | 
						
						
						
							| 
								
							 | 
							
								          if (force == true) {
							 | 
						
						
						
							| 
								
							 | 
							
								            if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1]
							 | 
						
						
						
							| 
								
							 | 
							
								                || openAll) {
							 | 
						
						
						
							| 
								
							 | 
							
								              this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
							 | 
						
						
						
							| 
								
							 | 
							
								            }
							 | 
						
						
						
							| 
								
							 | 
							
								          }
							 | 
						
						
						
							| 
								
							 | 
							
								          else {
							 | 
						
						
						
							| 
								
							 | 
							
								            if (this._nodeInActiveArea(parentNode)) {
							 | 
						
						
						
							| 
								
							 | 
							
								              this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
							 | 
						
						
						
							| 
								
							 | 
							
								            }
							 | 
						
						
						
							| 
								
							 | 
							
								          }
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * ONLY CALLED FROM _expandClusterNode
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove
							 | 
						
						
						
							| 
								
							 | 
							
								 * the child node from the parent contained_node object and put it back into the global nodes object.
							 | 
						
						
						
							| 
								
							 | 
							
								 * The same holds for the edge that was connected to the child node. It is moved back into the global edges object.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Node}    parentNode        | the parent node
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {String}  containedNodeId   | child_node id as it is contained in the containedNodes object of the parent node
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} recursive         | This will also check if the child needs to be expanded.
							 | 
						
						
						
							| 
								
							 | 
							
								 *                                      With force and recursive both true, the entire cluster is unpacked
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} force             | This will disregard the zoom level and will expel this child from the parent
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} openAll           | This will recursively force all nodes in the parent to be released
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) {
							 | 
						
						
						
							| 
								
							 | 
							
								  var childNode = parentNode.containedNodes[containedNodeId];
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // if child node has been added on smaller scale than current, kick out
							 | 
						
						
						
							| 
								
							 | 
							
								  if (childNode.formationScale < this.scale || force == true) {
							 | 
						
						
						
							| 
								
							 | 
							
								    // unselect all selected items
							 | 
						
						
						
							| 
								
							 | 
							
								    this._unselectAll();
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // put the child node back in the global nodes object
							 | 
						
						
						
							| 
								
							 | 
							
								    this.nodes[containedNodeId] = childNode;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // release the contained edges from this childNode back into the global edges
							 | 
						
						
						
							| 
								
							 | 
							
								    this._releaseContainedEdges(parentNode,childNode);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // reconnect rerouted edges to the childNode
							 | 
						
						
						
							| 
								
							 | 
							
								    this._connectEdgeBackToChild(parentNode,childNode);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // validate all edges in dynamicEdges
							 | 
						
						
						
							| 
								
							 | 
							
								    this._validateEdges(parentNode);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // undo the changes from the clustering operation on the parent node
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.mass -= childNode.mass;
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.clusterSize -= childNode.clusterSize;
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // place the child node near the parent, not at the exact same location to avoid chaos in the system
							 | 
						
						
						
							| 
								
							 | 
							
								    childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
							 | 
						
						
						
							| 
								
							 | 
							
								    childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // remove node from the list
							 | 
						
						
						
							| 
								
							 | 
							
								    delete parentNode.containedNodes[containedNodeId];
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // check if there are other childs with this clusterSession in the parent.
							 | 
						
						
						
							| 
								
							 | 
							
								    var othersPresent = false;
							 | 
						
						
						
							| 
								
							 | 
							
								    for (var childNodeId in parentNode.containedNodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								      if (parentNode.containedNodes.hasOwnProperty(childNodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								        if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) {
							 | 
						
						
						
							| 
								
							 | 
							
								          othersPresent = true;
							 | 
						
						
						
							| 
								
							 | 
							
								          break;
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								    // if there are no others, remove the cluster session from the list
							 | 
						
						
						
							| 
								
							 | 
							
								    if (othersPresent == false) {
							 | 
						
						
						
							| 
								
							 | 
							
								      parentNode.clusterSessions.pop();
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    this._repositionBezierNodes(childNode);
							 | 
						
						
						
							| 
								
							 | 
							
								//      this._repositionBezierNodes(parentNode);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // remove the clusterSession from the child node
							 | 
						
						
						
							| 
								
							 | 
							
								    childNode.clusterSession = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // recalculate the size of the node on the next time the node is rendered
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.clearSizeCache();
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // restart the simulation to reorganise all nodes
							 | 
						
						
						
							| 
								
							 | 
							
								    this.moving = true;
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // check if a further expansion step is possible if recursivity is enabled
							 | 
						
						
						
							| 
								
							 | 
							
								  if (recursive == true) {
							 | 
						
						
						
							| 
								
							 | 
							
								    this._expandClusterNode(childNode,recursive,force,openAll);
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * position the bezier nodes at the center of the edges
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param node
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._repositionBezierNodes = function(node) {
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < node.dynamicEdges.length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								    node.dynamicEdges[i].positionBezierNode();
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function checks if any nodes at the end of their trees have edges below a threshold length
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function is called only from updateClusters()
							 | 
						
						
						
							| 
								
							 | 
							
								 * forceLevelCollapse ignores the length of the edge and collapses one level
							 | 
						
						
						
							| 
								
							 | 
							
								 * This means that a node with only one edge will be clustered with its connected node
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} force
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._formClusters = function(force) {
							 | 
						
						
						
							| 
								
							 | 
							
								  if (force == false) {
							 | 
						
						
						
							| 
								
							 | 
							
								    this._formClustersByZoom();
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  else {
							 | 
						
						
						
							| 
								
							 | 
							
								    this._forceClustersByZoom();
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function handles the clustering by zooming out, this is based on a minimum edge distance
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._formClustersByZoom = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  var dx,dy,length,
							 | 
						
						
						
							| 
								
							 | 
							
								      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
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var edgeId in this.edges) {
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.edges.hasOwnProperty(edgeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      var edge = this.edges[edgeId];
							 | 
						
						
						
							| 
								
							 | 
							
								      if (edge.connected) {
							 | 
						
						
						
							| 
								
							 | 
							
								        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);
							 | 
						
						
						
							| 
								
							 | 
							
								            }
							 | 
						
						
						
							| 
								
							 | 
							
								          }
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function forces the network to cluster all nodes with only one connecting edge to their
							 | 
						
						
						
							| 
								
							 | 
							
								 * connected node.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._forceClustersByZoom = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								    // another node could have absorbed this child.
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      var childNode = this.nodes[nodeId];
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								      // the edges can be swallowed by another decrease
							 | 
						
						
						
							| 
								
							 | 
							
								      if (childNode.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) {
							 | 
						
						
						
							| 
								
							 | 
							
								        var edge = childNode.dynamicEdges[0];
							 | 
						
						
						
							| 
								
							 | 
							
								        var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								        // group to the largest node
							 | 
						
						
						
							| 
								
							 | 
							
								        if (childNode.id != parentNode.id) {
							 | 
						
						
						
							| 
								
							 | 
							
								          if (parentNode.mass > childNode.mass) {
							 | 
						
						
						
							| 
								
							 | 
							
								            this._addToCluster(parentNode,childNode,true);
							 | 
						
						
						
							| 
								
							 | 
							
								          }
							 | 
						
						
						
							| 
								
							 | 
							
								          else {
							 | 
						
						
						
							| 
								
							 | 
							
								            this._addToCluster(childNode,parentNode,true);
							 | 
						
						
						
							| 
								
							 | 
							
								          }
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * To keep the nodes of roughly equal size we normalize the cluster levels.
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function clusters a node to its smallest connected neighbour.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param node
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._clusterToSmallestNeighbour = function(node) {
							 | 
						
						
						
							| 
								
							 | 
							
								  var smallestNeighbour = -1;
							 | 
						
						
						
							| 
								
							 | 
							
								  var smallestNeighbourNode = null;
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < node.dynamicEdges.length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								    if (node.dynamicEdges[i] !== undefined) {
							 | 
						
						
						
							| 
								
							 | 
							
								      var neighbour = null;
							 | 
						
						
						
							| 
								
							 | 
							
								      if (node.dynamicEdges[i].fromId != node.id) {
							 | 
						
						
						
							| 
								
							 | 
							
								        neighbour = node.dynamicEdges[i].from;
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								      else if (node.dynamicEdges[i].toId != node.id) {
							 | 
						
						
						
							| 
								
							 | 
							
								        neighbour = node.dynamicEdges[i].to;
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								      if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
							 | 
						
						
						
							| 
								
							 | 
							
								        smallestNeighbour = neighbour.clusterSessions.length;
							 | 
						
						
						
							| 
								
							 | 
							
								        smallestNeighbourNode = neighbour;
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
							 | 
						
						
						
							| 
								
							 | 
							
								    this._addToCluster(neighbour, node, true);
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function forms clusters from hubs, it loops over all nodes
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} force         |   Disregard zoom level
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} onlyEqual     |   This only clusters a hub with a specific number of edges
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._formClustersByHub = function(force, onlyEqual) {
							 | 
						
						
						
							| 
								
							 | 
							
								  // we loop over all nodes in the list
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								    // we check if it is still available since it can be used by the clustering in this loop
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function forms a cluster from a specific preselected hub node
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Node}    hubNode       |   the node we will cluster as a hub
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} force         |   Disregard zoom level
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} onlyEqual     |   This only clusters a hub with a specific number of edges
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Number} [absorptionSizeOffset] |
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSizeOffset) {
							 | 
						
						
						
							| 
								
							 | 
							
								  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.clusterEdgeThreshold/this.scale;
							 | 
						
						
						
							| 
								
							 | 
							
								    var allowCluster = false;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // we create a list of edges because the dynamicEdges change over the course of this loop
							 | 
						
						
						
							| 
								
							 | 
							
								    var edgesIdarray = [];
							 | 
						
						
						
							| 
								
							 | 
							
								    var amountOfInitialEdges = hubNode.dynamicEdges.length;
							 | 
						
						
						
							| 
								
							 | 
							
								    for (var j = 0; j < amountOfInitialEdges; j++) {
							 | 
						
						
						
							| 
								
							 | 
							
								      edgesIdarray.push(hubNode.dynamicEdges[j].id);
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // 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.clusterEdgeThreshold
							 | 
						
						
						
							| 
								
							 | 
							
								    if (force == false) {
							 | 
						
						
						
							| 
								
							 | 
							
								      allowCluster = false;
							 | 
						
						
						
							| 
								
							 | 
							
								      for (j = 0; j < amountOfInitialEdges; j++) {
							 | 
						
						
						
							| 
								
							 | 
							
								        var edge = this.edges[edgesIdarray[j]];
							 | 
						
						
						
							| 
								
							 | 
							
								        if (edge !== undefined) {
							 | 
						
						
						
							| 
								
							 | 
							
								          if (edge.connected) {
							 | 
						
						
						
							| 
								
							 | 
							
								            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;
							 | 
						
						
						
							| 
								
							 | 
							
								              }
							 | 
						
						
						
							| 
								
							 | 
							
								            }
							 | 
						
						
						
							| 
								
							 | 
							
								          }
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // start the clustering if allowed
							 | 
						
						
						
							| 
								
							 | 
							
								    if ((!force && allowCluster) || force) {
							 | 
						
						
						
							| 
								
							 | 
							
								      // 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 nor do we want to cluster itself.
							 | 
						
						
						
							| 
								
							 | 
							
								          if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
							 | 
						
						
						
							| 
								
							 | 
							
								              (childNode.id != hubNode.id)) {
							 | 
						
						
						
							| 
								
							 | 
							
								            this._addToCluster(hubNode,childNode,force);
							 | 
						
						
						
							| 
								
							 | 
							
								          }
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function adds the child node to the parent node, creating a cluster if it is not already.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @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
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Boolean} force             | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._addToCluster = function(parentNode, childNode, force) {
							 | 
						
						
						
							| 
								
							 | 
							
								  // join child node in the parent node
							 | 
						
						
						
							| 
								
							 | 
							
								  parentNode.containedNodes[childNode.id] = childNode;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // manage all the edges connected to the child and parent nodes
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < childNode.dynamicEdges.length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								    var edge = childNode.dynamicEdges[i];
							 | 
						
						
						
							| 
								
							 | 
							
								    if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
							 | 
						
						
						
							| 
								
							 | 
							
								      this._addToContainedEdges(parentNode,childNode,edge);
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								    else {
							 | 
						
						
						
							| 
								
							 | 
							
								      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 += childNode.mass;
							 | 
						
						
						
							| 
								
							 | 
							
								  parentNode.clusterSize += childNode.clusterSize;
							 | 
						
						
						
							| 
								
							 | 
							
								  parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // keep track of the clustersessions so we can open the cluster up as it has been formed.
							 | 
						
						
						
							| 
								
							 | 
							
								  if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.clusterSessions.push(this.clusterSession);
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // 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 = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  else {
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.formationScale = this.scale; // The latest child has been added on this scale
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // recalculate the size of the node on the next time the node is rendered
							 | 
						
						
						
							| 
								
							 | 
							
								  parentNode.clearSizeCache();
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // set the pop-out scale for the childnode
							 | 
						
						
						
							| 
								
							 | 
							
								  parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // nullify the movement velocity of the child, this is to avoid hectic behaviour
							 | 
						
						
						
							| 
								
							 | 
							
								  childNode.clearVelocity();
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // 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;
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function will apply the changes made to the remainingEdges during the formation of the clusters.
							 | 
						
						
						
							| 
								
							 | 
							
								 * This is a seperate function to allow for level-wise collapsing of the node barnesHutTree.
							 | 
						
						
						
							| 
								
							 | 
							
								 * It has to be called if a level is collapsed. It is called by _formClusters().
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._updateDynamicEdges = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < this.nodeIndices.length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								    var node = this.nodes[this.nodeIndices[i]];
							 | 
						
						
						
							| 
								
							 | 
							
								    node.dynamicEdgesLength = node.dynamicEdges.length;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // this corrects for multiple edges pointing at the same other node
							 | 
						
						
						
							| 
								
							 | 
							
								    var correction = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								    if (node.dynamicEdgesLength > 1) {
							 | 
						
						
						
							| 
								
							 | 
							
								      for (var j = 0; j < node.dynamicEdgesLength - 1; j++) {
							 | 
						
						
						
							| 
								
							 | 
							
								        var edgeToId = node.dynamicEdges[j].toId;
							 | 
						
						
						
							| 
								
							 | 
							
								        var edgeFromId = node.dynamicEdges[j].fromId;
							 | 
						
						
						
							| 
								
							 | 
							
								        for (var k = j+1; k < node.dynamicEdgesLength; k++) {
							 | 
						
						
						
							| 
								
							 | 
							
								          if ((node.dynamicEdges[k].toId == edgeToId && node.dynamicEdges[k].fromId == edgeFromId) ||
							 | 
						
						
						
							| 
								
							 | 
							
								              (node.dynamicEdges[k].fromId == edgeToId && node.dynamicEdges[k].toId == edgeFromId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								            correction += 1;
							 | 
						
						
						
							| 
								
							 | 
							
								          }
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								    node.dynamicEdgesLength -= correction;
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This adds an edge from the childNode to the contained edges of the parent node
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param parentNode    | Node object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param childNode     | Node object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param edge          | Edge object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._addToContainedEdges = function(parentNode, childNode, edge) {
							 | 
						
						
						
							| 
								
							 | 
							
								  // create an array object if it does not yet exist for this childNode
							 | 
						
						
						
							| 
								
							 | 
							
								  if (!(parentNode.containedEdges.hasOwnProperty(childNode.id))) {
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.containedEdges[childNode.id] = []
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  // add this edge to the list
							 | 
						
						
						
							| 
								
							 | 
							
								  parentNode.containedEdges[childNode.id].push(edge);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // remove the edge from the global edges object
							 | 
						
						
						
							| 
								
							 | 
							
								  delete this.edges[edge.id];
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // remove the edge from the parent object
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								    if (parentNode.dynamicEdges[i].id == edge.id) {
							 | 
						
						
						
							| 
								
							 | 
							
								      parentNode.dynamicEdges.splice(i,1);
							 | 
						
						
						
							| 
								
							 | 
							
								      break;
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function connects an edge that was connected to a child node to the parent node.
							 | 
						
						
						
							| 
								
							 | 
							
								 * It keeps track of which nodes it has been connected to with the originalId array.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Node} parentNode    | Node object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Node} childNode     | Node object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Edge} edge          | Edge object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
							 | 
						
						
						
							| 
								
							 | 
							
								  // 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);
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
							 | 
						
						
						
							| 
								
							 | 
							
								 * these edges inside of the cluster.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param parentNode
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param childNode
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._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
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param parentNode    | Node object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param childNode     | Node object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param edge          | Edge object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._addToReroutedEdges = function(parentNode, childNode, edge) {
							 | 
						
						
						
							| 
								
							 | 
							
								  // create an array object if it does not yet exist for this childNode
							 | 
						
						
						
							| 
								
							 | 
							
								  // we store the edge in the rerouted edges so we can restore it when the cluster pops open
							 | 
						
						
						
							| 
								
							 | 
							
								  if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) {
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.reroutedEdges[childNode.id] = [];
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  parentNode.reroutedEdges[childNode.id].push(edge);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // this edge becomes part of the dynamicEdges of the cluster node
							 | 
						
						
						
							| 
								
							 | 
							
								  parentNode.dynamicEdges.push(edge);
							 | 
						
						
						
							| 
								
							 | 
							
								 };
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function connects an edge that was connected to a cluster node back to the child node.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param parentNode    | Node object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param childNode     | Node object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._connectEdgeBackToChild = function(parentNode, childNode) {
							 | 
						
						
						
							| 
								
							 | 
							
								  if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) {
							 | 
						
						
						
							| 
								
							 | 
							
								    for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								      var edge = parentNode.reroutedEdges[childNode.id][i];
							 | 
						
						
						
							| 
								
							 | 
							
								      if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) {
							 | 
						
						
						
							| 
								
							 | 
							
								        edge.originalFromId.pop();
							 | 
						
						
						
							| 
								
							 | 
							
								        edge.fromId = childNode.id;
							 | 
						
						
						
							| 
								
							 | 
							
								        edge.from = childNode;
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								      else {
							 | 
						
						
						
							| 
								
							 | 
							
								        edge.originalToId.pop();
							 | 
						
						
						
							| 
								
							 | 
							
								        edge.toId = childNode.id;
							 | 
						
						
						
							| 
								
							 | 
							
								        edge.to = childNode;
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								      // append this edge to the list of edges connecting to the childnode
							 | 
						
						
						
							| 
								
							 | 
							
								      childNode.dynamicEdges.push(edge);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								      // remove the edge from the parent object
							 | 
						
						
						
							| 
								
							 | 
							
								      for (var j = 0; j < parentNode.dynamicEdges.length; j++) {
							 | 
						
						
						
							| 
								
							 | 
							
								        if (parentNode.dynamicEdges[j].id == edge.id) {
							 | 
						
						
						
							| 
								
							 | 
							
								          parentNode.dynamicEdges.splice(j,1);
							 | 
						
						
						
							| 
								
							 | 
							
								          break;
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								    // remove the entry from the rerouted edges
							 | 
						
						
						
							| 
								
							 | 
							
								    delete parentNode.reroutedEdges[childNode.id];
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * When loops are clustered, an edge can be both in the rerouted array and the contained array.
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function is called last to verify that all edges in dynamicEdges are in fact connected to the
							 | 
						
						
						
							| 
								
							 | 
							
								 * parentNode
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param parentNode    | Node object
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._validateEdges = function(parentNode) {
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								    var edge = parentNode.dynamicEdges[i];
							 | 
						
						
						
							| 
								
							 | 
							
								    if (parentNode.id != edge.toId && parentNode.id != edge.fromId) {
							 | 
						
						
						
							| 
								
							 | 
							
								      parentNode.dynamicEdges.splice(i,1);
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function released the contained edges back into the global domain and puts them back into the
							 | 
						
						
						
							| 
								
							 | 
							
								 * dynamic edges of both parent and child.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Node} parentNode    |
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Node} childNode     |
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._releaseContainedEdges = function(parentNode, childNode) {
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								    var edge = parentNode.containedEdges[childNode.id][i];
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // put the edge back in the global edges object
							 | 
						
						
						
							| 
								
							 | 
							
								    this.edges[edge.id] = edge;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    // put the edge back in the dynamic edges of the child and parent
							 | 
						
						
						
							| 
								
							 | 
							
								    childNode.dynamicEdges.push(edge);
							 | 
						
						
						
							| 
								
							 | 
							
								    parentNode.dynamicEdges.push(edge);
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  // remove the entry from the contained edges
							 | 
						
						
						
							| 
								
							 | 
							
								  delete parentNode.containedEdges[childNode.id];
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								// ------------------- UTILITY FUNCTIONS ---------------------------- //
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This updates the node labels for all nodes (for debugging purposes)
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.updateLabels = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  var nodeId;
							 | 
						
						
						
							| 
								
							 | 
							
								  // update node labels
							 | 
						
						
						
							| 
								
							 | 
							
								  for (nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      var node = this.nodes[nodeId];
							 | 
						
						
						
							| 
								
							 | 
							
								      if (node.clusterSize > 1) {
							 | 
						
						
						
							| 
								
							 | 
							
								        node.label = "[".concat(String(node.clusterSize),"]");
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // update node labels
							 | 
						
						
						
							| 
								
							 | 
							
								  for (nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      node = this.nodes[nodeId];
							 | 
						
						
						
							| 
								
							 | 
							
								      if (node.clusterSize == 1) {
							 | 
						
						
						
							| 
								
							 | 
							
								        if (node.originalLabel !== undefined) {
							 | 
						
						
						
							| 
								
							 | 
							
								          node.label = node.originalLabel;
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								        else {
							 | 
						
						
						
							| 
								
							 | 
							
								          node.label = String(node.id);
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								//    /* Debug Override */
							 | 
						
						
						
							| 
								
							 | 
							
								//    for (nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								//      if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								//        node = this.nodes[nodeId];
							 | 
						
						
						
							| 
								
							 | 
							
								//        node.label = String(node.level);
							 | 
						
						
						
							| 
								
							 | 
							
								//      }
							 | 
						
						
						
							| 
								
							 | 
							
								//    }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
							 | 
						
						
						
							| 
								
							 | 
							
								 * if the rest of the nodes are already a few cluster levels in.
							 | 
						
						
						
							| 
								
							 | 
							
								 * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
							 | 
						
						
						
							| 
								
							 | 
							
								 * clustered enough to the clusterToSmallestNeighbours function.
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.normalizeClusterLevels = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  var maxLevel = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								  var minLevel = 1e9;
							 | 
						
						
						
							| 
								
							 | 
							
								  var clusterLevel = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								  var nodeId;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // we loop over all nodes in the list
							 | 
						
						
						
							| 
								
							 | 
							
								  for (nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      clusterLevel = this.nodes[nodeId].clusterSessions.length;
							 | 
						
						
						
							| 
								
							 | 
							
								      if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
							 | 
						
						
						
							| 
								
							 | 
							
								      if (minLevel > clusterLevel) {minLevel = clusterLevel;}
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
							 | 
						
						
						
							| 
								
							 | 
							
								    var amountOfNodes = this.nodeIndices.length;
							 | 
						
						
						
							| 
								
							 | 
							
								    var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
							 | 
						
						
						
							| 
								
							 | 
							
								    // we loop over all nodes in the list
							 | 
						
						
						
							| 
								
							 | 
							
								    for (nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								      if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								        if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
							 | 
						
						
						
							| 
								
							 | 
							
								          this._clusterToSmallestNeighbour(this.nodes[nodeId]);
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								    this._updateNodeIndexList();
							 | 
						
						
						
							| 
								
							 | 
							
								    this._updateDynamicEdges();
							 | 
						
						
						
							| 
								
							 | 
							
								    // if a cluster was formed, we increase the clusterSession
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.nodeIndices.length != amountOfNodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								      this.clusterSession += 1;
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This function determines if the cluster we want to decluster is in the active area
							 | 
						
						
						
							| 
								
							 | 
							
								 * this means around the zoom center
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param {Node} node
							 | 
						
						
						
							| 
								
							 | 
							
								 * @returns {boolean}
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._nodeInActiveArea = function(node) {
							 | 
						
						
						
							| 
								
							 | 
							
								  return (
							 | 
						
						
						
							| 
								
							 | 
							
								    Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
							 | 
						
						
						
							| 
								
							 | 
							
								      &&
							 | 
						
						
						
							| 
								
							 | 
							
								    Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
							 | 
						
						
						
							| 
								
							 | 
							
								    )
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * This is an adaptation of the original repositioning function. This is called if the system is clustered initially
							 | 
						
						
						
							| 
								
							 | 
							
								 * It puts large clusters away from the center and randomizes the order.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports.repositionNodes = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < this.nodeIndices.length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								    var node = this.nodes[this.nodeIndices[i]];
							 | 
						
						
						
							| 
								
							 | 
							
								    if ((node.xFixed == false || node.yFixed == false)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.mass);
							 | 
						
						
						
							| 
								
							 | 
							
								      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);
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * We determine how many connections denote an important hub.
							 | 
						
						
						
							| 
								
							 | 
							
								 * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._getHubSize = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  var average = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								  var averageSquared = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								  var hubCounter = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								  var largestHub = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var i = 0; i < this.nodeIndices.length; i++) {
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								    var node = this.nodes[this.nodeIndices[i]];
							 | 
						
						
						
							| 
								
							 | 
							
								    if (node.dynamicEdgesLength > largestHub) {
							 | 
						
						
						
							| 
								
							 | 
							
								      largestHub = node.dynamicEdgesLength;
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								    average += node.dynamicEdgesLength;
							 | 
						
						
						
							| 
								
							 | 
							
								    averageSquared += Math.pow(node.dynamicEdgesLength,2);
							 | 
						
						
						
							| 
								
							 | 
							
								    hubCounter += 1;
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  average = average / hubCounter;
							 | 
						
						
						
							| 
								
							 | 
							
								  averageSquared = averageSquared / hubCounter;
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  var variance = averageSquared - Math.pow(average,2);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  var standardDeviation = Math.sqrt(variance);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  this.hubThreshold = Math.floor(average + 2*standardDeviation);
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								  // always have at least one to cluster
							 | 
						
						
						
							| 
								
							 | 
							
								  if (this.hubThreshold > largestHub) {
							 | 
						
						
						
							| 
								
							 | 
							
								    this.hubThreshold = largestHub;
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								//  console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
							 | 
						
						
						
							| 
								
							 | 
							
								//  console.log("hubThreshold:",this.hubThreshold);
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
							 | 
						
						
						
							| 
								
							 | 
							
								 * with this amount we can cluster specifically on these chains.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @param   {Number} fraction     | between 0 and 1, the percentage of chains to reduce
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._reduceAmountOfChains = function(fraction) {
							 | 
						
						
						
							| 
								
							 | 
							
								  this.hubThreshold = 2;
							 | 
						
						
						
							| 
								
							 | 
							
								  var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
							 | 
						
						
						
							| 
								
							 | 
							
								        if (reduceAmount > 0) {
							 | 
						
						
						
							| 
								
							 | 
							
								          this._formClusterFromHub(this.nodes[nodeId],true,true,1);
							 | 
						
						
						
							| 
								
							 | 
							
								          reduceAmount -= 1;
							 | 
						
						
						
							| 
								
							 | 
							
								        }
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 | 
						
						
						
							| 
								
							 | 
							
								
							 | 
						
						
						
							| 
								
							 | 
							
								/**
							 | 
						
						
						
							| 
								
							 | 
							
								 * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
							 | 
						
						
						
							| 
								
							 | 
							
								 * with this amount we can cluster specifically on these chains.
							 | 
						
						
						
							| 
								
							 | 
							
								 *
							 | 
						
						
						
							| 
								
							 | 
							
								 * @private
							 | 
						
						
						
							| 
								
							 | 
							
								 */
							 | 
						
						
						
							| 
								
							 | 
							
								exports._getChainFraction = function() {
							 | 
						
						
						
							| 
								
							 | 
							
								  var chains = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								  var total = 0;
							 | 
						
						
						
							| 
								
							 | 
							
								  for (var nodeId in this.nodes) {
							 | 
						
						
						
							| 
								
							 | 
							
								    if (this.nodes.hasOwnProperty(nodeId)) {
							 | 
						
						
						
							| 
								
							 | 
							
								      if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
							 | 
						
						
						
							| 
								
							 | 
							
								        chains += 1;
							 | 
						
						
						
							| 
								
							 | 
							
								      }
							 | 
						
						
						
							| 
								
							 | 
							
								      total += 1;
							 | 
						
						
						
							| 
								
							 | 
							
								    }
							 | 
						
						
						
							| 
								
							 | 
							
								  }
							 | 
						
						
						
							| 
								
							 | 
							
								  return chains/total;
							 | 
						
						
						
							| 
								
							 | 
							
								};
							 |