|  |  | @ -2,15 +2,645 @@ | 
			
		
	
		
			
				
					|  |  |  | * Created by Alex on 2/20/2015. | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var public = require("./clustering/public"); | 
			
		
	
		
			
				
					|  |  |  | var support = require("./clustering/support"); | 
			
		
	
		
			
				
					|  |  |  | var backend = require("./clustering/backend"); | 
			
		
	
		
			
				
					|  |  |  | var Node = require('../Node'); | 
			
		
	
		
			
				
					|  |  |  | var Edge = require('../Edge'); | 
			
		
	
		
			
				
					|  |  |  | var util = require('../../util'); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | function ClusterEngine(network) { | 
			
		
	
		
			
				
					|  |  |  | this.network = network; | 
			
		
	
		
			
				
					|  |  |  | function ClusterEngine(data,options) { | 
			
		
	
		
			
				
					|  |  |  | this.nodes = data.nodes; | 
			
		
	
		
			
				
					|  |  |  | this.edges = data.edges; | 
			
		
	
		
			
				
					|  |  |  | this.nodeIndices = data.nodeIndices; | 
			
		
	
		
			
				
					|  |  |  | this.emitter = data.emitter; | 
			
		
	
		
			
				
					|  |  |  | this.clusteredNodes = {}; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * | 
			
		
	
		
			
				
					|  |  |  | * @param hubsize | 
			
		
	
		
			
				
					|  |  |  | * @param options | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype.clusterByConnectionCount = function(hubsize, options) { | 
			
		
	
		
			
				
					|  |  |  | if (hubsize === undefined) { | 
			
		
	
		
			
				
					|  |  |  | hubsize = this._getHubSize(); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else if (tyepof(hubsize) == "object") { | 
			
		
	
		
			
				
					|  |  |  | options = this._checkOptions(hubsize); | 
			
		
	
		
			
				
					|  |  |  | hubsize = this._getHubSize(); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var nodesToCluster = []; | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < this.nodeIndices.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | var node = this.nodes[this.nodeIndices[i]]; | 
			
		
	
		
			
				
					|  |  |  | if (node.edges.length >= hubsize) { | 
			
		
	
		
			
				
					|  |  |  | nodesToCluster.push(node.id); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < nodesToCluster.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | var node = this.nodes[nodesToCluster[i]]; | 
			
		
	
		
			
				
					|  |  |  | this.clusterByConnection(node,options,{},{},true); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | this.emitter.emit('dataChanged'); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * loop over all nodes, check if they adhere to the condition and cluster if needed. | 
			
		
	
		
			
				
					|  |  |  | * @param options | 
			
		
	
		
			
				
					|  |  |  | * @param doNotUpdateCalculationNodes | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype.clusterByNodeData = function(options, doNotUpdateCalculationNodes) { | 
			
		
	
		
			
				
					|  |  |  | if (options === undefined)               {throw new Error("Cannot call clusterByNodeData without options.");} | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition === undefined) {throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");} | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // check if the options object is fine, append if needed | 
			
		
	
		
			
				
					|  |  |  | options = this._checkOptions(options); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var childNodesObj = {}; | 
			
		
	
		
			
				
					|  |  |  | var childEdgesObj = {} | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // collect the nodes that will be in the cluster | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < this.nodeIndices.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | var nodeId = this.nodeIndices[i]; | 
			
		
	
		
			
				
					|  |  |  | var clonedOptions = this._cloneOptions(nodeId); | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition(clonedOptions) == true) { | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[nodeId] = this.nodes[nodeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * Cluster all nodes in the network that have only 1 edge | 
			
		
	
		
			
				
					|  |  |  | * @param options | 
			
		
	
		
			
				
					|  |  |  | * @param doNotUpdateCalculationNodes | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype.clusterOutliers = function(options, doNotUpdateCalculationNodes) { | 
			
		
	
		
			
				
					|  |  |  | options = this._checkOptions(options); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var clusters = [] | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // collect the nodes that will be in the cluster | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < this.nodeIndices.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | var childNodesObj = {}; | 
			
		
	
		
			
				
					|  |  |  | var childEdgesObj = {}; | 
			
		
	
		
			
				
					|  |  |  | var nodeId = this.nodeIndices[i]; | 
			
		
	
		
			
				
					|  |  |  | if (this.nodes[nodeId].edges.length == 1) { | 
			
		
	
		
			
				
					|  |  |  | var edge = this.nodes[nodeId].edges[0]; | 
			
		
	
		
			
				
					|  |  |  | var childNodeId = this._getConnectedId(edge, nodeId); | 
			
		
	
		
			
				
					|  |  |  | if (childNodeId != nodeId) { | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition === undefined) { | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[nodeId] = this.nodes[nodeId]; | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[childNodeId] = this.nodes[childNodeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | var clonedOptions = this._cloneOptions(nodeId); | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition(clonedOptions) == true) { | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[nodeId] = this.nodes[nodeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | clonedOptions = this._cloneOptions(childNodeId); | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition(clonedOptions) == true) { | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[childNodeId] = this.nodes[childNodeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | clusters.push({nodes:childNodesObj, edges:childEdgesObj}) | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < clusters.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | this._cluster(clusters[i].nodes, clusters[i].edges, options, true) | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | if (doNotUpdateCalculationNodes !== true) { | 
			
		
	
		
			
				
					|  |  |  | this.emitter.emit('dataChanged'); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * | 
			
		
	
		
			
				
					|  |  |  | * @param nodeId | 
			
		
	
		
			
				
					|  |  |  | * @param options | 
			
		
	
		
			
				
					|  |  |  | * @param doNotUpdateCalculationNodes | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype.clusterByConnection = function(nodeId, options, doNotUpdateCalculationNodes) { | 
			
		
	
		
			
				
					|  |  |  | // kill conditions | 
			
		
	
		
			
				
					|  |  |  | if (nodeId === undefined)             {throw new Error("No nodeId supplied to clusterByConnection!");} | 
			
		
	
		
			
				
					|  |  |  | if (this.nodes[nodeId] === undefined) {throw new Error("The nodeId given to clusterByConnection does not exist!");} | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var node = this.nodes[nodeId]; | 
			
		
	
		
			
				
					|  |  |  | options = this._checkOptions(options, node); | 
			
		
	
		
			
				
					|  |  |  | if (options.clusterNodeProperties.x === undefined)  {options.clusterNodeProperties.x = node.x; options.clusterNodeProperties.allowedToMoveX = !node.xFixed;} | 
			
		
	
		
			
				
					|  |  |  | if (options.clusterNodeProperties.y === undefined)  {options.clusterNodeProperties.y = node.y; options.clusterNodeProperties.allowedToMoveY = !node.yFixed;} | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var childNodesObj = {}; | 
			
		
	
		
			
				
					|  |  |  | var childEdgesObj = {} | 
			
		
	
		
			
				
					|  |  |  | var parentNodeId = node.id; | 
			
		
	
		
			
				
					|  |  |  | var parentClonedOptions = this._cloneOptions(parentNodeId); | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[parentNodeId] = node; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // collect the nodes that will be in the cluster | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < node.edges.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | var edge = node.edges[i]; | 
			
		
	
		
			
				
					|  |  |  | var childNodeId = this._getConnectedId(edge, parentNodeId); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | if (childNodeId !== parentNodeId) { | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition === undefined) { | 
			
		
	
		
			
				
					|  |  |  | childEdgesObj[edge.id] = edge; | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[childNodeId] = this.nodes[childNodeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | // clone the options and insert some additional parameters that could be interesting. | 
			
		
	
		
			
				
					|  |  |  | var childClonedOptions = this._cloneOptions(childNodeId); | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition(parentClonedOptions, childClonedOptions) == true) { | 
			
		
	
		
			
				
					|  |  |  | childEdgesObj[edge.id] = edge; | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[childNodeId] = this.nodes[childNodeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | childEdgesObj[edge.id] = edge; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * This returns a clone of the options or properties of the edge or node to be used for construction of new edges or check functions for new nodes. | 
			
		
	
		
			
				
					|  |  |  | * @param objId | 
			
		
	
		
			
				
					|  |  |  | * @param type | 
			
		
	
		
			
				
					|  |  |  | * @returns {{}} | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._cloneOptions = function(objId, type) { | 
			
		
	
		
			
				
					|  |  |  | var clonedOptions = {}; | 
			
		
	
		
			
				
					|  |  |  | if (type === undefined || type == 'node') { | 
			
		
	
		
			
				
					|  |  |  | util.deepExtend(clonedOptions, this.nodes[objId].options, true); | 
			
		
	
		
			
				
					|  |  |  | util.deepExtend(clonedOptions, this.nodes[objId].properties, true); | 
			
		
	
		
			
				
					|  |  |  | clonedOptions.amountOfConnections = this.nodes[objId].edges.length; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | util.deepExtend(clonedOptions, this.edges[objId].properties, true); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | return clonedOptions; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * This function creates the edges that will be attached to the cluster. | 
			
		
	
		
			
				
					|  |  |  | * | 
			
		
	
		
			
				
					|  |  |  | * @param childNodesObj | 
			
		
	
		
			
				
					|  |  |  | * @param childEdgesObj | 
			
		
	
		
			
				
					|  |  |  | * @param newEdges | 
			
		
	
		
			
				
					|  |  |  | * @param options | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._createClusterEdges = function (childNodesObj, childEdgesObj, newEdges, options) { | 
			
		
	
		
			
				
					|  |  |  | var edge, childNodeId, childNode; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var childKeys = Object.keys(childNodesObj); | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < childKeys.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | childNodeId = childKeys[i]; | 
			
		
	
		
			
				
					|  |  |  | childNode = childNodesObj[childNodeId]; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // mark all edges for removal from global and construct new edges from the cluster to others | 
			
		
	
		
			
				
					|  |  |  | for (var j = 0; j < childNode.edges.length; j++) { | 
			
		
	
		
			
				
					|  |  |  | edge = childNode.edges[j]; | 
			
		
	
		
			
				
					|  |  |  | childEdgesObj[edge.id] = edge; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var otherNodeId = edge.toId; | 
			
		
	
		
			
				
					|  |  |  | var otherOnTo = true; | 
			
		
	
		
			
				
					|  |  |  | if (edge.toId != childNodeId) { | 
			
		
	
		
			
				
					|  |  |  | otherNodeId = edge.toId; | 
			
		
	
		
			
				
					|  |  |  | otherOnTo = true; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else if (edge.fromId != childNodeId) { | 
			
		
	
		
			
				
					|  |  |  | otherNodeId = edge.fromId; | 
			
		
	
		
			
				
					|  |  |  | otherOnTo = false; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | if (childNodesObj[otherNodeId] === undefined) { | 
			
		
	
		
			
				
					|  |  |  | var clonedOptions = this._cloneOptions(edge.id, 'edge'); | 
			
		
	
		
			
				
					|  |  |  | util.deepExtend(clonedOptions, options.clusterEdgeProperties); | 
			
		
	
		
			
				
					|  |  |  | // avoid forcing the default color on edges that inherit color | 
			
		
	
		
			
				
					|  |  |  | if (edge.properties.color === undefined) { | 
			
		
	
		
			
				
					|  |  |  | delete clonedOptions.color; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | if (otherOnTo === true) { | 
			
		
	
		
			
				
					|  |  |  | clonedOptions.from = options.clusterNodeProperties.id; | 
			
		
	
		
			
				
					|  |  |  | clonedOptions.to = otherNodeId; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | clonedOptions.from = otherNodeId; | 
			
		
	
		
			
				
					|  |  |  | clonedOptions.to = options.clusterNodeProperties.id; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | clonedOptions.id = 'clusterEdge:' + util.randomUUID(); | 
			
		
	
		
			
				
					|  |  |  | newEdges.push(new Edge(clonedOptions,this,this.constants)) | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * This function checks the options that can be supplied to the different cluster functions | 
			
		
	
		
			
				
					|  |  |  | * for certain fields and inserts defaults if needed | 
			
		
	
		
			
				
					|  |  |  | * @param options | 
			
		
	
		
			
				
					|  |  |  | * @returns {*} | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._checkOptions = function(options) { | 
			
		
	
		
			
				
					|  |  |  | if (options === undefined) {options = {};} | 
			
		
	
		
			
				
					|  |  |  | if (options.clusterEdgeProperties === undefined)    {options.clusterEdgeProperties = {};} | 
			
		
	
		
			
				
					|  |  |  | if (options.clusterNodeProperties === undefined)    {options.clusterNodeProperties = {};} | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | return options; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * | 
			
		
	
		
			
				
					|  |  |  | * @param {Object}    childNodesObj         | object with node objects, id as keys, same as childNodes except it also contains a source node | 
			
		
	
		
			
				
					|  |  |  | * @param {Object}    childEdgesObj         | object with edge objects, id as keys | 
			
		
	
		
			
				
					|  |  |  | * @param {Array}     options               | object with {clusterNodeProperties, clusterEdgeProperties, processProperties} | 
			
		
	
		
			
				
					|  |  |  | * @param {Boolean}   doNotUpdateCalculationNodes | when true, do not wrap up | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._cluster = function(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes) { | 
			
		
	
		
			
				
					|  |  |  | // kill condition: no children so cant cluster | 
			
		
	
		
			
				
					|  |  |  | if (Object.keys(childNodesObj).length == 0) {return;} | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // check if we have an unique id; | 
			
		
	
		
			
				
					|  |  |  | if (options.clusterNodeProperties.id === undefined) {options.clusterNodeProperties.id = 'cluster:' + util.randomUUID();} | 
			
		
	
		
			
				
					|  |  |  | var clusterId = options.clusterNodeProperties.id; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // create the new edges that will connect to the cluster | 
			
		
	
		
			
				
					|  |  |  | var newEdges = []; | 
			
		
	
		
			
				
					|  |  |  | this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // construct the clusterNodeProperties | 
			
		
	
		
			
				
					|  |  |  | var clusterNodeProperties = options.clusterNodeProperties; | 
			
		
	
		
			
				
					|  |  |  | if (options.processProperties !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | // get the childNode options | 
			
		
	
		
			
				
					|  |  |  | var childNodesOptions = []; | 
			
		
	
		
			
				
					|  |  |  | for (var nodeId in childNodesObj) { | 
			
		
	
		
			
				
					|  |  |  | var clonedOptions = this._cloneOptions(nodeId); | 
			
		
	
		
			
				
					|  |  |  | childNodesOptions.push(clonedOptions); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // get clusterproperties based on childNodes | 
			
		
	
		
			
				
					|  |  |  | var childEdgesOptions = []; | 
			
		
	
		
			
				
					|  |  |  | for (var edgeId in childEdgesObj) { | 
			
		
	
		
			
				
					|  |  |  | var clonedOptions = this._cloneOptions(edgeId, 'edge'); | 
			
		
	
		
			
				
					|  |  |  | childEdgesOptions.push(clonedOptions); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions); | 
			
		
	
		
			
				
					|  |  |  | if (!clusterNodeProperties) { | 
			
		
	
		
			
				
					|  |  |  | throw new Error("The processClusterProperties function does not return properties!"); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | if (clusterNodeProperties.label === undefined) { | 
			
		
	
		
			
				
					|  |  |  | clusterNodeProperties.label = 'cluster'; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // give the clusterNode a postion if it does not have one. | 
			
		
	
		
			
				
					|  |  |  | var pos = undefined | 
			
		
	
		
			
				
					|  |  |  | if (clusterNodeProperties.x === undefined) { | 
			
		
	
		
			
				
					|  |  |  | pos = this._getClusterPosition(childNodesObj); | 
			
		
	
		
			
				
					|  |  |  | clusterNodeProperties.x = pos.x; | 
			
		
	
		
			
				
					|  |  |  | clusterNodeProperties.allowedToMoveX = true; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | if (clusterNodeProperties.x === undefined) { | 
			
		
	
		
			
				
					|  |  |  | if (pos === undefined) { | 
			
		
	
		
			
				
					|  |  |  | pos = this._getClusterPosition(childNodesObj); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | clusterNodeProperties.y = pos.y; | 
			
		
	
		
			
				
					|  |  |  | clusterNodeProperties.allowedToMoveY = true; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // force the ID to remain the same | 
			
		
	
		
			
				
					|  |  |  | clusterNodeProperties.id = clusterId; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // create the clusterNode | 
			
		
	
		
			
				
					|  |  |  | var clusterNode = new Node(clusterNodeProperties, this.images, this.groups, this.constants); | 
			
		
	
		
			
				
					|  |  |  | clusterNode.isCluster = true; | 
			
		
	
		
			
				
					|  |  |  | clusterNode.containedNodes = childNodesObj; | 
			
		
	
		
			
				
					|  |  |  | clusterNode.containedEdges = childEdgesObj; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // delete contained edges from global | 
			
		
	
		
			
				
					|  |  |  | for (var edgeId in childEdgesObj) { | 
			
		
	
		
			
				
					|  |  |  | if (childEdgesObj.hasOwnProperty(edgeId)) { | 
			
		
	
		
			
				
					|  |  |  | if (this.edges[edgeId] !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | if (this.edges[edgeId].via !== null) { | 
			
		
	
		
			
				
					|  |  |  | var viaId = this.edges[edgeId].via.id; | 
			
		
	
		
			
				
					|  |  |  | if (viaId) { | 
			
		
	
		
			
				
					|  |  |  | this.edges[edgeId].via = null | 
			
		
	
		
			
				
					|  |  |  | delete this.sectors['support']['nodes'][viaId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | this.edges[edgeId].disconnect(); | 
			
		
	
		
			
				
					|  |  |  | delete this.edges[edgeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // remove contained nodes from global | 
			
		
	
		
			
				
					|  |  |  | for (var nodeId in childNodesObj) { | 
			
		
	
		
			
				
					|  |  |  | if (childNodesObj.hasOwnProperty(nodeId)) { | 
			
		
	
		
			
				
					|  |  |  | this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties.id, node: this.nodes[nodeId]}; | 
			
		
	
		
			
				
					|  |  |  | delete this.nodes[nodeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // finally put the cluster node into global | 
			
		
	
		
			
				
					|  |  |  | this.nodes[clusterNodeProperties.id] = clusterNode; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // push new edges to global | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < newEdges.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | this.edges[newEdges[i].id] = newEdges[i]; | 
			
		
	
		
			
				
					|  |  |  | this.edges[newEdges[i].id].connect(); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // create bezier nodes for smooth curves if needed | 
			
		
	
		
			
				
					|  |  |  | this._createBezierNodes(newEdges); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // set ID to undefined so no duplicates arise | 
			
		
	
		
			
				
					|  |  |  | clusterNodeProperties.id = undefined; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // wrap up | 
			
		
	
		
			
				
					|  |  |  | if (doNotUpdateCalculationNodes !== true) { | 
			
		
	
		
			
				
					|  |  |  | this.emitter.emit('dataChanged'); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * Check if a node is a cluster. | 
			
		
	
		
			
				
					|  |  |  | * @param nodeId | 
			
		
	
		
			
				
					|  |  |  | * @returns {*} | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype.isCluster = function(nodeId) { | 
			
		
	
		
			
				
					|  |  |  | if (this.nodes[nodeId] !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | return this.nodes[nodeId].isCluster; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | console.log("Node does not exist.") | 
			
		
	
		
			
				
					|  |  |  | return false; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * get the position of the cluster node based on what's inside | 
			
		
	
		
			
				
					|  |  |  | * @param {object} childNodesObj    | object with node objects, id as keys | 
			
		
	
		
			
				
					|  |  |  | * @returns {{x: number, y: number}} | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._getClusterPosition = function(childNodesObj) { | 
			
		
	
		
			
				
					|  |  |  | var childKeys = Object.keys(childNodesObj); | 
			
		
	
		
			
				
					|  |  |  | var minX = childNodesObj[childKeys[0]].x; | 
			
		
	
		
			
				
					|  |  |  | var maxX = childNodesObj[childKeys[0]].x; | 
			
		
	
		
			
				
					|  |  |  | var minY = childNodesObj[childKeys[0]].y; | 
			
		
	
		
			
				
					|  |  |  | var maxY = childNodesObj[childKeys[0]].y; | 
			
		
	
		
			
				
					|  |  |  | var node; | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < childKeys.lenght; i++) { | 
			
		
	
		
			
				
					|  |  |  | node = childNodesObj[childKeys[0]]; | 
			
		
	
		
			
				
					|  |  |  | minX = node.x < minX ? node.x : minX; | 
			
		
	
		
			
				
					|  |  |  | maxX = node.x > maxX ? node.x : maxX; | 
			
		
	
		
			
				
					|  |  |  | minY = node.y < minY ? node.y : minY; | 
			
		
	
		
			
				
					|  |  |  | maxY = node.y > maxY ? node.y : maxY; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | return {x: 0.5*(minX + maxX), y: 0.5*(minY + maxY)}; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * Open a cluster by calling this function. | 
			
		
	
		
			
				
					|  |  |  | * @param {String}  clusterNodeId               | the ID of the cluster node | 
			
		
	
		
			
				
					|  |  |  | * @param {Boolean} doNotUpdateCalculationNodes | wrap up afterwards if not true | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype.openCluster = function(clusterNodeId, doNotUpdateCalculationNodes) { | 
			
		
	
		
			
				
					|  |  |  | // kill conditions | 
			
		
	
		
			
				
					|  |  |  | if (clusterNodeId === undefined)             {throw new Error("No clusterNodeId supplied to openCluster.");} | 
			
		
	
		
			
				
					|  |  |  | if (this.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");} | 
			
		
	
		
			
				
					|  |  |  | if (this.nodes[clusterNodeId].containedNodes === undefined) {console.log("The node:" + clusterNodeId + " is not a cluster."); return}; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var node = this.nodes[clusterNodeId]; | 
			
		
	
		
			
				
					|  |  |  | var containedNodes = node.containedNodes; | 
			
		
	
		
			
				
					|  |  |  | var containedEdges = node.containedEdges; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // release nodes | 
			
		
	
		
			
				
					|  |  |  | for (var nodeId in containedNodes) { | 
			
		
	
		
			
				
					|  |  |  | if (containedNodes.hasOwnProperty(nodeId)) { | 
			
		
	
		
			
				
					|  |  |  | this.nodes[nodeId] = containedNodes[nodeId]; | 
			
		
	
		
			
				
					|  |  |  | // inherit position | 
			
		
	
		
			
				
					|  |  |  | this.nodes[nodeId].x = node.x; | 
			
		
	
		
			
				
					|  |  |  | this.nodes[nodeId].y = node.y; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // inherit speed | 
			
		
	
		
			
				
					|  |  |  | this.nodes[nodeId].vx = node.vx; | 
			
		
	
		
			
				
					|  |  |  | this.nodes[nodeId].vy = node.vy; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | delete this.clusteredNodes[nodeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // release edges | 
			
		
	
		
			
				
					|  |  |  | for (var edgeId in containedEdges) { | 
			
		
	
		
			
				
					|  |  |  | if (containedEdges.hasOwnProperty(edgeId)) { | 
			
		
	
		
			
				
					|  |  |  | this.edges[edgeId] = containedEdges[edgeId]; | 
			
		
	
		
			
				
					|  |  |  | this.edges[edgeId].connect(); | 
			
		
	
		
			
				
					|  |  |  | var edge = this.edges[edgeId]; | 
			
		
	
		
			
				
					|  |  |  | if (edge.connected === false) { | 
			
		
	
		
			
				
					|  |  |  | if (this.clusteredNodes[edge.fromId] !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | this._connectEdge(edge, edge.fromId, true); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | if (this.clusteredNodes[edge.toId] !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | this._connectEdge(edge, edge.toId, false); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | this._createBezierNodes(containedEdges); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var edgeIds = []; | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < node.edges.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | edgeIds.push(node.edges[i].id); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // remove edges in clusterNode | 
			
		
	
		
			
				
					|  |  |  | for (var i = 0; i < edgeIds.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | var edge = this.edges[edgeIds[i]]; | 
			
		
	
		
			
				
					|  |  |  | // if the edge should have been connected to a contained node | 
			
		
	
		
			
				
					|  |  |  | if (edge.fromArray.length > 0 && edge.fromId == clusterNodeId) { | 
			
		
	
		
			
				
					|  |  |  | // the node in the from array was contained in the cluster | 
			
		
	
		
			
				
					|  |  |  | if (this.nodes[edge.fromArray[0].id] !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | this._connectEdge(edge, edge.fromArray[0].id, true); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else if (edge.toArray.length > 0 && edge.toId == clusterNodeId) { | 
			
		
	
		
			
				
					|  |  |  | // the node in the to array was contained in the cluster | 
			
		
	
		
			
				
					|  |  |  | if (this.nodes[edge.toArray[0].id] !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | this._connectEdge(edge, edge.toArray[0].id, false); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | var edgeId = edgeIds[i]; | 
			
		
	
		
			
				
					|  |  |  | var viaId = this.edges[edgeId].via.id; | 
			
		
	
		
			
				
					|  |  |  | if (viaId) { | 
			
		
	
		
			
				
					|  |  |  | this.edges[edgeId].via = null | 
			
		
	
		
			
				
					|  |  |  | delete this.sectors['support']['nodes'][viaId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | // this removes the edge from node.edges, which is why edgeIds is formed | 
			
		
	
		
			
				
					|  |  |  | this.edges[edgeId].disconnect(); | 
			
		
	
		
			
				
					|  |  |  | delete this.edges[edgeId]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // remove clusterNode | 
			
		
	
		
			
				
					|  |  |  | delete this.nodes[clusterNodeId]; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | if (doNotUpdateCalculationNodes !== true) { | 
			
		
	
		
			
				
					|  |  |  | this.emitter.emit('dataChanged'); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * Recalculate navigation nodes, color edges dirty, update nodes list etc. | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._wrapUp = function() { | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | this._updateNodeIndexList(); | 
			
		
	
		
			
				
					|  |  |  | this._updateCalculationNodes(); | 
			
		
	
		
			
				
					|  |  |  | this._markAllEdgesAsDirty(); | 
			
		
	
		
			
				
					|  |  |  | if (this.initializing !== true) { | 
			
		
	
		
			
				
					|  |  |  | this.moving = true; | 
			
		
	
		
			
				
					|  |  |  | this.start(); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * Connect an edge that was previously contained from cluster A to cluster B if the node that it was originally connected to | 
			
		
	
		
			
				
					|  |  |  | * is currently residing in cluster B | 
			
		
	
		
			
				
					|  |  |  | * @param edge | 
			
		
	
		
			
				
					|  |  |  | * @param nodeId | 
			
		
	
		
			
				
					|  |  |  | * @param from | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._connectEdge = function(edge, nodeId, from) { | 
			
		
	
		
			
				
					|  |  |  | var clusterStack = this._getClusterStack(nodeId); | 
			
		
	
		
			
				
					|  |  |  | if (from == true) { | 
			
		
	
		
			
				
					|  |  |  | edge.from = clusterStack[clusterStack.length - 1]; | 
			
		
	
		
			
				
					|  |  |  | edge.fromId = clusterStack[clusterStack.length - 1].id; | 
			
		
	
		
			
				
					|  |  |  | clusterStack.pop() | 
			
		
	
		
			
				
					|  |  |  | edge.fromArray = clusterStack; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | edge.to = clusterStack[clusterStack.length - 1]; | 
			
		
	
		
			
				
					|  |  |  | edge.toId = clusterStack[clusterStack.length - 1].id; | 
			
		
	
		
			
				
					|  |  |  | clusterStack.pop(); | 
			
		
	
		
			
				
					|  |  |  | edge.toArray = clusterStack; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | edge.connect(); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node | 
			
		
	
		
			
				
					|  |  |  | * @param nodeId | 
			
		
	
		
			
				
					|  |  |  | * @returns {Array} | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._getClusterStack = function(nodeId) { | 
			
		
	
		
			
				
					|  |  |  | var stack = []; | 
			
		
	
		
			
				
					|  |  |  | var max = 100; | 
			
		
	
		
			
				
					|  |  |  | var counter = 0; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | while (this.clusteredNodes[nodeId] !== undefined && counter < max) { | 
			
		
	
		
			
				
					|  |  |  | stack.push(this.clusteredNodes[nodeId].node); | 
			
		
	
		
			
				
					|  |  |  | nodeId = this.clusteredNodes[nodeId].clusterId; | 
			
		
	
		
			
				
					|  |  |  | counter++; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | stack.push(this.nodes[nodeId]); | 
			
		
	
		
			
				
					|  |  |  | return stack; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * Get the Id the node is connected to | 
			
		
	
		
			
				
					|  |  |  | * @param edge | 
			
		
	
		
			
				
					|  |  |  | * @param nodeId | 
			
		
	
		
			
				
					|  |  |  | * @returns {*} | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._getConnectedId = function(edge, nodeId) { | 
			
		
	
		
			
				
					|  |  |  | if (edge.toId != nodeId) { | 
			
		
	
		
			
				
					|  |  |  | return edge.toId; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else if (edge.fromId != nodeId) { | 
			
		
	
		
			
				
					|  |  |  | return edge.fromId; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | return edge.fromId; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * 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 | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | ClusterEngine.prototype._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.edges.length > largestHub) { | 
			
		
	
		
			
				
					|  |  |  | largestHub = node.edges.length; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | average += node.edges.length; | 
			
		
	
		
			
				
					|  |  |  | averageSquared += Math.pow(node.edges.length,2); | 
			
		
	
		
			
				
					|  |  |  | hubCounter += 1; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | average = average / hubCounter; | 
			
		
	
		
			
				
					|  |  |  | averageSquared = averageSquared / hubCounter; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var variance = averageSquared - Math.pow(average,2); | 
			
		
	
		
			
				
					|  |  |  | var standardDeviation = Math.sqrt(variance); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | var hubThreshold = Math.floor(average + 2*standardDeviation); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // always have at least one to cluster | 
			
		
	
		
			
				
					|  |  |  | if (hubThreshold > largestHub) { | 
			
		
	
		
			
				
					|  |  |  | hubThreshold = largestHub; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | return hubThreshold; | 
			
		
	
		
			
				
					|  |  |  | }; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  |