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.

620 lines
19 KiB

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