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.

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