Browse Source

Network: Cluster node handling due to dynamic data change. (#3399)

* First fix for opening clusters, added unit tests

* Added opening of child cluster to parent cluster

* Added more tests for multi-level clusters

* Commenting fixes

* Added unit tests for option allowSingleNodeCluster, fixes due to that

* Added test for allowSingleNodeCluster with nested clusters

* Fixes due to linting

* Removed TODO from code
revert-3409-performance
wimrijnders 6 years ago
committed by Yotam Berkowitz
parent
commit
f75a89366c
3 changed files with 556 additions and 40 deletions
  1. +97
    -25
      lib/network/modules/Clustering.js
  2. +61
    -0
      lib/network/modules/components/nodes/Cluster.js
  3. +398
    -15
      test/Network.test.js

+ 97
- 25
lib/network/modules/Clustering.js View File

@ -3,10 +3,8 @@
# TODO
- `edgeReplacedById` not cleaned up yet on cluster edge removal
- check correct working for everything for clustered clusters (could use a unit test)
- Handle recursive unclustering on node removal
- `updateState()` not complete; scan TODO's there
- allowSingleNodeCluster could be a global option as well; currently needs to always
be passed to clustering methods
----------------------------------------------
@ -63,7 +61,7 @@ A cluster node has the following extra fields:
- `isCluster : true` - indication that this is a cluster node
- `containedNodes` - hash of nodes contained in this cluster
- `containedEdges` - same for edges
- `edges` - hash of cluster edges for this node
- `edges` - array of cluster edges for this node
**NOTE:**
@ -598,9 +596,9 @@ class ClusterEngine {
// force the ID to remain the same
clusterNodeProperties.id = clusterId;
// create the clusterNode
// create the cluster Node
// Note that allowSingleNodeCluster, if present, is stored in the options as well
let clusterNode = this.body.functions.createNode(clusterNodeProperties, Cluster);
clusterNode.isCluster = true;
clusterNode.containedNodes = childNodesObj;
clusterNode.containedEdges = childEdgesObj;
// cache a copy from the cluster edge properties if we have to reconnect others later on
@ -695,13 +693,42 @@ class ClusterEngine {
*/
openCluster(clusterNodeId, options, refreshData = true) {
// kill conditions
if (clusterNodeId === undefined) {throw new Error("No clusterNodeId supplied to openCluster.");}
if (this.body.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");}
if (this.body.nodes[clusterNodeId].containedNodes === undefined) {
console.log("The node:" + clusterNodeId + " is not a cluster.");
return
if (clusterNodeId === undefined) {
throw new Error("No clusterNodeId supplied to openCluster.");
}
let clusterNode = this.body.nodes[clusterNodeId];
if (clusterNode === undefined) {
throw new Error("The clusterNodeId supplied to openCluster does not exist.");
}
if (clusterNode.isCluster !== true
|| clusterNode.containedNodes === undefined
|| clusterNode.containedEdges === undefined) {
throw new Error("The node:" + clusterNodeId + " is not a valid cluster.");
}
// Check if current cluster is clustered itself
let stack = this.findNode(clusterNodeId);
let parentIndex = stack.indexOf(clusterNodeId) - 1;
if (parentIndex >= 0) {
// Current cluster is clustered; transfer contained nodes and edges to parent
let parentClusterNodeId = stack[parentIndex];
let parentClusterNode = this.body.nodes[parentClusterNodeId];
// clustering.clusteredNodes and clustering.clusteredEdges remain unchanged
parentClusterNode._openChildCluster(clusterNodeId);
// All components of child cluster node have been transferred. It can die now.
delete this.body.nodes[clusterNodeId];
if (refreshData === true) {
this.body.emitter.emit('_dataChanged');
}
return;
}
// main body
let containedNodes = clusterNode.containedNodes;
let containedEdges = clusterNode.containedEdges;
@ -729,15 +756,11 @@ class ClusterEngine {
}
else {
// copy the position from the cluster
for (let nodeId in containedNodes) {
if (containedNodes.hasOwnProperty(nodeId)) {
let containedNode = this.body.nodes[nodeId];
containedNode = containedNodes[nodeId];
// inherit position
if (containedNode.options.fixed.x === false) {containedNode.x = clusterNode.x;}
if (containedNode.options.fixed.y === false) {containedNode.y = clusterNode.y;}
}
}
util.forEach(containedNodes, function(containedNode) {
// inherit position
if (containedNode.options.fixed.x === false) {containedNode.x = clusterNode.x;}
if (containedNode.options.fixed.y === false) {containedNode.y = clusterNode.y;}
});
}
// release nodes
@ -1201,6 +1224,12 @@ class ClusterEngine {
let deletedEdgeIds = [];
let self = this;
/**
* Utility function to iterate over clustering nodes only
*
* @param {Function} callback function to call for each cluster node
*/
let eachClusterNode = (callback) => {
for (nodeId in this.body.nodes) {
let node = this.body.nodes[nodeId];
@ -1266,7 +1295,25 @@ class ClusterEngine {
for (edgeId in this.body.edges) {
let edge = this.body.edges[edgeId];
if (!edge.endPointsValid()) {
// Explicitly scan the contained edges for validity
let isValid = true;
let replacedIds = edge.clusteringEdgeReplacingIds;
if (replacedIds !== undefined) {
let numValid = 0;
for (let n in replacedIds) {
let containedEdgeId = replacedIds[n];
let containedEdge = this.body.edges[containedEdgeId];
if (containedEdge !== undefined && containedEdge.endPointsValid()) {
numValid += 1;
}
}
isValid = (numValid > 0);
}
if (!edge.endPointsValid() || !isValid) {
deletedEdgeIds.push(edgeId);
}
}
@ -1302,7 +1349,7 @@ class ClusterEngine {
// Remove cluster edges from active list (this.body.edges).
// deletedEdgeIds still contains id of regular edges, but these should all
// be gone upon entering this method
// be gone when you reach here.
for (n in deletedEdgeIds) {
delete this.body.edges[deletedEdgeIds[n]];
}
@ -1343,8 +1390,33 @@ class ClusterEngine {
}
// TODO: Cluster nodes may now be empty or because of selected options may not be allowed to contain 1 node
// Remove these cluster nodes if necessary.
// Clusters may be nested to any level. Keep on opening until nothing to open
var changed = false;
var continueLoop = true;
while (continueLoop) {
let clustersToOpen = [];
// Determine the id's of clusters that need opening
eachClusterNode(function(clusterNode) {
let numNodes = Object.keys(clusterNode.containedNodes).length;
let allowSingle = (clusterNode.options.allowSingleNodeCluster === true);
if ((allowSingle && numNodes < 1) || (!allowSingle && numNodes < 2)) {
clustersToOpen.push(clusterNode.id);
}
});
// Open them
for (let n in clustersToOpen) {
this.openCluster(clustersToOpen[n], {}, false /* Don't refresh, we're in an refresh/update already */);
}
continueLoop = (clustersToOpen.length > 0);
changed = changed || continueLoop;
}
if (changed) {
this._updateState() // Redo this method (recursion possible! should be safe)
}
}

+ 61
- 0
lib/network/modules/components/nodes/Cluster.js View File

@ -23,6 +23,67 @@ class Cluster extends Node {
this.containedNodes = {};
this.containedEdges = {};
}
/**
* Transfer child cluster data to current and disconnect the child cluster.
*
* Please consult the header comment in 'Clustering.js' for the fields set here.
*
* @param {string|number} childClusterId id of child cluster to open
*/
_openChildCluster(childClusterId) {
let childCluster = this.body.nodes[childClusterId];
if (this.containedNodes[childClusterId] === undefined) {
throw new Error('node with id: ' + childClusterId + ' not in current cluster');
}
if (!childCluster.isCluster) {
throw new Error('node with id: ' + childClusterId + ' is not a cluster');
}
// Disconnect child cluster from current cluster
delete this.containedNodes[childClusterId];
for(let n in childCluster.edges) {
let edgeId = childCluster.edges[n].id;
delete this.containedEdges[edgeId];
}
// Transfer nodes and edges
for (let nodeId in childCluster.containedNodes) {
this.containedNodes[nodeId] = childCluster.containedNodes[nodeId];
}
childCluster.containedNodes = {};
for (let edgeId in childCluster.containedEdges) {
this.containedEdges[edgeId] = childCluster.containedEdges[edgeId];
}
childCluster.containedEdges = {};
// Transfer edges within cluster edges which are clustered
for (let n in childCluster.edges) {
let clusterEdge = childCluster.edges[n];
for (let m in this.edges) {
let parentClusterEdge = this.edges[m];
let index = parentClusterEdge.clusteringEdgeReplacingIds.indexOf(clusterEdge.id);
if (index === -1) continue;
for (let n in clusterEdge.clusteringEdgeReplacingIds) {
let srcId = clusterEdge.clusteringEdgeReplacingIds[n];
parentClusterEdge.clusteringEdgeReplacingIds.push(srcId);
// Maintain correct bookkeeping for transferred edge
this.body.edges[srcId].edgeReplacedById = parentClusterEdge.id;
}
// Remove cluster edge from parent cluster edge
parentClusterEdge.clusteringEdgeReplacingIds.splice(index, 1);
break; // Assumption: a clustered edge can only be present in a single clustering edge
}
}
childCluster.edges = [];
}
}
export default Cluster;

+ 398
- 15
test/Network.test.js View File

@ -1,3 +1,15 @@
/**
*
* Useful during debugging
* =======================
*
* console.log(JSON.stringify(output, null, 2));
*
* for (let i in network.body.edges) {
* let edge = network.body.edges[i];
* console.log("" + i + ": from: " + edge.fromId + ", to: " + edge.toId);
* }
*/
var fs = require('fs');
var assert = require('assert');
var vis = require('../dist/vis');
@ -7,9 +19,6 @@ var stdout = require('test-console').stdout;
var Validator = require("./../lib/shared/Validator").default;
//var {printStyle} = require('./../lib/shared/Validator');
// Useful during debugging:
// console.log(JSON.stringify(output, null, 2));
/**
* Merge all options of object b into object b
@ -153,8 +162,8 @@ function log(network) {
function assertNumNodes(network, expectedPresent, expectedVisible) {
if (expectedVisible === undefined) expectedVisible = expectedPresent;
assert.equal(Object.keys(network.body.nodes).length, expectedPresent);
assert.equal(network.body.nodeIndices.length, expectedVisible);
assert.equal(Object.keys(network.body.nodes).length, expectedPresent, "Total number of nodes does not match");
assert.equal(network.body.nodeIndices.length, expectedVisible, "Number of visible nodes does not match");
};
@ -164,8 +173,8 @@ function assertNumNodes(network, expectedPresent, expectedVisible) {
function assertNumEdges(network, expectedPresent, expectedVisible) {
if (expectedVisible === undefined) expectedVisible = expectedPresent;
assert.equal(Object.keys(network.body.edges).length, expectedPresent);
assert.equal(network.body.edgeIndices.length, expectedVisible);
assert.equal(Object.keys(network.body.edges).length, expectedPresent, "Total number of edges does not match");
assert.equal(network.body.edgeIndices.length, expectedVisible, "Number of visible edges does not match");
};
@ -478,6 +487,99 @@ describe('Edge', function () {
describe('Clustering', function () {
/**
* Helper function for clustering
*/
function clusterTo(network, clusterId, nodeList, allowSingle) {
var options = {
joinCondition: function(node) {
return nodeList.indexOf(node.id) !== -1;
},
clusterNodeProperties: {
id: clusterId,
label: clusterId
}
}
if (allowSingle === true) {
options.clusterNodeProperties.allowSingleNodeCluster = true
}
network.cluster(options);
}
it('properly handles options allowSingleNodeCluster', function() {
var [network, data, numNodes, numEdges] = createSampleNetwork();
data.edges.update({from: 1, to: 11,});
numEdges += 1;
assertNumNodes(network, numNodes);
assertNumEdges(network, numEdges);
clusterTo(network, 'c1', [3,4]);
numNodes += 1; // A clustering node is now hiding two nodes
numEdges += 1; // One clustering edges now hiding two edges
assertNumNodes(network, numNodes, numNodes - 2);
assertNumEdges(network, numEdges, numEdges - 2);
// Cluster of single node should fail, because by default allowSingleNodeCluster == false
clusterTo(network, 'c2', [14]);
assertNumNodes(network, numNodes, numNodes - 2); // Nothing changed
assertNumEdges(network, numEdges, numEdges - 2);
assert(network.body.nodes['c2'] === undefined); // Cluster not created
// Redo with allowSingleNodeCluster == true
clusterTo(network, 'c2', [14], true);
numNodes += 1;
numEdges += 1;
assertNumNodes(network, numNodes, numNodes - 3);
assertNumEdges(network, numEdges, numEdges - 3);
assert(network.body.nodes['c2'] !== undefined); // Cluster created
// allowSingleNodeCluster: true with two nodes
// removing one clustered node should retain cluster
clusterTo(network, 'c3', [11, 12], true);
numNodes += 1; // Added cluster
numEdges += 2;
assertNumNodes(network, numNodes, 6);
assertNumEdges(network, numEdges, 5);
data.nodes.remove(12);
assert(network.body.nodes['c3'] !== undefined); // Cluster should still be present
numNodes -= 1; // removed node
numEdges -= 3; // cluster edge C3-13 should be removed
assertNumNodes(network, numNodes, 6);
assertNumEdges(network, numEdges, 4);
});
it('removes nested clusters with allowSingleNodeCluster === true', function() {
var [network, data, numNodes, numEdges] = createSampleNetwork();
// Create a chain of nested clusters, three deep
clusterTo(network, 'c1', [4], true);
clusterTo(network, 'c2', ['c1'], true);
clusterTo(network, 'c3', ['c2'], true);
numNodes += 3;
numEdges += 3;
assertNumNodes(network, numNodes, numNodes - 3);
assertNumEdges(network, numEdges, numEdges - 3);
assert(network.body.nodes['c1'] !== undefined);
assert(network.body.nodes['c2'] !== undefined);
assert(network.body.nodes['c3'] !== undefined);
// The whole chain should be removed when the bottom-most node is deleted
data.nodes.remove(4);
numNodes -= 4;
numEdges -= 4;
assertNumNodes(network, numNodes);
assertNumEdges(network, numEdges);
assert(network.body.nodes['c1'] === undefined);
assert(network.body.nodes['c2'] === undefined);
assert(network.body.nodes['c3'] === undefined);
});
/**
* Check on fix for #1218
*/
@ -486,8 +588,8 @@ describe('Clustering', function () {
createCluster(network);
numNodes += 1; // A clustering node is now hiding two nodes
assertNumNodes(network, numNodes, numNodes - 2);
numEdges += 2; // Two clustering edges now hide two edges
assertNumNodes(network, numNodes, numNodes - 2);
assertNumEdges(network, numEdges, numEdges - 2);
//console.log("Creating node 21")
@ -500,8 +602,8 @@ describe('Clustering', function () {
// '1' is part of the cluster so should
// connect to cluster instead
data.edges.update([{from: 21, to: 1}]);
assertNumNodes(network, numNodes, numNodes - 2); // nodes unchanged
numEdges += 2; // A new clustering edge is hiding a new edge
assertNumNodes(network, numNodes, numNodes - 2); // nodes unchanged
assertNumEdges(network, numEdges, numEdges - 3);
});
@ -515,8 +617,8 @@ describe('Clustering', function () {
createCluster(network);
numNodes += 1; // A clustering node is now hiding two nodes
assertNumNodes(network, numNodes, numNodes - 2);
numEdges += 2; // Two clustering edges now hide two edges
assertNumNodes(network, numNodes, numNodes - 2);
assertNumEdges(network, numEdges, numEdges - 2);
// End block same as previous test
@ -531,15 +633,15 @@ describe('Clustering', function () {
// 12 was connected to 11, which is clustered
numNodes -= 1; // 12 removed, one less node
assertNumNodes(network, numNodes, numNodes - 2);
numEdges -= 3; // clustering edge c1-12 and 2 edges of 12 gone
assertNumNodes(network, numNodes, numNodes - 2);
assertNumEdges(network, numEdges, numEdges - 1);
//console.log("Unclustering c1");
network.openCluster("c1");
numNodes -= 1; // cluster node removed, one less node
assertNumNodes(network, numNodes, numNodes); // all are visible again
numEdges -= 1; // clustering edge gone, regular edge visible
assertNumNodes(network, numNodes, numNodes); // all are visible again
assertNumEdges(network, numEdges, numEdges); // all are visible again
});
@ -561,26 +663,28 @@ describe('Clustering', function () {
network.cluster(clusterOptionsByData);
numNodes += 1; // new cluster node
assertNumNodes(network, numNodes, numNodes - 3); // 3 clustered nodes
numEdges += 1; // 1 cluster edge expected
assertNumNodes(network, numNodes, numNodes - 3); // 3 clustered nodes
assertNumEdges(network, numEdges, numEdges - 3); // 3 edges hidden
//console.log("removing node 2, which is inside the cluster");
data.nodes.remove(2);
numNodes -= 1; // clustered node removed
assertNumNodes(network, numNodes, numNodes - 2); // view doesn't change
numEdges -= 2; // edges removed hidden in cluster
assertNumNodes(network, numNodes, numNodes - 2); // view doesn't change
assertNumEdges(network, numEdges, numEdges - 1); // view doesn't change
//console.log("Unclustering c1");
network.openCluster("c1")
numNodes -= 1; // cluster node gone
assertNumNodes(network, numNodes, numNodes); // all visible
numEdges -= 1; // cluster edge gone
assertNumNodes(network, numNodes, numNodes); // all visible
assertNumEdges(network, numEdges, numEdges); // all visible
//log(network);
});
/**
* Helper function for setting up a graph for testing clusterByEdgeCount()
*/
@ -711,10 +815,289 @@ describe('Clustering', function () {
assertJoinCondition(joinLevel_ , [[2,4,5]]);
});
///////////////////////////////////////////////////////////////
// Automatic opening of clusters due to dynamic data change
///////////////////////////////////////////////////////////////
/**
* Helper function, created nested clusters, three deep
*/
function createNetwork1() {
var [network, data, numNodes, numEdges] = createSampleNetwork();
clusterTo(network, 'c1', [3,4]);
numNodes += 1; // new cluster node
numEdges += 1; // 1 cluster edge expected
assertNumNodes(network, numNodes, numNodes - 2); // 2 clustered nodes
assertNumEdges(network, numEdges, numEdges - 2); // 2 edges hidden
clusterTo(network, 'c2', [2,'c1']);
numNodes += 1; // new cluster node
numEdges += 1; // 2 cluster edges expected
assertNumNodes(network, numNodes, numNodes - 4); // 4 clustered nodes, including c1
assertNumEdges(network, numEdges, numEdges - 4); // 4 edges hidden, including edge for c1
clusterTo(network, 'c3', [1,'c2']);
// Attempt at visualization: parentheses belong to the cluster one level above
// c3
// ( -c2 )
// ( -c1 )
// 14-13-12-11 1 -2 (-3-4)
numNodes += 1; // new cluster node
numEdges += 0; // No new cluster edge expected
assertNumNodes(network, numNodes, numNodes - 6); // 6 clustered nodes, including c1 and c2
assertNumEdges(network, numEdges, numEdges - 5); // 5 edges hidden, including edges for c1 and c2
return [network, data, numNodes, numEdges];
}
it('opens clusters automatically when nodes deleted', function () {
var [network, data, numNodes, numEdges] = createSampleNetwork();
// Simple case: cluster of two nodes, delete one node
clusterTo(network, 'c1', [3,4]);
numNodes += 1; // new cluster node
numEdges += 1; // 1 cluster edge expected
assertNumNodes(network, numNodes, numNodes - 2); // 2 clustered nodes
assertNumEdges(network, numEdges, numEdges - 2); // 2 edges hidden
data.nodes.remove(4);
numNodes -= 2; // deleting clustered node also removes cluster node
numEdges -= 2; // cluster edge should also be removed
assertNumNodes(network, numNodes, numNodes);
assertNumEdges(network, numEdges, numEdges);
// Extended case: nested nodes, three deep
[network, data, numNodes, numEdges] = createNetwork1();
data.nodes.remove(4);
// c3
// ( -c2 )
// 14-13-12-11 1 (-2 -3)
numNodes -= 2; // node removed, c1 also gone
numEdges -= 2;
assertNumNodes(network, numNodes, numNodes - 4);
assertNumEdges(network, numEdges, numEdges - 3);
data.nodes.remove(1);
// c2
// 14-13-12-11 (2 -3)
numNodes -= 2; // node removed, c3 also gone
numEdges -= 2;
assertNumNodes(network, numNodes, numNodes - 2);
assertNumEdges(network, numEdges, numEdges - 1);
data.nodes.remove(2);
// 14-13-12-11 3
numNodes -= 2; // node removed, c2 also gone
numEdges -= 1;
assertNumNodes(network, numNodes); // All visible again
assertNumEdges(network, numEdges);
// Same as previous step, but remove all the given nodes in one go
// The result should be the same.
[network, data, numNodes, numEdges] = createNetwork1(); // nested nodes, three deep
data.nodes.remove([1,2,4]);
// 14-13-12-11 3
assertNumNodes(network, 5);
assertNumEdges(network, 3);
});
///////////////////////////////////////////////////////////////
// Opening of clusters at various clustering depths
///////////////////////////////////////////////////////////////
/**
* Check correct opening of a single cluster.
* This is the 'simple' case.
*/
it('properly opens 1-level clusters', function () {
var [network, data, numNodes, numEdges] = createSampleNetwork();
// Pedantic: make a cluster of everything
clusterTo(network, 'c1', [1,2,3,4,11, 12, 13, 14]);
// c1(14-13-12-11 1-2-3-4)
numNodes += 1;
assertNumNodes(network, numNodes, 1); // Just the clustering node visible
assertNumEdges(network, numEdges, 0); // No extra edges!
network.clustering.openCluster('c1', {});
numNodes -= 1;
assertNumNodes(network, numNodes, numNodes); // Expecting same as original
assertNumEdges(network, numEdges, numEdges);
// One external connection
[network, data, numNodes, numEdges] = createSampleNetwork();
// 14-13-12-11 1-2-3-4
clusterTo(network, 'c1', [3,4]);
network.clustering.openCluster('c1', {});
assertNumNodes(network, numNodes, numNodes); // Expecting same as original
assertNumEdges(network, numEdges, numEdges);
// Two external connections
clusterTo(network, 'c1', [2,3]);
network.clustering.openCluster('c1', {});
assertNumNodes(network, numNodes, numNodes); // Expecting same as original
assertNumEdges(network, numEdges, numEdges);
// One external connection to cluster
clusterTo(network, 'c1', [1,2]);
clusterTo(network, 'c2', [3,4]);
// 14-13-12-11 c1(1-2-)-c2(-3-4)
network.clustering.openCluster('c1', {});
// 14-13-12-11 1-2-c2(-3-4)
numNodes += 1;
numEdges += 1;
assertNumNodes(network, numNodes, numNodes - 2);
assertNumEdges(network, numEdges, numEdges - 2);
// two external connections to clusters
[network, data, numNodes, numEdges] = createSampleNetwork();
data.edges.update({
from: 1,
to: 11,
});
numEdges += 1;
assertNumNodes(network, numNodes, numNodes);
assertNumEdges(network, numEdges, numEdges);
clusterTo(network, 'c1', [1,2]);
// 14-13-12-11-c1(-1-2-)-3-4
numNodes += 1;
numEdges += 2;
clusterTo(network, 'c2', [3,4]);
// 14-13-12-11-c1(-1-2-)-c2(-3-4)
// NOTE: clustering edges are hidden by clustering here!
numNodes += 1;
numEdges += 1;
clusterTo(network, 'c3', [11,12]);
// 14-13-c3(-12-11-)-c1(-1-2-)-c2(-3-4)
numNodes += 1;
numEdges += 2;
assertNumNodes(network, numNodes, numNodes - 6);
assertNumEdges(network, numEdges, numEdges - 8); // 6 regular edges hidden; also 2 clustering!!!!!
network.clustering.openCluster('c1', {});
numNodes -= 1;
numEdges -= 2;
// 14-13-c3(-12-11-)-1-2-c2(-3-4)
assertNumNodes(network, numNodes, numNodes - 4);
assertNumEdges(network, numEdges, numEdges - 5);
});
/**
* Check correct opening of nested clusters.
* The test uses clustering three levels deep and opens the middle one.
*/
it('properly opens clustered clusters', function () {
var [network, data, numNodes, numEdges] = createSampleNetwork();
data.edges.update({from: 1, to: 11,});
numEdges += 1;
clusterTo(network, 'c1', [3,4]);
clusterTo(network, 'c2', [2,'c1']);
clusterTo(network, 'c3', [1,'c2']);
// Attempt at visualization: parentheses belong to the cluster one level above
// -c3
// ( -c2 )
// ( -c1 )
// 14-13-12-11 -1 -2 (-3-4)
numNodes += 3;
numEdges += 3;
//console.log("numNodes: " + numNodes + "; numEdges: " + numEdges);
assertNumNodes(network, numNodes, numNodes - 6);
assertNumEdges(network, numEdges, numEdges - 6);
// Open the middle cluster
network.clustering.openCluster('c2', {});
// -c3
// ( -c1 )
// 14-13-12-11 -1 -2 (-3-4)
numNodes -= 1;
numEdges -= 1;
assertNumNodes(network, numNodes, numNodes - 5);
assertNumEdges(network, numEdges, numEdges - 5);
//
// Same, with one external connection to cluster
//
var [network, data, numNodes, numEdges] = createSampleNetwork();
data.edges.update({from: 1, to: 11,});
data.edges.update({from: 2, to: 12,});
numEdges += 2;
// 14-13-12-11-1-2-3-4
// |------|
assertNumNodes(network, numNodes);
assertNumEdges(network, numEdges);
clusterTo(network, 'c0', [11,12]);
clusterTo(network, 'c1', [3,4]);
clusterTo(network, 'c2', [2,'c1']);
clusterTo(network, 'c3', [1,'c2']);
// +----------------+
// | c3 |
// | +----------+ |
// | | c2 | |
// +-------+ | | +----+ | |
// | c0 | | | | c1 | | |
// 14-13-|-12-11-|-|-1-|-2-|-3-4| | |
// | | | | | | +----+ | |
// +-------+ | | | | |
// | | +----------+ |
// | | | |
// | +----------------+
// |------------|
// (I)
numNodes += 4;
numEdges = 15;
assertNumNodes(network, numNodes, 4);
assertNumEdges(network, numEdges, 3); // (I) link 2-12 is combined into cluster edge for 11-1
// Open the middle cluster
network.clustering.openCluster('c2', {});
// +--------------+
// | c3 |
// | |
// +-------+ | +----+ |
// | c0 | | | c1 | |
// 14-13-|-12-11-|-|-1--2-|-3-4| |
// | | | | | +----+ |
// +-------+ | | |
// | | | |
// | +--------------+
// |-----------|
// (I)
numNodes -= 1;
numEdges -= 2;
assertNumNodes(network, numNodes, 4); // visibility doesn't change, cluster opened within cluster
assertNumEdges(network, numEdges, 3); // (I)
// Open the top cluster
network.clustering.openCluster('c3', {});
//
// +-------+ +----+
// | c0 | | c1 |
// 14-13-|-12-11-|-1-2-|-3-4|
// | | | | +----+
// +-------+ |
// | |
// |--------|
// (II)
numNodes -= 1;
numEdges = 12;
assertNumNodes(network, numNodes, 6); // visibility doesn't change, cluster opened within cluster
assertNumEdges(network, numEdges, 6); // (II) link 2-12 visible again
});
}); // Clustering
describe('on node.js', function () {
it('should be running', function () {
assert(this.container !== null, 'Container div not found');

Loading…
Cancel
Save