vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1449 lines
46 KiB

9 years ago
  1. /* ===========================================================================
  2. # TODO
  3. - `edgeReplacedById` not cleaned up yet on cluster edge removal
  4. - allowSingleNodeCluster could be a global option as well; currently needs to always
  5. be passed to clustering methods
  6. ----------------------------------------------
  7. # State Model for Clustering
  8. The total state for clustering is non-trivial. It is useful to have a model
  9. available as to how it works. The following documents the relevant state items.
  10. ## Network State
  11. The following `network`-members are relevant to clustering:
  12. - `body.nodes` - all nodes actively participating in the network
  13. - `body.edges` - same for edges
  14. - `body.nodeIndices` - id's of nodes that are visible at a given moment
  15. - `body.edgeIndices` - same for edges
  16. This includes:
  17. - helper nodes for dragging in `manipulation`
  18. - helper nodes for edge type `dynamic`
  19. - cluster nodes and edges
  20. - there may be more than this.
  21. A node/edge may be missing in the `Indices` member if:
  22. - it is a helper node
  23. - the node or edge state has option `hidden` set
  24. - It is not visible due to clustering
  25. ## Clustering State
  26. For the hashes, the id's of the nodes/edges are used as key.
  27. Member `network.clustering` contains the following items:
  28. - `clusteredNodes` - hash with values: { clusterId: <id of cluster>, node: <node instance>}
  29. - `clusteredEdges` - hash with values: restore information for given edge
  30. Due to nesting of clusters, these members can contain cluster nodes and edges as well.
  31. The important thing to note here, is that the clustered nodes and edges also
  32. appear in the members of the cluster nodes. For data update, it is therefore
  33. important to scan these lists as well as the cluster nodes.
  34. ### Cluster Node
  35. A cluster node has the following extra fields:
  36. - `isCluster : true` - indication that this is a cluster node
  37. - `containedNodes` - hash of nodes contained in this cluster
  38. - `containedEdges` - same for edges
  39. - `edges` - array of cluster edges for this node
  40. **NOTE:**
  41. - `containedEdges` can also contain edges which are not clustered; e.g. an edge
  42. connecting two nodes in the same cluster.
  43. ### Cluster Edge
  44. These are the items in the `edges` member of a clustered node. They have the
  45. following relevant members:
  46. - 'clusteringEdgeReplacingIds` - array of id's of edges replaced by this edge
  47. Note that it's possible to nest clusters, so that `clusteringEdgeReplacingIds`
  48. can contain edge id's of other clusters.
  49. ### Clustered Edge
  50. This is any edge contained by a cluster edge. It gets the following additional
  51. member:
  52. - `edgeReplacedById` - id of the cluster edge in which current edge is clustered
  53. =========================================================================== */
  54. let util = require("../../util");
  55. var NetworkUtil = require('../NetworkUtil').default;
  56. var Cluster = require('./components/nodes/Cluster').default;
  57. var Edge = require('./components/Edge').default; // Only needed for check on type!
  58. var Node = require('./components/Node').default; // Only needed for check on type!
  59. /**
  60. * The clustering engine
  61. */
  62. class ClusterEngine {
  63. /**
  64. * @param {Object} body
  65. */
  66. constructor(body) {
  67. this.body = body;
  68. this.clusteredNodes = {}; // key: node id, value: { clusterId: <id of cluster>, node: <node instance>}
  69. this.clusteredEdges = {}; // key: edge id, value: restore information for given edge
  70. this.options = {};
  71. this.defaultOptions = {};
  72. util.extend(this.options, this.defaultOptions);
  73. this.body.emitter.on('_resetData', () => {this.clusteredNodes = {}; this.clusteredEdges = {};})
  74. }
  75. /**
  76. *
  77. * @param {number} hubsize
  78. * @param {Object} options
  79. */
  80. clusterByHubsize(hubsize, options) {
  81. if (hubsize === undefined) {
  82. hubsize = this._getHubSize();
  83. }
  84. else if (typeof(hubsize) === "object") {
  85. options = this._checkOptions(hubsize);
  86. hubsize = this._getHubSize();
  87. }
  88. let nodesToCluster = [];
  89. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  90. let node = this.body.nodes[this.body.nodeIndices[i]];
  91. if (node.edges.length >= hubsize) {
  92. nodesToCluster.push(node.id);
  93. }
  94. }
  95. for (let i = 0; i < nodesToCluster.length; i++) {
  96. this.clusterByConnection(nodesToCluster[i],options,true);
  97. }
  98. this.body.emitter.emit('_dataChanged');
  99. }
  100. /**
  101. * loop over all nodes, check if they adhere to the condition and cluster if needed.
  102. * @param {Object} options
  103. * @param {boolean} [refreshData=true]
  104. */
  105. cluster(options = {}, refreshData = true) {
  106. if (options.joinCondition === undefined) {throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");}
  107. // check if the options object is fine, append if needed
  108. options = this._checkOptions(options);
  109. let childNodesObj = {};
  110. let childEdgesObj = {};
  111. // collect the nodes that will be in the cluster
  112. for (let nodeId in this.body.nodes) {
  113. if (!this.body.nodes.hasOwnProperty(nodeId)) continue;
  114. let node = this.body.nodes[nodeId];
  115. let clonedOptions = NetworkUtil.cloneOptions(node);
  116. if (options.joinCondition(clonedOptions) === true) {
  117. childNodesObj[nodeId] = this.body.nodes[nodeId];
  118. // collect the edges that will be in the cluster
  119. for (let i = 0; i < node.edges.length; i++) {
  120. let edge = node.edges[i];
  121. if (this.clusteredEdges[edge.id] === undefined) {
  122. childEdgesObj[edge.id] = edge;
  123. }
  124. }
  125. }
  126. }
  127. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  128. }
  129. /**
  130. * Cluster all nodes in the network that have only X edges
  131. * @param {number} edgeCount
  132. * @param {Object} options
  133. * @param {boolean} [refreshData=true]
  134. */
  135. clusterByEdgeCount(edgeCount, options, refreshData = true) {
  136. options = this._checkOptions(options);
  137. let clusters = [];
  138. let usedNodes = {};
  139. let edge, edges, relevantEdgeCount;
  140. // collect the nodes that will be in the cluster
  141. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  142. let childNodesObj = {};
  143. let childEdgesObj = {};
  144. let nodeId = this.body.nodeIndices[i];
  145. let node = this.body.nodes[nodeId];
  146. // if this node is already used in another cluster this session, we do not have to re-evaluate it.
  147. if (usedNodes[nodeId] === undefined) {
  148. relevantEdgeCount = 0;
  149. edges = [];
  150. for (let j = 0; j < node.edges.length; j++) {
  151. edge = node.edges[j];
  152. if (this.clusteredEdges[edge.id] === undefined) {
  153. if (edge.toId !== edge.fromId) {
  154. relevantEdgeCount++;
  155. }
  156. edges.push(edge);
  157. }
  158. }
  159. // this node qualifies, we collect its neighbours to start the clustering process.
  160. if (relevantEdgeCount === edgeCount) {
  161. var checkJoinCondition = function(node) {
  162. if (options.joinCondition === undefined || options.joinCondition === null) {
  163. return true;
  164. }
  165. let clonedOptions = NetworkUtil.cloneOptions(node);
  166. return options.joinCondition(clonedOptions);
  167. }
  168. let gatheringSuccessful = true;
  169. for (let j = 0; j < edges.length; j++) {
  170. edge = edges[j];
  171. let childNodeId = this._getConnectedId(edge, nodeId);
  172. // add the nodes to the list by the join condition.
  173. if (checkJoinCondition(node)) {
  174. childEdgesObj[edge.id] = edge;
  175. childNodesObj[nodeId] = node;
  176. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  177. usedNodes[nodeId] = true;
  178. } else {
  179. // this node does not qualify after all.
  180. gatheringSuccessful = false;
  181. break;
  182. }
  183. }
  184. // add to the cluster queue
  185. if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0 && gatheringSuccessful === true) {
  186. /**
  187. * Search for cluster data that contains any of the node id's
  188. * @returns {Boolean} true if no joinCondition, otherwise return value of joinCondition
  189. */
  190. var findClusterData = function() {
  191. for (var n in clusters) {
  192. // Search for a cluster containing any of the node id's
  193. for (var m in childNodesObj) {
  194. if (clusters[n].nodes[m] !== undefined) {
  195. return clusters[n];
  196. }
  197. }
  198. }
  199. return undefined;
  200. };
  201. // If any of the found nodes is part of a cluster found in this method,
  202. // add the current values to that cluster
  203. var foundCluster = findClusterData();
  204. if (foundCluster !== undefined) {
  205. // Add nodes to found cluster if not present
  206. for (let m in childNodesObj) {
  207. if (foundCluster.nodes[m] === undefined) {
  208. foundCluster.nodes[m] = childNodesObj[m];
  209. }
  210. }
  211. // Add edges to found cluster, if not present
  212. for (let m in childEdgesObj) {
  213. if (foundCluster.edges[m] === undefined) {
  214. foundCluster.edges[m] = childEdgesObj[m];
  215. }
  216. }
  217. } else {
  218. // Create a new cluster group
  219. clusters.push({nodes: childNodesObj, edges: childEdgesObj})
  220. }
  221. }
  222. }
  223. }
  224. }
  225. for (let i = 0; i < clusters.length; i++) {
  226. this._cluster(clusters[i].nodes, clusters[i].edges, options, false)
  227. }
  228. if (refreshData === true) {
  229. this.body.emitter.emit('_dataChanged');
  230. }
  231. }
  232. /**
  233. * Cluster all nodes in the network that have only 1 edge
  234. * @param {Object} options
  235. * @param {boolean} [refreshData=true]
  236. */
  237. clusterOutliers(options, refreshData = true) {
  238. this.clusterByEdgeCount(1,options,refreshData);
  239. }
  240. /**
  241. * Cluster all nodes in the network that have only 2 edge
  242. * @param {Object} options
  243. * @param {boolean} [refreshData=true]
  244. */
  245. clusterBridges(options, refreshData = true) {
  246. this.clusterByEdgeCount(2,options,refreshData);
  247. }
  248. /**
  249. * suck all connected nodes of a node into the node.
  250. * @param {Node.id} nodeId
  251. * @param {Object} options
  252. * @param {boolean} [refreshData=true]
  253. */
  254. clusterByConnection(nodeId, options, refreshData = true) {
  255. // kill conditions
  256. if (nodeId === undefined) {throw new Error("No nodeId supplied to clusterByConnection!");}
  257. if (this.body.nodes[nodeId] === undefined) {throw new Error("The nodeId given to clusterByConnection does not exist!");}
  258. let node = this.body.nodes[nodeId];
  259. options = this._checkOptions(options, node);
  260. if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x;}
  261. if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y;}
  262. if (options.clusterNodeProperties.fixed === undefined) {
  263. options.clusterNodeProperties.fixed = {};
  264. options.clusterNodeProperties.fixed.x = node.options.fixed.x;
  265. options.clusterNodeProperties.fixed.y = node.options.fixed.y;
  266. }
  267. let childNodesObj = {};
  268. let childEdgesObj = {};
  269. let parentNodeId = node.id;
  270. let parentClonedOptions = NetworkUtil.cloneOptions(node);
  271. childNodesObj[parentNodeId] = node;
  272. // collect the nodes that will be in the cluster
  273. for (let i = 0; i < node.edges.length; i++) {
  274. let edge = node.edges[i];
  275. if (this.clusteredEdges[edge.id] === undefined) {
  276. let childNodeId = this._getConnectedId(edge, parentNodeId);
  277. // if the child node is not in a cluster
  278. if (this.clusteredNodes[childNodeId] === undefined) {
  279. if (childNodeId !== parentNodeId) {
  280. if (options.joinCondition === undefined) {
  281. childEdgesObj[edge.id] = edge;
  282. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  283. }
  284. else {
  285. // clone the options and insert some additional parameters that could be interesting.
  286. let childClonedOptions = NetworkUtil.cloneOptions(this.body.nodes[childNodeId]);
  287. if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) {
  288. childEdgesObj[edge.id] = edge;
  289. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  290. }
  291. }
  292. }
  293. else {
  294. // swallow the edge if it is self-referencing.
  295. childEdgesObj[edge.id] = edge;
  296. }
  297. }
  298. }
  299. }
  300. var childNodeIDs = Object.keys(childNodesObj).map(function(childNode){
  301. return childNodesObj[childNode].id;
  302. })
  303. for (childNode in childNodesObj) {
  304. var childNode = childNodesObj[childNode];
  305. for (var y=0; y < childNode.edges.length; y++){
  306. var childEdge = childNode.edges[y];
  307. if (childNodeIDs.indexOf(this._getConnectedId(childEdge,childNode.id)) > -1){
  308. childEdgesObj[childEdge.id] = childEdge;
  309. }
  310. }
  311. }
  312. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  313. }
  314. /**
  315. * This function creates the edges that will be attached to the cluster
  316. * It looks for edges that are connected to the nodes from the "outside' of the cluster.
  317. *
  318. * @param {{Node.id: vis.Node}} childNodesObj
  319. * @param {{vis.Edge.id: vis.Edge}} childEdgesObj
  320. * @param {Object} clusterNodeProperties
  321. * @param {Object} clusterEdgeProperties
  322. * @private
  323. */
  324. _createClusterEdges (childNodesObj, childEdgesObj, clusterNodeProperties, clusterEdgeProperties) {
  325. let edge, childNodeId, childNode, toId, fromId, otherNodeId;
  326. // loop over all child nodes and their edges to find edges going out of the cluster
  327. // these edges will be replaced by clusterEdges.
  328. let childKeys = Object.keys(childNodesObj);
  329. let createEdges = [];
  330. for (let i = 0; i < childKeys.length; i++) {
  331. childNodeId = childKeys[i];
  332. childNode = childNodesObj[childNodeId];
  333. // construct new edges from the cluster to others
  334. for (let j = 0; j < childNode.edges.length; j++) {
  335. edge = childNode.edges[j];
  336. // we only handle edges that are visible to the system, not the disabled ones from the clustering process.
  337. if (this.clusteredEdges[edge.id] === undefined) {
  338. // self-referencing edges will be added to the "hidden" list
  339. if (edge.toId == edge.fromId) {
  340. childEdgesObj[edge.id] = edge;
  341. }
  342. else {
  343. // set up the from and to.
  344. if (edge.toId == childNodeId) { // this is a double equals because ints and strings can be interchanged here.
  345. toId = clusterNodeProperties.id;
  346. fromId = edge.fromId;
  347. otherNodeId = fromId;
  348. }
  349. else {
  350. toId = edge.toId;
  351. fromId = clusterNodeProperties.id;
  352. otherNodeId = toId;
  353. }
  354. }
  355. // Only edges from the cluster outwards are being replaced.
  356. if (childNodesObj[otherNodeId] === undefined) {
  357. createEdges.push({edge: edge, fromId: fromId, toId: toId});
  358. }
  359. }
  360. }
  361. }
  362. //
  363. // Here we actually create the replacement edges.
  364. //
  365. // We could not do this in the loop above as the creation process
  366. // would add an edge to the edges array we are iterating over.
  367. //
  368. // NOTE: a clustered edge can have multiple base edges!
  369. //
  370. var newEdges = [];
  371. /**
  372. * Find a cluster edge which matches the given created edge.
  373. * @param {vis.Edge} createdEdge
  374. * @returns {vis.Edge}
  375. */
  376. var getNewEdge = function(createdEdge) {
  377. for (let j = 0; j < newEdges.length; j++) {
  378. let newEdge = newEdges[j];
  379. // We replace both to and from edges with a single cluster edge
  380. let matchToDirection = (createdEdge.fromId === newEdge.fromId && createdEdge.toId === newEdge.toId);
  381. let matchFromDirection = (createdEdge.fromId === newEdge.toId && createdEdge.toId === newEdge.fromId);
  382. if (matchToDirection || matchFromDirection ) {
  383. return newEdge;
  384. }
  385. }
  386. return null;
  387. };
  388. for (let j = 0; j < createEdges.length; j++) {
  389. let createdEdge = createEdges[j];
  390. let edge = createdEdge.edge;
  391. let newEdge = getNewEdge(createdEdge);
  392. if (newEdge === null) {
  393. // Create a clustered edge for this connection
  394. newEdge = this._createClusteredEdge(
  395. createdEdge.fromId,
  396. createdEdge.toId,
  397. edge,
  398. clusterEdgeProperties);
  399. newEdges.push(newEdge);
  400. } else {
  401. newEdge.clusteringEdgeReplacingIds.push(edge.id);
  402. }
  403. // also reference the new edge in the old edge
  404. this.body.edges[edge.id].edgeReplacedById = newEdge.id;
  405. // hide the replaced edge
  406. this._backupEdgeOptions(edge);
  407. edge.setOptions({physics:false});
  408. }
  409. }
  410. /**
  411. * This function checks the options that can be supplied to the different cluster functions
  412. * for certain fields and inserts defaults if needed
  413. * @param {Object} options
  414. * @returns {*}
  415. * @private
  416. */
  417. _checkOptions(options = {}) {
  418. if (options.clusterEdgeProperties === undefined) {options.clusterEdgeProperties = {};}
  419. if (options.clusterNodeProperties === undefined) {options.clusterNodeProperties = {};}
  420. return options;
  421. }
  422. /**
  423. *
  424. * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node
  425. * @param {Object} childEdgesObj | object with edge objects, id as keys
  426. * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties}
  427. * @param {boolean} refreshData | when true, do not wrap up
  428. * @private
  429. */
  430. _cluster(childNodesObj, childEdgesObj, options, refreshData = true) {
  431. // kill condition: no nodes don't bother
  432. if (Object.keys(childNodesObj).length == 0) {return;}
  433. // allow clusters of 1 if options allow
  434. if (Object.keys(childNodesObj).length == 1 && options.clusterNodeProperties.allowSingleNodeCluster != true) {return;}
  435. // check if this cluster call is not trying to cluster anything that is in another cluster.
  436. for (let nodeId in childNodesObj) {
  437. if (childNodesObj.hasOwnProperty(nodeId)) {
  438. if (this.clusteredNodes[nodeId] !== undefined) {
  439. return;
  440. }
  441. }
  442. }
  443. let clusterNodeProperties = util.deepExtend({},options.clusterNodeProperties);
  444. // construct the clusterNodeProperties
  445. if (options.processProperties !== undefined) {
  446. // get the childNode options
  447. let childNodesOptions = [];
  448. for (let nodeId in childNodesObj) {
  449. if (childNodesObj.hasOwnProperty(nodeId)) {
  450. let clonedOptions = NetworkUtil.cloneOptions(childNodesObj[nodeId]);
  451. childNodesOptions.push(clonedOptions);
  452. }
  453. }
  454. // get cluster properties based on childNodes
  455. let childEdgesOptions = [];
  456. for (let edgeId in childEdgesObj) {
  457. if (childEdgesObj.hasOwnProperty(edgeId)) {
  458. // these cluster edges will be removed on creation of the cluster.
  459. if (edgeId.substr(0, 12) !== "clusterEdge:") {
  460. let clonedOptions = NetworkUtil.cloneOptions(childEdgesObj[edgeId], 'edge');
  461. childEdgesOptions.push(clonedOptions);
  462. }
  463. }
  464. }
  465. clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions);
  466. if (!clusterNodeProperties) {
  467. throw new Error("The processProperties function does not return properties!");
  468. }
  469. }
  470. // check if we have an unique id;
  471. if (clusterNodeProperties.id === undefined) {clusterNodeProperties.id = 'cluster:' + util.randomUUID();}
  472. let clusterId = clusterNodeProperties.id;
  473. if (clusterNodeProperties.label === undefined) {
  474. clusterNodeProperties.label = 'cluster';
  475. }
  476. // give the clusterNode a position if it does not have one.
  477. let pos = undefined;
  478. if (clusterNodeProperties.x === undefined) {
  479. pos = this._getClusterPosition(childNodesObj);
  480. clusterNodeProperties.x = pos.x;
  481. }
  482. if (clusterNodeProperties.y === undefined) {
  483. if (pos === undefined) {pos = this._getClusterPosition(childNodesObj);}
  484. clusterNodeProperties.y = pos.y;
  485. }
  486. // force the ID to remain the same
  487. clusterNodeProperties.id = clusterId;
  488. // create the cluster Node
  489. // Note that allowSingleNodeCluster, if present, is stored in the options as well
  490. let clusterNode = this.body.functions.createNode(clusterNodeProperties, Cluster);
  491. clusterNode.containedNodes = childNodesObj;
  492. clusterNode.containedEdges = childEdgesObj;
  493. // cache a copy from the cluster edge properties if we have to reconnect others later on
  494. clusterNode.clusterEdgeProperties = options.clusterEdgeProperties;
  495. // finally put the cluster node into global
  496. this.body.nodes[clusterNodeProperties.id] = clusterNode;
  497. this._clusterEdges(childNodesObj, childEdgesObj, clusterNodeProperties, options.clusterEdgeProperties);
  498. // set ID to undefined so no duplicates arise
  499. clusterNodeProperties.id = undefined;
  500. // wrap up
  501. if (refreshData === true) {
  502. this.body.emitter.emit('_dataChanged');
  503. }
  504. }
  505. /**
  506. *
  507. * @param {Edge} edge
  508. * @private
  509. */
  510. _backupEdgeOptions(edge) {
  511. if (this.clusteredEdges[edge.id] === undefined) {
  512. this.clusteredEdges[edge.id] = {physics: edge.options.physics};
  513. }
  514. }
  515. /**
  516. *
  517. * @param {Edge} edge
  518. * @private
  519. */
  520. _restoreEdge(edge) {
  521. let originalOptions = this.clusteredEdges[edge.id];
  522. if (originalOptions !== undefined) {
  523. edge.setOptions({physics: originalOptions.physics});
  524. delete this.clusteredEdges[edge.id];
  525. }
  526. }
  527. /**
  528. * Check if a node is a cluster.
  529. * @param {Node.id} nodeId
  530. * @returns {*}
  531. */
  532. isCluster(nodeId) {
  533. if (this.body.nodes[nodeId] !== undefined) {
  534. return this.body.nodes[nodeId].isCluster === true;
  535. }
  536. else {
  537. console.log("Node does not exist.");
  538. return false;
  539. }
  540. }
  541. /**
  542. * get the position of the cluster node based on what's inside
  543. * @param {object} childNodesObj | object with node objects, id as keys
  544. * @returns {{x: number, y: number}}
  545. * @private
  546. */
  547. _getClusterPosition(childNodesObj) {
  548. let childKeys = Object.keys(childNodesObj);
  549. let minX = childNodesObj[childKeys[0]].x;
  550. let maxX = childNodesObj[childKeys[0]].x;
  551. let minY = childNodesObj[childKeys[0]].y;
  552. let maxY = childNodesObj[childKeys[0]].y;
  553. let node;
  554. for (let i = 1; i < childKeys.length; i++) {
  555. node = childNodesObj[childKeys[i]];
  556. minX = node.x < minX ? node.x : minX;
  557. maxX = node.x > maxX ? node.x : maxX;
  558. minY = node.y < minY ? node.y : minY;
  559. maxY = node.y > maxY ? node.y : maxY;
  560. }
  561. return {x: 0.5*(minX + maxX), y: 0.5*(minY + maxY)};
  562. }
  563. /**
  564. * Open a cluster by calling this function.
  565. * @param {vis.Edge.id} clusterNodeId | the ID of the cluster node
  566. * @param {Object} options
  567. * @param {boolean} refreshData | wrap up afterwards if not true
  568. */
  569. openCluster(clusterNodeId, options, refreshData = true) {
  570. // kill conditions
  571. if (clusterNodeId === undefined) {
  572. throw new Error("No clusterNodeId supplied to openCluster.");
  573. }
  574. let clusterNode = this.body.nodes[clusterNodeId];
  575. if (clusterNode === undefined) {
  576. throw new Error("The clusterNodeId supplied to openCluster does not exist.");
  577. }
  578. if (clusterNode.isCluster !== true
  579. || clusterNode.containedNodes === undefined
  580. || clusterNode.containedEdges === undefined) {
  581. throw new Error("The node:" + clusterNodeId + " is not a valid cluster.");
  582. }
  583. // Check if current cluster is clustered itself
  584. let stack = this.findNode(clusterNodeId);
  585. let parentIndex = stack.indexOf(clusterNodeId) - 1;
  586. if (parentIndex >= 0) {
  587. // Current cluster is clustered; transfer contained nodes and edges to parent
  588. let parentClusterNodeId = stack[parentIndex];
  589. let parentClusterNode = this.body.nodes[parentClusterNodeId];
  590. // clustering.clusteredNodes and clustering.clusteredEdges remain unchanged
  591. parentClusterNode._openChildCluster(clusterNodeId);
  592. // All components of child cluster node have been transferred. It can die now.
  593. delete this.body.nodes[clusterNodeId];
  594. if (refreshData === true) {
  595. this.body.emitter.emit('_dataChanged');
  596. }
  597. return;
  598. }
  599. // main body
  600. let containedNodes = clusterNode.containedNodes;
  601. let containedEdges = clusterNode.containedEdges;
  602. // allow the user to position the nodes after release.
  603. if (options !== undefined && options.releaseFunction !== undefined && typeof options.releaseFunction === 'function') {
  604. let positions = {};
  605. let clusterPosition = {x:clusterNode.x, y:clusterNode.y};
  606. for (let nodeId in containedNodes) {
  607. if (containedNodes.hasOwnProperty(nodeId)) {
  608. let containedNode = this.body.nodes[nodeId];
  609. positions[nodeId] = {x: containedNode.x, y: containedNode.y};
  610. }
  611. }
  612. let newPositions = options.releaseFunction(clusterPosition, positions);
  613. for (let nodeId in containedNodes) {
  614. if (containedNodes.hasOwnProperty(nodeId)) {
  615. let containedNode = this.body.nodes[nodeId];
  616. if (newPositions[nodeId] !== undefined) {
  617. containedNode.x = (newPositions[nodeId].x === undefined ? clusterNode.x : newPositions[nodeId].x);
  618. containedNode.y = (newPositions[nodeId].y === undefined ? clusterNode.y : newPositions[nodeId].y);
  619. }
  620. }
  621. }
  622. }
  623. else {
  624. // copy the position from the cluster
  625. util.forEach(containedNodes, function(containedNode) {
  626. // inherit position
  627. if (containedNode.options.fixed.x === false) {containedNode.x = clusterNode.x;}
  628. if (containedNode.options.fixed.y === false) {containedNode.y = clusterNode.y;}
  629. });
  630. }
  631. // release nodes
  632. for (let nodeId in containedNodes) {
  633. if (containedNodes.hasOwnProperty(nodeId)) {
  634. let containedNode = this.body.nodes[nodeId];
  635. // inherit speed
  636. containedNode.vx = clusterNode.vx;
  637. containedNode.vy = clusterNode.vy;
  638. containedNode.setOptions({physics:true});
  639. delete this.clusteredNodes[nodeId];
  640. }
  641. }
  642. // copy the clusterNode edges because we cannot iterate over an object that we add or remove from.
  643. let edgesToBeDeleted = [];
  644. for (let i = 0; i < clusterNode.edges.length; i++) {
  645. edgesToBeDeleted.push(clusterNode.edges[i]);
  646. }
  647. // actually handling the deleting.
  648. for (let i = 0; i < edgesToBeDeleted.length; i++) {
  649. let edge = edgesToBeDeleted[i];
  650. let otherNodeId = this._getConnectedId(edge, clusterNodeId);
  651. let otherNode = this.clusteredNodes[otherNodeId];
  652. for (let j = 0; j < edge.clusteringEdgeReplacingIds.length; j++) {
  653. let transferId = edge.clusteringEdgeReplacingIds[j];
  654. let transferEdge = this.body.edges[transferId];
  655. if (transferEdge === undefined) continue;
  656. // if the other node is in another cluster, we transfer ownership of this edge to the other cluster
  657. if (otherNode !== undefined) {
  658. // transfer ownership:
  659. let otherCluster = this.body.nodes[otherNode.clusterId];
  660. otherCluster.containedEdges[transferEdge.id] = transferEdge;
  661. // delete local reference
  662. delete containedEdges[transferEdge.id];
  663. // get to and from
  664. let fromId = transferEdge.fromId;
  665. let toId = transferEdge.toId;
  666. if (transferEdge.toId == otherNodeId) {
  667. toId = otherNode.clusterId;
  668. }
  669. else {
  670. fromId = otherNode.clusterId;
  671. }
  672. // create new cluster edge from the otherCluster
  673. this._createClusteredEdge(
  674. fromId,
  675. toId,
  676. transferEdge,
  677. otherCluster.clusterEdgeProperties,
  678. {hidden: false, physics: true});
  679. } else {
  680. this._restoreEdge(transferEdge);
  681. }
  682. }
  683. edge.remove();
  684. }
  685. // handle the releasing of the edges
  686. for (let edgeId in containedEdges) {
  687. if (containedEdges.hasOwnProperty(edgeId)) {
  688. this._restoreEdge(containedEdges[edgeId]);
  689. }
  690. }
  691. // remove clusterNode
  692. delete this.body.nodes[clusterNodeId];
  693. if (refreshData === true) {
  694. this.body.emitter.emit('_dataChanged');
  695. }
  696. }
  697. /**
  698. *
  699. * @param {Cluster.id} clusterId
  700. * @returns {Array.<Node.id>}
  701. */
  702. getNodesInCluster(clusterId) {
  703. let nodesArray = [];
  704. if (this.isCluster(clusterId) === true) {
  705. let containedNodes = this.body.nodes[clusterId].containedNodes;
  706. for (let nodeId in containedNodes) {
  707. if (containedNodes.hasOwnProperty(nodeId)) {
  708. nodesArray.push(this.body.nodes[nodeId].id)
  709. }
  710. }
  711. }
  712. return nodesArray;
  713. }
  714. /**
  715. * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node
  716. *
  717. * If a node can't be found in the chain, return an empty array.
  718. *
  719. * @param {string|number} nodeId
  720. * @returns {Array}
  721. */
  722. findNode(nodeId) {
  723. let stack = [];
  724. let max = 100;
  725. let counter = 0;
  726. let node;
  727. while (this.clusteredNodes[nodeId] !== undefined && counter < max) {
  728. node = this.body.nodes[nodeId]
  729. if (node === undefined) return [];
  730. stack.push(node.id);
  731. nodeId = this.clusteredNodes[nodeId].clusterId;
  732. counter++;
  733. }
  734. node = this.body.nodes[nodeId]
  735. if (node === undefined) return [];
  736. stack.push(node.id);
  737. stack.reverse();
  738. return stack;
  739. }
  740. /**
  741. * Using a clustered nodeId, update with the new options
  742. * @param {vis.Edge.id} clusteredNodeId
  743. * @param {object} newOptions
  744. */
  745. updateClusteredNode(clusteredNodeId, newOptions) {
  746. if (clusteredNodeId === undefined) {throw new Error("No clusteredNodeId supplied to updateClusteredNode.");}
  747. if (newOptions === undefined) {throw new Error("No newOptions supplied to updateClusteredNode.");}
  748. if (this.body.nodes[clusteredNodeId] === undefined) {throw new Error("The clusteredNodeId supplied to updateClusteredNode does not exist.");}
  749. this.body.nodes[clusteredNodeId].setOptions(newOptions);
  750. this.body.emitter.emit('_dataChanged');
  751. }
  752. /**
  753. * Using a base edgeId, update all related clustered edges with the new options
  754. * @param {vis.Edge.id} startEdgeId
  755. * @param {object} newOptions
  756. */
  757. updateEdge(startEdgeId, newOptions) {
  758. if (startEdgeId === undefined) {throw new Error("No startEdgeId supplied to updateEdge.");}
  759. if (newOptions === undefined) {throw new Error("No newOptions supplied to updateEdge.");}
  760. if (this.body.edges[startEdgeId] === undefined) {throw new Error("The startEdgeId supplied to updateEdge does not exist.");}
  761. let allEdgeIds = this.getClusteredEdges(startEdgeId);
  762. for (let i = 0; i < allEdgeIds.length; i++) {
  763. var edge = this.body.edges[allEdgeIds[i]];
  764. edge.setOptions(newOptions);
  765. }
  766. this.body.emitter.emit('_dataChanged');
  767. }
  768. /**
  769. * Get a stack of clusterEdgeId's (+base edgeid) that a base edge is the same as. cluster edge C -> cluster edge B -> cluster edge A -> base edge(edgeId)
  770. * @param {vis.Edge.id} edgeId
  771. * @returns {Array.<vis.Edge.id>}
  772. */
  773. getClusteredEdges(edgeId) {
  774. let stack = [];
  775. let max = 100;
  776. let counter = 0;
  777. while (edgeId !== undefined && this.body.edges[edgeId] !== undefined && counter < max) {
  778. stack.push(this.body.edges[edgeId].id);
  779. edgeId = this.body.edges[edgeId].edgeReplacedById;
  780. counter++;
  781. }
  782. stack.reverse();
  783. return stack;
  784. }
  785. /**
  786. * Get the base edge id of clusterEdgeId. cluster edge (clusteredEdgeId) -> cluster edge B -> cluster edge C -> base edge
  787. * @param {vis.Edge.id} clusteredEdgeId
  788. * @returns {vis.Edge.id} baseEdgeId
  789. *
  790. * TODO: deprecate in 5.0.0. Method getBaseEdges() is the correct one to use.
  791. */
  792. getBaseEdge(clusteredEdgeId) {
  793. // Just kludge this by returning the first base edge id found
  794. return this.getBaseEdges(clusteredEdgeId)[0];
  795. }
  796. /**
  797. * Get all regular edges for this clustered edge id.
  798. *
  799. * @param {vis.Edge.id} clusteredEdgeId
  800. * @returns {Array.<vis.Edge.id>} all baseEdgeId's under this clustered edge
  801. */
  802. getBaseEdges(clusteredEdgeId) {
  803. let IdsToHandle = [clusteredEdgeId];
  804. let doneIds = [];
  805. let foundIds = [];
  806. let max = 100;
  807. let counter = 0;
  808. while (IdsToHandle.length > 0 && counter < max) {
  809. let nextId = IdsToHandle.pop();
  810. if (nextId === undefined) continue; // Paranoia here and onwards
  811. let nextEdge = this.body.edges[nextId];
  812. if (nextEdge === undefined) continue;
  813. counter++;
  814. let replacingIds = nextEdge.clusteringEdgeReplacingIds;
  815. if (replacingIds === undefined) {
  816. // nextId is a base id
  817. foundIds.push(nextId);
  818. } else {
  819. // Another cluster edge, unravel this one as well
  820. for (let i = 0; i < replacingIds.length; ++i) {
  821. let replacingId = replacingIds[i];
  822. // Don't add if already handled
  823. // TODO: never triggers; find a test-case which does
  824. if (IdsToHandle.indexOf(replacingIds) !== -1 || doneIds.indexOf(replacingIds) !== -1) {
  825. continue;
  826. }
  827. IdsToHandle.push(replacingId);
  828. }
  829. }
  830. doneIds.push(nextId);
  831. }
  832. return foundIds;
  833. }
  834. /**
  835. * Get the Id the node is connected to
  836. * @param {vis.Edge} edge
  837. * @param {Node.id} nodeId
  838. * @returns {*}
  839. * @private
  840. */
  841. _getConnectedId(edge, nodeId) {
  842. if (edge.toId != nodeId) {
  843. return edge.toId;
  844. }
  845. else if (edge.fromId != nodeId) {
  846. return edge.fromId;
  847. }
  848. else {
  849. return edge.fromId;
  850. }
  851. }
  852. /**
  853. * We determine how many connections denote an important hub.
  854. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  855. *
  856. * @returns {number}
  857. * @private
  858. */
  859. _getHubSize() {
  860. let average = 0;
  861. let averageSquared = 0;
  862. let hubCounter = 0;
  863. let largestHub = 0;
  864. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  865. let node = this.body.nodes[this.body.nodeIndices[i]];
  866. if (node.edges.length > largestHub) {
  867. largestHub = node.edges.length;
  868. }
  869. average += node.edges.length;
  870. averageSquared += Math.pow(node.edges.length,2);
  871. hubCounter += 1;
  872. }
  873. average = average / hubCounter;
  874. averageSquared = averageSquared / hubCounter;
  875. let variance = averageSquared - Math.pow(average,2);
  876. let standardDeviation = Math.sqrt(variance);
  877. let hubThreshold = Math.floor(average + 2*standardDeviation);
  878. // always have at least one to cluster
  879. if (hubThreshold > largestHub) {
  880. hubThreshold = largestHub;
  881. }
  882. return hubThreshold;
  883. }
  884. /**
  885. * Create an edge for the cluster representation.
  886. *
  887. * @param {Node.id} fromId
  888. * @param {Node.id} toId
  889. * @param {vis.Edge} baseEdge
  890. * @param {Object} clusterEdgeProperties
  891. * @param {Object} extraOptions
  892. * @returns {Edge} newly created clustered edge
  893. * @private
  894. */
  895. _createClusteredEdge(fromId, toId, baseEdge, clusterEdgeProperties, extraOptions) {
  896. // copy the options of the edge we will replace
  897. let clonedOptions = NetworkUtil.cloneOptions(baseEdge, 'edge');
  898. // make sure the properties of clusterEdges are superimposed on it
  899. util.deepExtend(clonedOptions, clusterEdgeProperties);
  900. // set up the edge
  901. clonedOptions.from = fromId;
  902. clonedOptions.to = toId;
  903. clonedOptions.id = 'clusterEdge:' + util.randomUUID();
  904. // apply the edge specific options to it if specified
  905. if (extraOptions !== undefined) {
  906. util.deepExtend(clonedOptions, extraOptions);
  907. }
  908. let newEdge = this.body.functions.createEdge(clonedOptions);
  909. newEdge.clusteringEdgeReplacingIds = [baseEdge.id];
  910. newEdge.connect();
  911. // Register the new edge
  912. this.body.edges[newEdge.id] = newEdge;
  913. return newEdge;
  914. }
  915. /**
  916. * Add the passed child nodes and edges to the given cluster node.
  917. *
  918. * @param {Object|Node} childNodes hash of nodes or single node to add in cluster
  919. * @param {Object|Edge} childEdges hash of edges or single edge to take into account when clustering
  920. * @param {Node} clusterNode cluster node to add nodes and edges to
  921. * @param {Object} [clusterEdgeProperties]
  922. * @private
  923. */
  924. _clusterEdges(childNodes, childEdges, clusterNode, clusterEdgeProperties) {
  925. if (childEdges instanceof Edge) {
  926. let edge = childEdges;
  927. let obj = {};
  928. obj[edge.id] = edge;
  929. childEdges = obj;
  930. }
  931. if (childNodes instanceof Node) {
  932. let node = childNodes;
  933. let obj = {};
  934. obj[node.id] = node;
  935. childNodes = obj;
  936. }
  937. if (clusterNode === undefined || clusterNode === null) {
  938. throw new Error("_clusterEdges: parameter clusterNode required");
  939. }
  940. if (clusterEdgeProperties === undefined) {
  941. // Take the required properties from the cluster node
  942. clusterEdgeProperties = clusterNode.clusterEdgeProperties;
  943. }
  944. // create the new edges that will connect to the cluster.
  945. // All self-referencing edges will be added to childEdges here.
  946. this._createClusterEdges(childNodes, childEdges, clusterNode, clusterEdgeProperties);
  947. // disable the childEdges
  948. for (let edgeId in childEdges) {
  949. if (childEdges.hasOwnProperty(edgeId)) {
  950. if (this.body.edges[edgeId] !== undefined) {
  951. let edge = this.body.edges[edgeId];
  952. // cache the options before changing
  953. this._backupEdgeOptions(edge);
  954. // disable physics and hide the edge
  955. edge.setOptions({physics:false});
  956. }
  957. }
  958. }
  959. // disable the childNodes
  960. for (let nodeId in childNodes) {
  961. if (childNodes.hasOwnProperty(nodeId)) {
  962. this.clusteredNodes[nodeId] = {clusterId:clusterNode.id, node: this.body.nodes[nodeId]};
  963. this.body.nodes[nodeId].setOptions({physics:false});
  964. }
  965. }
  966. }
  967. /**
  968. * Determine in which cluster given nodeId resides.
  969. *
  970. * If not in cluster, return undefined.
  971. *
  972. * NOTE: If you know a cleaner way to do this, please enlighten me (wimrijnders).
  973. *
  974. * @param {Node.id} nodeId
  975. * @returns {Node|undefined} Node instance for cluster, if present
  976. * @private
  977. */
  978. _getClusterNodeForNode(nodeId) {
  979. if (nodeId === undefined) return undefined;
  980. let clusteredNode = this.clusteredNodes[nodeId];
  981. // NOTE: If no cluster info found, it should actually be an error
  982. if (clusteredNode === undefined) return undefined;
  983. let clusterId = clusteredNode.clusterId;
  984. if (clusterId === undefined) return undefined;
  985. return this.body.nodes[clusterId];
  986. }
  987. /**
  988. * Internal helper function for conditionally removing items in array
  989. *
  990. * Done like this because Array.filter() is not fully supported by all IE's.
  991. *
  992. * @param {Array} arr
  993. * @param {function} callback
  994. * @returns {Array}
  995. * @private
  996. */
  997. _filter(arr, callback) {
  998. let ret = [];
  999. for (var n in arr) {
  1000. if (callback(arr[n])) {
  1001. ret.push(arr[n]);
  1002. }
  1003. }
  1004. return ret;
  1005. }
  1006. /**
  1007. * Scan all edges for changes in clustering and adjust this if necessary.
  1008. *
  1009. * Call this (internally) after there has been a change in node or edge data.
  1010. */
  1011. _updateState() {
  1012. // Pre: States of this.body.nodes and this.body.edges consistent
  1013. // Pre: this.clusteredNodes and this.clusteredEdge consistent with containedNodes and containedEdges
  1014. // of cluster nodes.
  1015. let nodeId;
  1016. let edgeId;
  1017. let m, n;
  1018. let deletedNodeIds = [];
  1019. let deletedEdgeIds = [];
  1020. let self = this;
  1021. /**
  1022. * Utility function to iterate over clustering nodes only
  1023. *
  1024. * @param {Function} callback function to call for each cluster node
  1025. */
  1026. let eachClusterNode = (callback) => {
  1027. for (nodeId in this.body.nodes) {
  1028. let node = this.body.nodes[nodeId];
  1029. if (node.isCluster !== true) continue;
  1030. callback(node);
  1031. }
  1032. };
  1033. //
  1034. // Remove deleted regular nodes from clustering
  1035. //
  1036. // Determine the deleted nodes
  1037. for (nodeId in this.clusteredNodes) {
  1038. let node = this.body.nodes[nodeId];
  1039. if (node === undefined) {
  1040. deletedNodeIds.push(nodeId);
  1041. }
  1042. }
  1043. // Remove nodes from cluster nodes
  1044. eachClusterNode(function(clusterNode) {
  1045. for (n in deletedNodeIds) {
  1046. delete clusterNode.containedNodes[deletedNodeIds[n]];
  1047. }
  1048. });
  1049. // Remove nodes from cluster list
  1050. for (n in deletedNodeIds) {
  1051. delete this.clusteredNodes[deletedNodeIds[n]];
  1052. }
  1053. //
  1054. // Remove deleted edges from clustering
  1055. //
  1056. // Add the deleted clustered edges to the list
  1057. for (edgeId in this.clusteredEdges) {
  1058. let edge = this.body.edges[edgeId];
  1059. if (edge === undefined || !edge.endPointsValid()) {
  1060. deletedEdgeIds.push(edgeId);
  1061. }
  1062. }
  1063. // Cluster nodes can also contain edges which are not clustered,
  1064. // i.e. nodes 1-2 within cluster with an edge in between.
  1065. // So the cluster nodes also need to be scanned for invalid edges
  1066. eachClusterNode(function(clusterNode) {
  1067. for (edgeId in clusterNode.containedEdges) {
  1068. let edge = clusterNode.containedEdges[edgeId];
  1069. if (!edge.endPointsValid() && deletedEdgeIds.indexOf(edgeId) === -1) {
  1070. deletedEdgeIds.push(edgeId);
  1071. }
  1072. }
  1073. });
  1074. // Also scan for cluster edges which need to be removed in the active list.
  1075. // Regular edges have been removed beforehand, so this only picks up the cluster edges.
  1076. for (edgeId in this.body.edges) {
  1077. let edge = this.body.edges[edgeId];
  1078. // Explicitly scan the contained edges for validity
  1079. let isValid = true;
  1080. let replacedIds = edge.clusteringEdgeReplacingIds;
  1081. if (replacedIds !== undefined) {
  1082. let numValid = 0;
  1083. for (let n in replacedIds) {
  1084. let containedEdgeId = replacedIds[n];
  1085. let containedEdge = this.body.edges[containedEdgeId];
  1086. if (containedEdge !== undefined && containedEdge.endPointsValid()) {
  1087. numValid += 1;
  1088. }
  1089. }
  1090. isValid = (numValid > 0);
  1091. }
  1092. if (!edge.endPointsValid() || !isValid) {
  1093. deletedEdgeIds.push(edgeId);
  1094. }
  1095. }
  1096. // Remove edges from cluster nodes
  1097. eachClusterNode(function(clusterNode) {
  1098. for (n in deletedEdgeIds) {
  1099. let deletedEdgeId = deletedEdgeIds[n];
  1100. delete clusterNode.containedEdges[deletedEdgeId];
  1101. for (m in clusterNode.edges) {
  1102. let edge = clusterNode.edges[m];
  1103. if (edge.id === deletedEdgeId) {
  1104. clusterNode.edges[m] = null; // Don't want to directly delete here, because in the loop
  1105. continue;
  1106. }
  1107. edge.clusteringEdgeReplacingIds = self._filter(edge.clusteringEdgeReplacingIds, function(id) {
  1108. return deletedEdgeIds.indexOf(id) === -1;
  1109. });
  1110. }
  1111. // Clean up the nulls
  1112. clusterNode.edges = self._filter(clusterNode.edges, function(item) {return item !== null});
  1113. }
  1114. });
  1115. // Remove from cluster list
  1116. for (n in deletedEdgeIds) {
  1117. delete this.clusteredEdges[deletedEdgeIds[n]];
  1118. }
  1119. // Remove cluster edges from active list (this.body.edges).
  1120. // deletedEdgeIds still contains id of regular edges, but these should all
  1121. // be gone when you reach here.
  1122. for (n in deletedEdgeIds) {
  1123. delete this.body.edges[deletedEdgeIds[n]];
  1124. }
  1125. //
  1126. // Check changed cluster state of edges
  1127. //
  1128. // Iterating over keys here, because edges may be removed in the loop
  1129. let ids = Object.keys(this.body.edges);
  1130. for (n in ids) {
  1131. let edgeId = ids[n];
  1132. let edge = this.body.edges[edgeId];
  1133. let shouldBeClustered = this._isClusteredNode(edge.fromId) || this._isClusteredNode(edge.toId);
  1134. if (shouldBeClustered === this._isClusteredEdge(edge.id)) {
  1135. continue; // all is well
  1136. }
  1137. if (shouldBeClustered) {
  1138. // add edge to clustering
  1139. let clusterFrom = this._getClusterNodeForNode(edge.fromId);
  1140. if (clusterFrom !== undefined) {
  1141. this._clusterEdges(this.body.nodes[edge.fromId], edge, clusterFrom);
  1142. }
  1143. let clusterTo = this._getClusterNodeForNode(edge.toId);
  1144. if (clusterTo !== undefined) {
  1145. this._clusterEdges(this.body.nodes[edge.toId], edge, clusterTo);
  1146. }
  1147. // TODO: check that it works for both edges clustered
  1148. } else {
  1149. // undo clustering for this edge
  1150. throw new Error('remove edge from clustering not implemented!');
  1151. }
  1152. }
  1153. // Clusters may be nested to any level. Keep on opening until nothing to open
  1154. var changed = false;
  1155. var continueLoop = true;
  1156. while (continueLoop) {
  1157. let clustersToOpen = [];
  1158. // Determine the id's of clusters that need opening
  1159. eachClusterNode(function(clusterNode) {
  1160. let numNodes = Object.keys(clusterNode.containedNodes).length;
  1161. let allowSingle = (clusterNode.options.allowSingleNodeCluster === true);
  1162. if ((allowSingle && numNodes < 1) || (!allowSingle && numNodes < 2)) {
  1163. clustersToOpen.push(clusterNode.id);
  1164. }
  1165. });
  1166. // Open them
  1167. for (let n in clustersToOpen) {
  1168. this.openCluster(clustersToOpen[n], {}, false /* Don't refresh, we're in an refresh/update already */);
  1169. }
  1170. continueLoop = (clustersToOpen.length > 0);
  1171. changed = changed || continueLoop;
  1172. }
  1173. if (changed) {
  1174. this._updateState() // Redo this method (recursion possible! should be safe)
  1175. }
  1176. }
  1177. /**
  1178. * Determine if node with given id is part of a cluster.
  1179. *
  1180. * @param {Node.id} nodeId
  1181. * @return {boolean} true if part of a cluster.
  1182. */
  1183. _isClusteredNode(nodeId) {
  1184. return this.clusteredNodes[nodeId] !== undefined;
  1185. }
  1186. /**
  1187. * Determine if edge with given id is not visible due to clustering.
  1188. *
  1189. * An edge is considered clustered if:
  1190. * - it is directly replaced by a clustering edge
  1191. * - any of its connecting nodes is in a cluster
  1192. *
  1193. * @param {vis.Edge.id} edgeId
  1194. * @return {boolean} true if part of a cluster.
  1195. */
  1196. _isClusteredEdge(edgeId) {
  1197. return this.clusteredEdges[edgeId] !== undefined;
  1198. }
  1199. }
  1200. export default ClusterEngine;