supervisor.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. ;(function(undefined) {
  2. 'use strict';
  3. if (typeof sigma === 'undefined')
  4. throw 'sigma is not declared';
  5. /**
  6. * Sigma ForceAtlas2.5 Supervisor
  7. * ===============================
  8. *
  9. * Author: Guillaume Plique (Yomguithereal)
  10. * Version: 0.1
  11. */
  12. var _root = this;
  13. /**
  14. * Feature detection
  15. * ------------------
  16. */
  17. var webWorkers = 'Worker' in _root;
  18. /**
  19. * Supervisor Object
  20. * ------------------
  21. */
  22. function Supervisor(sigInst, options) {
  23. var _this = this,
  24. workerFn = sigInst.getForceAtlas2Worker &&
  25. sigInst.getForceAtlas2Worker();
  26. options = options || {};
  27. // _root URL Polyfill
  28. _root.URL = _root.URL || _root.webkitURL;
  29. // Properties
  30. this.sigInst = sigInst;
  31. this.graph = this.sigInst.graph;
  32. this.ppn = 10;
  33. this.ppe = 3;
  34. this.config = {};
  35. this.shouldUseWorker =
  36. options.worker === false ? false : true && webWorkers;
  37. this.workerUrl = options.workerUrl;
  38. // State
  39. this.started = false;
  40. this.running = false;
  41. // Web worker or classic DOM events?
  42. if (this.shouldUseWorker) {
  43. if (!this.workerUrl) {
  44. var blob = this.makeBlob(workerFn);
  45. this.worker = new Worker(URL.createObjectURL(blob));
  46. }
  47. else {
  48. this.worker = new Worker(this.workerUrl);
  49. }
  50. // Post Message Polyfill
  51. this.worker.postMessage =
  52. this.worker.webkitPostMessage || this.worker.postMessage;
  53. }
  54. else {
  55. eval(workerFn);
  56. }
  57. // Worker message receiver
  58. this.msgName = (this.worker) ? 'message' : 'newCoords';
  59. this.listener = function(e) {
  60. // Retrieving data
  61. _this.nodesByteArray = new Float32Array(e.data.nodes);
  62. // If ForceAtlas2 is running, we act accordingly
  63. if (_this.running) {
  64. // Applying layout
  65. _this.applyLayoutChanges();
  66. // Send data back to worker and loop
  67. _this.sendByteArrayToWorker();
  68. // Rendering graph
  69. _this.sigInst.refresh();
  70. }
  71. };
  72. (this.worker || document).addEventListener(this.msgName, this.listener);
  73. // Filling byteArrays
  74. this.graphToByteArrays();
  75. // Binding on kill to properly terminate layout when parent is killed
  76. sigInst.bind('kill', function() {
  77. sigInst.killForceAtlas2();
  78. });
  79. }
  80. Supervisor.prototype.makeBlob = function(workerFn) {
  81. var blob;
  82. try {
  83. blob = new Blob([workerFn], {type: 'application/javascript'});
  84. }
  85. catch (e) {
  86. _root.BlobBuilder = _root.BlobBuilder ||
  87. _root.WebKitBlobBuilder ||
  88. _root.MozBlobBuilder;
  89. blob = new BlobBuilder();
  90. blob.append(workerFn);
  91. blob = blob.getBlob();
  92. }
  93. return blob;
  94. };
  95. Supervisor.prototype.graphToByteArrays = function() {
  96. var nodes = this.graph.nodes(),
  97. edges = this.graph.edges(),
  98. nbytes = nodes.length * this.ppn,
  99. ebytes = edges.length * this.ppe,
  100. nIndex = {},
  101. i,
  102. j,
  103. l;
  104. // Allocating Byte arrays with correct nb of bytes
  105. this.nodesByteArray = new Float32Array(nbytes);
  106. this.edgesByteArray = new Float32Array(ebytes);
  107. // Iterate through nodes
  108. for (i = j = 0, l = nodes.length; i < l; i++) {
  109. // Populating index
  110. nIndex[nodes[i].id] = j;
  111. // Populating byte array
  112. this.nodesByteArray[j] = nodes[i].x;
  113. this.nodesByteArray[j + 1] = nodes[i].y;
  114. this.nodesByteArray[j + 2] = 0;
  115. this.nodesByteArray[j + 3] = 0;
  116. this.nodesByteArray[j + 4] = 0;
  117. this.nodesByteArray[j + 5] = 0;
  118. this.nodesByteArray[j + 6] = 1 + this.graph.degree(nodes[i].id);
  119. this.nodesByteArray[j + 7] = 1;
  120. this.nodesByteArray[j + 8] = nodes[i].size;
  121. this.nodesByteArray[j + 9] = 0;
  122. j += this.ppn;
  123. }
  124. // Iterate through edges
  125. for (i = j = 0, l = edges.length; i < l; i++) {
  126. this.edgesByteArray[j] = nIndex[edges[i].source];
  127. this.edgesByteArray[j + 1] = nIndex[edges[i].target];
  128. this.edgesByteArray[j + 2] = edges[i].weight || 0;
  129. j += this.ppe;
  130. }
  131. };
  132. // TODO: make a better send function
  133. Supervisor.prototype.applyLayoutChanges = function() {
  134. var nodes = this.graph.nodes(),
  135. j = 0,
  136. realIndex;
  137. // Moving nodes
  138. for (var i = 0, l = this.nodesByteArray.length; i < l; i += this.ppn) {
  139. nodes[j].x = this.nodesByteArray[i];
  140. nodes[j].y = this.nodesByteArray[i + 1];
  141. j++;
  142. }
  143. };
  144. Supervisor.prototype.sendByteArrayToWorker = function(action) {
  145. var content = {
  146. action: action || 'loop',
  147. nodes: this.nodesByteArray.buffer
  148. };
  149. var buffers = [this.nodesByteArray.buffer];
  150. if (action === 'start') {
  151. content.config = this.config || {};
  152. content.edges = this.edgesByteArray.buffer;
  153. buffers.push(this.edgesByteArray.buffer);
  154. }
  155. if (this.shouldUseWorker)
  156. this.worker.postMessage(content, buffers);
  157. else
  158. _root.postMessage(content, '*');
  159. };
  160. Supervisor.prototype.start = function() {
  161. if (this.running)
  162. return;
  163. this.running = true;
  164. // Do not refresh edgequadtree during layout:
  165. var k,
  166. c;
  167. for (k in this.sigInst.cameras) {
  168. c = this.sigInst.cameras[k];
  169. c.edgequadtree._enabled = false;
  170. }
  171. if (!this.started) {
  172. // Sending init message to worker
  173. this.sendByteArrayToWorker('start');
  174. this.started = true;
  175. }
  176. else {
  177. this.sendByteArrayToWorker();
  178. }
  179. };
  180. Supervisor.prototype.stop = function() {
  181. if (!this.running)
  182. return;
  183. // Allow to refresh edgequadtree:
  184. var k,
  185. c,
  186. bounds;
  187. for (k in this.sigInst.cameras) {
  188. c = this.sigInst.cameras[k];
  189. c.edgequadtree._enabled = true;
  190. // Find graph boundaries:
  191. bounds = sigma.utils.getBoundaries(
  192. this.graph,
  193. c.readPrefix
  194. );
  195. // Refresh edgequadtree:
  196. if (c.settings('drawEdges') && c.settings('enableEdgeHovering'))
  197. c.edgequadtree.index(this.sigInst.graph, {
  198. prefix: c.readPrefix,
  199. bounds: {
  200. x: bounds.minX,
  201. y: bounds.minY,
  202. width: bounds.maxX - bounds.minX,
  203. height: bounds.maxY - bounds.minY
  204. }
  205. });
  206. }
  207. this.running = false;
  208. };
  209. Supervisor.prototype.killWorker = function() {
  210. if (this.worker) {
  211. this.worker.terminate();
  212. }
  213. else {
  214. _root.postMessage({action: 'kill'}, '*');
  215. document.removeEventListener(this.msgName, this.listener);
  216. }
  217. };
  218. Supervisor.prototype.configure = function(config) {
  219. // Setting configuration
  220. this.config = config;
  221. if (!this.started)
  222. return;
  223. var data = {action: 'config', config: this.config};
  224. if (this.shouldUseWorker)
  225. this.worker.postMessage(data);
  226. else
  227. _root.postMessage(data, '*');
  228. };
  229. /**
  230. * Interface
  231. * ----------
  232. */
  233. sigma.prototype.startForceAtlas2 = function(config) {
  234. // Create supervisor if undefined
  235. if (!this.supervisor)
  236. this.supervisor = new Supervisor(this, config);
  237. // Configuration provided?
  238. if (config)
  239. this.supervisor.configure(config);
  240. // Start algorithm
  241. this.supervisor.start();
  242. return this;
  243. };
  244. sigma.prototype.stopForceAtlas2 = function() {
  245. if (!this.supervisor)
  246. return this;
  247. // Pause algorithm
  248. this.supervisor.stop();
  249. return this;
  250. };
  251. sigma.prototype.killForceAtlas2 = function() {
  252. if (!this.supervisor)
  253. return this;
  254. // Stop Algorithm
  255. this.supervisor.stop();
  256. // Kill Worker
  257. this.supervisor.killWorker();
  258. // Kill supervisor
  259. this.supervisor = null;
  260. return this;
  261. };
  262. sigma.prototype.configForceAtlas2 = function(config) {
  263. if (!this.supervisor)
  264. this.supervisor = new Supervisor(this, config);
  265. this.supervisor.configure(config);
  266. return this;
  267. };
  268. sigma.prototype.isForceAtlas2Running = function(config) {
  269. return !!this.supervisor && this.supervisor.running;
  270. };
  271. }).call(this);