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.

764 lines
26 KiB

9 years ago
9 years ago
  1. let util = require("../../util");
  2. import Cluster from './components/nodes/Cluster'
  3. class ClusterEngine {
  4. constructor(body) {
  5. this.body = body;
  6. this.clusteredNodes = {};
  7. this.options = {};
  8. this.defaultOptions = {};
  9. util.extend(this.options, this.defaultOptions);
  10. this.body.emitter.on('_resetData', () => {this.clusteredNodes = {};})
  11. }
  12. setOptions(options) {
  13. if (options !== undefined) {
  14. }
  15. }
  16. /**
  17. *
  18. * @param hubsize
  19. * @param options
  20. */
  21. clusterByHubsize(hubsize, options) {
  22. if (hubsize === undefined) {
  23. hubsize = this._getHubSize();
  24. }
  25. else if (typeof(hubsize) === "object") {
  26. options = this._checkOptions(hubsize);
  27. hubsize = this._getHubSize();
  28. }
  29. let nodesToCluster = [];
  30. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  31. let node = this.body.nodes[this.body.nodeIndices[i]];
  32. if (node.edges.length >= hubsize) {
  33. nodesToCluster.push(node.id);
  34. }
  35. }
  36. for (let i = 0; i < nodesToCluster.length; i++) {
  37. this.clusterByConnection(nodesToCluster[i],options,true);
  38. }
  39. this.body.emitter.emit('_dataChanged');
  40. }
  41. /**
  42. * loop over all nodes, check if they adhere to the condition and cluster if needed.
  43. * @param options
  44. * @param refreshData
  45. */
  46. cluster(options = {}, refreshData = true) {
  47. if (options.joinCondition === undefined) {throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");}
  48. // check if the options object is fine, append if needed
  49. options = this._checkOptions(options);
  50. let childNodesObj = {};
  51. let childEdgesObj = {};
  52. // collect the nodes that will be in the cluster
  53. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  54. let nodeId = this.body.nodeIndices[i];
  55. let node = this.body.nodes[nodeId];
  56. let clonedOptions = this._cloneOptions(node);
  57. if (options.joinCondition(clonedOptions) === true) {
  58. childNodesObj[nodeId] = this.body.nodes[nodeId];
  59. // collect the nodes that will be in the cluster
  60. for (let i = 0; i < node.edges.length; i++) {
  61. let edge = node.edges[i];
  62. if (edge.hiddenByCluster !== true) {
  63. childEdgesObj[edge.id] = edge;
  64. }
  65. }
  66. }
  67. }
  68. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  69. }
  70. /**
  71. * Cluster all nodes in the network that have only X edges
  72. * @param edgeCount
  73. * @param options
  74. * @param refreshData
  75. */
  76. clusterByEdgeCount(edgeCount, options, refreshData = true) {
  77. options = this._checkOptions(options);
  78. let clusters = [];
  79. let usedNodes = {};
  80. let edge, edges, node, nodeId, relevantEdgeCount;
  81. // collect the nodes that will be in the cluster
  82. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  83. let childNodesObj = {};
  84. let childEdgesObj = {};
  85. nodeId = this.body.nodeIndices[i];
  86. // if this node is already used in another cluster this session, we do not have to re-evaluate it.
  87. if (usedNodes[nodeId] === undefined) {
  88. relevantEdgeCount = 0;
  89. node = this.body.nodes[nodeId];
  90. edges = [];
  91. for (let j = 0; j < node.edges.length; j++) {
  92. edge = node.edges[j];
  93. if (edge.hiddenByCluster !== true) {
  94. if (edge.toId !== edge.fromId) {
  95. relevantEdgeCount++;
  96. }
  97. edges.push(edge);
  98. }
  99. }
  100. // this node qualifies, we collect its neighbours to start the clustering process.
  101. if (relevantEdgeCount === edgeCount) {
  102. let gatheringSuccessful = true;
  103. for (let j = 0; j < edges.length; j++) {
  104. edge = edges[j];
  105. let childNodeId = this._getConnectedId(edge, nodeId);
  106. // add the nodes to the list by the join condition.
  107. if (options.joinCondition === undefined) {
  108. childEdgesObj[edge.id] = edge;
  109. childNodesObj[nodeId] = this.body.nodes[nodeId];
  110. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  111. usedNodes[nodeId] = true;
  112. }
  113. else {
  114. let clonedOptions = this._cloneOptions(this.body.nodes[nodeId]);
  115. if (options.joinCondition(clonedOptions) === true) {
  116. childEdgesObj[edge.id] = edge;
  117. childNodesObj[nodeId] = this.body.nodes[nodeId];
  118. usedNodes[nodeId] = true;
  119. }
  120. else {
  121. // this node does not qualify after all.
  122. gatheringSuccessful = false;
  123. break;
  124. }
  125. }
  126. }
  127. // add to the cluster queue
  128. if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0 && gatheringSuccessful === true) {
  129. clusters.push({nodes: childNodesObj, edges: childEdgesObj})
  130. }
  131. }
  132. }
  133. }
  134. for (let i = 0; i < clusters.length; i++) {
  135. this._cluster(clusters[i].nodes, clusters[i].edges, options, false)
  136. }
  137. if (refreshData === true) {
  138. this.body.emitter.emit('_dataChanged');
  139. }
  140. }
  141. /**
  142. * Cluster all nodes in the network that have only 1 edge
  143. * @param options
  144. * @param refreshData
  145. */
  146. clusterOutliers(options, refreshData = true) {
  147. this.clusterByEdgeCount(1,options,refreshData);
  148. }
  149. /**
  150. * Cluster all nodes in the network that have only 2 edge
  151. * @param options
  152. * @param refreshData
  153. */
  154. clusterBridges(options, refreshData = true) {
  155. this.clusterByEdgeCount(2,options,refreshData);
  156. }
  157. /**
  158. * suck all connected nodes of a node into the node.
  159. * @param nodeId
  160. * @param options
  161. * @param refreshData
  162. */
  163. clusterByConnection(nodeId, options, refreshData = true) {
  164. // kill conditions
  165. if (nodeId === undefined) {throw new Error("No nodeId supplied to clusterByConnection!");}
  166. if (this.body.nodes[nodeId] === undefined) {throw new Error("The nodeId given to clusterByConnection does not exist!");}
  167. let node = this.body.nodes[nodeId];
  168. options = this._checkOptions(options, node);
  169. if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x;}
  170. if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y;}
  171. if (options.clusterNodeProperties.fixed === undefined) {
  172. options.clusterNodeProperties.fixed = {};
  173. options.clusterNodeProperties.fixed.x = node.options.fixed.x;
  174. options.clusterNodeProperties.fixed.y = node.options.fixed.y;
  175. }
  176. let childNodesObj = {};
  177. let childEdgesObj = {};
  178. let parentNodeId = node.id;
  179. let parentClonedOptions = this._cloneOptions(node);
  180. childNodesObj[parentNodeId] = node;
  181. // collect the nodes that will be in the cluster
  182. for (let i = 0; i < node.edges.length; i++) {
  183. let edge = node.edges[i];
  184. if (edge.hiddenByCluster !== true) {
  185. let childNodeId = this._getConnectedId(edge, parentNodeId);
  186. // if the child node is not in a cluster (may not be needed now with the edge.hiddenByCluster check)
  187. if (this.clusteredNodes[childNodeId] === undefined) {
  188. if (childNodeId !== parentNodeId) {
  189. if (options.joinCondition === undefined) {
  190. childEdgesObj[edge.id] = edge;
  191. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  192. }
  193. else {
  194. // clone the options and insert some additional parameters that could be interesting.
  195. let childClonedOptions = this._cloneOptions(this.body.nodes[childNodeId]);
  196. if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) {
  197. childEdgesObj[edge.id] = edge;
  198. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  199. }
  200. }
  201. }
  202. else {
  203. // swallow the edge if it is self-referencing.
  204. childEdgesObj[edge.id] = edge;
  205. }
  206. }
  207. }
  208. }
  209. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  210. }
  211. /**
  212. * This returns a clone of the options or options of the edge or node to be used for construction of new edges or check functions for new nodes.
  213. * @param objId
  214. * @param type
  215. * @returns {{}}
  216. * @private
  217. */
  218. _cloneOptions(item, type) {
  219. let clonedOptions = {};
  220. if (type === undefined || type === 'node') {
  221. util.deepExtend(clonedOptions, item.options, true);
  222. clonedOptions.x = item.x;
  223. clonedOptions.y = item.y;
  224. clonedOptions.amountOfConnections = item.edges.length;
  225. }
  226. else {
  227. util.deepExtend(clonedOptions, item.options, true);
  228. }
  229. return clonedOptions;
  230. }
  231. /**
  232. * This function creates the edges that will be attached to the cluster
  233. * It looks for edges that are connected to the nodes from the "outside' of the cluster.
  234. *
  235. * @param childNodesObj
  236. * @param newEdges
  237. * @param options
  238. * @private
  239. */
  240. _createClusterEdges (childNodesObj, childEdgesObj, clusterNodeProperties, clusterEdgeProperties) {
  241. let edge, childNodeId, childNode, toId, fromId, otherNodeId;
  242. // loop over all child nodes and their edges to find edges going out of the cluster
  243. // these edges will be replaced by clusterEdges.
  244. let childKeys = Object.keys(childNodesObj);
  245. let createEdges = [];
  246. for (let i = 0; i < childKeys.length; i++) {
  247. childNodeId = childKeys[i];
  248. childNode = childNodesObj[childNodeId];
  249. // construct new edges from the cluster to others
  250. for (let j = 0; j < childNode.edges.length; j++) {
  251. edge = childNode.edges[j];
  252. // we only handle edges that are visible to the system, not the disabled ones from the clustering process.
  253. if (edge.hiddenByCluster !== true) {
  254. // self-referencing edges will be added to the "hidden" list
  255. if (edge.toId == edge.fromId) {
  256. childEdgesObj[edge.id] = edge;
  257. }
  258. else {
  259. // set up the from and to.
  260. if (edge.toId == childNodeId) { // this is a double equals because ints and strings can be interchanged here.
  261. toId = clusterNodeProperties.id;
  262. fromId = edge.fromId;
  263. otherNodeId = fromId;
  264. }
  265. else {
  266. toId = edge.toId;
  267. fromId = clusterNodeProperties.id;
  268. otherNodeId = toId;
  269. }
  270. }
  271. // Only edges from the cluster outwards are being replaced.
  272. if (childNodesObj[otherNodeId] === undefined) {
  273. createEdges.push({edge: edge, fromId: fromId, toId: toId});
  274. }
  275. }
  276. }
  277. }
  278. // here we actually create the replacement edges. We could not do this in the loop above as the creation process
  279. // would add an edge to the edges array we are iterating over.
  280. for (let j = 0; j < createEdges.length; j++) {
  281. let edge = createEdges[j].edge;
  282. // copy the options of the edge we will replace
  283. let clonedOptions = this._cloneOptions(edge, 'edge');
  284. // make sure the properties of clusterEdges are superimposed on it
  285. util.deepExtend(clonedOptions, clusterEdgeProperties);
  286. // set up the edge
  287. clonedOptions.from = createEdges[j].fromId;
  288. clonedOptions.to = createEdges[j].toId;
  289. clonedOptions.id = 'clusterEdge:' + util.randomUUID();
  290. //clonedOptions.id = '(cf: ' + createEdges[j].fromId + " to: " + createEdges[j].toId + ")" + Math.random();
  291. // create the edge and give a reference to the one it replaced.
  292. let newEdge = this.body.functions.createEdge(clonedOptions);
  293. newEdge.clusteringEdgeReplacingId = edge.id;
  294. // connect the edge.
  295. this.body.edges[newEdge.id] = newEdge;
  296. newEdge.connect();
  297. // hide the replaced edge
  298. edge.setOptions({physics:false, hidden:true});
  299. edge.hiddenByCluster = true;
  300. }
  301. }
  302. /**
  303. * This function checks the options that can be supplied to the different cluster functions
  304. * for certain fields and inserts defaults if needed
  305. * @param options
  306. * @returns {*}
  307. * @private
  308. */
  309. _checkOptions(options = {}) {
  310. if (options.clusterEdgeProperties === undefined) {options.clusterEdgeProperties = {};}
  311. if (options.clusterNodeProperties === undefined) {options.clusterNodeProperties = {};}
  312. return options;
  313. }
  314. /**
  315. *
  316. * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node
  317. * @param {Object} childEdgesObj | object with edge objects, id as keys
  318. * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties}
  319. * @param {Boolean} refreshData | when true, do not wrap up
  320. * @private
  321. */
  322. _cluster(childNodesObj, childEdgesObj, options, refreshData = true) {
  323. // kill condition: no children so can't cluster or only one node in the cluster, dont bother
  324. if (Object.keys(childNodesObj).length < 2) {return;}
  325. // check if this cluster call is not trying to cluster anything that is in another cluster.
  326. for (let nodeId in childNodesObj) {
  327. if (childNodesObj.hasOwnProperty(nodeId)) {
  328. if (this.clusteredNodes[nodeId] !== undefined) {
  329. return;
  330. }
  331. }
  332. }
  333. let clusterNodeProperties = util.deepExtend({},options.clusterNodeProperties);
  334. // construct the clusterNodeProperties
  335. if (options.processProperties !== undefined) {
  336. // get the childNode options
  337. let childNodesOptions = [];
  338. for (let nodeId in childNodesObj) {
  339. if (childNodesObj.hasOwnProperty(nodeId)) {
  340. let clonedOptions = this._cloneOptions(childNodesObj[nodeId]);
  341. childNodesOptions.push(clonedOptions);
  342. }
  343. }
  344. // get clusterproperties based on childNodes
  345. let childEdgesOptions = [];
  346. for (let edgeId in childEdgesObj) {
  347. if (childEdgesObj.hasOwnProperty(edgeId)) {
  348. // these cluster edges will be removed on creation of the cluster.
  349. if (edgeId.substr(0, 12) !== "clusterEdge:") {
  350. let clonedOptions = this._cloneOptions(childEdgesObj[edgeId], 'edge');
  351. childEdgesOptions.push(clonedOptions);
  352. }
  353. }
  354. }
  355. clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions);
  356. if (!clusterNodeProperties) {
  357. throw new Error("The processProperties function does not return properties!");
  358. }
  359. }
  360. // check if we have an unique id;
  361. if (clusterNodeProperties.id === undefined) {clusterNodeProperties.id = 'cluster:' + util.randomUUID();}
  362. let clusterId = clusterNodeProperties.id;
  363. if (clusterNodeProperties.label === undefined) {
  364. clusterNodeProperties.label = 'cluster';
  365. }
  366. // give the clusterNode a postion if it does not have one.
  367. let pos = undefined;
  368. if (clusterNodeProperties.x === undefined) {
  369. pos = this._getClusterPosition(childNodesObj);
  370. clusterNodeProperties.x = pos.x;
  371. }
  372. if (clusterNodeProperties.y === undefined) {
  373. if (pos === undefined) {pos = this._getClusterPosition(childNodesObj);}
  374. clusterNodeProperties.y = pos.y;
  375. }
  376. // force the ID to remain the same
  377. clusterNodeProperties.id = clusterId;
  378. // create the clusterNode
  379. let clusterNode = this.body.functions.createNode(clusterNodeProperties, Cluster);
  380. clusterNode.isCluster = true;
  381. clusterNode.containedNodes = childNodesObj;
  382. clusterNode.containedEdges = childEdgesObj;
  383. // cache a copy from the cluster edge properties if we have to reconnect others later on
  384. clusterNode.clusterEdgeProperties = options.clusterEdgeProperties;
  385. // finally put the cluster node into global
  386. this.body.nodes[clusterNodeProperties.id] = clusterNode;
  387. // create the new edges that will connect to the cluster, all self-referencing edges will be added to childEdgesObject here.
  388. this._createClusterEdges(childNodesObj, childEdgesObj, clusterNodeProperties, options.clusterEdgeProperties);
  389. // disable the childEdges
  390. for (let edgeId in childEdgesObj) {
  391. if (childEdgesObj.hasOwnProperty(edgeId)) {
  392. if (this.body.edges[edgeId] !== undefined) {
  393. let edge = this.body.edges[edgeId];
  394. edge.setOptions({physics:false, hidden:true});
  395. edge.hiddenByCluster = true;
  396. }
  397. }
  398. }
  399. // disable the childNodes
  400. for (let nodeId in childNodesObj) {
  401. if (childNodesObj.hasOwnProperty(nodeId)) {
  402. this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties.id, node: this.body.nodes[nodeId]};
  403. this.body.nodes[nodeId].setOptions({hidden:true, physics:false});
  404. }
  405. }
  406. // set ID to undefined so no duplicates arise
  407. clusterNodeProperties.id = undefined;
  408. // wrap up
  409. if (refreshData === true) {
  410. this.body.emitter.emit('_dataChanged');
  411. }
  412. }
  413. /**
  414. * Check if a node is a cluster.
  415. * @param nodeId
  416. * @returns {*}
  417. */
  418. isCluster(nodeId) {
  419. if (this.body.nodes[nodeId] !== undefined) {
  420. return this.body.nodes[nodeId].isCluster === true;
  421. }
  422. else {
  423. console.log("Node does not exist.");
  424. return false;
  425. }
  426. }
  427. /**
  428. * get the position of the cluster node based on what's inside
  429. * @param {object} childNodesObj | object with node objects, id as keys
  430. * @returns {{x: number, y: number}}
  431. * @private
  432. */
  433. _getClusterPosition(childNodesObj) {
  434. let childKeys = Object.keys(childNodesObj);
  435. let minX = childNodesObj[childKeys[0]].x;
  436. let maxX = childNodesObj[childKeys[0]].x;
  437. let minY = childNodesObj[childKeys[0]].y;
  438. let maxY = childNodesObj[childKeys[0]].y;
  439. let node;
  440. for (let i = 1; i < childKeys.length; i++) {
  441. node = childNodesObj[childKeys[i]];
  442. minX = node.x < minX ? node.x : minX;
  443. maxX = node.x > maxX ? node.x : maxX;
  444. minY = node.y < minY ? node.y : minY;
  445. maxY = node.y > maxY ? node.y : maxY;
  446. }
  447. return {x: 0.5*(minX + maxX), y: 0.5*(minY + maxY)};
  448. }
  449. /**
  450. * Open a cluster by calling this function.
  451. * @param {String} clusterNodeId | the ID of the cluster node
  452. * @param {Boolean} refreshData | wrap up afterwards if not true
  453. */
  454. openCluster(clusterNodeId, options, refreshData = true) {
  455. // kill conditions
  456. if (clusterNodeId === undefined) {throw new Error("No clusterNodeId supplied to openCluster.");}
  457. if (this.body.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");}
  458. if (this.body.nodes[clusterNodeId].containedNodes === undefined) {
  459. console.log("The node:" + clusterNodeId + " is not a cluster.");
  460. return
  461. }
  462. let clusterNode = this.body.nodes[clusterNodeId];
  463. let containedNodes = clusterNode.containedNodes;
  464. let containedEdges = clusterNode.containedEdges;
  465. // allow the user to position the nodes after release.
  466. if (options !== undefined && options.releaseFunction !== undefined && typeof options.releaseFunction === 'function') {
  467. let positions = {};
  468. let clusterPosition = {x:clusterNode.x, y:clusterNode.y};
  469. for (let nodeId in containedNodes) {
  470. if (containedNodes.hasOwnProperty(nodeId)) {
  471. let containedNode = this.body.nodes[nodeId];
  472. positions[nodeId] = {x: containedNode.x, y: containedNode.y};
  473. }
  474. }
  475. let newPositions = options.releaseFunction(clusterPosition, positions);
  476. for (let nodeId in containedNodes) {
  477. if (containedNodes.hasOwnProperty(nodeId)) {
  478. let containedNode = this.body.nodes[nodeId];
  479. if (newPositions[nodeId] !== undefined) {
  480. containedNode.x = (newPositions[nodeId].x === undefined ? clusterNode.x : newPositions[nodeId].x);
  481. containedNode.y = (newPositions[nodeId].y === undefined ? clusterNode.y : newPositions[nodeId].y);
  482. }
  483. }
  484. }
  485. }
  486. else {
  487. // copy the position from the cluster
  488. for (let nodeId in containedNodes) {
  489. if (containedNodes.hasOwnProperty(nodeId)) {
  490. let containedNode = this.body.nodes[nodeId];
  491. containedNode = containedNodes[nodeId];
  492. // inherit position
  493. if (containedNode.options.fixed.x === false) {containedNode.x = clusterNode.x;}
  494. if (containedNode.options.fixed.y === false) {containedNode.y = clusterNode.y;}
  495. }
  496. }
  497. }
  498. // release nodes
  499. for (let nodeId in containedNodes) {
  500. if (containedNodes.hasOwnProperty(nodeId)) {
  501. let containedNode = this.body.nodes[nodeId];
  502. // inherit speed
  503. containedNode.vx = clusterNode.vx;
  504. containedNode.vy = clusterNode.vy;
  505. // we use these methods to avoid reinstantiating the shape, which happens with setOptions.
  506. containedNode.setOptions({hidden:false, physics:true});
  507. delete this.clusteredNodes[nodeId];
  508. }
  509. }
  510. // copy the clusterNode edges because we cannot iterate over an object that we add or remove from.
  511. let edgesToBeDeleted = [];
  512. for (let i = 0; i < clusterNode.edges.length; i++) {
  513. edgesToBeDeleted.push(clusterNode.edges[i]);
  514. }
  515. // actually handling the deleting.
  516. for (let i = 0; i < edgesToBeDeleted.length; i++) {
  517. let edge = edgesToBeDeleted[i];
  518. let otherNodeId = this._getConnectedId(edge, clusterNodeId);
  519. // if the other node is in another cluster, we transfer ownership of this edge to the other cluster
  520. if (this.clusteredNodes[otherNodeId] !== undefined) {
  521. // transfer ownership:
  522. let otherCluster = this.body.nodes[this.clusteredNodes[otherNodeId].clusterId];
  523. let transferEdge = this.body.edges[edge.clusteringEdgeReplacingId];
  524. if (transferEdge !== undefined) {
  525. otherCluster.containedEdges[transferEdge.id] = transferEdge;
  526. // delete local reference
  527. delete containedEdges[transferEdge.id];
  528. // create new cluster edge from the otherCluster:
  529. // get to and from
  530. let fromId = transferEdge.fromId;
  531. let toId = transferEdge.toId;
  532. if (transferEdge.toId == otherNodeId) {
  533. toId = this.clusteredNodes[otherNodeId].clusterId;
  534. }
  535. else {
  536. fromId = this.clusteredNodes[otherNodeId].clusterId;
  537. }
  538. // clone the options and apply the cluster options to them
  539. let clonedOptions = this._cloneOptions(transferEdge, 'edge');
  540. util.deepExtend(clonedOptions, otherCluster.clusterEdgeProperties);
  541. // apply the edge specific options to it.
  542. let id = 'clusterEdge:' + util.randomUUID();
  543. util.deepExtend(clonedOptions, {from: fromId, to: toId, hidden: false, physics: true, id: id});
  544. // create it
  545. let newEdge = this.body.functions.createEdge(clonedOptions);
  546. newEdge.clusteringEdgeReplacingId = transferEdge.id;
  547. this.body.edges[id] = newEdge;
  548. this.body.edges[id].connect();
  549. }
  550. }
  551. else {
  552. let replacedEdge = this.body.edges[edge.clusteringEdgeReplacingId];
  553. if (replacedEdge !== undefined) {
  554. replacedEdge.setOptions({physics: true, hidden: false});
  555. replacedEdge.hiddenByCluster = false;
  556. }
  557. }
  558. edge.cleanup();
  559. // this removes the edge from node.edges, which is why edgeIds is formed
  560. edge.disconnect();
  561. delete this.body.edges[edge.id];
  562. }
  563. // handle the releasing of the edges
  564. for (let edgeId in containedEdges) {
  565. if (containedEdges.hasOwnProperty(edgeId)) {
  566. let edge = containedEdges[edgeId];
  567. edge.setOptions({physics: true, hidden: false});
  568. edge.hiddenByCluster = undefined;
  569. delete edge.hiddenByCluster;
  570. }
  571. }
  572. // remove clusterNode
  573. delete this.body.nodes[clusterNodeId];
  574. if (refreshData === true) {
  575. this.body.emitter.emit('_dataChanged');
  576. }
  577. }
  578. getNodesInCluster(clusterId) {
  579. let nodesArray = []
  580. if (this.isCluster(clusterId) === true) {
  581. let containedNodes = this.body.nodes[clusterId].containedNodes;
  582. for (let nodeId in containedNodes) {
  583. if (containedNodes.hasOwnProperty(nodeId)) {
  584. nodesArray.push(nodeId)
  585. }
  586. }
  587. }
  588. return nodesArray;
  589. }
  590. /**
  591. * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node
  592. * @param nodeId
  593. * @returns {Array}
  594. */
  595. findNode(nodeId) {
  596. let stack = [];
  597. let max = 100;
  598. let counter = 0;
  599. while (this.clusteredNodes[nodeId] !== undefined && counter < max) {
  600. stack.push(this.clusteredNodes[nodeId].node);
  601. nodeId = this.clusteredNodes[nodeId].clusterId;
  602. counter++;
  603. }
  604. stack.push(this.body.nodes[nodeId]);
  605. return stack;
  606. }
  607. /**
  608. * Get the Id the node is connected to
  609. * @param edge
  610. * @param nodeId
  611. * @returns {*}
  612. * @private
  613. */
  614. _getConnectedId(edge, nodeId) {
  615. if (edge.toId != nodeId) {
  616. return edge.toId;
  617. }
  618. else if (edge.fromId != nodeId) {
  619. return edge.fromId;
  620. }
  621. else {
  622. return edge.fromId;
  623. }
  624. }
  625. /**
  626. * We determine how many connections denote an important hub.
  627. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  628. *
  629. * @private
  630. */
  631. _getHubSize() {
  632. let average = 0;
  633. let averageSquared = 0;
  634. let hubCounter = 0;
  635. let largestHub = 0;
  636. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  637. let node = this.body.nodes[this.body.nodeIndices[i]];
  638. if (node.edges.length > largestHub) {
  639. largestHub = node.edges.length;
  640. }
  641. average += node.edges.length;
  642. averageSquared += Math.pow(node.edges.length,2);
  643. hubCounter += 1;
  644. }
  645. average = average / hubCounter;
  646. averageSquared = averageSquared / hubCounter;
  647. let variance = averageSquared - Math.pow(average,2);
  648. let standardDeviation = Math.sqrt(variance);
  649. let hubThreshold = Math.floor(average + 2*standardDeviation);
  650. // always have at least one to cluster
  651. if (hubThreshold > largestHub) {
  652. hubThreshold = largestHub;
  653. }
  654. return hubThreshold;
  655. };
  656. }
  657. export default ClusterEngine;