Graph database Analysis of the Steam Network
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.

1129 lines
34 KiB

  1. ;(function(undefined) {
  2. 'use strict';
  3. /**
  4. * Sigma ForceAtlas2.5 Webworker
  5. * ==============================
  6. *
  7. * Author: Guillaume Plique (Yomguithereal)
  8. * Algorithm author: Mathieu Jacomy @ Sciences Po Medialab & WebAtlas
  9. * Version: 1.0.3
  10. */
  11. var _root = this,
  12. inWebWorker = !('document' in _root);
  13. /**
  14. * Worker Function Wrapper
  15. * ------------------------
  16. *
  17. * The worker has to be wrapped into a single stringified function
  18. * to be passed afterwards as a BLOB object to the supervisor.
  19. */
  20. var Worker = function(undefined) {
  21. 'use strict';
  22. /**
  23. * Worker settings and properties
  24. */
  25. var W = {
  26. // Properties
  27. ppn: 10,
  28. ppe: 3,
  29. ppr: 9,
  30. maxForce: 10,
  31. iterations: 0,
  32. converged: false,
  33. // Possible to change through config
  34. settings: {
  35. linLogMode: false,
  36. outboundAttractionDistribution: false,
  37. adjustSizes: false,
  38. edgeWeightInfluence: 0,
  39. scalingRatio: 1,
  40. strongGravityMode: false,
  41. gravity: 1,
  42. slowDown: 1,
  43. barnesHutOptimize: false,
  44. barnesHutTheta: 0.5,
  45. startingIterations: 1,
  46. iterationsPerRender: 1
  47. }
  48. };
  49. var NodeMatrix,
  50. EdgeMatrix,
  51. RegionMatrix;
  52. /**
  53. * Helpers
  54. */
  55. function extend() {
  56. var i,
  57. k,
  58. res = {},
  59. l = arguments.length;
  60. for (i = l - 1; i >= 0; i--)
  61. for (k in arguments[i])
  62. res[k] = arguments[i][k];
  63. return res;
  64. }
  65. function __emptyObject(obj) {
  66. var k;
  67. for (k in obj)
  68. if (!('hasOwnProperty' in obj) || obj.hasOwnProperty(k))
  69. delete obj[k];
  70. return obj;
  71. }
  72. /**
  73. * Matrices properties accessors
  74. */
  75. var nodeProperties = {
  76. x: 0,
  77. y: 1,
  78. dx: 2,
  79. dy: 3,
  80. old_dx: 4,
  81. old_dy: 5,
  82. mass: 6,
  83. convergence: 7,
  84. size: 8,
  85. fixed: 9
  86. };
  87. var edgeProperties = {
  88. source: 0,
  89. target: 1,
  90. weight: 2
  91. };
  92. var regionProperties = {
  93. node: 0,
  94. centerX: 1,
  95. centerY: 2,
  96. size: 3,
  97. nextSibling: 4,
  98. firstChild: 5,
  99. mass: 6,
  100. massCenterX: 7,
  101. massCenterY: 8
  102. };
  103. function np(i, p) {
  104. // DEBUG: safeguards
  105. if ((i % W.ppn) !== 0)
  106. throw 'np: non correct (' + i + ').';
  107. if (i !== parseInt(i))
  108. throw 'np: non int.';
  109. if (p in nodeProperties)
  110. return i + nodeProperties[p];
  111. else
  112. throw 'ForceAtlas2.Worker - ' +
  113. 'Inexistant node property given (' + p + ').';
  114. }
  115. function ep(i, p) {
  116. // DEBUG: safeguards
  117. if ((i % W.ppe) !== 0)
  118. throw 'ep: non correct (' + i + ').';
  119. if (i !== parseInt(i))
  120. throw 'ep: non int.';
  121. if (p in edgeProperties)
  122. return i + edgeProperties[p];
  123. else
  124. throw 'ForceAtlas2.Worker - ' +
  125. 'Inexistant edge property given (' + p + ').';
  126. }
  127. function rp(i, p) {
  128. // DEBUG: safeguards
  129. if ((i % W.ppr) !== 0)
  130. throw 'rp: non correct (' + i + ').';
  131. if (i !== parseInt(i))
  132. throw 'rp: non int.';
  133. if (p in regionProperties)
  134. return i + regionProperties[p];
  135. else
  136. throw 'ForceAtlas2.Worker - ' +
  137. 'Inexistant region property given (' + p + ').';
  138. }
  139. // DEBUG
  140. function nan(v) {
  141. if (isNaN(v))
  142. throw 'NaN alert!';
  143. }
  144. /**
  145. * Algorithm initialization
  146. */
  147. function init(nodes, edges, config) {
  148. config = config || {};
  149. var i, l;
  150. // Matrices
  151. NodeMatrix = nodes;
  152. EdgeMatrix = edges;
  153. // Length
  154. W.nodesLength = NodeMatrix.length;
  155. W.edgesLength = EdgeMatrix.length;
  156. // Merging configuration
  157. configure(config);
  158. }
  159. function configure(o) {
  160. W.settings = extend(o, W.settings);
  161. }
  162. /**
  163. * Algorithm pass
  164. */
  165. // MATH: get distances stuff and power 2 issues
  166. function pass() {
  167. var a, i, j, l, r, n, n1, n2, e, w, g, k, m;
  168. var outboundAttCompensation,
  169. coefficient,
  170. xDist,
  171. yDist,
  172. ewc,
  173. mass,
  174. distance,
  175. size,
  176. factor;
  177. // 1) Initializing layout data
  178. //-----------------------------
  179. // Resetting positions & computing max values
  180. for (n = 0; n < W.nodesLength; n += W.ppn) {
  181. NodeMatrix[np(n, 'old_dx')] = NodeMatrix[np(n, 'dx')];
  182. NodeMatrix[np(n, 'old_dy')] = NodeMatrix[np(n, 'dy')];
  183. NodeMatrix[np(n, 'dx')] = 0;
  184. NodeMatrix[np(n, 'dy')] = 0;
  185. }
  186. // If outbound attraction distribution, compensate
  187. if (W.settings.outboundAttractionDistribution) {
  188. outboundAttCompensation = 0;
  189. for (n = 0; n < W.nodesLength; n += W.ppn) {
  190. outboundAttCompensation += NodeMatrix[np(n, 'mass')];
  191. }
  192. outboundAttCompensation /= W.nodesLength;
  193. }
  194. // 1.bis) Barnes-Hut computation
  195. //------------------------------
  196. if (W.settings.barnesHutOptimize) {
  197. var minX = Infinity,
  198. maxX = -Infinity,
  199. minY = Infinity,
  200. maxY = -Infinity,
  201. q, q0, q1, q2, q3;
  202. // Setting up
  203. // RegionMatrix = new Float32Array(W.nodesLength / W.ppn * 4 * W.ppr);
  204. RegionMatrix = [];
  205. // Computing min and max values
  206. for (n = 0; n < W.nodesLength; n += W.ppn) {
  207. minX = Math.min(minX, NodeMatrix[np(n, 'x')]);
  208. maxX = Math.max(maxX, NodeMatrix[np(n, 'x')]);
  209. minY = Math.min(minY, NodeMatrix[np(n, 'y')]);
  210. maxY = Math.max(maxY, NodeMatrix[np(n, 'y')]);
  211. }
  212. // Build the Barnes Hut root region
  213. RegionMatrix[rp(0, 'node')] = -1;
  214. RegionMatrix[rp(0, 'centerX')] = (minX + maxX) / 2;
  215. RegionMatrix[rp(0, 'centerY')] = (minY + maxY) / 2;
  216. RegionMatrix[rp(0, 'size')] = Math.max(maxX - minX, maxY - minY);
  217. RegionMatrix[rp(0, 'nextSibling')] = -1;
  218. RegionMatrix[rp(0, 'firstChild')] = -1;
  219. RegionMatrix[rp(0, 'mass')] = 0;
  220. RegionMatrix[rp(0, 'massCenterX')] = 0;
  221. RegionMatrix[rp(0, 'massCenterY')] = 0;
  222. // Add each node in the tree
  223. l = 1;
  224. for (n = 0; n < W.nodesLength; n += W.ppn) {
  225. // Current region, starting with root
  226. r = 0;
  227. while (true) {
  228. // Are there sub-regions?
  229. // We look at first child index
  230. if (RegionMatrix[rp(r, 'firstChild')] >= 0) {
  231. // There are sub-regions
  232. // We just iterate to find a "leave" of the tree
  233. // that is an empty region or a region with a single node
  234. // (see next case)
  235. // Find the quadrant of n
  236. if (NodeMatrix[np(n, 'x')] < RegionMatrix[rp(r, 'centerX')]) {
  237. if (NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  238. // Top Left quarter
  239. q = RegionMatrix[rp(r, 'firstChild')];
  240. }
  241. else {
  242. // Bottom Left quarter
  243. q = RegionMatrix[rp(r, 'firstChild')] + W.ppr;
  244. }
  245. }
  246. else {
  247. if (NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  248. // Top Right quarter
  249. q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 2;
  250. }
  251. else {
  252. // Bottom Right quarter
  253. q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 3;
  254. }
  255. }
  256. // Update center of mass and mass (we only do it for non-leave regions)
  257. RegionMatrix[rp(r, 'massCenterX')] =
  258. (RegionMatrix[rp(r, 'massCenterX')] * RegionMatrix[rp(r, 'mass')] +
  259. NodeMatrix[np(n, 'x')] * NodeMatrix[np(n, 'mass')]) /
  260. (RegionMatrix[rp(r, 'mass')] + NodeMatrix[np(n, 'mass')]);
  261. RegionMatrix[rp(r, 'massCenterY')] =
  262. (RegionMatrix[rp(r, 'massCenterY')] * RegionMatrix[rp(r, 'mass')] +
  263. NodeMatrix[np(n, 'y')] * NodeMatrix[np(n, 'mass')]) /
  264. (RegionMatrix[rp(r, 'mass')] + NodeMatrix[np(n, 'mass')]);
  265. RegionMatrix[rp(r, 'mass')] += NodeMatrix[np(n, 'mass')];
  266. // Iterate on the right quadrant
  267. r = q;
  268. continue;
  269. }
  270. else {
  271. // There are no sub-regions: we are in a "leave"
  272. // Is there a node in this leave?
  273. if (RegionMatrix[rp(r, 'node')] < 0) {
  274. // There is no node in region:
  275. // we record node n and go on
  276. RegionMatrix[rp(r, 'node')] = n;
  277. break;
  278. }
  279. else {
  280. // There is a node in this region
  281. // We will need to create sub-regions, stick the two
  282. // nodes (the old one r[0] and the new one n) in two
  283. // subregions. If they fall in the same quadrant,
  284. // we will iterate.
  285. // Create sub-regions
  286. RegionMatrix[rp(r, 'firstChild')] = l * W.ppr;
  287. w = RegionMatrix[rp(r, 'size')] / 2; // new size (half)
  288. // NOTE: we use screen coordinates
  289. // from Top Left to Bottom Right
  290. // Top Left sub-region
  291. g = RegionMatrix[rp(r, 'firstChild')];
  292. RegionMatrix[rp(g, 'node')] = -1;
  293. RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] - w;
  294. RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] - w;
  295. RegionMatrix[rp(g, 'size')] = w;
  296. RegionMatrix[rp(g, 'nextSibling')] = g + W.ppr;
  297. RegionMatrix[rp(g, 'firstChild')] = -1;
  298. RegionMatrix[rp(g, 'mass')] = 0;
  299. RegionMatrix[rp(g, 'massCenterX')] = 0;
  300. RegionMatrix[rp(g, 'massCenterY')] = 0;
  301. // Bottom Left sub-region
  302. g += W.ppr;
  303. RegionMatrix[rp(g, 'node')] = -1;
  304. RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] - w;
  305. RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] + w;
  306. RegionMatrix[rp(g, 'size')] = w;
  307. RegionMatrix[rp(g, 'nextSibling')] = g + W.ppr;
  308. RegionMatrix[rp(g, 'firstChild')] = -1;
  309. RegionMatrix[rp(g, 'mass')] = 0;
  310. RegionMatrix[rp(g, 'massCenterX')] = 0;
  311. RegionMatrix[rp(g, 'massCenterY')] = 0;
  312. // Top Right sub-region
  313. g += W.ppr;
  314. RegionMatrix[rp(g, 'node')] = -1;
  315. RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] + w;
  316. RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] - w;
  317. RegionMatrix[rp(g, 'size')] = w;
  318. RegionMatrix[rp(g, 'nextSibling')] = g + W.ppr;
  319. RegionMatrix[rp(g, 'firstChild')] = -1;
  320. RegionMatrix[rp(g, 'mass')] = 0;
  321. RegionMatrix[rp(g, 'massCenterX')] = 0;
  322. RegionMatrix[rp(g, 'massCenterY')] = 0;
  323. // Bottom Right sub-region
  324. g += W.ppr;
  325. RegionMatrix[rp(g, 'node')] = -1;
  326. RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] + w;
  327. RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] + w;
  328. RegionMatrix[rp(g, 'size')] = w;
  329. RegionMatrix[rp(g, 'nextSibling')] = RegionMatrix[rp(r, 'nextSibling')];
  330. RegionMatrix[rp(g, 'firstChild')] = -1;
  331. RegionMatrix[rp(g, 'mass')] = 0;
  332. RegionMatrix[rp(g, 'massCenterX')] = 0;
  333. RegionMatrix[rp(g, 'massCenterY')] = 0;
  334. l += 4;
  335. // Now the goal is to find two different sub-regions
  336. // for the two nodes: the one previously recorded (r[0])
  337. // and the one we want to add (n)
  338. // Find the quadrant of the old node
  339. if (NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'x')] < RegionMatrix[rp(r, 'centerX')]) {
  340. if (NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  341. // Top Left quarter
  342. q = RegionMatrix[rp(r, 'firstChild')];
  343. }
  344. else {
  345. // Bottom Left quarter
  346. q = RegionMatrix[rp(r, 'firstChild')] + W.ppr;
  347. }
  348. }
  349. else {
  350. if (NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  351. // Top Right quarter
  352. q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 2;
  353. }
  354. else {
  355. // Bottom Right quarter
  356. q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 3;
  357. }
  358. }
  359. // We remove r[0] from the region r, add its mass to r and record it in q
  360. RegionMatrix[rp(r, 'mass')] = NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')];
  361. RegionMatrix[rp(r, 'massCenterX')] = NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'x')];
  362. RegionMatrix[rp(r, 'massCenterY')] = NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')];
  363. RegionMatrix[rp(q, 'node')] = RegionMatrix[rp(r, 'node')];
  364. RegionMatrix[rp(r, 'node')] = -1;
  365. // Find the quadrant of n
  366. if (NodeMatrix[np(n, 'x')] < RegionMatrix[rp(r, 'centerX')]) {
  367. if (NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  368. // Top Left quarter
  369. q2 = RegionMatrix[rp(r, 'firstChild')];
  370. }
  371. else {
  372. // Bottom Left quarter
  373. q2 = RegionMatrix[rp(r, 'firstChild')] + W.ppr;
  374. }
  375. }
  376. else {
  377. if(NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
  378. // Top Right quarter
  379. q2 = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 2;
  380. }
  381. else {
  382. // Bottom Right quarter
  383. q2 = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 3;
  384. }
  385. }
  386. if (q === q2) {
  387. // If both nodes are in the same quadrant,
  388. // we have to try it again on this quadrant
  389. r = q;
  390. continue;
  391. }
  392. // If both quadrants are different, we record n
  393. // in its quadrant
  394. RegionMatrix[rp(q2, 'node')] = n;
  395. break;
  396. }
  397. }
  398. }
  399. }
  400. }
  401. // 2) Repulsion
  402. //--------------
  403. // NOTES: adjustSizes = antiCollision & scalingRatio = coefficient
  404. if (W.settings.barnesHutOptimize) {
  405. coefficient = W.settings.scalingRatio;
  406. // Applying repulsion through regions
  407. for (n = 0; n < W.nodesLength; n += W.ppn) {
  408. // Computing leaf quad nodes iteration
  409. r = 0; // Starting with root region
  410. while (true) {
  411. if (RegionMatrix[rp(r, 'firstChild')] >= 0) {
  412. // The region has sub-regions
  413. // We run the Barnes Hut test to see if we are at the right distance
  414. distance = Math.sqrt(
  415. (Math.pow(NodeMatrix[np(n, 'x')] - RegionMatrix[rp(r, 'massCenterX')], 2)) +
  416. (Math.pow(NodeMatrix[np(n, 'y')] - RegionMatrix[rp(r, 'massCenterY')], 2))
  417. );
  418. if (2 * RegionMatrix[rp(r, 'size')] / distance < W.settings.barnesHutTheta) {
  419. // We treat the region as a single body, and we repulse
  420. xDist = NodeMatrix[np(n, 'x')] - RegionMatrix[rp(r, 'massCenterX')];
  421. yDist = NodeMatrix[np(n, 'y')] - RegionMatrix[rp(r, 'massCenterY')];
  422. if (W.settings.adjustSizes) {
  423. //-- Linear Anti-collision Repulsion
  424. if (distance > 0) {
  425. factor = coefficient * NodeMatrix[np(n, 'mass')] *
  426. RegionMatrix[rp(r, 'mass')] / distance / distance;
  427. NodeMatrix[np(n, 'dx')] += xDist * factor;
  428. NodeMatrix[np(n, 'dy')] += yDist * factor;
  429. }
  430. else if (distance < 0) {
  431. factor = -coefficient * NodeMatrix[np(n, 'mass')] *
  432. RegionMatrix[rp(r, 'mass')] / distance;
  433. NodeMatrix[np(n, 'dx')] += xDist * factor;
  434. NodeMatrix[np(n, 'dy')] += yDist * factor;
  435. }
  436. }
  437. else {
  438. //-- Linear Repulsion
  439. if (distance > 0) {
  440. factor = coefficient * NodeMatrix[np(n, 'mass')] *
  441. RegionMatrix[rp(r, 'mass')] / distance / distance;
  442. NodeMatrix[np(n, 'dx')] += xDist * factor;
  443. NodeMatrix[np(n, 'dy')] += yDist * factor;
  444. }
  445. }
  446. // When this is done, we iterate. We have to look at the next sibling.
  447. if (RegionMatrix[rp(r, 'nextSibling')] < 0)
  448. break; // No next sibling: we have finished the tree
  449. r = RegionMatrix[rp(r, 'nextSibling')];
  450. continue;
  451. }
  452. else {
  453. // The region is too close and we have to look at sub-regions
  454. r = RegionMatrix[rp(r, 'firstChild')];
  455. continue;
  456. }
  457. }
  458. else {
  459. // The region has no sub-region
  460. // If there is a node r[0] and it is not n, then repulse
  461. if (RegionMatrix[rp(r, 'node')] >= 0 && RegionMatrix[rp(r, 'node')] !== n) {
  462. xDist = NodeMatrix[np(n, 'x')] - NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'x')];
  463. yDist = NodeMatrix[np(n, 'y')] - NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')];
  464. distance = Math.sqrt(xDist * xDist + yDist * yDist);
  465. if (W.settings.adjustSizes) {
  466. //-- Linear Anti-collision Repulsion
  467. if (distance > 0) {
  468. factor = coefficient * NodeMatrix[np(n, 'mass')] *
  469. NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')] / distance / distance;
  470. NodeMatrix[np(n, 'dx')] += xDist * factor;
  471. NodeMatrix[np(n, 'dy')] += yDist * factor;
  472. }
  473. else if (distance < 0) {
  474. factor = -coefficient * NodeMatrix[np(n, 'mass')] *
  475. NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')] / distance;
  476. NodeMatrix[np(n, 'dx')] += xDist * factor;
  477. NodeMatrix[np(n, 'dy')] += yDist * factor;
  478. }
  479. }
  480. else {
  481. //-- Linear Repulsion
  482. if (distance > 0) {
  483. factor = coefficient * NodeMatrix[np(n, 'mass')] *
  484. NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')] / distance / distance;
  485. NodeMatrix[np(n, 'dx')] += xDist * factor;
  486. NodeMatrix[np(n, 'dy')] += yDist * factor;
  487. }
  488. }
  489. }
  490. // When this is done, we iterate. We have to look at the next sibling.
  491. if (RegionMatrix[rp(r, 'nextSibling')] < 0)
  492. break; // No next sibling: we have finished the tree
  493. r = RegionMatrix[rp(r, 'nextSibling')];
  494. continue;
  495. }
  496. }
  497. }
  498. }
  499. else {
  500. coefficient = W.settings.scalingRatio;
  501. // Square iteration
  502. for (n1 = 0; n1 < W.nodesLength; n1 += W.ppn) {
  503. for (n2 = 0; n2 < n1; n2 += W.ppn) {
  504. // Common to both methods
  505. xDist = NodeMatrix[np(n1, 'x')] - NodeMatrix[np(n2, 'x')];
  506. yDist = NodeMatrix[np(n1, 'y')] - NodeMatrix[np(n2, 'y')];
  507. if (W.settings.adjustSizes) {
  508. //-- Anticollision Linear Repulsion
  509. distance = Math.sqrt(xDist * xDist + yDist * yDist) -
  510. NodeMatrix[np(n1, 'size')] -
  511. NodeMatrix[np(n2, 'size')];
  512. if (distance > 0) {
  513. factor = coefficient *
  514. NodeMatrix[np(n1, 'mass')] *
  515. NodeMatrix[np(n2, 'mass')] /
  516. distance / distance;
  517. // Updating nodes' dx and dy
  518. NodeMatrix[np(n1, 'dx')] += xDist * factor;
  519. NodeMatrix[np(n1, 'dy')] += yDist * factor;
  520. NodeMatrix[np(n2, 'dx')] += xDist * factor;
  521. NodeMatrix[np(n2, 'dy')] += yDist * factor;
  522. }
  523. else if (distance < 0) {
  524. factor = 100 * coefficient *
  525. NodeMatrix[np(n1, 'mass')] *
  526. NodeMatrix[np(n2, 'mass')];
  527. // Updating nodes' dx and dy
  528. NodeMatrix[np(n1, 'dx')] += xDist * factor;
  529. NodeMatrix[np(n1, 'dy')] += yDist * factor;
  530. NodeMatrix[np(n2, 'dx')] -= xDist * factor;
  531. NodeMatrix[np(n2, 'dy')] -= yDist * factor;
  532. }
  533. }
  534. else {
  535. //-- Linear Repulsion
  536. distance = Math.sqrt(xDist * xDist + yDist * yDist);
  537. if (distance > 0) {
  538. factor = coefficient *
  539. NodeMatrix[np(n1, 'mass')] *
  540. NodeMatrix[np(n2, 'mass')] /
  541. distance / distance;
  542. // Updating nodes' dx and dy
  543. NodeMatrix[np(n1, 'dx')] += xDist * factor;
  544. NodeMatrix[np(n1, 'dy')] += yDist * factor;
  545. NodeMatrix[np(n2, 'dx')] -= xDist * factor;
  546. NodeMatrix[np(n2, 'dy')] -= yDist * factor;
  547. }
  548. }
  549. }
  550. }
  551. }
  552. // 3) Gravity
  553. //------------
  554. g = W.settings.gravity / W.settings.scalingRatio;
  555. coefficient = W.settings.scalingRatio;
  556. for (n = 0; n < W.nodesLength; n += W.ppn) {
  557. factor = 0;
  558. // Common to both methods
  559. xDist = NodeMatrix[np(n, 'x')];
  560. yDist = NodeMatrix[np(n, 'y')];
  561. distance = Math.sqrt(
  562. Math.pow(xDist, 2) + Math.pow(yDist, 2)
  563. );
  564. if (W.settings.strongGravityMode) {
  565. //-- Strong gravity
  566. if (distance > 0)
  567. factor = coefficient * NodeMatrix[np(n, 'mass')] * g;
  568. }
  569. else {
  570. //-- Linear Anti-collision Repulsion n
  571. if (distance > 0)
  572. factor = coefficient * NodeMatrix[np(n, 'mass')] * g / distance;
  573. }
  574. // Updating node's dx and dy
  575. NodeMatrix[np(n, 'dx')] -= xDist * factor;
  576. NodeMatrix[np(n, 'dy')] -= yDist * factor;
  577. }
  578. // 4) Attraction
  579. //---------------
  580. coefficient = 1 *
  581. (W.settings.outboundAttractionDistribution ?
  582. outboundAttCompensation :
  583. 1);
  584. // TODO: simplify distance
  585. // TODO: coefficient is always used as -c --> optimize?
  586. for (e = 0; e < W.edgesLength; e += W.ppe) {
  587. n1 = EdgeMatrix[ep(e, 'source')];
  588. n2 = EdgeMatrix[ep(e, 'target')];
  589. w = EdgeMatrix[ep(e, 'weight')];
  590. // Edge weight influence
  591. ewc = Math.pow(w, W.settings.edgeWeightInfluence);
  592. // Common measures
  593. xDist = NodeMatrix[np(n1, 'x')] - NodeMatrix[np(n2, 'x')];
  594. yDist = NodeMatrix[np(n1, 'y')] - NodeMatrix[np(n2, 'y')];
  595. // Applying attraction to nodes
  596. if (W.settings.adjustSizes) {
  597. distance = Math.sqrt(
  598. (Math.pow(xDist, 2) + Math.pow(yDist, 2)) -
  599. NodeMatrix[np(n1, 'size')] -
  600. NodeMatrix[np(n2, 'size')]
  601. );
  602. if (W.settings.linLogMode) {
  603. if (W.settings.outboundAttractionDistribution) {
  604. //-- LinLog Degree Distributed Anti-collision Attraction
  605. if (distance > 0) {
  606. factor = -coefficient * ewc * Math.log(1 + distance) /
  607. distance /
  608. NodeMatrix[np(n1, 'mass')];
  609. }
  610. }
  611. else {
  612. //-- LinLog Anti-collision Attraction
  613. if (distance > 0) {
  614. factor = -coefficient * ewc * Math.log(1 + distance) / distance;
  615. }
  616. }
  617. }
  618. else {
  619. if (W.settings.outboundAttractionDistribution) {
  620. //-- Linear Degree Distributed Anti-collision Attraction
  621. if (distance > 0) {
  622. factor = -coefficient * ewc / NodeMatrix[np(n1, 'mass')];
  623. }
  624. }
  625. else {
  626. //-- Linear Anti-collision Attraction
  627. if (distance > 0) {
  628. factor = -coefficient * ewc;
  629. }
  630. }
  631. }
  632. }
  633. else {
  634. distance = Math.sqrt(
  635. Math.pow(xDist, 2) + Math.pow(yDist, 2)
  636. );
  637. if (W.settings.linLogMode) {
  638. if (W.settings.outboundAttractionDistribution) {
  639. //-- LinLog Degree Distributed Attraction
  640. if (distance > 0) {
  641. factor = -coefficient * ewc * Math.log(1 + distance) /
  642. distance /
  643. NodeMatrix[np(n1, 'mass')];
  644. }
  645. }
  646. else {
  647. //-- LinLog Attraction
  648. if (distance > 0)
  649. factor = -coefficient * ewc * Math.log(1 + distance) / distance;
  650. }
  651. }
  652. else {
  653. if (W.settings.outboundAttractionDistribution) {
  654. //-- Linear Attraction Mass Distributed
  655. // NOTE: Distance is set to 1 to override next condition
  656. distance = 1;
  657. factor = -coefficient * ewc / NodeMatrix[np(n1, 'mass')];
  658. }
  659. else {
  660. //-- Linear Attraction
  661. // NOTE: Distance is set to 1 to override next condition
  662. distance = 1;
  663. factor = -coefficient * ewc;
  664. }
  665. }
  666. }
  667. // Updating nodes' dx and dy
  668. // TODO: if condition or factor = 1?
  669. if (distance > 0) {
  670. // Updating nodes' dx and dy
  671. NodeMatrix[np(n1, 'dx')] += xDist * factor;
  672. NodeMatrix[np(n1, 'dy')] += yDist * factor;
  673. NodeMatrix[np(n2, 'dx')] -= xDist * factor;
  674. NodeMatrix[np(n2, 'dy')] -= yDist * factor;
  675. }
  676. }
  677. // 5) Apply Forces
  678. //-----------------
  679. var force,
  680. swinging,
  681. traction,
  682. nodespeed;
  683. // MATH: sqrt and square distances
  684. if (W.settings.adjustSizes) {
  685. for (n = 0; n < W.nodesLength; n += W.ppn) {
  686. if (!NodeMatrix[np(n, 'fixed')]) {
  687. force = Math.sqrt(
  688. Math.pow(NodeMatrix[np(n, 'dx')], 2) +
  689. Math.pow(NodeMatrix[np(n, 'dy')], 2)
  690. );
  691. if (force > W.maxForce) {
  692. NodeMatrix[np(n, 'dx')] =
  693. NodeMatrix[np(n, 'dx')] * W.maxForce / force;
  694. NodeMatrix[np(n, 'dy')] =
  695. NodeMatrix[np(n, 'dy')] * W.maxForce / force;
  696. }
  697. swinging = NodeMatrix[np(n, 'mass')] *
  698. Math.sqrt(
  699. (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) *
  700. (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) +
  701. (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')]) *
  702. (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')])
  703. );
  704. traction = Math.sqrt(
  705. (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) *
  706. (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) +
  707. (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')]) *
  708. (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')])
  709. ) / 2;
  710. nodespeed =
  711. 0.1 * Math.log(1 + traction) / (1 + Math.sqrt(swinging));
  712. // Updating node's positon
  713. NodeMatrix[np(n, 'x')] =
  714. NodeMatrix[np(n, 'x')] + NodeMatrix[np(n, 'dx')] *
  715. (nodespeed / W.settings.slowDown);
  716. NodeMatrix[np(n, 'y')] =
  717. NodeMatrix[np(n, 'y')] + NodeMatrix[np(n, 'dy')] *
  718. (nodespeed / W.settings.slowDown);
  719. }
  720. }
  721. }
  722. else {
  723. for (n = 0; n < W.nodesLength; n += W.ppn) {
  724. if (!NodeMatrix[np(n, 'fixed')]) {
  725. swinging = NodeMatrix[np(n, 'mass')] *
  726. Math.sqrt(
  727. (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) *
  728. (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) +
  729. (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')]) *
  730. (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')])
  731. );
  732. traction = Math.sqrt(
  733. (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) *
  734. (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) +
  735. (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')]) *
  736. (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')])
  737. ) / 2;
  738. nodespeed = NodeMatrix[np(n, 'convergence')] *
  739. Math.log(1 + traction) / (1 + Math.sqrt(swinging));
  740. // Updating node convergence
  741. NodeMatrix[np(n, 'convergence')] =
  742. Math.min(1, Math.sqrt(
  743. nodespeed *
  744. (Math.pow(NodeMatrix[np(n, 'dx')], 2) +
  745. Math.pow(NodeMatrix[np(n, 'dy')], 2)) /
  746. (1 + Math.sqrt(swinging))
  747. ));
  748. // Updating node's positon
  749. NodeMatrix[np(n, 'x')] =
  750. NodeMatrix[np(n, 'x')] + NodeMatrix[np(n, 'dx')] *
  751. (nodespeed / W.settings.slowDown);
  752. NodeMatrix[np(n, 'y')] =
  753. NodeMatrix[np(n, 'y')] + NodeMatrix[np(n, 'dy')] *
  754. (nodespeed / W.settings.slowDown);
  755. }
  756. }
  757. }
  758. // Counting one more iteration
  759. W.iterations++;
  760. }
  761. /**
  762. * Message reception & sending
  763. */
  764. // Sending data back to the supervisor
  765. var sendNewCoords;
  766. if (typeof window !== 'undefined' && window.document) {
  767. // From same document as sigma
  768. sendNewCoords = function() {
  769. var e;
  770. if (document.createEvent) {
  771. e = document.createEvent('Event');
  772. e.initEvent('newCoords', true, false);
  773. }
  774. else {
  775. e = document.createEventObject();
  776. e.eventType = 'newCoords';
  777. }
  778. e.eventName = 'newCoords';
  779. e.data = {
  780. nodes: NodeMatrix.buffer
  781. };
  782. requestAnimationFrame(function() {
  783. document.dispatchEvent(e);
  784. });
  785. };
  786. }
  787. else {
  788. // From a WebWorker
  789. sendNewCoords = function() {
  790. self.postMessage(
  791. {nodes: NodeMatrix.buffer},
  792. [NodeMatrix.buffer]
  793. );
  794. };
  795. }
  796. // Algorithm run
  797. function run(n) {
  798. for (var i = 0; i < n; i++)
  799. pass();
  800. sendNewCoords();
  801. }
  802. // On supervisor message
  803. var listener = function(e) {
  804. switch (e.data.action) {
  805. case 'start':
  806. init(
  807. new Float32Array(e.data.nodes),
  808. new Float32Array(e.data.edges),
  809. e.data.config
  810. );
  811. // First iteration(s)
  812. run(W.settings.startingIterations);
  813. break;
  814. case 'loop':
  815. NodeMatrix = new Float32Array(e.data.nodes);
  816. run(W.settings.iterationsPerRender);
  817. break;
  818. case 'config':
  819. // Merging new settings
  820. configure(e.data.config);
  821. break;
  822. case 'kill':
  823. // Deleting context for garbage collection
  824. __emptyObject(W);
  825. NodeMatrix = null;
  826. EdgeMatrix = null;
  827. RegionMatrix = null;
  828. self.removeEventListener('message', listener);
  829. break;
  830. default:
  831. }
  832. };
  833. // Adding event listener
  834. self.addEventListener('message', listener);
  835. };
  836. /**
  837. * Exporting
  838. * ----------
  839. *
  840. * Crush the worker function and make it accessible by sigma's instances so
  841. * the supervisor can call it.
  842. */
  843. function crush(fnString) {
  844. var pattern,
  845. i,
  846. l;
  847. var np = [
  848. 'x',
  849. 'y',
  850. 'dx',
  851. 'dy',
  852. 'old_dx',
  853. 'old_dy',
  854. 'mass',
  855. 'convergence',
  856. 'size',
  857. 'fixed'
  858. ];
  859. var ep = [
  860. 'source',
  861. 'target',
  862. 'weight'
  863. ];
  864. var rp = [
  865. 'node',
  866. 'centerX',
  867. 'centerY',
  868. 'size',
  869. 'nextSibling',
  870. 'firstChild',
  871. 'mass',
  872. 'massCenterX',
  873. 'massCenterY'
  874. ];
  875. // rp
  876. // NOTE: Must go first
  877. for (i = 0, l = rp.length; i < l; i++) {
  878. pattern = new RegExp('rp\\(([^,]*), \'' + rp[i] + '\'\\)', 'g');
  879. fnString = fnString.replace(
  880. pattern,
  881. (i === 0) ? '$1' : '$1 + ' + i
  882. );
  883. }
  884. // np
  885. for (i = 0, l = np.length; i < l; i++) {
  886. pattern = new RegExp('np\\(([^,]*), \'' + np[i] + '\'\\)', 'g');
  887. fnString = fnString.replace(
  888. pattern,
  889. (i === 0) ? '$1' : '$1 + ' + i
  890. );
  891. }
  892. // ep
  893. for (i = 0, l = ep.length; i < l; i++) {
  894. pattern = new RegExp('ep\\(([^,]*), \'' + ep[i] + '\'\\)', 'g');
  895. fnString = fnString.replace(
  896. pattern,
  897. (i === 0) ? '$1' : '$1 + ' + i
  898. );
  899. }
  900. return fnString;
  901. }
  902. // Exporting
  903. function getWorkerFn() {
  904. var fnString = crush ? crush(Worker.toString()) : Worker.toString();
  905. return ';(' + fnString + ').call(this);';
  906. }
  907. if (inWebWorker) {
  908. // We are in a webworker, so we launch the Worker function
  909. eval(getWorkerFn());
  910. }
  911. else {
  912. // We are requesting the worker from sigma, we retrieve it therefore
  913. if (typeof sigma === 'undefined')
  914. throw 'sigma is not declared';
  915. sigma.prototype.getForceAtlas2Worker = getWorkerFn;
  916. }
  917. }).call(this);