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.

743 lines
25 KiB

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