sigma.layout.noverlap.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. ;(function(undefined) {
  2. 'use strict';
  3. if (typeof sigma === 'undefined')
  4. throw new Error('sigma is not declared');
  5. // Initialize package:
  6. sigma.utils.pkg('sigma.layout.noverlap');
  7. /**
  8. * Noverlap Layout
  9. * ===============================
  10. *
  11. * Author: @apitts / Andrew Pitts
  12. * Algorithm: @jacomyma / Mathieu Jacomy (originally contributed to Gephi and ported to sigma.js under the MIT license by @andpitts with permission)
  13. * Acknowledgement: @sheyman / Sébastien Heymann (some inspiration has been taken from other MIT licensed layout algorithms authored by @sheyman)
  14. * Version: 0.1
  15. */
  16. var settings = {
  17. speed: 3,
  18. scaleNodes: 1.2,
  19. nodeMargin: 5.0,
  20. gridSize: 20,
  21. permittedExpansion: 1.1,
  22. rendererIndex: 0,
  23. maxIterations: 500
  24. };
  25. var _instance = {};
  26. /**
  27. * Event emitter Object
  28. * ------------------
  29. */
  30. var _eventEmitter = {};
  31. /**
  32. * Noverlap Object
  33. * ------------------
  34. */
  35. function Noverlap() {
  36. var self = this;
  37. this.init = function (sigInst, options) {
  38. options = options || {};
  39. // Properties
  40. this.sigInst = sigInst;
  41. this.config = sigma.utils.extend(options, settings);
  42. this.easing = options.easing;
  43. this.duration = options.duration;
  44. if (options.nodes) {
  45. this.nodes = options.nodes;
  46. delete options.nodes;
  47. }
  48. if (!sigma.plugins || typeof sigma.plugins.animate === 'undefined') {
  49. throw new Error('sigma.plugins.animate is not declared');
  50. }
  51. // State
  52. this.running = false;
  53. };
  54. /**
  55. * Single layout iteration.
  56. */
  57. this.atomicGo = function () {
  58. if (!this.running || this.iterCount < 1) return false;
  59. var nodes = this.nodes || this.sigInst.graph.nodes(),
  60. nodesCount = nodes.length,
  61. i,
  62. n,
  63. n1,
  64. n2,
  65. xmin = Infinity,
  66. xmax = -Infinity,
  67. ymin = Infinity,
  68. ymax = -Infinity,
  69. xwidth,
  70. yheight,
  71. xcenter,
  72. ycenter,
  73. grid,
  74. row,
  75. col,
  76. minXBox,
  77. maxXBox,
  78. minYBox,
  79. maxYBox,
  80. adjacentNodes,
  81. subRow,
  82. subCol,
  83. nxmin,
  84. nxmax,
  85. nymin,
  86. nymax;
  87. this.iterCount--;
  88. this.running = false;
  89. for (i=0; i < nodesCount; i++) {
  90. n = nodes[i];
  91. n.dn.dx = 0;
  92. n.dn.dy = 0;
  93. //Find the min and max for both x and y across all nodes
  94. xmin = Math.min(xmin, n.dn_x - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
  95. xmax = Math.max(xmax, n.dn_x + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
  96. ymin = Math.min(ymin, n.dn_y - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
  97. ymax = Math.max(ymax, n.dn_y + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
  98. }
  99. xwidth = xmax - xmin;
  100. yheight = ymax - ymin;
  101. xcenter = (xmin + xmax) / 2;
  102. ycenter = (ymin + ymax) / 2;
  103. xmin = xcenter - self.config.permittedExpansion*xwidth / 2;
  104. xmax = xcenter + self.config.permittedExpansion*xwidth / 2;
  105. ymin = ycenter - self.config.permittedExpansion*yheight / 2;
  106. ymax = ycenter + self.config.permittedExpansion*yheight / 2;
  107. grid = {}; //An object of objects where grid[row][col] is an array of node ids representing nodes that fall in that grid. Nodes can fall in more than one grid
  108. for(row = 0; row < self.config.gridSize; row++) {
  109. grid[row] = {};
  110. for(col = 0; col < self.config.gridSize; col++) {
  111. grid[row][col] = [];
  112. }
  113. }
  114. //Place nodes in grid
  115. for (i=0; i < nodesCount; i++) {
  116. n = nodes[i];
  117. nxmin = n.dn_x - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
  118. nxmax = n.dn_x + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
  119. nymin = n.dn_y - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
  120. nymax = n.dn_y + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
  121. minXBox = Math.floor(self.config.gridSize* (nxmin - xmin) / (xmax - xmin) );
  122. maxXBox = Math.floor(self.config.gridSize* (nxmax - xmin) / (xmax - xmin) );
  123. minYBox = Math.floor(self.config.gridSize* (nymin - ymin) / (ymax - ymin) );
  124. maxYBox = Math.floor(self.config.gridSize* (nymax - ymin) / (ymax - ymin) );
  125. for(col = minXBox; col <= maxXBox; col++) {
  126. for(row = minYBox; row <= maxYBox; row++) {
  127. grid[row][col].push(n.id);
  128. }
  129. }
  130. }
  131. adjacentNodes = {}; //An object that stores the node ids of adjacent nodes (either in same grid box or adjacent grid box) for all nodes
  132. for(row = 0; row < self.config.gridSize; row++) {
  133. for(col = 0; col < self.config.gridSize; col++) {
  134. grid[row][col].forEach(function(nodeId) {
  135. if(!adjacentNodes[nodeId]) {
  136. adjacentNodes[nodeId] = [];
  137. }
  138. for(subRow = Math.max(0, row - 1); subRow <= Math.min(row + 1, self.config.gridSize - 1); subRow++) {
  139. for(subCol = Math.max(0, col - 1); subCol <= Math.min(col + 1, self.config.gridSize - 1); subCol++) {
  140. grid[subRow][subCol].forEach(function(subNodeId) {
  141. if(subNodeId !== nodeId && adjacentNodes[nodeId].indexOf(subNodeId) === -1) {
  142. adjacentNodes[nodeId].push(subNodeId);
  143. }
  144. });
  145. }
  146. }
  147. });
  148. }
  149. }
  150. //If two nodes overlap then repulse them
  151. for (i=0; i < nodesCount; i++) {
  152. n1 = nodes[i];
  153. adjacentNodes[n1.id].forEach(function(nodeId) {
  154. var n2 = self.sigInst.graph.nodes(nodeId);
  155. var xDist = n2.dn_x - n1.dn_x;
  156. var yDist = n2.dn_y - n1.dn_y;
  157. var dist = Math.sqrt(xDist*xDist + yDist*yDist);
  158. var collision = (dist < ((n1.dn_size*self.config.scaleNodes + self.config.nodeMargin) + (n2.dn_size*self.config.scaleNodes + self.config.nodeMargin)));
  159. if(collision) {
  160. self.running = true;
  161. if(dist > 0) {
  162. n2.dn.dx += xDist / dist * (1 + n1.dn_size);
  163. n2.dn.dy += yDist / dist * (1 + n1.dn_size);
  164. } else {
  165. n2.dn.dx += xwidth * 0.01 * (0.5 - Math.random());
  166. n2.dn.dy += yheight * 0.01 * (0.5 - Math.random());
  167. }
  168. }
  169. });
  170. }
  171. for (i=0; i < nodesCount; i++) {
  172. n = nodes[i];
  173. if(!n.fixed) {
  174. n.dn_x = n.dn_x + n.dn.dx * 0.1 * self.config.speed;
  175. n.dn_y = n.dn_y + n.dn.dy * 0.1 * self.config.speed;
  176. }
  177. }
  178. if(this.running && this.iterCount < 1) {
  179. this.running = false;
  180. }
  181. return this.running;
  182. };
  183. this.go = function () {
  184. this.iterCount = this.config.maxIterations;
  185. while (this.running) {
  186. this.atomicGo();
  187. };
  188. this.stop();
  189. };
  190. this.start = function() {
  191. if (this.running) return;
  192. var nodes = this.sigInst.graph.nodes();
  193. var prefix = this.sigInst.renderers[self.config.rendererIndex].options.prefix;
  194. this.running = true;
  195. // Init nodes
  196. for (var i = 0; i < nodes.length; i++) {
  197. nodes[i].dn_x = nodes[i][prefix + 'x'];
  198. nodes[i].dn_y = nodes[i][prefix + 'y'];
  199. nodes[i].dn_size = nodes[i][prefix + 'size'];
  200. nodes[i].dn = {
  201. dx: 0,
  202. dy: 0
  203. };
  204. }
  205. _eventEmitter[self.sigInst.id].dispatchEvent('start');
  206. this.go();
  207. };
  208. this.stop = function() {
  209. var nodes = this.sigInst.graph.nodes();
  210. this.running = false;
  211. if (this.easing) {
  212. _eventEmitter[self.sigInst.id].dispatchEvent('interpolate');
  213. sigma.plugins.animate(
  214. self.sigInst,
  215. {
  216. x: 'dn_x',
  217. y: 'dn_y'
  218. },
  219. {
  220. easing: self.easing,
  221. onComplete: function() {
  222. self.sigInst.refresh();
  223. for (var i = 0; i < nodes.length; i++) {
  224. nodes[i].dn = null;
  225. nodes[i].dn_x = null;
  226. nodes[i].dn_y = null;
  227. }
  228. _eventEmitter[self.sigInst.id].dispatchEvent('stop');
  229. },
  230. duration: self.duration
  231. }
  232. );
  233. }
  234. else {
  235. // Apply changes
  236. for (var i = 0; i < nodes.length; i++) {
  237. nodes[i].x = nodes[i].dn_x;
  238. nodes[i].y = nodes[i].dn_y;
  239. }
  240. this.sigInst.refresh();
  241. for (var i = 0; i < nodes.length; i++) {
  242. nodes[i].dn = null;
  243. nodes[i].dn_x = null;
  244. nodes[i].dn_y = null;
  245. }
  246. _eventEmitter[self.sigInst.id].dispatchEvent('stop');
  247. }
  248. };
  249. this.kill = function() {
  250. this.sigInst = null;
  251. this.config = null;
  252. this.easing = null;
  253. };
  254. };
  255. /**
  256. * Interface
  257. * ----------
  258. */
  259. /**
  260. * Configure the layout algorithm.
  261. * Recognized options:
  262. * **********************
  263. * Here is the exhaustive list of every accepted parameter in the settings
  264. * object:
  265. *
  266. * {?number} speed A larger value increases the convergence speed at the cost of precision
  267. * {?number} scaleNodes The ratio to scale nodes by - a larger ratio will lead to more space around larger nodes
  268. * {?number} nodeMargin A fixed margin to apply around nodes regardless of size
  269. * {?number} maxIterations The maximum number of iterations to perform before the layout completes.
  270. * {?integer} gridSize The number of rows and columns to use when partioning nodes into a grid for efficient computation
  271. * {?number} permittedExpansion A permitted expansion factor to the overall size of the network applied at each iteration
  272. * {?integer} rendererIndex The index of the renderer to use for node co-ordinates. Defaults to zero.
  273. * {?(function|string)} easing Either the name of an easing in the sigma.utils.easings package or a function. If not specified, the
  274. * quadraticInOut easing from this package will be used instead.
  275. * {?number} duration The duration of the animation. If not specified, the "animationsTime" setting value of the sigma instance will be used instead.
  276. *
  277. *
  278. * @param {object} config The optional configuration object.
  279. *
  280. * @return {sigma.classes.dispatcher} Returns an event emitter.
  281. */
  282. sigma.prototype.configNoverlap = function(config) {
  283. var sigInst = this;
  284. if (!config) throw new Error('Missing argument: "config"');
  285. // Create instance if undefined
  286. if (!_instance[sigInst.id]) {
  287. _instance[sigInst.id] = new Noverlap();
  288. _eventEmitter[sigInst.id] = {};
  289. sigma.classes.dispatcher.extend(_eventEmitter[sigInst.id]);
  290. // Binding on kill to clear the references
  291. sigInst.bind('kill', function() {
  292. _instance[sigInst.id].kill();
  293. _instance[sigInst.id] = null;
  294. _eventEmitter[sigInst.id] = null;
  295. });
  296. }
  297. _instance[sigInst.id].init(sigInst, config);
  298. return _eventEmitter[sigInst.id];
  299. };
  300. /**
  301. * Start the layout algorithm. It will use the existing configuration if no
  302. * new configuration is passed.
  303. * Recognized options:
  304. * **********************
  305. * Here is the exhaustive list of every accepted parameter in the settings
  306. * object
  307. *
  308. * {?number} speed A larger value increases the convergence speed at the cost of precision
  309. * {?number} scaleNodes The ratio to scale nodes by - a larger ratio will lead to more space around larger nodes
  310. * {?number} nodeMargin A fixed margin to apply around nodes regardless of size
  311. * {?number} maxIterations The maximum number of iterations to perform before the layout completes.
  312. * {?integer} gridSize The number of rows and columns to use when partioning nodes into a grid for efficient computation
  313. * {?number} permittedExpansion A permitted expansion factor to the overall size of the network applied at each iteration
  314. * {?integer} rendererIndex The index of the renderer to use for node co-ordinates. Defaults to zero.
  315. * {?(function|string)} easing Either the name of an easing in the sigma.utils.easings package or a function. If not specified, the
  316. * quadraticInOut easing from this package will be used instead.
  317. * {?number} duration The duration of the animation. If not specified, the "animationsTime" setting value of the sigma instance will be used instead.
  318. *
  319. *
  320. *
  321. * @param {object} config The optional configuration object.
  322. *
  323. * @return {sigma.classes.dispatcher} Returns an event emitter.
  324. */
  325. sigma.prototype.startNoverlap = function(config) {
  326. var sigInst = this;
  327. if (config) {
  328. this.configNoverlap(sigInst, config);
  329. }
  330. _instance[sigInst.id].start();
  331. return _eventEmitter[sigInst.id];
  332. };
  333. /**
  334. * Returns true if the layout has started and is not completed.
  335. *
  336. * @return {boolean}
  337. */
  338. sigma.prototype.isNoverlapRunning = function() {
  339. var sigInst = this;
  340. return !!_instance[sigInst.id] && _instance[sigInst.id].running;
  341. };
  342. }).call(this);