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.

966 lines
31 KiB

9 years ago
9 years ago
  1. let util = require("../../util");
  2. var NetworkUtil = require('../NetworkUtil').default;
  3. var Cluster = require('./components/nodes/Cluster').default;
  4. class ClusterEngine {
  5. constructor(body) {
  6. this.body = body;
  7. this.clusteredNodes = {}; // Set of all nodes which are in a cluster
  8. this.clusteredEdges = {}; // Set of all edges replaced by a clustering edge
  9. this.options = {};
  10. this.defaultOptions = {};
  11. util.extend(this.options, this.defaultOptions);
  12. this.body.emitter.on('_resetData', () => {this.clusteredNodes = {}; this.clusteredEdges = {};})
  13. }
  14. /**
  15. *
  16. * @param hubsize
  17. * @param options
  18. */
  19. clusterByHubsize(hubsize, options) {
  20. if (hubsize === undefined) {
  21. hubsize = this._getHubSize();
  22. }
  23. else if (typeof(hubsize) === "object") {
  24. options = this._checkOptions(hubsize);
  25. hubsize = this._getHubSize();
  26. }
  27. let nodesToCluster = [];
  28. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  29. let node = this.body.nodes[this.body.nodeIndices[i]];
  30. if (node.edges.length >= hubsize) {
  31. nodesToCluster.push(node.id);
  32. }
  33. }
  34. for (let i = 0; i < nodesToCluster.length; i++) {
  35. this.clusterByConnection(nodesToCluster[i],options,true);
  36. }
  37. this.body.emitter.emit('_dataChanged');
  38. }
  39. /**
  40. * loop over all nodes, check if they adhere to the condition and cluster if needed.
  41. * @param options
  42. * @param refreshData
  43. */
  44. cluster(options = {}, refreshData = true) {
  45. if (options.joinCondition === undefined) {throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");}
  46. // check if the options object is fine, append if needed
  47. options = this._checkOptions(options);
  48. let childNodesObj = {};
  49. let childEdgesObj = {};
  50. // collect the nodes that will be in the cluster
  51. for (let nodeId in this.body.nodes) {
  52. if (!this.body.nodes.hasOwnProperty(nodeId)) continue;
  53. let node = this.body.nodes[nodeId];
  54. let clonedOptions = NetworkUtil.cloneOptions(node);
  55. if (options.joinCondition(clonedOptions) === true) {
  56. childNodesObj[nodeId] = this.body.nodes[nodeId];
  57. // collect the edges that will be in the cluster
  58. for (let i = 0; i < node.edges.length; i++) {
  59. let edge = node.edges[i];
  60. if (this.clusteredEdges[edge.id] === undefined) {
  61. childEdgesObj[edge.id] = edge;
  62. }
  63. }
  64. }
  65. }
  66. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  67. }
  68. /**
  69. * Cluster all nodes in the network that have only X edges
  70. * @param edgeCount
  71. * @param options
  72. * @param refreshData
  73. */
  74. clusterByEdgeCount(edgeCount, options, refreshData = true) {
  75. options = this._checkOptions(options);
  76. let clusters = [];
  77. let usedNodes = {};
  78. let edge, edges, node, nodeId, relevantEdgeCount;
  79. // collect the nodes that will be in the cluster
  80. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  81. let childNodesObj = {};
  82. let childEdgesObj = {};
  83. nodeId = this.body.nodeIndices[i];
  84. // if this node is already used in another cluster this session, we do not have to re-evaluate it.
  85. if (usedNodes[nodeId] === undefined) {
  86. relevantEdgeCount = 0;
  87. node = this.body.nodes[nodeId];
  88. edges = [];
  89. for (let j = 0; j < node.edges.length; j++) {
  90. edge = node.edges[j];
  91. if (this.clusteredEdges[edge.id] === undefined) {
  92. if (edge.toId !== edge.fromId) {
  93. relevantEdgeCount++;
  94. }
  95. edges.push(edge);
  96. }
  97. }
  98. // this node qualifies, we collect its neighbours to start the clustering process.
  99. if (relevantEdgeCount === edgeCount) {
  100. let gatheringSuccessful = true;
  101. for (let j = 0; j < edges.length; j++) {
  102. edge = edges[j];
  103. let childNodeId = this._getConnectedId(edge, nodeId);
  104. // add the nodes to the list by the join condition.
  105. if (options.joinCondition === undefined) {
  106. childEdgesObj[edge.id] = edge;
  107. childNodesObj[nodeId] = this.body.nodes[nodeId];
  108. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  109. usedNodes[nodeId] = true;
  110. }
  111. else {
  112. let clonedOptions = NetworkUtil.cloneOptions(this.body.nodes[nodeId]);
  113. if (options.joinCondition(clonedOptions) === true) {
  114. childEdgesObj[edge.id] = edge;
  115. childNodesObj[nodeId] = this.body.nodes[nodeId];
  116. usedNodes[nodeId] = true;
  117. }
  118. else {
  119. // this node does not qualify after all.
  120. gatheringSuccessful = false;
  121. break;
  122. }
  123. }
  124. }
  125. // add to the cluster queue
  126. if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0 && gatheringSuccessful === true) {
  127. clusters.push({nodes: childNodesObj, edges: childEdgesObj})
  128. }
  129. }
  130. }
  131. }
  132. for (let i = 0; i < clusters.length; i++) {
  133. this._cluster(clusters[i].nodes, clusters[i].edges, options, false)
  134. }
  135. if (refreshData === true) {
  136. this.body.emitter.emit('_dataChanged');
  137. }
  138. }
  139. /**
  140. * Cluster all nodes in the network that have only 1 edge
  141. * @param options
  142. * @param refreshData
  143. */
  144. clusterOutliers(options, refreshData = true) {
  145. this.clusterByEdgeCount(1,options,refreshData);
  146. }
  147. /**
  148. * Cluster all nodes in the network that have only 2 edge
  149. * @param options
  150. * @param refreshData
  151. */
  152. clusterBridges(options, refreshData = true) {
  153. this.clusterByEdgeCount(2,options,refreshData);
  154. }
  155. /**
  156. * suck all connected nodes of a node into the node.
  157. * @param nodeId
  158. * @param options
  159. * @param refreshData
  160. */
  161. clusterByConnection(nodeId, options, refreshData = true) {
  162. // kill conditions
  163. if (nodeId === undefined) {throw new Error("No nodeId supplied to clusterByConnection!");}
  164. if (this.body.nodes[nodeId] === undefined) {throw new Error("The nodeId given to clusterByConnection does not exist!");}
  165. let node = this.body.nodes[nodeId];
  166. options = this._checkOptions(options, node);
  167. if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x;}
  168. if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y;}
  169. if (options.clusterNodeProperties.fixed === undefined) {
  170. options.clusterNodeProperties.fixed = {};
  171. options.clusterNodeProperties.fixed.x = node.options.fixed.x;
  172. options.clusterNodeProperties.fixed.y = node.options.fixed.y;
  173. }
  174. let childNodesObj = {};
  175. let childEdgesObj = {};
  176. let parentNodeId = node.id;
  177. let parentClonedOptions = NetworkUtil.cloneOptions(node);
  178. childNodesObj[parentNodeId] = node;
  179. // collect the nodes that will be in the cluster
  180. for (let i = 0; i < node.edges.length; i++) {
  181. let edge = node.edges[i];
  182. if (this.clusteredEdges[edge.id] === undefined) {
  183. let childNodeId = this._getConnectedId(edge, parentNodeId);
  184. // if the child node is not in a cluster
  185. if (this.clusteredNodes[childNodeId] === undefined) {
  186. if (childNodeId !== parentNodeId) {
  187. if (options.joinCondition === undefined) {
  188. childEdgesObj[edge.id] = edge;
  189. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  190. }
  191. else {
  192. // clone the options and insert some additional parameters that could be interesting.
  193. let childClonedOptions = NetworkUtil.cloneOptions(this.body.nodes[childNodeId]);
  194. if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) {
  195. childEdgesObj[edge.id] = edge;
  196. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  197. }
  198. }
  199. }
  200. else {
  201. // swallow the edge if it is self-referencing.
  202. childEdgesObj[edge.id] = edge;
  203. }
  204. }
  205. }
  206. }
  207. var childNodeIDs = Object.keys(childNodesObj).map(function(childNode){
  208. return childNodesObj[childNode].id;
  209. })
  210. for (childNode in childNodesObj) {
  211. var childNode = childNodesObj[childNode];
  212. for (var y=0; y < childNode.edges.length; y++){
  213. var childEdge = childNode.edges[y];
  214. if (childNodeIDs.indexOf(this._getConnectedId(childEdge,childNode.id)) > -1){
  215. childEdgesObj[childEdge.id] = childEdge;
  216. }
  217. }
  218. }
  219. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  220. }
  221. /**
  222. * This function creates the edges that will be attached to the cluster
  223. * It looks for edges that are connected to the nodes from the "outside' of the cluster.
  224. *
  225. * @param childNodesObj
  226. * @param childEdgesObj
  227. * @param clusterNodeProperties
  228. * @param clusterEdgeProperties
  229. * @private
  230. */
  231. _createClusterEdges (childNodesObj, childEdgesObj, clusterNodeProperties, clusterEdgeProperties) {
  232. let edge, childNodeId, childNode, toId, fromId, otherNodeId;
  233. // loop over all child nodes and their edges to find edges going out of the cluster
  234. // these edges will be replaced by clusterEdges.
  235. let childKeys = Object.keys(childNodesObj);
  236. let createEdges = [];
  237. for (let i = 0; i < childKeys.length; i++) {
  238. childNodeId = childKeys[i];
  239. childNode = childNodesObj[childNodeId];
  240. // construct new edges from the cluster to others
  241. for (let j = 0; j < childNode.edges.length; j++) {
  242. edge = childNode.edges[j];
  243. // we only handle edges that are visible to the system, not the disabled ones from the clustering process.
  244. if (this.clusteredEdges[edge.id] === undefined) {
  245. // self-referencing edges will be added to the "hidden" list
  246. if (edge.toId == edge.fromId) {
  247. childEdgesObj[edge.id] = edge;
  248. }
  249. else {
  250. // set up the from and to.
  251. if (edge.toId == childNodeId) { // this is a double equals because ints and strings can be interchanged here.
  252. toId = clusterNodeProperties.id;
  253. fromId = edge.fromId;
  254. otherNodeId = fromId;
  255. }
  256. else {
  257. toId = edge.toId;
  258. fromId = clusterNodeProperties.id;
  259. otherNodeId = toId;
  260. }
  261. }
  262. // Only edges from the cluster outwards are being replaced.
  263. if (childNodesObj[otherNodeId] === undefined) {
  264. createEdges.push({edge: edge, fromId: fromId, toId: toId});
  265. }
  266. }
  267. }
  268. }
  269. //
  270. // Here we actually create the replacement edges.
  271. //
  272. // We could not do this in the loop above as the creation process
  273. // would add an edge to the edges array we are iterating over.
  274. //
  275. // NOTE: a clustered edge can have multiple base edges!
  276. //
  277. var newEdges = [];
  278. /**
  279. * Find a cluster edge which matches the given created edge.
  280. */
  281. var getNewEdge = function(createdEdge) {
  282. for (let j = 0; j < newEdges.length; j++) {
  283. let newEdge = newEdges[j];
  284. // We replace both to and from edges with a single cluster edge
  285. let matchToDirection = (createdEdge.fromId === newEdge.fromId && createdEdge.toId === newEdge.toId);
  286. let matchFromDirection = (createdEdge.fromId === newEdge.toId && createdEdge.toId === newEdge.fromId);
  287. if (matchToDirection || matchFromDirection ) {
  288. return newEdge;
  289. }
  290. }
  291. return null;
  292. };
  293. for (let j = 0; j < createEdges.length; j++) {
  294. let createdEdge = createEdges[j];
  295. let edge = createdEdge.edge;
  296. let newEdge = getNewEdge(createdEdge);
  297. if (newEdge === null) {
  298. // Create a clustered edge for this connection
  299. newEdge = this._createClusteredEdge(
  300. createdEdge.fromId,
  301. createdEdge.toId,
  302. edge,
  303. clusterEdgeProperties);
  304. newEdges.push(newEdge);
  305. } else {
  306. newEdge.clusteringEdgeReplacingIds.push(edge.id);
  307. }
  308. // also reference the new edge in the old edge
  309. this.body.edges[edge.id].edgeReplacedById = newEdge.id;
  310. // hide the replaced edge
  311. this._backupEdgeOptions(edge);
  312. edge.setOptions({physics:false});
  313. }
  314. }
  315. /**
  316. * This function checks the options that can be supplied to the different cluster functions
  317. * for certain fields and inserts defaults if needed
  318. * @param options
  319. * @returns {*}
  320. * @private
  321. */
  322. _checkOptions(options = {}) {
  323. if (options.clusterEdgeProperties === undefined) {options.clusterEdgeProperties = {};}
  324. if (options.clusterNodeProperties === undefined) {options.clusterNodeProperties = {};}
  325. return options;
  326. }
  327. /**
  328. *
  329. * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node
  330. * @param {Object} childEdgesObj | object with edge objects, id as keys
  331. * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties}
  332. * @param {Boolean} refreshData | when true, do not wrap up
  333. * @private
  334. */
  335. _cluster(childNodesObj, childEdgesObj, options, refreshData = true) {
  336. // kill condition: no nodes don't bother
  337. if (Object.keys(childNodesObj).length == 0) {return;}
  338. // allow clusters of 1 if options allow
  339. if (Object.keys(childNodesObj).length == 1 && options.clusterNodeProperties.allowSingleNodeCluster != true) {return;}
  340. // check if this cluster call is not trying to cluster anything that is in another cluster.
  341. for (let nodeId in childNodesObj) {
  342. if (childNodesObj.hasOwnProperty(nodeId)) {
  343. if (this.clusteredNodes[nodeId] !== undefined) {
  344. return;
  345. }
  346. }
  347. }
  348. let clusterNodeProperties = util.deepExtend({},options.clusterNodeProperties);
  349. // construct the clusterNodeProperties
  350. if (options.processProperties !== undefined) {
  351. // get the childNode options
  352. let childNodesOptions = [];
  353. for (let nodeId in childNodesObj) {
  354. if (childNodesObj.hasOwnProperty(nodeId)) {
  355. let clonedOptions = NetworkUtil.cloneOptions(childNodesObj[nodeId]);
  356. childNodesOptions.push(clonedOptions);
  357. }
  358. }
  359. // get cluster properties based on childNodes
  360. let childEdgesOptions = [];
  361. for (let edgeId in childEdgesObj) {
  362. if (childEdgesObj.hasOwnProperty(edgeId)) {
  363. // these cluster edges will be removed on creation of the cluster.
  364. if (edgeId.substr(0, 12) !== "clusterEdge:") {
  365. let clonedOptions = NetworkUtil.cloneOptions(childEdgesObj[edgeId], 'edge');
  366. childEdgesOptions.push(clonedOptions);
  367. }
  368. }
  369. }
  370. clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions);
  371. if (!clusterNodeProperties) {
  372. throw new Error("The processProperties function does not return properties!");
  373. }
  374. }
  375. // check if we have an unique id;
  376. if (clusterNodeProperties.id === undefined) {clusterNodeProperties.id = 'cluster:' + util.randomUUID();}
  377. let clusterId = clusterNodeProperties.id;
  378. if (clusterNodeProperties.label === undefined) {
  379. clusterNodeProperties.label = 'cluster';
  380. }
  381. // give the clusterNode a position if it does not have one.
  382. let pos = undefined;
  383. if (clusterNodeProperties.x === undefined) {
  384. pos = this._getClusterPosition(childNodesObj);
  385. clusterNodeProperties.x = pos.x;
  386. }
  387. if (clusterNodeProperties.y === undefined) {
  388. if (pos === undefined) {pos = this._getClusterPosition(childNodesObj);}
  389. clusterNodeProperties.y = pos.y;
  390. }
  391. // force the ID to remain the same
  392. clusterNodeProperties.id = clusterId;
  393. // create the clusterNode
  394. let clusterNode = this.body.functions.createNode(clusterNodeProperties, Cluster);
  395. clusterNode.isCluster = true;
  396. clusterNode.containedNodes = childNodesObj;
  397. clusterNode.containedEdges = childEdgesObj;
  398. // cache a copy from the cluster edge properties if we have to reconnect others later on
  399. clusterNode.clusterEdgeProperties = options.clusterEdgeProperties;
  400. // finally put the cluster node into global
  401. this.body.nodes[clusterNodeProperties.id] = clusterNode;
  402. // create the new edges that will connect to the cluster, all self-referencing edges will be added to childEdgesObject here.
  403. this._createClusterEdges(childNodesObj, childEdgesObj, clusterNodeProperties, options.clusterEdgeProperties);
  404. // disable the childEdges
  405. for (let edgeId in childEdgesObj) {
  406. if (childEdgesObj.hasOwnProperty(edgeId)) {
  407. if (this.body.edges[edgeId] !== undefined) {
  408. let edge = this.body.edges[edgeId];
  409. // cache the options before changing
  410. this._backupEdgeOptions(edge);
  411. // disable physics and hide the edge
  412. edge.setOptions({physics:false});
  413. }
  414. }
  415. }
  416. // disable the childNodes
  417. for (let nodeId in childNodesObj) {
  418. if (childNodesObj.hasOwnProperty(nodeId)) {
  419. this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties.id, node: this.body.nodes[nodeId]};
  420. this.body.nodes[nodeId].setOptions({physics:false});
  421. }
  422. }
  423. // set ID to undefined so no duplicates arise
  424. clusterNodeProperties.id = undefined;
  425. // wrap up
  426. if (refreshData === true) {
  427. this.body.emitter.emit('_dataChanged');
  428. }
  429. }
  430. _backupEdgeOptions(edge) {
  431. if (this.clusteredEdges[edge.id] === undefined) {
  432. this.clusteredEdges[edge.id] = {physics: edge.options.physics};
  433. }
  434. }
  435. _restoreEdge(edge) {
  436. let originalOptions = this.clusteredEdges[edge.id];
  437. if (originalOptions !== undefined) {
  438. edge.setOptions({physics: originalOptions.physics});
  439. delete this.clusteredEdges[edge.id];
  440. }
  441. }
  442. /**
  443. * Check if a node is a cluster.
  444. * @param nodeId
  445. * @returns {*}
  446. */
  447. isCluster(nodeId) {
  448. if (this.body.nodes[nodeId] !== undefined) {
  449. return this.body.nodes[nodeId].isCluster === true;
  450. }
  451. else {
  452. console.log("Node does not exist.");
  453. return false;
  454. }
  455. }
  456. /**
  457. * get the position of the cluster node based on what's inside
  458. * @param {object} childNodesObj | object with node objects, id as keys
  459. * @returns {{x: number, y: number}}
  460. * @private
  461. */
  462. _getClusterPosition(childNodesObj) {
  463. let childKeys = Object.keys(childNodesObj);
  464. let minX = childNodesObj[childKeys[0]].x;
  465. let maxX = childNodesObj[childKeys[0]].x;
  466. let minY = childNodesObj[childKeys[0]].y;
  467. let maxY = childNodesObj[childKeys[0]].y;
  468. let node;
  469. for (let i = 1; i < childKeys.length; i++) {
  470. node = childNodesObj[childKeys[i]];
  471. minX = node.x < minX ? node.x : minX;
  472. maxX = node.x > maxX ? node.x : maxX;
  473. minY = node.y < minY ? node.y : minY;
  474. maxY = node.y > maxY ? node.y : maxY;
  475. }
  476. return {x: 0.5*(minX + maxX), y: 0.5*(minY + maxY)};
  477. }
  478. /**
  479. * Open a cluster by calling this function.
  480. * @param {String} clusterNodeId | the ID of the cluster node
  481. * @param {Boolean} refreshData | wrap up afterwards if not true
  482. */
  483. openCluster(clusterNodeId, options, refreshData = true) {
  484. // kill conditions
  485. if (clusterNodeId === undefined) {throw new Error("No clusterNodeId supplied to openCluster.");}
  486. if (this.body.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");}
  487. if (this.body.nodes[clusterNodeId].containedNodes === undefined) {
  488. console.log("The node:" + clusterNodeId + " is not a cluster.");
  489. return
  490. }
  491. let clusterNode = this.body.nodes[clusterNodeId];
  492. let containedNodes = clusterNode.containedNodes;
  493. let containedEdges = clusterNode.containedEdges;
  494. // allow the user to position the nodes after release.
  495. if (options !== undefined && options.releaseFunction !== undefined && typeof options.releaseFunction === 'function') {
  496. let positions = {};
  497. let clusterPosition = {x:clusterNode.x, y:clusterNode.y};
  498. for (let nodeId in containedNodes) {
  499. if (containedNodes.hasOwnProperty(nodeId)) {
  500. let containedNode = this.body.nodes[nodeId];
  501. positions[nodeId] = {x: containedNode.x, y: containedNode.y};
  502. }
  503. }
  504. let newPositions = options.releaseFunction(clusterPosition, positions);
  505. for (let nodeId in containedNodes) {
  506. if (containedNodes.hasOwnProperty(nodeId)) {
  507. let containedNode = this.body.nodes[nodeId];
  508. if (newPositions[nodeId] !== undefined) {
  509. containedNode.x = (newPositions[nodeId].x === undefined ? clusterNode.x : newPositions[nodeId].x);
  510. containedNode.y = (newPositions[nodeId].y === undefined ? clusterNode.y : newPositions[nodeId].y);
  511. }
  512. }
  513. }
  514. }
  515. else {
  516. // copy the position from the cluster
  517. for (let nodeId in containedNodes) {
  518. if (containedNodes.hasOwnProperty(nodeId)) {
  519. let containedNode = this.body.nodes[nodeId];
  520. containedNode = containedNodes[nodeId];
  521. // inherit position
  522. if (containedNode.options.fixed.x === false) {containedNode.x = clusterNode.x;}
  523. if (containedNode.options.fixed.y === false) {containedNode.y = clusterNode.y;}
  524. }
  525. }
  526. }
  527. // release nodes
  528. for (let nodeId in containedNodes) {
  529. if (containedNodes.hasOwnProperty(nodeId)) {
  530. let containedNode = this.body.nodes[nodeId];
  531. // inherit speed
  532. containedNode.vx = clusterNode.vx;
  533. containedNode.vy = clusterNode.vy;
  534. containedNode.setOptions({physics:true});
  535. delete this.clusteredNodes[nodeId];
  536. }
  537. }
  538. // copy the clusterNode edges because we cannot iterate over an object that we add or remove from.
  539. let edgesToBeDeleted = [];
  540. for (let i = 0; i < clusterNode.edges.length; i++) {
  541. edgesToBeDeleted.push(clusterNode.edges[i]);
  542. }
  543. // actually handling the deleting.
  544. for (let i = 0; i < edgesToBeDeleted.length; i++) {
  545. let edge = edgesToBeDeleted[i];
  546. let otherNodeId = this._getConnectedId(edge, clusterNodeId);
  547. let otherNode = this.clusteredNodes[otherNodeId];
  548. for (let j = 0; j < edge.clusteringEdgeReplacingIds.length; j++) {
  549. let transferId = edge.clusteringEdgeReplacingIds[j];
  550. let transferEdge = this.body.edges[transferId];
  551. if (transferEdge === undefined) continue;
  552. // if the other node is in another cluster, we transfer ownership of this edge to the other cluster
  553. if (otherNode !== undefined) {
  554. // transfer ownership:
  555. let otherCluster = this.body.nodes[otherNode.clusterId];
  556. otherCluster.containedEdges[transferEdge.id] = transferEdge;
  557. // delete local reference
  558. delete containedEdges[transferEdge.id];
  559. // get to and from
  560. let fromId = transferEdge.fromId;
  561. let toId = transferEdge.toId;
  562. if (transferEdge.toId == otherNodeId) {
  563. toId = otherNode.clusterId;
  564. }
  565. else {
  566. fromId = otherNode.clusterId;
  567. }
  568. // create new cluster edge from the otherCluster
  569. this._createClusteredEdge(
  570. fromId,
  571. toId,
  572. transferEdge,
  573. otherCluster.clusterEdgeProperties,
  574. {hidden: false, physics: true});
  575. } else {
  576. this._restoreEdge(transferEdge);
  577. }
  578. }
  579. edge.cleanup();
  580. // this removes the edge from node.edges, which is why edgeIds is formed
  581. edge.disconnect();
  582. delete this.body.edges[edge.id];
  583. }
  584. // handle the releasing of the edges
  585. for (let edgeId in containedEdges) {
  586. if (containedEdges.hasOwnProperty(edgeId)) {
  587. this._restoreEdge(containedEdges[edgeId]);
  588. }
  589. }
  590. // remove clusterNode
  591. delete this.body.nodes[clusterNodeId];
  592. if (refreshData === true) {
  593. this.body.emitter.emit('_dataChanged');
  594. }
  595. }
  596. getNodesInCluster(clusterId) {
  597. let nodesArray = [];
  598. if (this.isCluster(clusterId) === true) {
  599. let containedNodes = this.body.nodes[clusterId].containedNodes;
  600. for (let nodeId in containedNodes) {
  601. if (containedNodes.hasOwnProperty(nodeId)) {
  602. nodesArray.push(this.body.nodes[nodeId].id)
  603. }
  604. }
  605. }
  606. return nodesArray;
  607. }
  608. /**
  609. * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node
  610. *
  611. * If a node can't be found in the chain, return an empty array.
  612. *
  613. * @param {string|number} nodeId
  614. * @returns {Array}
  615. */
  616. findNode(nodeId) {
  617. let stack = [];
  618. let max = 100;
  619. let counter = 0;
  620. let node;
  621. while (this.clusteredNodes[nodeId] !== undefined && counter < max) {
  622. node = this.body.nodes[nodeId]
  623. if (node === undefined) return [];
  624. stack.push(node.id);
  625. nodeId = this.clusteredNodes[nodeId].clusterId;
  626. counter++;
  627. }
  628. node = this.body.nodes[nodeId]
  629. if (node === undefined) return [];
  630. stack.push(node.id);
  631. stack.reverse();
  632. return stack;
  633. }
  634. /**
  635. * Using a clustered nodeId, update with the new options
  636. * @param clusteredNodeId
  637. * @param {object} newOptions
  638. */
  639. updateClusteredNode(clusteredNodeId, newOptions) {
  640. if (clusteredNodeId === undefined) {throw new Error("No clusteredNodeId supplied to updateClusteredNode.");}
  641. if (newOptions === undefined) {throw new Error("No newOptions supplied to updateClusteredNode.");}
  642. if (this.body.nodes[clusteredNodeId] === undefined) {throw new Error("The clusteredNodeId supplied to updateClusteredNode does not exist.");}
  643. this.body.nodes[clusteredNodeId].setOptions(newOptions);
  644. this.body.emitter.emit('_dataChanged');
  645. }
  646. /**
  647. * Using a base edgeId, update all related clustered edges with the new options
  648. * @param startEdgeId
  649. * @param {object} newOptions
  650. */
  651. updateEdge(startEdgeId, newOptions) {
  652. if (startEdgeId === undefined) {throw new Error("No startEdgeId supplied to updateEdge.");}
  653. if (newOptions === undefined) {throw new Error("No newOptions supplied to updateEdge.");}
  654. if (this.body.edges[startEdgeId] === undefined) {throw new Error("The startEdgeId supplied to updateEdge does not exist.");}
  655. let allEdgeIds = this.getClusteredEdges(startEdgeId);
  656. for (let i = 0; i < allEdgeIds.length; i++) {
  657. var edge = this.body.edges[allEdgeIds[i]];
  658. edge.setOptions(newOptions);
  659. }
  660. this.body.emitter.emit('_dataChanged');
  661. }
  662. /**
  663. * 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)
  664. * @param edgeId
  665. * @returns {Array}
  666. */
  667. getClusteredEdges(edgeId) {
  668. let stack = [];
  669. let max = 100;
  670. let counter = 0;
  671. while (edgeId !== undefined && this.body.edges[edgeId] !== undefined && counter < max) {
  672. stack.push(this.body.edges[edgeId].id);
  673. edgeId = this.body.edges[edgeId].edgeReplacedById;
  674. counter++;
  675. }
  676. stack.reverse();
  677. return stack;
  678. }
  679. /**
  680. * Get the base edge id of clusterEdgeId. cluster edge (clusteredEdgeId) -> cluster edge B -> cluster edge C -> base edge
  681. * @param clusteredEdgeId
  682. * @returns baseEdgeId
  683. *
  684. * TODO: deprecate in 5.0.0. Method getBaseEdges() is the correct one to use.
  685. */
  686. getBaseEdge(clusteredEdgeId) {
  687. // Just kludge this by returning the first base edge id found
  688. return this.getBaseEdges(clusteredEdgeId)[0];
  689. }
  690. /**
  691. * Get all regular edges for this clustered edge id.
  692. *
  693. * @param {Number} clusteredEdgeId
  694. * @returns {Array[Number} all baseEdgeId's under this clustered edge
  695. */
  696. getBaseEdges(clusteredEdgeId) {
  697. let IdsToHandle = [clusteredEdgeId];
  698. let doneIds = [];
  699. let foundIds = [];
  700. let max = 100;
  701. let counter = 0;
  702. while (IdsToHandle.length > 0 && counter < max) {
  703. let nextId = IdsToHandle.pop();
  704. if (nextId === undefined) continue; // Paranoia here and onwards
  705. let nextEdge = this.body.edges[nextId];
  706. if (nextEdge === undefined) continue;
  707. counter++;
  708. let replacingIds = nextEdge.clusteringEdgeReplacingIds;
  709. if (replacingIds === undefined) {
  710. // nextId is a base id
  711. foundIds.push(nextId);
  712. } else {
  713. // Another cluster edge, unravel this one as well
  714. for (let i = 0; i < replacingIds.length; ++i) {
  715. let replacingId = replacingIds[i];
  716. // Don't add if already handled
  717. // TODO: never triggers; find a test-case which does
  718. if (IdsToHandle.indexOf(replacingIds) !== -1 || doneIds.indexOf(replacingIds) !== -1) {
  719. continue;
  720. }
  721. IdsToHandle.push(replacingId);
  722. }
  723. }
  724. doneIds.push(nextId);
  725. }
  726. return foundIds;
  727. }
  728. /**
  729. * Get the Id the node is connected to
  730. * @param edge
  731. * @param nodeId
  732. * @returns {*}
  733. * @private
  734. */
  735. _getConnectedId(edge, nodeId) {
  736. if (edge.toId != nodeId) {
  737. return edge.toId;
  738. }
  739. else if (edge.fromId != nodeId) {
  740. return edge.fromId;
  741. }
  742. else {
  743. return edge.fromId;
  744. }
  745. }
  746. /**
  747. * We determine how many connections denote an important hub.
  748. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  749. *
  750. * @private
  751. */
  752. _getHubSize() {
  753. let average = 0;
  754. let averageSquared = 0;
  755. let hubCounter = 0;
  756. let largestHub = 0;
  757. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  758. let node = this.body.nodes[this.body.nodeIndices[i]];
  759. if (node.edges.length > largestHub) {
  760. largestHub = node.edges.length;
  761. }
  762. average += node.edges.length;
  763. averageSquared += Math.pow(node.edges.length,2);
  764. hubCounter += 1;
  765. }
  766. average = average / hubCounter;
  767. averageSquared = averageSquared / hubCounter;
  768. let variance = averageSquared - Math.pow(average,2);
  769. let standardDeviation = Math.sqrt(variance);
  770. let hubThreshold = Math.floor(average + 2*standardDeviation);
  771. // always have at least one to cluster
  772. if (hubThreshold > largestHub) {
  773. hubThreshold = largestHub;
  774. }
  775. return hubThreshold;
  776. }
  777. /**
  778. * Create an edge for the cluster representation.
  779. *
  780. * @return {Edge} newly created clustered edge
  781. * @private
  782. */
  783. _createClusteredEdge(fromId, toId, baseEdge, clusterEdgeProperties, extraOptions) {
  784. // copy the options of the edge we will replace
  785. let clonedOptions = NetworkUtil.cloneOptions(baseEdge, 'edge');
  786. // make sure the properties of clusterEdges are superimposed on it
  787. util.deepExtend(clonedOptions, clusterEdgeProperties);
  788. // set up the edge
  789. clonedOptions.from = fromId;
  790. clonedOptions.to = toId;
  791. clonedOptions.id = 'clusterEdge:' + util.randomUUID();
  792. // apply the edge specific options to it if specified
  793. if (extraOptions !== undefined) {
  794. util.deepExtend(clonedOptions, extraOptions);
  795. }
  796. let newEdge = this.body.functions.createEdge(clonedOptions);
  797. newEdge.clusteringEdgeReplacingIds = [baseEdge.id];
  798. newEdge.connect();
  799. // Register the new edge
  800. this.body.edges[newEdge.id] = newEdge;
  801. return newEdge;
  802. }
  803. /**
  804. * Determine if node with given id is part of a cluster.
  805. *
  806. * @return {boolean} true if part of a cluster.
  807. */
  808. _isClusteredNode(nodeId) {
  809. return this.clusteredNodes[nodeId] !== undefined;
  810. }
  811. /**
  812. * Determine if edge with given id is not visible due to clustering.
  813. *
  814. * An edge is considered clustered if:
  815. * - it is directly replaced by a clustering edge
  816. * - any of its connecting nodes is in a cluster
  817. *
  818. * @return {boolean} true if part of a cluster.
  819. */
  820. _isClusteredEdge(edgeId) {
  821. return this.clusteredEdges[edgeId] !== undefined;
  822. }
  823. }
  824. export default ClusterEngine;