|  |  | @ -6,12 +6,13 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | constructor(body) { | 
			
		
	
		
			
				
					|  |  |  | this.body = body; | 
			
		
	
		
			
				
					|  |  |  | this.clusteredNodes = {}; | 
			
		
	
		
			
				
					|  |  |  | this.clusteredEdges = {}; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | this.options = {}; | 
			
		
	
		
			
				
					|  |  |  | this.defaultOptions = {}; | 
			
		
	
		
			
				
					|  |  |  | util.extend(this.options, this.defaultOptions); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | this.body.emitter.on('_resetData', () => {this.clusteredNodes = {};}) | 
			
		
	
		
			
				
					|  |  |  | this.body.emitter.on('_resetData', () => {this.clusteredNodes = {}; this.clusteredEdges = {};}) | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | setOptions(options) { | 
			
		
	
	
		
			
				
					|  |  | @ -68,14 +69,14 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | for (let i = 0; i < this.body.nodeIndices.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | let nodeId = this.body.nodeIndices[i]; | 
			
		
	
		
			
				
					|  |  |  | let node = this.body.nodes[nodeId]; | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil._cloneOptions(node); | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil.cloneOptions(node); | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition(clonedOptions) === true) { | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[nodeId] = this.body.nodes[nodeId]; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // collect the nodes that will be in the cluster | 
			
		
	
		
			
				
					|  |  |  | for (let i = 0; i < node.edges.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | let edge = node.edges[i]; | 
			
		
	
		
			
				
					|  |  |  | if (edge.hiddenByCluster !== true) { | 
			
		
	
		
			
				
					|  |  |  | if (this.clusteredEdges[edge.id] === undefined) { | 
			
		
	
		
			
				
					|  |  |  | childEdgesObj[edge.id] = edge; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
	
		
			
				
					|  |  | @ -110,7 +111,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | edges = []; | 
			
		
	
		
			
				
					|  |  |  | for (let j = 0; j < node.edges.length; j++) { | 
			
		
	
		
			
				
					|  |  |  | edge = node.edges[j]; | 
			
		
	
		
			
				
					|  |  |  | if (edge.hiddenByCluster !== true) { | 
			
		
	
		
			
				
					|  |  |  | if (this.clusteredEdges[edge.id] === undefined) { | 
			
		
	
		
			
				
					|  |  |  | if (edge.toId !== edge.fromId) { | 
			
		
	
		
			
				
					|  |  |  | relevantEdgeCount++; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
	
		
			
				
					|  |  | @ -132,7 +133,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | usedNodes[nodeId] = true; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil._cloneOptions(this.body.nodes[nodeId]); | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil.cloneOptions(this.body.nodes[nodeId]); | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition(clonedOptions) === true) { | 
			
		
	
		
			
				
					|  |  |  | childEdgesObj[edge.id] = edge; | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[nodeId] = this.body.nodes[nodeId]; | 
			
		
	
	
		
			
				
					|  |  | @ -208,16 +209,16 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | let childNodesObj = {}; | 
			
		
	
		
			
				
					|  |  |  | let childEdgesObj = {}; | 
			
		
	
		
			
				
					|  |  |  | let parentNodeId = node.id; | 
			
		
	
		
			
				
					|  |  |  | let parentClonedOptions = NetworkUtil._cloneOptions(node); | 
			
		
	
		
			
				
					|  |  |  | let parentClonedOptions = NetworkUtil.cloneOptions(node); | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[parentNodeId] = node; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // collect the nodes that will be in the cluster | 
			
		
	
		
			
				
					|  |  |  | for (let i = 0; i < node.edges.length; i++) { | 
			
		
	
		
			
				
					|  |  |  | let edge = node.edges[i]; | 
			
		
	
		
			
				
					|  |  |  | if (edge.hiddenByCluster !== true) { | 
			
		
	
		
			
				
					|  |  |  | if (this.clusteredEdges[edge.id] === undefined) { | 
			
		
	
		
			
				
					|  |  |  | let childNodeId = this._getConnectedId(edge, parentNodeId); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // if the child node is not in a cluster (may not be needed now with the edge.hiddenByCluster check) | 
			
		
	
		
			
				
					|  |  |  | // if the child node is not in a cluster | 
			
		
	
		
			
				
					|  |  |  | if (this.clusteredNodes[childNodeId] === undefined) { | 
			
		
	
		
			
				
					|  |  |  | if (childNodeId !== parentNodeId) { | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition === undefined) { | 
			
		
	
	
		
			
				
					|  |  | @ -226,7 +227,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | // clone the options and insert some additional parameters that could be interesting. | 
			
		
	
		
			
				
					|  |  |  | let childClonedOptions = NetworkUtil._cloneOptions(this.body.nodes[childNodeId]); | 
			
		
	
		
			
				
					|  |  |  | let childClonedOptions = NetworkUtil.cloneOptions(this.body.nodes[childNodeId]); | 
			
		
	
		
			
				
					|  |  |  | if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) { | 
			
		
	
		
			
				
					|  |  |  | childEdgesObj[edge.id] = edge; | 
			
		
	
		
			
				
					|  |  |  | childNodesObj[childNodeId] = this.body.nodes[childNodeId]; | 
			
		
	
	
		
			
				
					|  |  | @ -250,8 +251,9 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | * It looks for edges that are connected to the nodes from the "outside' of the cluster. | 
			
		
	
		
			
				
					|  |  |  | * | 
			
		
	
		
			
				
					|  |  |  | * @param childNodesObj | 
			
		
	
		
			
				
					|  |  |  | * @param newEdges | 
			
		
	
		
			
				
					|  |  |  | * @param options | 
			
		
	
		
			
				
					|  |  |  | * @param childEdgesObj | 
			
		
	
		
			
				
					|  |  |  | * @param clusterNodeProperties | 
			
		
	
		
			
				
					|  |  |  | * @param clusterEdgeProperties | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | _createClusterEdges (childNodesObj, childEdgesObj, clusterNodeProperties, clusterEdgeProperties) { | 
			
		
	
	
		
			
				
					|  |  | @ -269,7 +271,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | for (let j = 0; j < childNode.edges.length; j++) { | 
			
		
	
		
			
				
					|  |  |  | edge = childNode.edges[j]; | 
			
		
	
		
			
				
					|  |  |  | // we only handle edges that are visible to the system, not the disabled ones from the clustering process. | 
			
		
	
		
			
				
					|  |  |  | if (edge.hiddenByCluster !== true) { | 
			
		
	
		
			
				
					|  |  |  | if (this.clusteredEdges[edge.id] === undefined) { | 
			
		
	
		
			
				
					|  |  |  | // self-referencing edges will be added to the "hidden" list | 
			
		
	
		
			
				
					|  |  |  | if (edge.toId == edge.fromId) { | 
			
		
	
		
			
				
					|  |  |  | childEdgesObj[edge.id] = edge; | 
			
		
	
	
		
			
				
					|  |  | @ -301,7 +303,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | for (let j = 0; j < createEdges.length; j++) { | 
			
		
	
		
			
				
					|  |  |  | let edge = createEdges[j].edge; | 
			
		
	
		
			
				
					|  |  |  | // copy the options of the edge we will replace | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil._cloneOptions(edge, 'edge'); | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil.cloneOptions(edge, 'edge'); | 
			
		
	
		
			
				
					|  |  |  | // make sure the properties of clusterEdges are superimposed on it | 
			
		
	
		
			
				
					|  |  |  | util.deepExtend(clonedOptions, clusterEdgeProperties); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
	
		
			
				
					|  |  | @ -320,8 +322,8 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | newEdge.connect(); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // hide the replaced edge | 
			
		
	
		
			
				
					|  |  |  | this._backupEdgeOptions(edge); | 
			
		
	
		
			
				
					|  |  |  | edge.setOptions({physics:false, hidden:true}); | 
			
		
	
		
			
				
					|  |  |  | edge.hiddenByCluster = true; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
	
		
			
				
					|  |  | @ -349,7 +351,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | * @private | 
			
		
	
		
			
				
					|  |  |  | */ | 
			
		
	
		
			
				
					|  |  |  | _cluster(childNodesObj, childEdgesObj, options, refreshData = true) { | 
			
		
	
		
			
				
					|  |  |  | // kill condition: no children so can't cluster or only one node in the cluster, dont bother | 
			
		
	
		
			
				
					|  |  |  | // kill condition: no children so can't cluster or only one node in the cluster, don't bother | 
			
		
	
		
			
				
					|  |  |  | if (Object.keys(childNodesObj).length < 2) {return;} | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // check if this cluster call is not trying to cluster anything that is in another cluster. | 
			
		
	
	
		
			
				
					|  |  | @ -369,18 +371,18 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | let childNodesOptions = []; | 
			
		
	
		
			
				
					|  |  |  | for (let nodeId in childNodesObj) { | 
			
		
	
		
			
				
					|  |  |  | if (childNodesObj.hasOwnProperty(nodeId)) { | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil._cloneOptions(childNodesObj[nodeId]); | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil.cloneOptions(childNodesObj[nodeId]); | 
			
		
	
		
			
				
					|  |  |  | childNodesOptions.push(clonedOptions); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // get clusterproperties based on childNodes | 
			
		
	
		
			
				
					|  |  |  | // get cluster properties based on childNodes | 
			
		
	
		
			
				
					|  |  |  | let childEdgesOptions = []; | 
			
		
	
		
			
				
					|  |  |  | for (let edgeId in childEdgesObj) { | 
			
		
	
		
			
				
					|  |  |  | if (childEdgesObj.hasOwnProperty(edgeId)) { | 
			
		
	
		
			
				
					|  |  |  | // these cluster edges will be removed on creation of the cluster. | 
			
		
	
		
			
				
					|  |  |  | if (edgeId.substr(0, 12) !== "clusterEdge:") { | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil._cloneOptions(childEdgesObj[edgeId], 'edge'); | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil.cloneOptions(childEdgesObj[edgeId], 'edge'); | 
			
		
	
		
			
				
					|  |  |  | childEdgesOptions.push(clonedOptions); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
	
		
			
				
					|  |  | @ -401,7 +403,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // give the clusterNode a postion if it does not have one. | 
			
		
	
		
			
				
					|  |  |  | // give the clusterNode a position if it does not have one. | 
			
		
	
		
			
				
					|  |  |  | let pos = undefined; | 
			
		
	
		
			
				
					|  |  |  | if (clusterNodeProperties.x === undefined) { | 
			
		
	
		
			
				
					|  |  |  | pos = this._getClusterPosition(childNodesObj); | 
			
		
	
	
		
			
				
					|  |  | @ -434,8 +436,10 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | if (childEdgesObj.hasOwnProperty(edgeId)) { | 
			
		
	
		
			
				
					|  |  |  | if (this.body.edges[edgeId] !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | let edge = this.body.edges[edgeId]; | 
			
		
	
		
			
				
					|  |  |  | // cache the options before changing | 
			
		
	
		
			
				
					|  |  |  | this._backupEdgeOptions(edge); | 
			
		
	
		
			
				
					|  |  |  | // disable physics and hide the edge | 
			
		
	
		
			
				
					|  |  |  | edge.setOptions({physics:false, hidden:true}); | 
			
		
	
		
			
				
					|  |  |  | edge.hiddenByCluster = true; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
	
		
			
				
					|  |  | @ -457,6 +461,20 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | _backupEdgeOptions(edge) { | 
			
		
	
		
			
				
					|  |  |  | if (this.clusteredEdges[edge.id] === undefined) { | 
			
		
	
		
			
				
					|  |  |  | this.clusteredEdges[edge.id] = {physics: edge.options.physics, hidden: edge.options.hidden}; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | _restoreEdge(edge) { | 
			
		
	
		
			
				
					|  |  |  | let originalOptions = this.clusteredEdges[edge.id]; | 
			
		
	
		
			
				
					|  |  |  | if (originalOptions !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | edge.setOptions({physics: originalOptions.physics, hidden: originalOptions.hidden}); | 
			
		
	
		
			
				
					|  |  |  | delete this.clusteredEdges[edge.id]; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  | * Check if a node is a cluster. | 
			
		
	
	
		
			
				
					|  |  | @ -561,7 +579,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | containedNode.vx = clusterNode.vx; | 
			
		
	
		
			
				
					|  |  |  | containedNode.vy = clusterNode.vy; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // we use these methods to avoid reinstantiating the shape, which happens with setOptions. | 
			
		
	
		
			
				
					|  |  |  | // we use these methods to avoid re-instantiating the shape, which happens with setOptions. | 
			
		
	
		
			
				
					|  |  |  | containedNode.setOptions({hidden:false, physics:true}); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | delete this.clusteredNodes[nodeId]; | 
			
		
	
	
		
			
				
					|  |  | @ -602,7 +620,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // clone the options and apply the cluster options to them | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil._cloneOptions(transferEdge, 'edge'); | 
			
		
	
		
			
				
					|  |  |  | let clonedOptions = NetworkUtil.cloneOptions(transferEdge, 'edge'); | 
			
		
	
		
			
				
					|  |  |  | util.deepExtend(clonedOptions, otherCluster.clusterEdgeProperties); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | // apply the edge specific options to it. | 
			
		
	
	
		
			
				
					|  |  | @ -619,8 +637,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | else { | 
			
		
	
		
			
				
					|  |  |  | let replacedEdge = this.body.edges[edge.clusteringEdgeReplacingId]; | 
			
		
	
		
			
				
					|  |  |  | if (replacedEdge !== undefined) { | 
			
		
	
		
			
				
					|  |  |  | replacedEdge.setOptions({physics: true, hidden: false}); | 
			
		
	
		
			
				
					|  |  |  | replacedEdge.hiddenByCluster = false; | 
			
		
	
		
			
				
					|  |  |  | this._restoreEdge(replacedEdge); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | edge.cleanup(); | 
			
		
	
	
		
			
				
					|  |  | @ -632,10 +649,7 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | // handle the releasing of the edges | 
			
		
	
		
			
				
					|  |  |  | for (let edgeId in containedEdges) { | 
			
		
	
		
			
				
					|  |  |  | if (containedEdges.hasOwnProperty(edgeId)) { | 
			
		
	
		
			
				
					|  |  |  | let edge = containedEdges[edgeId]; | 
			
		
	
		
			
				
					|  |  |  | edge.setOptions({physics: true, hidden: false}); | 
			
		
	
		
			
				
					|  |  |  | edge.hiddenByCluster = undefined; | 
			
		
	
		
			
				
					|  |  |  | delete edge.hiddenByCluster; | 
			
		
	
		
			
				
					|  |  |  | this._restoreEdge(containedEdges[edgeId]); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
	
		
			
				
					|  |  | @ -648,12 +662,12 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | getNodesInCluster(clusterId) { | 
			
		
	
		
			
				
					|  |  |  | let nodesArray = [] | 
			
		
	
		
			
				
					|  |  |  | let nodesArray = []; | 
			
		
	
		
			
				
					|  |  |  | if (this.isCluster(clusterId) === true) { | 
			
		
	
		
			
				
					|  |  |  | let containedNodes = this.body.nodes[clusterId].containedNodes; | 
			
		
	
		
			
				
					|  |  |  | for (let nodeId in containedNodes) { | 
			
		
	
		
			
				
					|  |  |  | if (containedNodes.hasOwnProperty(nodeId)) { | 
			
		
	
		
			
				
					|  |  |  | nodesArray.push(nodeId) | 
			
		
	
		
			
				
					|  |  |  | nodesArray.push(this.body.nodes[nodeId].id) | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
	
		
			
				
					|  |  | @ -672,11 +686,13 @@ class ClusterEngine { | 
			
		
	
		
			
				
					|  |  |  | let counter = 0; | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | while (this.clusteredNodes[nodeId] !== undefined && counter < max) { | 
			
		
	
		
			
				
					|  |  |  | stack.push(this.clusteredNodes[nodeId].node); | 
			
		
	
		
			
				
					|  |  |  | stack.push(this.body.nodes[nodeId].id); | 
			
		
	
		
			
				
					|  |  |  | nodeId = this.clusteredNodes[nodeId].clusterId; | 
			
		
	
		
			
				
					|  |  |  | counter++; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | stack.push(this.body.nodes[nodeId]); | 
			
		
	
		
			
				
					|  |  |  | stack.push(this.body.nodes[nodeId].id); | 
			
		
	
		
			
				
					|  |  |  | stack.reverse(); | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
		
			
				
					|  |  |  | return stack; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
			
		
	
	
		
			
				
					|  |  |  |