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.

826 lines
28 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. /**
  15. *
  16. * @param hubsize
  17. * @param options
  18. */
  19. clusterByHubsize(hubsize, options) {
  20. if (hubsize === undefined) {
  21. hubsize = this._getHubSize();
  22. }
  23. else if (typeof(hubsize) === "object") {
  24. options = this._checkOptions(hubsize);
  25. hubsize = this._getHubSize();
  26. }
  27. let nodesToCluster = [];
  28. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  29. let node = this.body.nodes[this.body.nodeIndices[i]];
  30. if (node.edges.length >= hubsize) {
  31. nodesToCluster.push(node.id);
  32. }
  33. }
  34. for (let i = 0; i < nodesToCluster.length; i++) {
  35. this.clusterByConnection(nodesToCluster[i],options,true);
  36. }
  37. this.body.emitter.emit('_dataChanged');
  38. }
  39. /**
  40. * loop over all nodes, check if they adhere to the condition and cluster if needed.
  41. * @param options
  42. * @param refreshData
  43. */
  44. cluster(options = {}, refreshData = true) {
  45. if (options.joinCondition === undefined) {throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");}
  46. // check if the options object is fine, append if needed
  47. options = this._checkOptions(options);
  48. let childNodesObj = {};
  49. let childEdgesObj = {};
  50. // collect the nodes that will be in the cluster
  51. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  52. let nodeId = this.body.nodeIndices[i];
  53. let node = this.body.nodes[nodeId];
  54. let clonedOptions = NetworkUtil.cloneOptions(node);
  55. if (options.joinCondition(clonedOptions) === true) {
  56. childNodesObj[nodeId] = this.body.nodes[nodeId];
  57. // collect the nodes that will be in the cluster
  58. for (let i = 0; i < node.edges.length; i++) {
  59. let edge = node.edges[i];
  60. if (this.clusteredEdges[edge.id] === undefined) {
  61. childEdgesObj[edge.id] = edge;
  62. }
  63. }
  64. }
  65. }
  66. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  67. }
  68. /**
  69. * Cluster all nodes in the network that have only X edges
  70. * @param edgeCount
  71. * @param options
  72. * @param refreshData
  73. */
  74. clusterByEdgeCount(edgeCount, options, refreshData = true) {
  75. options = this._checkOptions(options);
  76. let clusters = [];
  77. let usedNodes = {};
  78. let edge, edges, node, nodeId, relevantEdgeCount;
  79. // collect the nodes that will be in the cluster
  80. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  81. let childNodesObj = {};
  82. let childEdgesObj = {};
  83. nodeId = this.body.nodeIndices[i];
  84. // if this node is already used in another cluster this session, we do not have to re-evaluate it.
  85. if (usedNodes[nodeId] === undefined) {
  86. relevantEdgeCount = 0;
  87. node = this.body.nodes[nodeId];
  88. edges = [];
  89. for (let j = 0; j < node.edges.length; j++) {
  90. edge = node.edges[j];
  91. if (this.clusteredEdges[edge.id] === undefined) {
  92. if (edge.toId !== edge.fromId) {
  93. relevantEdgeCount++;
  94. }
  95. edges.push(edge);
  96. }
  97. }
  98. // this node qualifies, we collect its neighbours to start the clustering process.
  99. if (relevantEdgeCount === edgeCount) {
  100. let gatheringSuccessful = true;
  101. for (let j = 0; j < edges.length; j++) {
  102. edge = edges[j];
  103. let childNodeId = this._getConnectedId(edge, nodeId);
  104. // add the nodes to the list by the join condition.
  105. if (options.joinCondition === undefined) {
  106. childEdgesObj[edge.id] = edge;
  107. childNodesObj[nodeId] = this.body.nodes[nodeId];
  108. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  109. usedNodes[nodeId] = true;
  110. }
  111. else {
  112. let clonedOptions = NetworkUtil.cloneOptions(this.body.nodes[nodeId]);
  113. if (options.joinCondition(clonedOptions) === true) {
  114. childEdgesObj[edge.id] = edge;
  115. childNodesObj[nodeId] = this.body.nodes[nodeId];
  116. usedNodes[nodeId] = true;
  117. }
  118. else {
  119. // this node does not qualify after all.
  120. gatheringSuccessful = false;
  121. break;
  122. }
  123. }
  124. }
  125. // add to the cluster queue
  126. if (Object.keys(childNodesObj).length > 0 && Object.keys(childEdgesObj).length > 0 && gatheringSuccessful === true) {
  127. clusters.push({nodes: childNodesObj, edges: childEdgesObj})
  128. }
  129. }
  130. }
  131. }
  132. for (let i = 0; i < clusters.length; i++) {
  133. this._cluster(clusters[i].nodes, clusters[i].edges, options, false)
  134. }
  135. if (refreshData === true) {
  136. this.body.emitter.emit('_dataChanged');
  137. }
  138. }
  139. /**
  140. * Cluster all nodes in the network that have only 1 edge
  141. * @param options
  142. * @param refreshData
  143. */
  144. clusterOutliers(options, refreshData = true) {
  145. this.clusterByEdgeCount(1,options,refreshData);
  146. }
  147. /**
  148. * Cluster all nodes in the network that have only 2 edge
  149. * @param options
  150. * @param refreshData
  151. */
  152. clusterBridges(options, refreshData = true) {
  153. this.clusterByEdgeCount(2,options,refreshData);
  154. }
  155. /**
  156. * suck all connected nodes of a node into the node.
  157. * @param nodeId
  158. * @param options
  159. * @param refreshData
  160. */
  161. clusterByConnection(nodeId, options, refreshData = true) {
  162. // kill conditions
  163. if (nodeId === undefined) {throw new Error("No nodeId supplied to clusterByConnection!");}
  164. if (this.body.nodes[nodeId] === undefined) {throw new Error("The nodeId given to clusterByConnection does not exist!");}
  165. let node = this.body.nodes[nodeId];
  166. options = this._checkOptions(options, node);
  167. if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x;}
  168. if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y;}
  169. if (options.clusterNodeProperties.fixed === undefined) {
  170. options.clusterNodeProperties.fixed = {};
  171. options.clusterNodeProperties.fixed.x = node.options.fixed.x;
  172. options.clusterNodeProperties.fixed.y = node.options.fixed.y;
  173. }
  174. let childNodesObj = {};
  175. let childEdgesObj = {};
  176. let parentNodeId = node.id;
  177. let parentClonedOptions = NetworkUtil.cloneOptions(node);
  178. childNodesObj[parentNodeId] = node;
  179. // collect the nodes that will be in the cluster
  180. for (let i = 0; i < node.edges.length; i++) {
  181. let edge = node.edges[i];
  182. if (this.clusteredEdges[edge.id] === undefined) {
  183. let childNodeId = this._getConnectedId(edge, parentNodeId);
  184. // if the child node is not in a cluster
  185. if (this.clusteredNodes[childNodeId] === undefined) {
  186. if (childNodeId !== parentNodeId) {
  187. if (options.joinCondition === undefined) {
  188. childEdgesObj[edge.id] = edge;
  189. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  190. }
  191. else {
  192. // clone the options and insert some additional parameters that could be interesting.
  193. let childClonedOptions = NetworkUtil.cloneOptions(this.body.nodes[childNodeId]);
  194. if (options.joinCondition(parentClonedOptions, childClonedOptions) === true) {
  195. childEdgesObj[edge.id] = edge;
  196. childNodesObj[childNodeId] = this.body.nodes[childNodeId];
  197. }
  198. }
  199. }
  200. else {
  201. // swallow the edge if it is self-referencing.
  202. childEdgesObj[edge.id] = edge;
  203. }
  204. }
  205. }
  206. }
  207. this._cluster(childNodesObj, childEdgesObj, options, refreshData);
  208. }
  209. /**
  210. * This function creates the edges that will be attached to the cluster
  211. * It looks for edges that are connected to the nodes from the "outside' of the cluster.
  212. *
  213. * @param childNodesObj
  214. * @param childEdgesObj
  215. * @param clusterNodeProperties
  216. * @param clusterEdgeProperties
  217. * @private
  218. */
  219. _createClusterEdges (childNodesObj, childEdgesObj, clusterNodeProperties, clusterEdgeProperties) {
  220. let edge, childNodeId, childNode, toId, fromId, otherNodeId;
  221. // loop over all child nodes and their edges to find edges going out of the cluster
  222. // these edges will be replaced by clusterEdges.
  223. let childKeys = Object.keys(childNodesObj);
  224. let createEdges = [];
  225. for (let i = 0; i < childKeys.length; i++) {
  226. childNodeId = childKeys[i];
  227. childNode = childNodesObj[childNodeId];
  228. // construct new edges from the cluster to others
  229. for (let j = 0; j < childNode.edges.length; j++) {
  230. edge = childNode.edges[j];
  231. // we only handle edges that are visible to the system, not the disabled ones from the clustering process.
  232. if (this.clusteredEdges[edge.id] === undefined) {
  233. // self-referencing edges will be added to the "hidden" list
  234. if (edge.toId == edge.fromId) {
  235. childEdgesObj[edge.id] = edge;
  236. }
  237. else {
  238. // set up the from and to.
  239. if (edge.toId == childNodeId) { // this is a double equals because ints and strings can be interchanged here.
  240. toId = clusterNodeProperties.id;
  241. fromId = edge.fromId;
  242. otherNodeId = fromId;
  243. }
  244. else {
  245. toId = edge.toId;
  246. fromId = clusterNodeProperties.id;
  247. otherNodeId = toId;
  248. }
  249. }
  250. // Only edges from the cluster outwards are being replaced.
  251. if (childNodesObj[otherNodeId] === undefined) {
  252. createEdges.push({edge: edge, fromId: fromId, toId: toId});
  253. }
  254. }
  255. }
  256. }
  257. // here we actually create the replacement edges. We could not do this in the loop above as the creation process
  258. // would add an edge to the edges array we are iterating over.
  259. for (let j = 0; j < createEdges.length; j++) {
  260. let edge = createEdges[j].edge;
  261. // copy the options of the edge we will replace
  262. let clonedOptions = NetworkUtil.cloneOptions(edge, 'edge');
  263. // make sure the properties of clusterEdges are superimposed on it
  264. util.deepExtend(clonedOptions, clusterEdgeProperties);
  265. // set up the edge
  266. clonedOptions.from = createEdges[j].fromId;
  267. clonedOptions.to = createEdges[j].toId;
  268. clonedOptions.id = 'clusterEdge:' + util.randomUUID();
  269. //clonedOptions.id = '(cf: ' + createEdges[j].fromId + " to: " + createEdges[j].toId + ")" + Math.random();
  270. // create the edge and give a reference to the one it replaced.
  271. let newEdge = this.body.functions.createEdge(clonedOptions);
  272. newEdge.clusteringEdgeReplacingId = edge.id;
  273. // also reference the new edge in the old edge
  274. this.body.edges[edge.id].edgeReplacedById = newEdge.id;
  275. // connect the edge.
  276. this.body.edges[newEdge.id] = newEdge;
  277. newEdge.connect();
  278. // hide the replaced edge
  279. this._backupEdgeOptions(edge);
  280. edge.setOptions({physics:false, hidden: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, don't 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 cluster properties 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 position 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. // cache the options before changing
  376. this._backupEdgeOptions(edge);
  377. // disable physics and hide the edge
  378. edge.setOptions({physics:false, hidden:true});
  379. }
  380. }
  381. }
  382. // disable the childNodes
  383. for (let nodeId in childNodesObj) {
  384. if (childNodesObj.hasOwnProperty(nodeId)) {
  385. this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties.id, node: this.body.nodes[nodeId]};
  386. this.body.nodes[nodeId].setOptions({hidden:true, physics:false});
  387. }
  388. }
  389. // set ID to undefined so no duplicates arise
  390. clusterNodeProperties.id = undefined;
  391. // wrap up
  392. if (refreshData === true) {
  393. this.body.emitter.emit('_dataChanged');
  394. }
  395. }
  396. _backupEdgeOptions(edge) {
  397. if (this.clusteredEdges[edge.id] === undefined) {
  398. this.clusteredEdges[edge.id] = {physics: edge.options.physics, hidden: edge.options.hidden};
  399. }
  400. }
  401. _restoreEdge(edge) {
  402. let originalOptions = this.clusteredEdges[edge.id];
  403. if (originalOptions !== undefined) {
  404. edge.setOptions({physics: originalOptions.physics, hidden: originalOptions.hidden});
  405. delete this.clusteredEdges[edge.id];
  406. }
  407. }
  408. /**
  409. * Check if a node is a cluster.
  410. * @param nodeId
  411. * @returns {*}
  412. */
  413. isCluster(nodeId) {
  414. if (this.body.nodes[nodeId] !== undefined) {
  415. return this.body.nodes[nodeId].isCluster === true;
  416. }
  417. else {
  418. console.log("Node does not exist.");
  419. return false;
  420. }
  421. }
  422. /**
  423. * get the position of the cluster node based on what's inside
  424. * @param {object} childNodesObj | object with node objects, id as keys
  425. * @returns {{x: number, y: number}}
  426. * @private
  427. */
  428. _getClusterPosition(childNodesObj) {
  429. let childKeys = Object.keys(childNodesObj);
  430. let minX = childNodesObj[childKeys[0]].x;
  431. let maxX = childNodesObj[childKeys[0]].x;
  432. let minY = childNodesObj[childKeys[0]].y;
  433. let maxY = childNodesObj[childKeys[0]].y;
  434. let node;
  435. for (let i = 1; i < childKeys.length; i++) {
  436. node = childNodesObj[childKeys[i]];
  437. minX = node.x < minX ? node.x : minX;
  438. maxX = node.x > maxX ? node.x : maxX;
  439. minY = node.y < minY ? node.y : minY;
  440. maxY = node.y > maxY ? node.y : maxY;
  441. }
  442. return {x: 0.5*(minX + maxX), y: 0.5*(minY + maxY)};
  443. }
  444. /**
  445. * Open a cluster by calling this function.
  446. * @param {String} clusterNodeId | the ID of the cluster node
  447. * @param {Boolean} refreshData | wrap up afterwards if not true
  448. */
  449. openCluster(clusterNodeId, options, refreshData = true) {
  450. // kill conditions
  451. if (clusterNodeId === undefined) {throw new Error("No clusterNodeId supplied to openCluster.");}
  452. if (this.body.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");}
  453. if (this.body.nodes[clusterNodeId].containedNodes === undefined) {
  454. console.log("The node:" + clusterNodeId + " is not a cluster.");
  455. return
  456. }
  457. let clusterNode = this.body.nodes[clusterNodeId];
  458. let containedNodes = clusterNode.containedNodes;
  459. let containedEdges = clusterNode.containedEdges;
  460. // allow the user to position the nodes after release.
  461. if (options !== undefined && options.releaseFunction !== undefined && typeof options.releaseFunction === 'function') {
  462. let positions = {};
  463. let clusterPosition = {x:clusterNode.x, y:clusterNode.y};
  464. for (let nodeId in containedNodes) {
  465. if (containedNodes.hasOwnProperty(nodeId)) {
  466. let containedNode = this.body.nodes[nodeId];
  467. positions[nodeId] = {x: containedNode.x, y: containedNode.y};
  468. }
  469. }
  470. let newPositions = options.releaseFunction(clusterPosition, positions);
  471. for (let nodeId in containedNodes) {
  472. if (containedNodes.hasOwnProperty(nodeId)) {
  473. let containedNode = this.body.nodes[nodeId];
  474. if (newPositions[nodeId] !== undefined) {
  475. containedNode.x = (newPositions[nodeId].x === undefined ? clusterNode.x : newPositions[nodeId].x);
  476. containedNode.y = (newPositions[nodeId].y === undefined ? clusterNode.y : newPositions[nodeId].y);
  477. }
  478. }
  479. }
  480. }
  481. else {
  482. // copy the position from the cluster
  483. for (let nodeId in containedNodes) {
  484. if (containedNodes.hasOwnProperty(nodeId)) {
  485. let containedNode = this.body.nodes[nodeId];
  486. containedNode = containedNodes[nodeId];
  487. // inherit position
  488. if (containedNode.options.fixed.x === false) {containedNode.x = clusterNode.x;}
  489. if (containedNode.options.fixed.y === false) {containedNode.y = clusterNode.y;}
  490. }
  491. }
  492. }
  493. // release nodes
  494. for (let nodeId in containedNodes) {
  495. if (containedNodes.hasOwnProperty(nodeId)) {
  496. let containedNode = this.body.nodes[nodeId];
  497. // inherit speed
  498. containedNode.vx = clusterNode.vx;
  499. containedNode.vy = clusterNode.vy;
  500. // we use these methods to avoid re-instantiating the shape, which happens with setOptions.
  501. containedNode.setOptions({hidden:false, physics:true});
  502. delete this.clusteredNodes[nodeId];
  503. }
  504. }
  505. // copy the clusterNode edges because we cannot iterate over an object that we add or remove from.
  506. let edgesToBeDeleted = [];
  507. for (let i = 0; i < clusterNode.edges.length; i++) {
  508. edgesToBeDeleted.push(clusterNode.edges[i]);
  509. }
  510. // actually handling the deleting.
  511. for (let i = 0; i < edgesToBeDeleted.length; i++) {
  512. let edge = edgesToBeDeleted[i];
  513. let otherNodeId = this._getConnectedId(edge, clusterNodeId);
  514. // if the other node is in another cluster, we transfer ownership of this edge to the other cluster
  515. if (this.clusteredNodes[otherNodeId] !== undefined) {
  516. // transfer ownership:
  517. let otherCluster = this.body.nodes[this.clusteredNodes[otherNodeId].clusterId];
  518. let transferEdge = this.body.edges[edge.clusteringEdgeReplacingId];
  519. if (transferEdge !== undefined) {
  520. otherCluster.containedEdges[transferEdge.id] = transferEdge;
  521. // delete local reference
  522. delete containedEdges[transferEdge.id];
  523. // create new cluster edge from the otherCluster:
  524. // get to and from
  525. let fromId = transferEdge.fromId;
  526. let toId = transferEdge.toId;
  527. if (transferEdge.toId == otherNodeId) {
  528. toId = this.clusteredNodes[otherNodeId].clusterId;
  529. }
  530. else {
  531. fromId = this.clusteredNodes[otherNodeId].clusterId;
  532. }
  533. // clone the options and apply the cluster options to them
  534. let clonedOptions = NetworkUtil.cloneOptions(transferEdge, 'edge');
  535. util.deepExtend(clonedOptions, otherCluster.clusterEdgeProperties);
  536. // apply the edge specific options to it.
  537. let id = 'clusterEdge:' + util.randomUUID();
  538. util.deepExtend(clonedOptions, {from: fromId, to: toId, hidden: false, physics: true, id: id});
  539. // create it
  540. let newEdge = this.body.functions.createEdge(clonedOptions);
  541. newEdge.clusteringEdgeReplacingId = transferEdge.id;
  542. this.body.edges[id] = newEdge;
  543. this.body.edges[id].connect();
  544. }
  545. }
  546. else {
  547. let replacedEdge = this.body.edges[edge.clusteringEdgeReplacingId];
  548. if (replacedEdge !== undefined) {
  549. this._restoreEdge(replacedEdge);
  550. }
  551. }
  552. edge.cleanup();
  553. // this removes the edge from node.edges, which is why edgeIds is formed
  554. edge.disconnect();
  555. delete this.body.edges[edge.id];
  556. }
  557. // handle the releasing of the edges
  558. for (let edgeId in containedEdges) {
  559. if (containedEdges.hasOwnProperty(edgeId)) {
  560. this._restoreEdge(containedEdges[edgeId]);
  561. }
  562. }
  563. // remove clusterNode
  564. delete this.body.nodes[clusterNodeId];
  565. if (refreshData === true) {
  566. this.body.emitter.emit('_dataChanged');
  567. }
  568. }
  569. getNodesInCluster(clusterId) {
  570. let nodesArray = [];
  571. if (this.isCluster(clusterId) === true) {
  572. let containedNodes = this.body.nodes[clusterId].containedNodes;
  573. for (let nodeId in containedNodes) {
  574. if (containedNodes.hasOwnProperty(nodeId)) {
  575. nodesArray.push(this.body.nodes[nodeId].id)
  576. }
  577. }
  578. }
  579. return nodesArray;
  580. }
  581. /**
  582. * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node
  583. * @param nodeId
  584. * @returns {Array}
  585. */
  586. findNode(nodeId) {
  587. let stack = [];
  588. let max = 100;
  589. let counter = 0;
  590. while (this.clusteredNodes[nodeId] !== undefined && counter < max) {
  591. stack.push(this.body.nodes[nodeId].id);
  592. nodeId = this.clusteredNodes[nodeId].clusterId;
  593. counter++;
  594. }
  595. stack.push(this.body.nodes[nodeId].id);
  596. stack.reverse();
  597. return stack;
  598. }
  599. /**
  600. * Using a clustered nodeId, update with the new options
  601. * @param clusteredNodeId
  602. * @param {object} newOptions
  603. */
  604. updateClusteredNode(clusteredNodeId, newOptions) {
  605. if (clusteredNodeId === undefined) {throw new Error("No clusteredNodeId supplied to updateClusteredNode.");}
  606. if (newOptions === undefined) {throw new Error("No newOptions supplied to updateClusteredNode.");}
  607. if (this.body.nodes[clusteredNodeId] === undefined) {throw new Error("The clusteredNodeId supplied to updateClusteredNode does not exist.");}
  608. this.body.nodes[clusteredNodeId].setOptions(newOptions);
  609. this.body.emitter.emit('_dataChanged');
  610. }
  611. /**
  612. * Using a base edgeId, update all related clustered edges with the new options
  613. * @param startEdgeId
  614. * @param {object} newOptions
  615. */
  616. updateEdge(startEdgeId, newOptions) {
  617. if (startEdgeId === undefined) {throw new Error("No startEdgeId supplied to updateEdge.");}
  618. if (newOptions === undefined) {throw new Error("No newOptions supplied to updateEdge.");}
  619. if (this.body.edges[startEdgeId] === undefined) {throw new Error("The startEdgeId supplied to updateEdge does not exist.");}
  620. let allEdgeIds = this.getClusteredEdges(startEdgeId);
  621. for (let i = 0; i < allEdgeIds.length; i++) {
  622. var edge = this.body.edges[allEdgeIds[i]];
  623. edge.setOptions(newOptions);
  624. }
  625. this.body.emitter.emit('_dataChanged');
  626. }
  627. /**
  628. * Get a stack of clusterEdgeId's (+base edgeid) that a base edge is the same as. cluster edge C -> cluster edge B -> cluster edge A -> base edge(edgeId)
  629. * @param edgeId
  630. * @returns {Array}
  631. */
  632. getClusteredEdges(edgeId) {
  633. let stack = [];
  634. let max = 100;
  635. let counter = 0;
  636. while (edgeId !== undefined && this.body.edges[edgeId] !== undefined && counter < max) {
  637. stack.push(this.body.edges[edgeId].id);
  638. edgeId = this.body.edges[edgeId].edgeReplacedById;
  639. counter++;
  640. }
  641. stack.reverse();
  642. return stack;
  643. }
  644. /**
  645. * Get the base edge id of clusterEdgeId. cluster edge (clusteredEdgeId) -> cluster edge B -> cluster edge C -> base edge
  646. * @param clusteredEdgeId
  647. * @returns baseEdgeId
  648. */
  649. getBaseEdge(clusteredEdgeId) {
  650. let baseEdgeId = clusteredEdgeId;
  651. let max = 100;
  652. let counter = 0;
  653. while (clusteredEdgeId !== undefined && this.body.edges[clusteredEdgeId] !== undefined && counter < max) {
  654. clusteredEdgeId = this.body.edges[clusteredEdgeId].clusteringEdgeReplacingId;
  655. counter++;
  656. if (clusteredEdgeId !== undefined) {
  657. baseEdgeId = clusteredEdgeId;
  658. }
  659. }
  660. return baseEdgeId;
  661. }
  662. /**
  663. * Get the Id the node is connected to
  664. * @param edge
  665. * @param nodeId
  666. * @returns {*}
  667. * @private
  668. */
  669. _getConnectedId(edge, nodeId) {
  670. if (edge.toId != nodeId) {
  671. return edge.toId;
  672. }
  673. else if (edge.fromId != nodeId) {
  674. return edge.fromId;
  675. }
  676. else {
  677. return edge.fromId;
  678. }
  679. }
  680. /**
  681. * We determine how many connections denote an important hub.
  682. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  683. *
  684. * @private
  685. */
  686. _getHubSize() {
  687. let average = 0;
  688. let averageSquared = 0;
  689. let hubCounter = 0;
  690. let largestHub = 0;
  691. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  692. let node = this.body.nodes[this.body.nodeIndices[i]];
  693. if (node.edges.length > largestHub) {
  694. largestHub = node.edges.length;
  695. }
  696. average += node.edges.length;
  697. averageSquared += Math.pow(node.edges.length,2);
  698. hubCounter += 1;
  699. }
  700. average = average / hubCounter;
  701. averageSquared = averageSquared / hubCounter;
  702. let variance = averageSquared - Math.pow(average,2);
  703. let standardDeviation = Math.sqrt(variance);
  704. let hubThreshold = Math.floor(average + 2*standardDeviation);
  705. // always have at least one to cluster
  706. if (hubThreshold > largestHub) {
  707. hubThreshold = largestHub;
  708. }
  709. return hubThreshold;
  710. };
  711. }
  712. export default ClusterEngine;