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.

552 lines
18 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. var returnValues = [];
  307. if (argument === undefined) {
  308. for (var sector in this.sectors["active"]) {
  309. if (this.sectors["active"].hasOwnProperty(sector)) {
  310. // switch the global references to those of this sector
  311. this._switchToActiveSector(sector);
  312. returnValues.push( this[runFunction]() );
  313. }
  314. }
  315. }
  316. else {
  317. for (var sector in this.sectors["active"]) {
  318. if (this.sectors["active"].hasOwnProperty(sector)) {
  319. // switch the global references to those of this sector
  320. this._switchToActiveSector(sector);
  321. var args = Array.prototype.splice.call(arguments, 1);
  322. if (args.length > 1) {
  323. returnValues.push( this[runFunction](args[0],args[1]) );
  324. }
  325. else {
  326. returnValues.push( this[runFunction](argument) );
  327. }
  328. }
  329. }
  330. }
  331. // we revert the global references back to our active sector
  332. this._loadLatestSector();
  333. return returnValues;
  334. };
  335. /**
  336. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  337. *
  338. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  339. * | we dont pass the function itself because then the "this" is the window object
  340. * | instead of the Network object
  341. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  342. * @private
  343. */
  344. exports._doInSupportSector = function(runFunction,argument) {
  345. var returnValues = false;
  346. if (argument === undefined) {
  347. this._switchToSupportSector();
  348. returnValues = this[runFunction]();
  349. }
  350. else {
  351. this._switchToSupportSector();
  352. var args = Array.prototype.splice.call(arguments, 1);
  353. if (args.length > 1) {
  354. returnValues = this[runFunction](args[0],args[1]);
  355. }
  356. else {
  357. returnValues = this[runFunction](argument);
  358. }
  359. }
  360. // we revert the global references back to our active sector
  361. this._loadLatestSector();
  362. return returnValues;
  363. };
  364. /**
  365. * This runs a function in all frozen sectors. This is used in the _redraw().
  366. *
  367. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  368. * | we don't pass the function itself because then the "this" is the window object
  369. * | instead of the Network object
  370. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  371. * @private
  372. */
  373. exports._doInAllFrozenSectors = function(runFunction,argument) {
  374. if (argument === undefined) {
  375. for (var sector in this.sectors["frozen"]) {
  376. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  377. // switch the global references to those of this sector
  378. this._switchToFrozenSector(sector);
  379. this[runFunction]();
  380. }
  381. }
  382. }
  383. else {
  384. for (var sector in this.sectors["frozen"]) {
  385. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  386. // switch the global references to those of this sector
  387. this._switchToFrozenSector(sector);
  388. var args = Array.prototype.splice.call(arguments, 1);
  389. if (args.length > 1) {
  390. this[runFunction](args[0],args[1]);
  391. }
  392. else {
  393. this[runFunction](argument);
  394. }
  395. }
  396. }
  397. }
  398. this._loadLatestSector();
  399. };
  400. /**
  401. * This runs a function in all sectors. This is used in the _redraw().
  402. *
  403. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  404. * | we don't pass the function itself because then the "this" is the window object
  405. * | instead of the Network object
  406. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  407. * @private
  408. */
  409. exports._doInAllSectors = function(runFunction,argument) {
  410. var args = Array.prototype.splice.call(arguments, 1);
  411. if (argument === undefined) {
  412. this._doInAllActiveSectors(runFunction);
  413. this._doInAllFrozenSectors(runFunction);
  414. }
  415. else {
  416. if (args.length > 1) {
  417. this._doInAllActiveSectors(runFunction,args[0],args[1]);
  418. this._doInAllFrozenSectors(runFunction,args[0],args[1]);
  419. }
  420. else {
  421. this._doInAllActiveSectors(runFunction,argument);
  422. this._doInAllFrozenSectors(runFunction,argument);
  423. }
  424. }
  425. };
  426. /**
  427. * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
  428. * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
  429. *
  430. * @private
  431. */
  432. exports._clearNodeIndexList = function() {
  433. var sector = this._sector();
  434. this.sectors["active"][sector]["nodeIndices"] = [];
  435. this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
  436. };
  437. /**
  438. * Draw the encompassing sector node
  439. *
  440. * @param ctx
  441. * @param sectorType
  442. * @private
  443. */
  444. exports._drawSectorNodes = function(ctx,sectorType) {
  445. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  446. for (var sector in this.sectors[sectorType]) {
  447. if (this.sectors[sectorType].hasOwnProperty(sector)) {
  448. if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
  449. this._switchToSector(sector,sectorType);
  450. minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
  451. for (var nodeId in this.nodes) {
  452. if (this.nodes.hasOwnProperty(nodeId)) {
  453. node = this.nodes[nodeId];
  454. node.resize(ctx);
  455. if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
  456. if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
  457. if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
  458. if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
  459. }
  460. }
  461. node = this.sectors[sectorType][sector]["drawingNode"];
  462. node.x = 0.5 * (maxX + minX);
  463. node.y = 0.5 * (maxY + minY);
  464. node.width = 2 * (node.x - minX);
  465. node.height = 2 * (node.y - minY);
  466. node.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2));
  467. node.setScale(this.scale);
  468. node._drawCircle(ctx);
  469. }
  470. }
  471. }
  472. };
  473. exports._drawAllSectorNodes = function(ctx) {
  474. this._drawSectorNodes(ctx,"frozen");
  475. this._drawSectorNodes(ctx,"active");
  476. this._loadLatestSector();
  477. };