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.

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