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.

763 lines
26 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.clusteredEdges = {};
  9. this.options = {};
  10. this.defaultOptions = {};
  11. util.extend(this.options, this.defaultOptions);
  12. this.body.emitter.on('_resetData', () => {this.clusteredNodes = {}; this.clusteredEdges = {};})
  13. }
  14. setOptions(options) {
  15. if (options !== undefined) {
  16. }
  17. }
  18. /**
  19. *
  20. * @param hubsize
  21. * @param options
  22. */
  23. clusterByHubsize(hubsize, options) {
  24. if (hubsize === undefined) {
  25. hubsize = this._getHubSize();
  26. }
  27. else if (typeof(hubsize) === "object") {
  28. options = this._checkOptions(hubsize);
  29. hubsize = this._getHubSize();
  30. }
  31. let nodesToCluster = [];
  32. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  33. let node = this.body.nodes[this.body.nodeIndices[i]];
  34. if (node.edges.length >= hubsize) {
  35. nodesToCluster.push(node.id);
  36. }
  37. }
  38. for (let i = 0; i < nodesToCluster.length; i++) {
  39. this.clusterByConnection(nodesToCluster[i],options,true);
  40. }
  41. this.body.emitter.emit('_dataChanged');
  42. }
  43. /**
  44. * loop over all nodes, check if they adhere to the condition and cluster if needed.
  45. * @param options
  46. * @param refreshData
  47. */
  48. cluster(options = {}, refreshData = true) {
  49. if (options.joinCondition === undefined) {throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");}
  50. // check if the options object is fine, append if needed
  51. options = this._checkOptions(options);
  52. let childNodesObj = {};
  53. let childEdgesObj = {};
  54. // collect the nodes that will be in the cluster
  55. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  56. let nodeId = this.body.nodeIndices[i];
  57. let node = this.body.nodes[nodeId];
  58. let clonedOptions = NetworkUtil.cloneOptions(node);
  59. if (options.joinCondition(clonedOptions) === true) {
  60. childNodesObj[nodeId] = this.body.nodes[nodeId];
  61. // collect the nodes that will be in the cluster
  62. for (let i = 0; i < node.edges.length; i++) {
  63. let edge = node.edges[i];
  64. if (this.clusteredEdges[edge.id] === undefined) {
  65. childEdgesObj[edge.id] = edge;
  66. }
  67. }
  68. }
  69. }
  70. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  71. }
  72. /**
  73. * Cluster all nodes in the network that have only X edges
  74. * @param edgeCount
  75. * @param options
  76. * @param refreshData
  77. */
  78. clusterByEdgeCount(edgeCount, options, refreshData = true) {
  79. options = this._checkOptions(options);
  80. let clusters = [];
  81. let usedNodes = {};
  82. let edge, edges, node, nodeId, relevantEdgeCount;
  83. // collect the nodes that will be in the cluster
  84. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  85. let childNodesObj = {};
  86. let childEdgesObj = {};
  87. nodeId = this.body.nodeIndices[i];
  88. // if this node is already used in another cluster this session, we do not have to re-evaluate it.
  89. if (usedNodes[nodeId] === undefined) {
  90. relevantEdgeCount = 0;
  91. node = this.body.nodes[nodeId];
  92. edges = [];
  93. for (let j = 0; j < node.edges.length; j++) {
  94. edge = node.edges[j];
  95. if (this.clusteredEdges[edge.id] === undefined) {
  96. if (edge.toId !== edge.fromId) {
  97. relevantEdgeCount++;
  98. }
  99. edges.push(edge);
  100. }
  101. }
  102. // this node qualifies, we collect its neighbours to start the clustering process.
  103. if (relevantEdgeCount === edgeCount) {
  104. let gatheringSuccessful = true;
  105. for (let j = 0; j < edges.length; j++) {
  106. edge = edges[j];
  107. let childNodeId = this._getConnectedId(edge, nodeId);
  108. // add the nodes to the list by the join condition.
  109. if (options.joinCondition === undefined) {
  110. childEdgesObj[edge.id] = edge;
  111. childNodesObj[nodeId] = this.body.nodes[nodeId];
  112. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  113. usedNodes[nodeId] = true;
  114. }
  115. else {
  116. let clonedOptions = NetworkUtil.cloneOptions(this.body.nodes[nodeId]);
  117. if (options.joinCondition(clonedOptions) === true) {
  118. childEdgesObj[edge.id] = edge;
  119. childNodesObj[nodeId] = this.body.nodes[nodeId];
  120. usedNodes[nodeId] = true;
  121. }
  122. else {
  123. // this node does not qualify after all.
  124. gatheringSuccessful = false;
  125. break;
  126. }
  127. }
  128. }
  129. // add to the cluster queue
  130. if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0 && gatheringSuccessful === true) {
  131. clusters.push({nodes: childNodesObj, edges: childEdgesObj})
  132. }
  133. }
  134. }
  135. }
  136. for (let i = 0; i < clusters.length; i++) {
  137. this._cluster(clusters[i].nodes, clusters[i].edges, options, false)
  138. }
  139. if (refreshData === true) {
  140. this.body.emitter.emit('_dataChanged');
  141. }
  142. }
  143. /**
  144. * Cluster all nodes in the network that have only 1 edge
  145. * @param options
  146. * @param refreshData
  147. */
  148. clusterOutliers(options, refreshData = true) {
  149. this.clusterByEdgeCount(1,options,refreshData);
  150. }
  151. /**
  152. * Cluster all nodes in the network that have only 2 edge
  153. * @param options
  154. * @param refreshData
  155. */
  156. clusterBridges(options, refreshData = true) {
  157. this.clusterByEdgeCount(2,options,refreshData);
  158. }
  159. /**
  160. * suck all connected nodes of a node into the node.
  161. * @param nodeId
  162. * @param options
  163. * @param refreshData
  164. */
  165. clusterByConnection(nodeId, options, refreshData = true) {
  166. // kill conditions
  167. if (nodeId === undefined) {throw new Error("No nodeId supplied to clusterByConnection!");}
  168. if (this.body.nodes[nodeId] === undefined) {throw new Error("The nodeId given to clusterByConnection does not exist!");}
  169. let node = this.body.nodes[nodeId];
  170. options = this._checkOptions(options, node);
  171. if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x;}
  172. if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y;}
  173. if (options.clusterNodeProperties.fixed === undefined) {
  174. options.clusterNodeProperties.fixed = {};
  175. options.clusterNodeProperties.fixed.x = node.options.fixed.x;
  176. options.clusterNodeProperties.fixed.y = node.options.fixed.y;
  177. }
  178. let childNodesObj = {};
  179. let childEdgesObj = {};
  180. let parentNodeId = node.id;
  181. let parentClonedOptions = NetworkUtil.cloneOptions(node);
  182. childNodesObj[parentNodeId] = node;
  183. // collect the nodes that will be in the cluster
  184. for (let i = 0; i < node.edges.length; i++) {
  185. let edge = node.edges[i];
  186. if (this.clusteredEdges[edge.id] === undefined) {
  187. let childNodeId = this._getConnectedId(edge, parentNodeId);
  188. // if the child node is not in a cluster
  189. if (this.clusteredNodes[childNodeId] === undefined) {
  190. if (childNodeId !== parentNodeId) {
  191. if (options.joinCondition === undefined) {
  192. childEdgesObj[edge.id] = edge;
  193. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  194. }
  195. else {
  196. // clone the options and insert some additional parameters that could be interesting.
  197. let childClonedOptions = NetworkUtil.cloneOptions(this.body.nodes[childNodeId]);
  198. if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) {
  199. childEdgesObj[edge.id] = edge;
  200. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  201. }
  202. }
  203. }
  204. else {
  205. // swallow the edge if it is self-referencing.
  206. childEdgesObj[edge.id] = edge;
  207. }
  208. }
  209. }
  210. }
  211. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  212. }
  213. /**
  214. * This function creates the edges that will be attached to the cluster
  215. * It looks for edges that are connected to the nodes from the "outside' of the cluster.
  216. *
  217. * @param childNodesObj
  218. * @param childEdgesObj
  219. * @param clusterNodeProperties
  220. * @param clusterEdgeProperties
  221. * @private
  222. */
  223. _createClusterEdges (childNodesObj, childEdgesObj, clusterNodeProperties, clusterEdgeProperties) {
  224. let edge, childNodeId, childNode, toId, fromId, otherNodeId;
  225. let nodeIdField = this.body.data.nodes._fieldId;
  226. let edgeIdField = this.body.data.edges._fieldId;
  227. // loop over all child nodes and their edges to find edges going out of the cluster
  228. // these edges will be replaced by clusterEdges.
  229. let childKeys = Object.keys(childNodesObj);
  230. let createEdges = [];
  231. for (let i = 0; i < childKeys.length; i++) {
  232. childNodeId = childKeys[i];
  233. childNode = childNodesObj[childNodeId];
  234. // construct new edges from the cluster to others
  235. for (let j = 0; j < childNode.edges.length; j++) {
  236. edge = childNode.edges[j];
  237. // we only handle edges that are visible to the system, not the disabled ones from the clustering process.
  238. if (this.clusteredEdges[edge.id] === undefined) {
  239. // self-referencing edges will be added to the "hidden" list
  240. if (edge.toId == edge.fromId) {
  241. childEdgesObj[edge.id] = edge;
  242. }
  243. else {
  244. // set up the from and to.
  245. if (edge.toId == childNodeId) { // this is a double equals because ints and strings can be interchanged here.
  246. toId = clusterNodeProperties[nodeIdField];
  247. fromId = edge.fromId;
  248. otherNodeId = fromId;
  249. }
  250. else {
  251. toId = edge.toId;
  252. fromId = clusterNodeProperties[nodeIdField];
  253. otherNodeId = toId;
  254. }
  255. }
  256. // Only edges from the cluster outwards are being replaced.
  257. if (childNodesObj[otherNodeId] === undefined) {
  258. createEdges.push({edge: edge, fromId: fromId, toId: toId});
  259. }
  260. }
  261. }
  262. }
  263. // here we actually create the replacement edges. We could not do this in the loop above as the creation process
  264. // would add an edge to the edges array we are iterating over.
  265. for (let j = 0; j < createEdges.length; j++) {
  266. let edge = createEdges[j].edge;
  267. // copy the options of the edge we will replace
  268. let clonedOptions = NetworkUtil.cloneOptions(edge, 'edge');
  269. // make sure the properties of clusterEdges are superimposed on it
  270. util.deepExtend(clonedOptions, clusterEdgeProperties);
  271. // set up the edge
  272. clonedOptions.from = createEdges[j].fromId;
  273. clonedOptions.to = createEdges[j].toId;
  274. clonedOptions[edgeIdField] = 'clusterEdge:' + util.randomUUID();
  275. //clonedOptions.id = '(cf: ' + createEdges[j].fromId + " to: " + createEdges[j].toId + ")" + Math.random();
  276. // create the edge and give a reference to the one it replaced.
  277. let newEdge = this.body.functions.createEdge(clonedOptions);
  278. newEdge.clusteringEdgeReplacingId = edge.id;
  279. // connect the edge.
  280. this.body.edges[newEdge.id] = newEdge;
  281. newEdge.connect();
  282. // hide the replaced edge
  283. this._backupEdgeOptions(edge);
  284. edge.setOptions({physics:false, hidden:true});
  285. }
  286. }
  287. /**
  288. * This function checks the options that can be supplied to the different cluster functions
  289. * for certain fields and inserts defaults if needed
  290. * @param options
  291. * @returns {*}
  292. * @private
  293. */
  294. _checkOptions(options = {}) {
  295. if (options.clusterEdgeProperties === undefined) {options.clusterEdgeProperties = {};}
  296. if (options.clusterNodeProperties === undefined) {options.clusterNodeProperties = {};}
  297. return options;
  298. }
  299. /**
  300. *
  301. * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node
  302. * @param {Object} childEdgesObj | object with edge objects, id as keys
  303. * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties}
  304. * @param {Boolean} refreshData | when true, do not wrap up
  305. * @private
  306. */
  307. _cluster(childNodesObj, childEdgesObj, options, refreshData = true) {
  308. let nodeIdField = this.body.data.nodes._fieldId;
  309. // kill condition: no children so can't cluster or only one node in the cluster, don't bother
  310. if (Object.keys(childNodesObj).length < 2) {return;}
  311. // check if this cluster call is not trying to cluster anything that is in another cluster.
  312. for (let nodeId in childNodesObj) {
  313. if (childNodesObj.hasOwnProperty(nodeId)) {
  314. if (this.clusteredNodes[nodeId] !== undefined) {
  315. return;
  316. }
  317. }
  318. }
  319. let clusterNodeProperties = util.deepExtend({},options.clusterNodeProperties);
  320. // construct the clusterNodeProperties
  321. if (options.processProperties !== undefined) {
  322. // get the childNode options
  323. let childNodesOptions = [];
  324. for (let nodeId in childNodesObj) {
  325. if (childNodesObj.hasOwnProperty(nodeId)) {
  326. let clonedOptions = NetworkUtil.cloneOptions(childNodesObj[nodeId]);
  327. childNodesOptions.push(clonedOptions);
  328. }
  329. }
  330. // get cluster properties based on childNodes
  331. let childEdgesOptions = [];
  332. for (let edgeId in childEdgesObj) {
  333. if (childEdgesObj.hasOwnProperty(edgeId)) {
  334. // these cluster edges will be removed on creation of the cluster.
  335. if (edgeId.substr(0, 12) !== "clusterEdge:") {
  336. let clonedOptions = NetworkUtil.cloneOptions(childEdgesObj[edgeId], 'edge');
  337. childEdgesOptions.push(clonedOptions);
  338. }
  339. }
  340. }
  341. clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions);
  342. if (!clusterNodeProperties) {
  343. throw new Error("The processProperties function does not return properties!");
  344. }
  345. }
  346. // check if we have an unique id;
  347. if (clusterNodeProperties[nodeIdField] === undefined) {clusterNodeProperties[nodeIdField] = 'cluster:' + util.randomUUID();}
  348. let clusterId = clusterNodeProperties[nodeIdField];
  349. if (clusterNodeProperties.label === undefined) {
  350. clusterNodeProperties.label = 'cluster';
  351. }
  352. // give the clusterNode a position if it does not have one.
  353. let pos = undefined;
  354. if (clusterNodeProperties.x === undefined) {
  355. pos = this._getClusterPosition(childNodesObj);
  356. clusterNodeProperties.x = pos.x;
  357. }
  358. if (clusterNodeProperties.y === undefined) {
  359. if (pos === undefined) {pos = this._getClusterPosition(childNodesObj);}
  360. clusterNodeProperties.y = pos.y;
  361. }
  362. // force the ID to remain the same
  363. clusterNodeProperties[nodeIdField] = clusterId;
  364. // create the clusterNode
  365. let clusterNode = this.body.functions.createNode(clusterNodeProperties, Cluster);
  366. clusterNode.isCluster = true;
  367. clusterNode.containedNodes = childNodesObj;
  368. clusterNode.containedEdges = childEdgesObj;
  369. // cache a copy from the cluster edge properties if we have to reconnect others later on
  370. clusterNode.clusterEdgeProperties = options.clusterEdgeProperties;
  371. // finally put the cluster node into global
  372. this.body.nodes[clusterNodeProperties[nodeIdField]] = clusterNode;
  373. // create the new edges that will connect to the cluster, all self-referencing edges will be added to childEdgesObject here.
  374. this._createClusterEdges(childNodesObj, childEdgesObj, clusterNodeProperties, options.clusterEdgeProperties);
  375. // disable the childEdges
  376. for (let edgeId in childEdgesObj) {
  377. if (childEdgesObj.hasOwnProperty(edgeId)) {
  378. if (this.body.edges[edgeId] !== undefined) {
  379. let edge = this.body.edges[edgeId];
  380. // cache the options before changing
  381. this._backupEdgeOptions(edge);
  382. // disable physics and hide the edge
  383. edge.setOptions({physics:false, hidden:true});
  384. }
  385. }
  386. }
  387. // disable the childNodes
  388. for (let nodeId in childNodesObj) {
  389. if (childNodesObj.hasOwnProperty(nodeId)) {
  390. this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties[nodeIdField], node: this.body.nodes[nodeId]};
  391. this.body.nodes[nodeId].setOptions({hidden:true, physics:false});
  392. }
  393. }
  394. // set ID to undefined so no duplicates arise
  395. clusterNodeProperties[nodeIdField] = undefined;
  396. // wrap up
  397. if (refreshData === true) {
  398. this.body.emitter.emit('_dataChanged');
  399. }
  400. }
  401. _backupEdgeOptions(edge) {
  402. if (this.clusteredEdges[edge.id] === undefined) {
  403. this.clusteredEdges[edge.id] = {physics: edge.options.physics, hidden: edge.options.hidden};
  404. }
  405. }
  406. _restoreEdge(edge) {
  407. let originalOptions = this.clusteredEdges[edge.id];
  408. if (originalOptions !== undefined) {
  409. edge.setOptions({physics: originalOptions.physics, hidden: originalOptions.hidden});
  410. delete this.clusteredEdges[edge.id];
  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 re-instantiating 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 = NetworkUtil.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. this._restoreEdge(replacedEdge);
  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. this._restoreEdge(containedEdges[edgeId]);
  566. }
  567. }
  568. // remove clusterNode
  569. delete this.body.nodes[clusterNodeId];
  570. if (refreshData === true) {
  571. this.body.emitter.emit('_dataChanged');
  572. }
  573. }
  574. getNodesInCluster(clusterId) {
  575. let nodesArray = [];
  576. if (this.isCluster(clusterId) === true) {
  577. let containedNodes = this.body.nodes[clusterId].containedNodes;
  578. for (let nodeId in containedNodes) {
  579. if (containedNodes.hasOwnProperty(nodeId)) {
  580. nodesArray.push(this.body.nodes[nodeId].id)
  581. }
  582. }
  583. }
  584. return nodesArray;
  585. }
  586. /**
  587. * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node
  588. * @param nodeId
  589. * @returns {Array}
  590. */
  591. findNode(nodeId) {
  592. let stack = [];
  593. let max = 100;
  594. let counter = 0;
  595. while (this.clusteredNodes[nodeId] !== undefined && counter < max) {
  596. stack.push(this.body.nodes[nodeId].id);
  597. nodeId = this.clusteredNodes[nodeId].clusterId;
  598. counter++;
  599. }
  600. stack.push(this.body.nodes[nodeId].id);
  601. stack.reverse();
  602. return stack;
  603. }
  604. /**
  605. * Get the Id the node is connected to
  606. * @param edge
  607. * @param nodeId
  608. * @returns {*}
  609. * @private
  610. */
  611. _getConnectedId(edge, nodeId) {
  612. if (edge.toId != nodeId) {
  613. return edge.toId;
  614. }
  615. else if (edge.fromId != nodeId) {
  616. return edge.fromId;
  617. }
  618. else {
  619. return edge.fromId;
  620. }
  621. }
  622. /**
  623. * We determine how many connections denote an important hub.
  624. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  625. *
  626. * @private
  627. */
  628. _getHubSize() {
  629. let average = 0;
  630. let averageSquared = 0;
  631. let hubCounter = 0;
  632. let largestHub = 0;
  633. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  634. let node = this.body.nodes[this.body.nodeIndices[i]];
  635. if (node.edges.length > largestHub) {
  636. largestHub = node.edges.length;
  637. }
  638. average += node.edges.length;
  639. averageSquared += Math.pow(node.edges.length,2);
  640. hubCounter += 1;
  641. }
  642. average = average / hubCounter;
  643. averageSquared = averageSquared / hubCounter;
  644. let variance = averageSquared - Math.pow(average,2);
  645. let standardDeviation = Math.sqrt(variance);
  646. let hubThreshold = Math.floor(average + 2*standardDeviation);
  647. // always have at least one to cluster
  648. if (hubThreshold > largestHub) {
  649. hubThreshold = largestHub;
  650. }
  651. return hubThreshold;
  652. };
  653. }
  654. export default ClusterEngine;