|
|
@ -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; |
|
|
|
} |
|
|
|
|
|
|
|