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.

548 lines
17 KiB

  1. var util = require('../../util');
  2. /**
  3. * Creation of the SectorMixin var.
  4. *
  5. * This contains all the functions the Network object can use to employ the sector system.
  6. * The sector system is always used by Network, though the benefits only apply to the use of clustering.
  7. * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
  8. */
  9. /**
  10. * This function is only called by the setData function of the Network object.
  11. * This loads the global references into the active sector. This initializes the sector.
  12. *
  13. * @private
  14. */
  15. exports._putDataInSector = function() {
  16. this.sectors["active"][this._sector()].nodes = this.nodes;
  17. this.sectors["active"][this._sector()].edges = this.edges;
  18. this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
  19. };
  20. /**
  21. * /**
  22. * This function sets the global references to nodes, edges and nodeIndices back to
  23. * those of the supplied (active) sector. If a type is defined, do the specific type
  24. *
  25. * @param {String} sectorId
  26. * @param {String} [sectorType] | "active" or "frozen"
  27. * @private
  28. */
  29. exports._switchToSector = function(sectorId, sectorType) {
  30. if (sectorType === undefined || sectorType == "active") {
  31. this._switchToActiveSector(sectorId);
  32. }
  33. else {
  34. this._switchToFrozenSector(sectorId);
  35. }
  36. };
  37. /**
  38. * This function sets the global references to nodes, edges and nodeIndices back to
  39. * those of the supplied active sector.
  40. *
  41. * @param sectorId
  42. * @private
  43. */
  44. exports._switchToActiveSector = function(sectorId) {
  45. this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
  46. this.nodes = this.sectors["active"][sectorId]["nodes"];
  47. this.edges = this.sectors["active"][sectorId]["edges"];
  48. };
  49. /**
  50. * This function sets the global references to nodes, edges and nodeIndices back to
  51. * those of the supplied active sector.
  52. *
  53. * @private
  54. */
  55. exports._switchToSupportSector = function() {
  56. this.nodeIndices = this.sectors["support"]["nodeIndices"];
  57. this.nodes = this.sectors["support"]["nodes"];
  58. this.edges = this.sectors["support"]["edges"];
  59. };
  60. /**
  61. * This function sets the global references to nodes, edges and nodeIndices back to
  62. * those of the supplied frozen sector.
  63. *
  64. * @param sectorId
  65. * @private
  66. */
  67. exports._switchToFrozenSector = function(sectorId) {
  68. this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
  69. this.nodes = this.sectors["frozen"][sectorId]["nodes"];
  70. this.edges = this.sectors["frozen"][sectorId]["edges"];
  71. };
  72. /**
  73. * This function sets the global references to nodes, edges and nodeIndices back to
  74. * those of the currently active sector.
  75. *
  76. * @private
  77. */
  78. exports._loadLatestSector = function() {
  79. this._switchToSector(this._sector());
  80. };
  81. /**
  82. * This function returns the currently active sector Id
  83. *
  84. * @returns {String}
  85. * @private
  86. */
  87. exports._sector = function() {
  88. return this.activeSector[this.activeSector.length-1];
  89. };
  90. /**
  91. * This function returns the previously active sector Id
  92. *
  93. * @returns {String}
  94. * @private
  95. */
  96. exports._previousSector = function() {
  97. if (this.activeSector.length > 1) {
  98. return this.activeSector[this.activeSector.length-2];
  99. }
  100. else {
  101. throw new TypeError('there are not enough sectors in the this.activeSector array.');
  102. }
  103. };
  104. /**
  105. * We add the active sector at the end of the this.activeSector array
  106. * This ensures it is the currently active sector returned by _sector() and it reaches the top
  107. * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
  108. *
  109. * @param newId
  110. * @private
  111. */
  112. exports._setActiveSector = function(newId) {
  113. this.activeSector.push(newId);
  114. };
  115. /**
  116. * We remove the currently active sector id from the active sector stack. This happens when
  117. * we reactivate the previously active sector
  118. *
  119. * @private
  120. */
  121. exports._forgetLastSector = function() {
  122. this.activeSector.pop();
  123. };
  124. /**
  125. * This function creates a new active sector with the supplied newId. This newId
  126. * is the expanding node id.
  127. *
  128. * @param {String} newId | Id of the new active sector
  129. * @private
  130. */
  131. exports._createNewSector = function(newId) {
  132. // create the new sector
  133. this.sectors["active"][newId] = {"nodes":{},
  134. "edges":{},
  135. "nodeIndices":[],
  136. "formationScale": this.scale,
  137. "drawingNode": undefined};
  138. // create the new sector render node. This gives visual feedback that you are in a new sector.
  139. this.sectors["active"][newId]['drawingNode'] = new Node(
  140. {id:newId,
  141. color: {
  142. background: "#eaefef",
  143. border: "495c5e"
  144. }
  145. },{},{},this.constants);
  146. this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
  147. };
  148. /**
  149. * This function removes the currently active sector. This is called when we create a new
  150. * active sector.
  151. *
  152. * @param {String} sectorId | Id of the active sector that will be removed
  153. * @private
  154. */
  155. exports._deleteActiveSector = function(sectorId) {
  156. delete this.sectors["active"][sectorId];
  157. };
  158. /**
  159. * This function removes the currently active sector. This is called when we reactivate
  160. * the previously active sector.
  161. *
  162. * @param {String} sectorId | Id of the active sector that will be removed
  163. * @private
  164. */
  165. exports._deleteFrozenSector = function(sectorId) {
  166. delete this.sectors["frozen"][sectorId];
  167. };
  168. /**
  169. * Freezing an active sector means moving it from the "active" object to the "frozen" object.
  170. * We copy the references, then delete the active entree.
  171. *
  172. * @param sectorId
  173. * @private
  174. */
  175. exports._freezeSector = function(sectorId) {
  176. // we move the set references from the active to the frozen stack.
  177. this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
  178. // we have moved the sector data into the frozen set, we now remove it from the active set
  179. this._deleteActiveSector(sectorId);
  180. };
  181. /**
  182. * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
  183. * object to the "active" object.
  184. *
  185. * @param sectorId
  186. * @private
  187. */
  188. exports._activateSector = function(sectorId) {
  189. // we move the set references from the frozen to the active stack.
  190. this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId];
  191. // we have moved the sector data into the active set, we now remove it from the frozen stack
  192. this._deleteFrozenSector(sectorId);
  193. };
  194. /**
  195. * This function merges the data from the currently active sector with a frozen sector. This is used
  196. * in the process of reverting back to the previously active sector.
  197. * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it
  198. * upon the creation of a new active sector.
  199. *
  200. * @param sectorId
  201. * @private
  202. */
  203. exports._mergeThisWithFrozen = function(sectorId) {
  204. // copy all nodes
  205. for (var nodeId in this.nodes) {
  206. if (this.nodes.hasOwnProperty(nodeId)) {
  207. this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId];
  208. }
  209. }
  210. // copy all edges (if not fully clustered, else there are no edges)
  211. for (var edgeId in this.edges) {
  212. if (this.edges.hasOwnProperty(edgeId)) {
  213. this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId];
  214. }
  215. }
  216. // merge the nodeIndices
  217. for (var i = 0; i < this.nodeIndices.length; i++) {
  218. this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]);
  219. }
  220. };
  221. /**
  222. * This clusters the sector to one cluster. It was a single cluster before this process started so
  223. * we revert to that state. The clusterToFit function with a maximum size of 1 node does this.
  224. *
  225. * @private
  226. */
  227. exports._collapseThisToSingleCluster = function() {
  228. this.clusterToFit(1,false);
  229. };
  230. /**
  231. * We create a new active sector from the node that we want to open.
  232. *
  233. * @param node
  234. * @private
  235. */
  236. exports._addSector = function(node) {
  237. // this is the currently active sector
  238. var sector = this._sector();
  239. // // this should allow me to select nodes from a frozen set.
  240. // if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
  241. // console.log("the node is part of the active sector");
  242. // }
  243. // else {
  244. // console.log("I dont know what the fuck happened!!");
  245. // }
  246. // when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
  247. delete this.nodes[node.id];
  248. var unqiueIdentifier = util.randomUUID();
  249. // we fully freeze the currently active sector
  250. this._freezeSector(sector);
  251. // we create a new active sector. This sector has the Id of the node to ensure uniqueness
  252. this._createNewSector(unqiueIdentifier);
  253. // we add the active sector to the sectors array to be able to revert these steps later on
  254. this._setActiveSector(unqiueIdentifier);
  255. // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier
  256. this._switchToSector(this._sector());
  257. // finally we add the node we removed from our previous active sector to the new active sector
  258. this.nodes[node.id] = node;
  259. };
  260. /**
  261. * We close the sector that is currently open and revert back to the one before.
  262. * If the active sector is the "default" sector, nothing happens.
  263. *
  264. * @private
  265. */
  266. exports._collapseSector = function() {
  267. // the currently active sector
  268. var sector = this._sector();
  269. // we cannot collapse the default sector
  270. if (sector != "default") {
  271. if ((this.nodeIndices.length == 1) ||
  272. (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  273. (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  274. var previousSector = this._previousSector();
  275. // we collapse the sector back to a single cluster
  276. this._collapseThisToSingleCluster();
  277. // we move the remaining nodes, edges and nodeIndices to the previous sector.
  278. // This previous sector is the one we will reactivate
  279. this._mergeThisWithFrozen(previousSector);
  280. // the previously active (frozen) sector now has all the data from the currently active sector.
  281. // we can now delete the active sector.
  282. this._deleteActiveSector(sector);
  283. // we activate the previously active (and currently frozen) sector.
  284. this._activateSector(previousSector);
  285. // we load the references from the newly active sector into the global references
  286. this._switchToSector(previousSector);
  287. // we forget the previously active sector because we reverted to the one before
  288. this._forgetLastSector();
  289. // finally, we update the node index list.
  290. this._updateNodeIndexList();
  291. // we refresh the list with calulation nodes and calculation node indices.
  292. this._updateCalculationNodes();
  293. }
  294. }
  295. };
  296. /**
  297. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  298. *
  299. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  300. * | we dont pass the function itself because then the "this" is the window object
  301. * | instead of the Network object
  302. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  303. * @private
  304. */
  305. exports._doInAllActiveSectors = function(runFunction,argument) {
  306. if (argument === undefined) {
  307. for (var sector in this.sectors["active"]) {
  308. if (this.sectors["active"].hasOwnProperty(sector)) {
  309. // switch the global references to those of this sector
  310. this._switchToActiveSector(sector);
  311. this[runFunction]();
  312. }
  313. }
  314. }
  315. else {
  316. for (var sector in this.sectors["active"]) {
  317. if (this.sectors["active"].hasOwnProperty(sector)) {
  318. // switch the global references to those of this sector
  319. this._switchToActiveSector(sector);
  320. var args = Array.prototype.splice.call(arguments, 1);
  321. if (args.length > 1) {
  322. this[runFunction](args[0],args[1]);
  323. }
  324. else {
  325. this[runFunction](argument);
  326. }
  327. }
  328. }
  329. }
  330. // we revert the global references back to our active sector
  331. this._loadLatestSector();
  332. };
  333. /**
  334. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  335. *
  336. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  337. * | we dont pass the function itself because then the "this" is the window object
  338. * | instead of the Network object
  339. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  340. * @private
  341. */
  342. exports._doInSupportSector = function(runFunction,argument) {
  343. if (argument === undefined) {
  344. this._switchToSupportSector();
  345. this[runFunction]();
  346. }
  347. else {
  348. this._switchToSupportSector();
  349. var args = Array.prototype.splice.call(arguments, 1);
  350. if (args.length > 1) {
  351. this[runFunction](args[0],args[1]);
  352. }
  353. else {
  354. this[runFunction](argument);
  355. }
  356. }
  357. // we revert the global references back to our active sector
  358. this._loadLatestSector();
  359. };
  360. /**
  361. * This runs a function in all frozen sectors. This is used in the _redraw().
  362. *
  363. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  364. * | we don't pass the function itself because then the "this" is the window object
  365. * | instead of the Network object
  366. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  367. * @private
  368. */
  369. exports._doInAllFrozenSectors = function(runFunction,argument) {
  370. if (argument === undefined) {
  371. for (var sector in this.sectors["frozen"]) {
  372. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  373. // switch the global references to those of this sector
  374. this._switchToFrozenSector(sector);
  375. this[runFunction]();
  376. }
  377. }
  378. }
  379. else {
  380. for (var sector in this.sectors["frozen"]) {
  381. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  382. // switch the global references to those of this sector
  383. this._switchToFrozenSector(sector);
  384. var args = Array.prototype.splice.call(arguments, 1);
  385. if (args.length > 1) {
  386. this[runFunction](args[0],args[1]);
  387. }
  388. else {
  389. this[runFunction](argument);
  390. }
  391. }
  392. }
  393. }
  394. this._loadLatestSector();
  395. };
  396. /**
  397. * This runs a function in all sectors. This is used in the _redraw().
  398. *
  399. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  400. * | we don't pass the function itself because then the "this" is the window object
  401. * | instead of the Network object
  402. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  403. * @private
  404. */
  405. exports._doInAllSectors = function(runFunction,argument) {
  406. var args = Array.prototype.splice.call(arguments, 1);
  407. if (argument === undefined) {
  408. this._doInAllActiveSectors(runFunction);
  409. this._doInAllFrozenSectors(runFunction);
  410. }
  411. else {
  412. if (args.length > 1) {
  413. this._doInAllActiveSectors(runFunction,args[0],args[1]);
  414. this._doInAllFrozenSectors(runFunction,args[0],args[1]);
  415. }
  416. else {
  417. this._doInAllActiveSectors(runFunction,argument);
  418. this._doInAllFrozenSectors(runFunction,argument);
  419. }
  420. }
  421. };
  422. /**
  423. * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
  424. * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
  425. *
  426. * @private
  427. */
  428. exports._clearNodeIndexList = function() {
  429. var sector = this._sector();
  430. this.sectors["active"][sector]["nodeIndices"] = [];
  431. this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
  432. };
  433. /**
  434. * Draw the encompassing sector node
  435. *
  436. * @param ctx
  437. * @param sectorType
  438. * @private
  439. */
  440. exports._drawSectorNodes = function(ctx,sectorType) {
  441. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  442. for (var sector in this.sectors[sectorType]) {
  443. if (this.sectors[sectorType].hasOwnProperty(sector)) {
  444. if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
  445. this._switchToSector(sector,sectorType);
  446. minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
  447. for (var nodeId in this.nodes) {
  448. if (this.nodes.hasOwnProperty(nodeId)) {
  449. node = this.nodes[nodeId];
  450. node.resize(ctx);
  451. if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
  452. if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
  453. if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
  454. if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
  455. }
  456. }
  457. node = this.sectors[sectorType][sector]["drawingNode"];
  458. node.x = 0.5 * (maxX + minX);
  459. node.y = 0.5 * (maxY + minY);
  460. node.width = 2 * (node.x - minX);
  461. node.height = 2 * (node.y - minY);
  462. node.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2));
  463. node.setScale(this.scale);
  464. node._drawCircle(ctx);
  465. }
  466. }
  467. }
  468. };
  469. exports._drawAllSectorNodes = function(ctx) {
  470. this._drawSectorNodes(ctx,"frozen");
  471. this._drawSectorNodes(ctx,"active");
  472. this._loadLatestSector();
  473. };