Browse Source

Multiple base edges in clustered edge (#3268)

* First interim commit

* Fixes during testing

* Allow multiple edges to be hidden by a clustered edge.

Fix for #3245.

This fix adjusts the clustering edges so that theyi can refer to multiple edges instead of just one.

This API method is now insufficient, since multiple base edges can be returned.

- Added replacing method `clustering.getBaseEdges()`
- Adjusted example `changingClusteredEdgeNodes` for the new method. This is the *only* place where `getBaseEdge()` was used
- Adjusted documentation for new method and deprecation old method.

Method `getbaseEdge()` should now be considered `deprecated`, and in due time should be removed.

* Edits of method name in example

* Edits of method name in example

* adjusted deprecation method getBaseEdge() in docs

* Adjusted deprecation method for getBaseEdge() in docs
revert-3409-performance
wimrijnders 7 years ago
committed by yotamberk
parent
commit
fc7b5f0808
3 changed files with 167 additions and 62 deletions
  1. +14
    -1
      docs/network/index.html
  2. +3
    -3
      examples/network/other/changingClusteredEdgesNodes.html
  3. +150
    -58
      lib/network/modules/Clustering.js

+ 14
- 1
docs/network/index.html View File

@ -645,7 +645,20 @@ var locales = {
<tr class="hidden" parent="getBaseEdge">
<td class="midMethods">Returns: Value</td>
<td>When a clusteredEdgeId is available, this method will return the original baseEdgeId provided in <code>data.edges</code><br/>
ie. After clustering the 'SelectEdge' event is fired but provides only the clustered edge. This method can then be used to return the baseEdgeId.
ie. After clustering the 'SelectEdge' event is fired but provides only the clustered edge. This method can then be used to return the baseEdgeId.<br><br>
<b>This method is deprecated. Please use <code>getBaseEdges()</code> instead.</b>
</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','getBaseEdges', this);">
<td colspan="2"><span parent="getBaseEdges" class="right-caret" id="method_getBaseEdges"></span>
getBaseEdges(<code>String clusteredEdgeId</code>)
</td>
</tr>
<tr class="hidden" parent="getBaseEdges">
<td class="midMethods">Returns: Array</td>
<td>For the given <code>clusteredEdgeId</code>, this method will return all the original base edge id's provided in <code>data.edges</code>.
For a non-clustered (i.e. 'base') edge, <code>clusteredEdgeId</code> is returned.<br/><br/>
Only the base edge id's are returned. All clustered edges id's under <code>clusteredEdgeId</code> are skipped, but scanned recursively to return their base id's.<br/>
</td>
</tr>
<tr class="collapsible toggle" onclick="toggleTable('methodTable','updateEdge', this);">

+ 3
- 3
examples/network/other/changingClusteredEdgesNodes.html View File

@ -27,9 +27,9 @@
<p>
Demonstrating getBaseEdge, getClusteredEdges updateEdge and updateClusteredNode. <br/><ul><li>Clicking on the cluster will change it to a star (updateClusteredNode).</li>
Demonstrating getBaseEdges, getClusteredEdges updateEdge and updateClusteredNode. <br/><ul><li>Clicking on the cluster will change it to a star (updateClusteredNode).</li>
<li>Clicking on an edge will make it red regardless of whether it is a clusteredEdge or not (updateEdge)</li>
<li>Clicking on an edge will also show the results of getBaseEdge and getClusteredEdge</li>
<li>Clicking on an edge will also show the results of getBaseEdges and getClusteredEdge</li>
</ul>
</p>
@ -94,7 +94,7 @@ Demonstrating getBaseEdge, getClusteredEdges updateEdge and updateClusteredNode.
var obj = {};
obj.clicked_id = params.edges[0];
network.clustering.updateEdge(params.edges[0], {color : '#aa0000'});
obj.base_edge = network.clustering.getBaseEdge(params.edges[0]);
obj.base_edges = network.clustering.getBaseEdges(params.edges[0]);
obj.all_clustered_edges = network.clustering.getClusteredEdges(params.edges[0]);
document.getElementById('eventSpan').innerHTML = '<h2>selectEdge event:</h2>' + JSON.stringify(obj, null, 4);
}

+ 150
- 58
lib/network/modules/Clustering.js View File

@ -305,37 +305,62 @@ class ClusterEngine {
}
}
// here we actually create the replacement edges. We could not do this in the loop above as the creation process
//
// Here we actually create the replacement edges.
//
// We could not do this in the loop above as the creation process
// would add an edge to the edges array we are iterating over.
//
// NOTE: a clustered edge can have multiple base edges!
//
var newEdges = [];
/**
* Find a cluster edge which matches the given created edge.
*/
var getNewEdge = function(createdEdge) {
for (let j = 0; j < newEdges.length; j++) {
let newEdge = newEdges[j];
// We replace both to and from edges with a single cluster edge
let matchToDirection = (createdEdge.fromId === newEdge.fromId && createdEdge.toId === newEdge.toId);
let matchFromDirection = (createdEdge.fromId === newEdge.toId && createdEdge.toId === newEdge.fromId);
if (matchToDirection || matchFromDirection ) {
return newEdge;
}
}
return null;
};
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');
// make sure the properties of clusterEdges are superimposed on it
util.deepExtend(clonedOptions, clusterEdgeProperties);
// set up the edge
clonedOptions.from = createEdges[j].fromId;
clonedOptions.to = createEdges[j].toId;
clonedOptions.id = 'clusterEdge:' + util.randomUUID();
//clonedOptions.id = '(cf: ' + createEdges[j].fromId + " to: " + createEdges[j].toId + ")" + Math.random();
// create the edge and give a reference to the one it replaced.
let newEdge = this.body.functions.createEdge(clonedOptions);
newEdge.clusteringEdgeReplacingId = edge.id;
let createdEdge = createEdges[j];
let edge = createdEdge.edge;
let newEdge = getNewEdge(createdEdge);
if (newEdge === null) {
// Create a clustered edge for this connection
newEdge = this._createClusteredEdge(
createdEdge.fromId,
createdEdge.toId,
edge,
clusterEdgeProperties);
newEdges.push(newEdge);
} else {
newEdge.clusteringEdgeReplacingIds.push(edge.id);
}
// also reference the new edge in the old edge
this.body.edges[edge.id].edgeReplacedById = newEdge.id;
// connect the edge.
this.body.edges[newEdge.id] = newEdge;
newEdge.connect();
// hide the replaced edge
this._backupEdgeOptions(edge);
edge.setOptions({physics:false});
}
}
/**
@ -606,52 +631,47 @@ class ClusterEngine {
// actually handling the deleting.
for (let i = 0; i < edgesToBeDeleted.length; i++) {
let edge = edgesToBeDeleted[i];
let otherNodeId = this._getConnectedId(edge, clusterNodeId);
// if the other node is in another cluster, we transfer ownership of this edge to the other cluster
if (this.clusteredNodes[otherNodeId] !== undefined) {
// transfer ownership:
let otherCluster = this.body.nodes[this.clusteredNodes[otherNodeId].clusterId];
let transferEdge = this.body.edges[edge.clusteringEdgeReplacingId];
if (transferEdge !== undefined) {
let edge = edgesToBeDeleted[i];
let otherNodeId = this._getConnectedId(edge, clusterNodeId);
let otherNode = this.clusteredNodes[otherNodeId];
for (let j = 0; j < edge.clusteringEdgeReplacingIds.length; j++) {
let transferId = edge.clusteringEdgeReplacingIds[j];
let transferEdge = this.body.edges[transferId];
if (transferEdge === undefined) continue;
// if the other node is in another cluster, we transfer ownership of this edge to the other cluster
if (otherNode !== undefined) {
// transfer ownership:
let otherCluster = this.body.nodes[otherNode.clusterId];
otherCluster.containedEdges[transferEdge.id] = transferEdge;
// delete local reference
delete containedEdges[transferEdge.id];
// create new cluster edge from the otherCluster:
// get to and from
let fromId = transferEdge.fromId;
let toId = transferEdge.toId;
if (transferEdge.toId == otherNodeId) {
toId = this.clusteredNodes[otherNodeId].clusterId;
toId = otherNode.clusterId;
}
else {
fromId = this.clusteredNodes[otherNodeId].clusterId;
fromId = otherNode.clusterId;
}
// clone the options and apply the cluster options to them
let clonedOptions = NetworkUtil.cloneOptions(transferEdge, 'edge');
util.deepExtend(clonedOptions, otherCluster.clusterEdgeProperties);
// apply the edge specific options to it.
let id = 'clusterEdge:' + util.randomUUID();
util.deepExtend(clonedOptions, {from: fromId, to: toId, hidden: false, physics: true, id: id});
// create new cluster edge from the otherCluster
this._createClusteredEdge(
fromId,
toId,
transferEdge,
otherCluster.clusterEdgeProperties,
{hidden: false, physics: true});
// create it
let newEdge = this.body.functions.createEdge(clonedOptions);
newEdge.clusteringEdgeReplacingId = transferEdge.id;
this.body.edges[id] = newEdge;
this.body.edges[id].connect();
}
}
else {
let replacedEdge = this.body.edges[edge.clusteringEdgeReplacingId];
if (replacedEdge !== undefined) {
this._restoreEdge(replacedEdge);
} else {
this._restoreEdge(transferEdge);
}
}
edge.cleanup();
// this removes the edge from node.edges, which is why edgeIds is formed
edge.disconnect();
@ -773,22 +793,61 @@ class ClusterEngine {
* Get the base edge id of clusterEdgeId. cluster edge (clusteredEdgeId) -> cluster edge B -> cluster edge C -> base edge
* @param clusteredEdgeId
* @returns baseEdgeId
*
* TODO: deprecate in 5.0.0. Method getBaseEdges() is the correct one to use.
*/
getBaseEdge(clusteredEdgeId) {
let baseEdgeId = clusteredEdgeId;
let max = 100;
// Just kludge this by returning the first base edge id found
return this.getBaseEdges(clusteredEdgeId)[0];
}
/**
* Get all regular edges for this clustered edge id.
*
* @param {Number} clusteredEdgeId
* @returns {Array[Number} all baseEdgeId's under this clustered edge
*/
getBaseEdges(clusteredEdgeId) {
let IdsToHandle = [clusteredEdgeId];
let doneIds = [];
let foundIds = [];
let max = 100;
let counter = 0;
while (clusteredEdgeId !== undefined && this.body.edges[clusteredEdgeId] !== undefined && counter < max) {
clusteredEdgeId = this.body.edges[clusteredEdgeId].clusteringEdgeReplacingId;
while (IdsToHandle.length > 0 && counter < max) {
let nextId = IdsToHandle.pop();
if (nextId === undefined) continue; // Paranoia here and onwards
let nextEdge = this.body.edges[nextId];
if (nextEdge === undefined) continue;
counter++;
if (clusteredEdgeId !== undefined) {
baseEdgeId = clusteredEdgeId;
let replacingIds = nextEdge.clusteringEdgeReplacingIds;
if (replacingIds === undefined) {
// nextId is a base id
foundIds.push(nextId);
} else {
// Another cluster edge, unravel this one as well
for (let i = 0; i < replacingIds.length; ++i) {
let replacingId = replacingIds[i];
// Don't add if already handled
// TODO: never triggers; find a test-case which does
if (IdsToHandle.indexOf(replacingIds) !== -1 || doneIds.indexOf(replacingIds) !== -1) {
continue;
}
IdsToHandle.push(replacingId);
}
}
doneIds.push(nextId);
}
return baseEdgeId;
return foundIds;
}
/**
* Get the Id the node is connected to
* @param edge
@ -846,6 +905,39 @@ class ClusterEngine {
};
/**
* Create an edge for the cluster representation.
*
* @return {Edge} newly created clustered edge
* @private
*/
_createClusteredEdge(fromId, toId, baseEdge, clusterEdgeProperties, extraOptions) {
// copy the options of the edge we will replace
let clonedOptions = NetworkUtil.cloneOptions(baseEdge, 'edge');
// make sure the properties of clusterEdges are superimposed on it
util.deepExtend(clonedOptions, clusterEdgeProperties);
// set up the edge
clonedOptions.from = fromId;
clonedOptions.to = toId;
clonedOptions.id = 'clusterEdge:' + util.randomUUID();
// apply the edge specific options to it if specified
if (extraOptions !== undefined) {
util.deepExtend(clonedOptions, extraOptions);
}
let newEdge = this.body.functions.createEdge(clonedOptions);
newEdge.clusteringEdgeReplacingIds = [baseEdge.id];
newEdge.connect();
// Register the new edge
this.body.edges[newEdge.id] = newEdge;
return newEdge;
}
/**
* Determine if node with given id is part of a cluster.
*

Loading…
Cancel
Save