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.

628 lines
19 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. var Node = require('../Node');
  2. var Edge = require('../Edge');
  3. var util = require('../../util');
  4. /**
  5. *
  6. * @param hubsize
  7. * @param options
  8. */
  9. exports.clusterByConnectionCount = function(hubsize, options) {
  10. if (hubsize === undefined) {
  11. hubsize = this._getHubSize();
  12. }
  13. else if (tyepof(hubsize) == "object") {
  14. options = this._checkOptions(hubsize);
  15. hubsize = this._getHubSize();
  16. }
  17. var nodesToCluster = [];
  18. for (var i = 0; i < this.nodeIndices.length; i++) {
  19. var node = this.nodes[this.nodeIndices[i]];
  20. if (node.edges.length >= hubsize) {
  21. nodesToCluster.push(node.id);
  22. }
  23. }
  24. for (var i = 0; i < nodesToCluster.length; i++) {
  25. var node = this.nodes[nodesToCluster[i]];
  26. this.clusterByConnection(node,options,{},{},true);
  27. }
  28. this._wrapUp();
  29. }
  30. /**
  31. * loop over all nodes, check if they adhere to the condition and cluster if needed.
  32. * @param options
  33. * @param doNotUpdateCalculationNodes
  34. */
  35. exports.clusterByNodeData = function(options, doNotUpdateCalculationNodes) {
  36. if (options === undefined) {throw new Error("Cannot call clusterByNodeData without options.");}
  37. if (options.joinCondition === undefined) {throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");}
  38. // check if the options object is fine, append if needed
  39. options = this._checkOptions(options);
  40. var childNodesObj = {};
  41. var childEdgesObj = {}
  42. // collect the nodes that will be in the cluster
  43. for (var i = 0; i < this.nodeIndices.length; i++) {
  44. var nodeId = this.nodeIndices[i];
  45. var clonedOptions = this._cloneOptions(nodeId);
  46. if (options.joinCondition(clonedOptions) == true) {
  47. childNodesObj[nodeId] = this.nodes[nodeId];
  48. }
  49. }
  50. this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes);
  51. }
  52. /**
  53. * Cluster all nodes in the network that have only 1 edge
  54. * @param options
  55. * @param doNotUpdateCalculationNodes
  56. */
  57. exports.clusterOutliers = function(options, doNotUpdateCalculationNodes) {
  58. options = this._checkOptions(options);
  59. var clusters = []
  60. // collect the nodes that will be in the cluster
  61. for (var i = 0; i < this.nodeIndices.length; i++) {
  62. var childNodesObj = {};
  63. var childEdgesObj = {};
  64. var nodeId = this.nodeIndices[i];
  65. if (this.nodes[nodeId].edges.length == 1) {
  66. var edge = this.nodes[nodeId].edges[0];
  67. var childNodeId = this._getConnectedId(edge, nodeId);
  68. if (childNodeId != nodeId) {
  69. if (options.joinCondition === undefined) {
  70. childNodesObj[nodeId] = this.nodes[nodeId];
  71. childNodesObj[childNodeId] = this.nodes[childNodeId];
  72. }
  73. else {
  74. var clonedOptions = this._cloneOptions(nodeId);
  75. if (options.joinCondition(clonedOptions) == true) {
  76. childNodesObj[nodeId] = this.nodes[nodeId];
  77. }
  78. clonedOptions = this._cloneOptions(childNodeId);
  79. if (options.joinCondition(clonedOptions) == true) {
  80. childNodesObj[childNodeId] = this.nodes[childNodeId];
  81. }
  82. }
  83. clusters.push({nodes:childNodesObj, edges:childEdgesObj})
  84. }
  85. }
  86. }
  87. for (var i = 0; i < clusters.length; i++) {
  88. this._cluster(clusters[i].nodes, clusters[i].edges, options, true)
  89. }
  90. if (doNotUpdateCalculationNodes !== true) {
  91. this._wrapUp();
  92. }
  93. }
  94. /**
  95. *
  96. * @param nodeId
  97. * @param options
  98. * @param doNotUpdateCalculationNodes
  99. */
  100. exports.clusterByConnection = function(nodeId, options, doNotUpdateCalculationNodes) {
  101. // kill conditions
  102. if (nodeId === undefined) {throw new Error("No nodeId supplied to clusterByConnection!");}
  103. if (this.nodes[nodeId] === undefined) {throw new Error("The nodeId given to clusterByConnection does not exist!");}
  104. var node = this.nodes[nodeId];
  105. options = this._checkOptions(options, node);
  106. if (options.clusterNodeProperties.x === undefined) {options.clusterNodeProperties.x = node.x; options.clusterNodeProperties.allowedToMoveX = !node.xFixed;}
  107. if (options.clusterNodeProperties.y === undefined) {options.clusterNodeProperties.y = node.y; options.clusterNodeProperties.allowedToMoveY = !node.yFixed;}
  108. var childNodesObj = {};
  109. var childEdgesObj = {}
  110. var parentNodeId = node.id;
  111. var parentClonedOptions = this._cloneOptions(parentNodeId);
  112. childNodesObj[parentNodeId] = node;
  113. // collect the nodes that will be in the cluster
  114. for (var i = 0; i < node.edges.length; i++) {
  115. var edge = node.edges[i];
  116. var childNodeId = this._getConnectedId(edge, parentNodeId);
  117. if (childNodeId !== parentNodeId) {
  118. if (options.joinCondition === undefined) {
  119. childEdgesObj[edge.id] = edge;
  120. childNodesObj[childNodeId] = this.nodes[childNodeId];
  121. }
  122. else {
  123. // clone the options and insert some additional parameters that could be interesting.
  124. var childClonedOptions = this._cloneOptions(childNodeId);
  125. if (options.joinCondition(parentClonedOptions, childClonedOptions) == true) {
  126. childEdgesObj[edge.id] = edge;
  127. childNodesObj[childNodeId] = this.nodes[childNodeId];
  128. }
  129. }
  130. }
  131. else {
  132. childEdgesObj[edge.id] = edge;
  133. }
  134. }
  135. this._cluster(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes);
  136. }
  137. /**
  138. * This returns a clone of the options or properties of the edge or node to be used for construction of new edges or check functions for new nodes.
  139. * @param objId
  140. * @param type
  141. * @returns {{}}
  142. * @private
  143. */
  144. exports._cloneOptions = function(objId, type) {
  145. var clonedOptions = {};
  146. if (type === undefined || type == 'node') {
  147. util.deepExtend(clonedOptions, this.nodes[objId].options, true);
  148. util.deepExtend(clonedOptions, this.nodes[objId].properties, true);
  149. clonedOptions.amountOfConnections = this.nodes[objId].edges.length;
  150. }
  151. else {
  152. util.deepExtend(clonedOptions, this.edges[objId].properties, true);
  153. }
  154. return clonedOptions;
  155. }
  156. /**
  157. * This function creates the edges that will be attached to the cluster.
  158. *
  159. * @param childNodesObj
  160. * @param childEdgesObj
  161. * @param newEdges
  162. * @param options
  163. * @private
  164. */
  165. exports._createClusterEdges = function (childNodesObj, childEdgesObj, newEdges, options) {
  166. var edge, childNodeId, childNode;
  167. var childKeys = Object.keys(childNodesObj);
  168. for (var i = 0; i < childKeys.length; i++) {
  169. childNodeId = childKeys[i];
  170. childNode = childNodesObj[childNodeId];
  171. // mark all edges for removal from global and construct new edges from the cluster to others
  172. for (var j = 0; j < childNode.edges.length; j++) {
  173. edge = childNode.edges[j];
  174. childEdgesObj[edge.id] = edge;
  175. var otherNodeId = edge.toId;
  176. var otherOnTo = true;
  177. if (edge.toId != childNodeId) {
  178. otherNodeId = edge.toId;
  179. otherOnTo = true;
  180. }
  181. else if (edge.fromId != childNodeId) {
  182. otherNodeId = edge.fromId;
  183. otherOnTo = false;
  184. }
  185. if (childNodesObj[otherNodeId] === undefined) {
  186. var clonedOptions = this._cloneOptions(edge.id, 'edge');
  187. util.deepExtend(clonedOptions, options.clusterEdgeProperties);
  188. // avoid forcing the default color on edges that inherit color
  189. if (edge.properties.color === undefined) {
  190. delete clonedOptions.color;
  191. }
  192. if (otherOnTo === true) {
  193. clonedOptions.from = options.clusterNodeProperties.id;
  194. clonedOptions.to = otherNodeId;
  195. }
  196. else {
  197. clonedOptions.from = otherNodeId;
  198. clonedOptions.to = options.clusterNodeProperties.id;
  199. }
  200. clonedOptions.id = 'clusterEdge:' + util.randomUUID();
  201. newEdges.push(new Edge(clonedOptions,this,this.constants))
  202. }
  203. }
  204. }
  205. }
  206. /**
  207. * This function checks the options that can be supplied to the different cluster functions
  208. * for certain fields and inserts defaults if needed
  209. * @param options
  210. * @returns {*}
  211. * @private
  212. */
  213. exports._checkOptions = function(options) {
  214. if (options === undefined) {options = {};}
  215. if (options.clusterEdgeProperties === undefined) {options.clusterEdgeProperties = {};}
  216. if (options.clusterNodeProperties === undefined) {options.clusterNodeProperties = {};}
  217. return options;
  218. }
  219. /**
  220. *
  221. * @param {Object} childNodesObj | object with node objects, id as keys, same as childNodes except it also contains a source node
  222. * @param {Object} childEdgesObj | object with edge objects, id as keys
  223. * @param {Array} options | object with {clusterNodeProperties, clusterEdgeProperties, processProperties}
  224. * @param {Boolean} doNotUpdateCalculationNodes | when true, do not wrap up
  225. * @private
  226. */
  227. exports._cluster = function(childNodesObj, childEdgesObj, options, doNotUpdateCalculationNodes) {
  228. // kill condition: no children so cant cluster
  229. if (Object.keys(childNodesObj).length == 0) {return;}
  230. // check if we have an unique id;
  231. if (options.clusterNodeProperties.id === undefined) {options.clusterNodeProperties.id = 'cluster:' + util.randomUUID();}
  232. var clusterId = options.clusterNodeProperties.id;
  233. // create the new edges that will connect to the cluster
  234. var newEdges = [];
  235. this._createClusterEdges(childNodesObj, childEdgesObj, newEdges, options);
  236. // construct the clusterNodeProperties
  237. var clusterNodeProperties = options.clusterNodeProperties;
  238. if (options.processProperties !== undefined) {
  239. // get the childNode options
  240. var childNodesOptions = [];
  241. for (var nodeId in childNodesObj) {
  242. var clonedOptions = this._cloneOptions(nodeId);
  243. childNodesOptions.push(clonedOptions);
  244. }
  245. // get clusterproperties based on childNodes
  246. var childEdgesOptions = [];
  247. for (var edgeId in childEdgesObj) {
  248. var clonedOptions = this._cloneOptions(edgeId, 'edge');
  249. childEdgesOptions.push(clonedOptions);
  250. }
  251. clusterNodeProperties = options.processProperties(clusterNodeProperties, childNodesOptions, childEdgesOptions);
  252. if (!clusterNodeProperties) {
  253. throw new Error("The processClusterProperties function does not return properties!");
  254. }
  255. }
  256. if (clusterNodeProperties.label === undefined) {
  257. clusterNodeProperties.label = 'cluster';
  258. }
  259. // give the clusterNode a postion if it does not have one.
  260. var pos = undefined
  261. if (clusterNodeProperties.x === undefined) {
  262. pos = this._getClusterPosition(childNodesObj);
  263. clusterNodeProperties.x = pos.x;
  264. clusterNodeProperties.allowedToMoveX = true;
  265. }
  266. if (clusterNodeProperties.x === undefined) {
  267. if (pos === undefined) {
  268. pos = this._getClusterPosition(childNodesObj);
  269. }
  270. clusterNodeProperties.y = pos.y;
  271. clusterNodeProperties.allowedToMoveY = true;
  272. }
  273. // force the ID to remain the same
  274. clusterNodeProperties.id = clusterId;
  275. // create the clusterNode
  276. var clusterNode = new Node(clusterNodeProperties, this.images, this.groups, this.constants);
  277. clusterNode.isCluster = true;
  278. clusterNode.containedNodes = childNodesObj;
  279. clusterNode.containedEdges = childEdgesObj;
  280. // delete contained edges from global
  281. for (var edgeId in childEdgesObj) {
  282. if (childEdgesObj.hasOwnProperty(edgeId)) {
  283. if (this.edges[edgeId] !== undefined) {
  284. if (this.edges[edgeId].via !== null) {
  285. var viaId = this.edges[edgeId].via.id;
  286. if (viaId) {
  287. this.edges[edgeId].via = null
  288. delete this.sectors['support']['nodes'][viaId];
  289. }
  290. }
  291. this.edges[edgeId].disconnect();
  292. delete this.edges[edgeId];
  293. }
  294. }
  295. }
  296. // remove contained nodes from global
  297. for (var nodeId in childNodesObj) {
  298. if (childNodesObj.hasOwnProperty(nodeId)) {
  299. this.clusteredNodes[nodeId] = {clusterId:clusterNodeProperties.id, node: this.nodes[nodeId]};
  300. delete this.nodes[nodeId];
  301. }
  302. }
  303. // finally put the cluster node into global
  304. this.nodes[clusterNodeProperties.id] = clusterNode;
  305. // push new edges to global
  306. for (var i = 0; i < newEdges.length; i++) {
  307. this.edges[newEdges[i].id] = newEdges[i];
  308. this.edges[newEdges[i].id].connect();
  309. }
  310. // create bezier nodes for smooth curves if needed
  311. this._createBezierNodes(newEdges);
  312. // set ID to undefined so no duplicates arise
  313. clusterNodeProperties.id = undefined;
  314. // wrap up
  315. if (doNotUpdateCalculationNodes !== true) {
  316. this._wrapUp();
  317. }
  318. }
  319. /**
  320. * Check if a node is a cluster.
  321. * @param nodeId
  322. * @returns {*}
  323. */
  324. exports.isCluster = function(nodeId) {
  325. if (this.nodes[nodeId] !== undefined) {
  326. return this.nodes[nodeId].isCluster;
  327. }
  328. else {
  329. console.log("Node does not exist.")
  330. return false;
  331. }
  332. }
  333. /**
  334. * get the position of the cluster node based on what's inside
  335. * @param {object} childNodesObj | object with node objects, id as keys
  336. * @returns {{x: number, y: number}}
  337. * @private
  338. */
  339. exports._getClusterPosition = function(childNodesObj) {
  340. var childKeys = Object.keys(childNodesObj);
  341. var minX = childNodesObj[childKeys[0]].x;
  342. var maxX = childNodesObj[childKeys[0]].x;
  343. var minY = childNodesObj[childKeys[0]].y;
  344. var maxY = childNodesObj[childKeys[0]].y;
  345. var node;
  346. for (var i = 0; i < childKeys.lenght; i++) {
  347. node = childNodesObj[childKeys[0]];
  348. minX = node.x < minX ? node.x : minX;
  349. maxX = node.x > maxX ? node.x : maxX;
  350. minY = node.y < minY ? node.y : minY;
  351. maxY = node.y > maxY ? node.y : maxY;
  352. }
  353. return {x: 0.5*(minX + maxX), y: 0.5*(minY + maxY)};
  354. }
  355. /**
  356. * Open a cluster by calling this function.
  357. * @param {String} clusterNodeId | the ID of the cluster node
  358. * @param {Boolean} doNotUpdateCalculationNodes | wrap up afterwards if not true
  359. */
  360. exports.openCluster = function(clusterNodeId, doNotUpdateCalculationNodes) {
  361. // kill conditions
  362. if (clusterNodeId === undefined) {throw new Error("No clusterNodeId supplied to openCluster.");}
  363. if (this.nodes[clusterNodeId] === undefined) {throw new Error("The clusterNodeId supplied to openCluster does not exist.");}
  364. if (this.nodes[clusterNodeId].containedNodes === undefined) {console.log("The node:" + clusterNodeId + " is not a cluster."); return};
  365. var node = this.nodes[clusterNodeId];
  366. var containedNodes = node.containedNodes;
  367. var containedEdges = node.containedEdges;
  368. // release nodes
  369. for (var nodeId in containedNodes) {
  370. if (containedNodes.hasOwnProperty(nodeId)) {
  371. this.nodes[nodeId] = containedNodes[nodeId];
  372. // inherit position
  373. this.nodes[nodeId].x = node.x;
  374. this.nodes[nodeId].y = node.y;
  375. // inherit speed
  376. this.nodes[nodeId].vx = node.vx;
  377. this.nodes[nodeId].vy = node.vy;
  378. delete this.clusteredNodes[nodeId];
  379. }
  380. }
  381. // release edges
  382. for (var edgeId in containedEdges) {
  383. if (containedEdges.hasOwnProperty(edgeId)) {
  384. this.edges[edgeId] = containedEdges[edgeId];
  385. this.edges[edgeId].connect();
  386. var edge = this.edges[edgeId];
  387. if (edge.connected === false) {
  388. if (this.clusteredNodes[edge.fromId] !== undefined) {
  389. this._connectEdge(edge, edge.fromId, true);
  390. }
  391. if (this.clusteredNodes[edge.toId] !== undefined) {
  392. this._connectEdge(edge, edge.toId, false);
  393. }
  394. }
  395. }
  396. }
  397. this._createBezierNodes(containedEdges);
  398. var edgeIds = [];
  399. for (var i = 0; i < node.edges.length; i++) {
  400. edgeIds.push(node.edges[i].id);
  401. }
  402. // remove edges in clusterNode
  403. for (var i = 0; i < edgeIds.length; i++) {
  404. var edge = this.edges[edgeIds[i]];
  405. // if the edge should have been connected to a contained node
  406. if (edge.fromArray.length > 0 && edge.fromId == clusterNodeId) {
  407. // the node in the from array was contained in the cluster
  408. if (this.nodes[edge.fromArray[0].id] !== undefined) {
  409. this._connectEdge(edge, edge.fromArray[0].id, true);
  410. }
  411. }
  412. else if (edge.toArray.length > 0 && edge.toId == clusterNodeId) {
  413. // the node in the to array was contained in the cluster
  414. if (this.nodes[edge.toArray[0].id] !== undefined) {
  415. this._connectEdge(edge, edge.toArray[0].id, false);
  416. }
  417. }
  418. else {
  419. var edgeId = edgeIds[i];
  420. var viaId = this.edges[edgeId].via.id;
  421. if (viaId) {
  422. this.edges[edgeId].via = null
  423. delete this.sectors['support']['nodes'][viaId];
  424. }
  425. // this removes the edge from node.edges, which is why edgeIds is formed
  426. this.edges[edgeId].disconnect();
  427. delete this.edges[edgeId];
  428. }
  429. }
  430. // remove clusterNode
  431. delete this.nodes[clusterNodeId];
  432. if (doNotUpdateCalculationNodes !== true) {
  433. this._wrapUp();
  434. }
  435. }
  436. /**
  437. * Recalculate navigation nodes, color edges dirty, update nodes list etc.
  438. * @private
  439. */
  440. exports._wrapUp = function() {
  441. this._updateNodeIndexList();
  442. this._updateCalculationNodes();
  443. this._markAllEdgesAsDirty();
  444. if (this.initializing !== true) {
  445. this.moving = true;
  446. this.start();
  447. }
  448. }
  449. /**
  450. * Connect an edge that was previously contained from cluster A to cluster B if the node that it was originally connected to
  451. * is currently residing in cluster B
  452. * @param edge
  453. * @param nodeId
  454. * @param from
  455. * @private
  456. */
  457. exports._connectEdge = function(edge, nodeId, from) {
  458. var clusterStack = this._getClusterStack(nodeId);
  459. if (from == true) {
  460. edge.from = clusterStack[clusterStack.length - 1];
  461. edge.fromId = clusterStack[clusterStack.length - 1].id;
  462. clusterStack.pop()
  463. edge.fromArray = clusterStack;
  464. }
  465. else {
  466. edge.to = clusterStack[clusterStack.length - 1];
  467. edge.toId = clusterStack[clusterStack.length - 1].id;
  468. clusterStack.pop();
  469. edge.toArray = clusterStack;
  470. }
  471. edge.connect();
  472. }
  473. /**
  474. * Get the stack clusterId's that a certain node resides in. cluster A -> cluster B -> cluster C -> node
  475. * @param nodeId
  476. * @returns {Array}
  477. * @private
  478. */
  479. exports._getClusterStack = function(nodeId) {
  480. var stack = [];
  481. var max = 100;
  482. var counter = 0;
  483. while (this.clusteredNodes[nodeId] !== undefined && counter < max) {
  484. stack.push(this.clusteredNodes[nodeId].node);
  485. nodeId = this.clusteredNodes[nodeId].clusterId;
  486. counter++;
  487. }
  488. stack.push(this.nodes[nodeId]);
  489. return stack;
  490. }
  491. /**
  492. * Get the Id the node is connected to
  493. * @param edge
  494. * @param nodeId
  495. * @returns {*}
  496. * @private
  497. */
  498. exports._getConnectedId = function(edge, nodeId) {
  499. if (edge.toId != nodeId) {
  500. return edge.toId;
  501. }
  502. else if (edge.fromId != nodeId) {
  503. return edge.fromId;
  504. }
  505. else {
  506. return edge.fromId;
  507. }
  508. }
  509. /**
  510. * We determine how many connections denote an important hub.
  511. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  512. *
  513. * @private
  514. */
  515. exports._getHubSize = function() {
  516. var average = 0;
  517. var averageSquared = 0;
  518. var hubCounter = 0;
  519. var largestHub = 0;
  520. for (var i = 0; i < this.nodeIndices.length; i++) {
  521. var node = this.nodes[this.nodeIndices[i]];
  522. if (node.edges.length > largestHub) {
  523. largestHub = node.edges.length;
  524. }
  525. average += node.edges.length;
  526. averageSquared += Math.pow(node.edges.length,2);
  527. hubCounter += 1;
  528. }
  529. average = average / hubCounter;
  530. averageSquared = averageSquared / hubCounter;
  531. var variance = averageSquared - Math.pow(average,2);
  532. var standardDeviation = Math.sqrt(variance);
  533. var hubThreshold = Math.floor(average + 2*standardDeviation);
  534. // always have at least one to cluster
  535. if (hubThreshold > largestHub) {
  536. hubThreshold = largestHub;
  537. }
  538. return hubThreshold;
  539. };