1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129 |
- ;(function(undefined) {
- 'use strict';
- /**
- * Sigma ForceAtlas2.5 Webworker
- * ==============================
- *
- * Author: Guillaume Plique (Yomguithereal)
- * Algorithm author: Mathieu Jacomy @ Sciences Po Medialab & WebAtlas
- * Version: 1.0.3
- */
- var _root = this,
- inWebWorker = !('document' in _root);
- /**
- * Worker Function Wrapper
- * ------------------------
- *
- * The worker has to be wrapped into a single stringified function
- * to be passed afterwards as a BLOB object to the supervisor.
- */
- var Worker = function(undefined) {
- 'use strict';
- /**
- * Worker settings and properties
- */
- var W = {
- // Properties
- ppn: 10,
- ppe: 3,
- ppr: 9,
- maxForce: 10,
- iterations: 0,
- converged: false,
- // Possible to change through config
- settings: {
- linLogMode: false,
- outboundAttractionDistribution: false,
- adjustSizes: false,
- edgeWeightInfluence: 0,
- scalingRatio: 1,
- strongGravityMode: false,
- gravity: 1,
- slowDown: 1,
- barnesHutOptimize: false,
- barnesHutTheta: 0.5,
- startingIterations: 1,
- iterationsPerRender: 1
- }
- };
- var NodeMatrix,
- EdgeMatrix,
- RegionMatrix;
- /**
- * Helpers
- */
- function extend() {
- var i,
- k,
- res = {},
- l = arguments.length;
- for (i = l - 1; i >= 0; i--)
- for (k in arguments[i])
- res[k] = arguments[i][k];
- return res;
- }
- function __emptyObject(obj) {
- var k;
- for (k in obj)
- if (!('hasOwnProperty' in obj) || obj.hasOwnProperty(k))
- delete obj[k];
- return obj;
- }
- /**
- * Matrices properties accessors
- */
- var nodeProperties = {
- x: 0,
- y: 1,
- dx: 2,
- dy: 3,
- old_dx: 4,
- old_dy: 5,
- mass: 6,
- convergence: 7,
- size: 8,
- fixed: 9
- };
- var edgeProperties = {
- source: 0,
- target: 1,
- weight: 2
- };
- var regionProperties = {
- node: 0,
- centerX: 1,
- centerY: 2,
- size: 3,
- nextSibling: 4,
- firstChild: 5,
- mass: 6,
- massCenterX: 7,
- massCenterY: 8
- };
- function np(i, p) {
- // DEBUG: safeguards
- if ((i % W.ppn) !== 0)
- throw 'np: non correct (' + i + ').';
- if (i !== parseInt(i))
- throw 'np: non int.';
- if (p in nodeProperties)
- return i + nodeProperties[p];
- else
- throw 'ForceAtlas2.Worker - ' +
- 'Inexistant node property given (' + p + ').';
- }
- function ep(i, p) {
- // DEBUG: safeguards
- if ((i % W.ppe) !== 0)
- throw 'ep: non correct (' + i + ').';
- if (i !== parseInt(i))
- throw 'ep: non int.';
- if (p in edgeProperties)
- return i + edgeProperties[p];
- else
- throw 'ForceAtlas2.Worker - ' +
- 'Inexistant edge property given (' + p + ').';
- }
- function rp(i, p) {
- // DEBUG: safeguards
- if ((i % W.ppr) !== 0)
- throw 'rp: non correct (' + i + ').';
- if (i !== parseInt(i))
- throw 'rp: non int.';
- if (p in regionProperties)
- return i + regionProperties[p];
- else
- throw 'ForceAtlas2.Worker - ' +
- 'Inexistant region property given (' + p + ').';
- }
- // DEBUG
- function nan(v) {
- if (isNaN(v))
- throw 'NaN alert!';
- }
- /**
- * Algorithm initialization
- */
- function init(nodes, edges, config) {
- config = config || {};
- var i, l;
- // Matrices
- NodeMatrix = nodes;
- EdgeMatrix = edges;
- // Length
- W.nodesLength = NodeMatrix.length;
- W.edgesLength = EdgeMatrix.length;
- // Merging configuration
- configure(config);
- }
- function configure(o) {
- W.settings = extend(o, W.settings);
- }
- /**
- * Algorithm pass
- */
- // MATH: get distances stuff and power 2 issues
- function pass() {
- var a, i, j, l, r, n, n1, n2, e, w, g, k, m;
- var outboundAttCompensation,
- coefficient,
- xDist,
- yDist,
- ewc,
- mass,
- distance,
- size,
- factor;
- // 1) Initializing layout data
- //-----------------------------
- // Resetting positions & computing max values
- for (n = 0; n < W.nodesLength; n += W.ppn) {
- NodeMatrix[np(n, 'old_dx')] = NodeMatrix[np(n, 'dx')];
- NodeMatrix[np(n, 'old_dy')] = NodeMatrix[np(n, 'dy')];
- NodeMatrix[np(n, 'dx')] = 0;
- NodeMatrix[np(n, 'dy')] = 0;
- }
- // If outbound attraction distribution, compensate
- if (W.settings.outboundAttractionDistribution) {
- outboundAttCompensation = 0;
- for (n = 0; n < W.nodesLength; n += W.ppn) {
- outboundAttCompensation += NodeMatrix[np(n, 'mass')];
- }
- outboundAttCompensation /= W.nodesLength;
- }
- // 1.bis) Barnes-Hut computation
- //------------------------------
- if (W.settings.barnesHutOptimize) {
- var minX = Infinity,
- maxX = -Infinity,
- minY = Infinity,
- maxY = -Infinity,
- q, q0, q1, q2, q3;
- // Setting up
- // RegionMatrix = new Float32Array(W.nodesLength / W.ppn * 4 * W.ppr);
- RegionMatrix = [];
- // Computing min and max values
- for (n = 0; n < W.nodesLength; n += W.ppn) {
- minX = Math.min(minX, NodeMatrix[np(n, 'x')]);
- maxX = Math.max(maxX, NodeMatrix[np(n, 'x')]);
- minY = Math.min(minY, NodeMatrix[np(n, 'y')]);
- maxY = Math.max(maxY, NodeMatrix[np(n, 'y')]);
- }
- // Build the Barnes Hut root region
- RegionMatrix[rp(0, 'node')] = -1;
- RegionMatrix[rp(0, 'centerX')] = (minX + maxX) / 2;
- RegionMatrix[rp(0, 'centerY')] = (minY + maxY) / 2;
- RegionMatrix[rp(0, 'size')] = Math.max(maxX - minX, maxY - minY);
- RegionMatrix[rp(0, 'nextSibling')] = -1;
- RegionMatrix[rp(0, 'firstChild')] = -1;
- RegionMatrix[rp(0, 'mass')] = 0;
- RegionMatrix[rp(0, 'massCenterX')] = 0;
- RegionMatrix[rp(0, 'massCenterY')] = 0;
- // Add each node in the tree
- l = 1;
- for (n = 0; n < W.nodesLength; n += W.ppn) {
- // Current region, starting with root
- r = 0;
- while (true) {
- // Are there sub-regions?
- // We look at first child index
- if (RegionMatrix[rp(r, 'firstChild')] >= 0) {
- // There are sub-regions
- // We just iterate to find a "leave" of the tree
- // that is an empty region or a region with a single node
- // (see next case)
- // Find the quadrant of n
- if (NodeMatrix[np(n, 'x')] < RegionMatrix[rp(r, 'centerX')]) {
- if (NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
- // Top Left quarter
- q = RegionMatrix[rp(r, 'firstChild')];
- }
- else {
- // Bottom Left quarter
- q = RegionMatrix[rp(r, 'firstChild')] + W.ppr;
- }
- }
- else {
- if (NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
- // Top Right quarter
- q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 2;
- }
- else {
- // Bottom Right quarter
- q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 3;
- }
- }
- // Update center of mass and mass (we only do it for non-leave regions)
- RegionMatrix[rp(r, 'massCenterX')] =
- (RegionMatrix[rp(r, 'massCenterX')] * RegionMatrix[rp(r, 'mass')] +
- NodeMatrix[np(n, 'x')] * NodeMatrix[np(n, 'mass')]) /
- (RegionMatrix[rp(r, 'mass')] + NodeMatrix[np(n, 'mass')]);
- RegionMatrix[rp(r, 'massCenterY')] =
- (RegionMatrix[rp(r, 'massCenterY')] * RegionMatrix[rp(r, 'mass')] +
- NodeMatrix[np(n, 'y')] * NodeMatrix[np(n, 'mass')]) /
- (RegionMatrix[rp(r, 'mass')] + NodeMatrix[np(n, 'mass')]);
- RegionMatrix[rp(r, 'mass')] += NodeMatrix[np(n, 'mass')];
- // Iterate on the right quadrant
- r = q;
- continue;
- }
- else {
- // There are no sub-regions: we are in a "leave"
- // Is there a node in this leave?
- if (RegionMatrix[rp(r, 'node')] < 0) {
- // There is no node in region:
- // we record node n and go on
- RegionMatrix[rp(r, 'node')] = n;
- break;
- }
- else {
- // There is a node in this region
- // We will need to create sub-regions, stick the two
- // nodes (the old one r[0] and the new one n) in two
- // subregions. If they fall in the same quadrant,
- // we will iterate.
- // Create sub-regions
- RegionMatrix[rp(r, 'firstChild')] = l * W.ppr;
- w = RegionMatrix[rp(r, 'size')] / 2; // new size (half)
- // NOTE: we use screen coordinates
- // from Top Left to Bottom Right
- // Top Left sub-region
- g = RegionMatrix[rp(r, 'firstChild')];
- RegionMatrix[rp(g, 'node')] = -1;
- RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] - w;
- RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] - w;
- RegionMatrix[rp(g, 'size')] = w;
- RegionMatrix[rp(g, 'nextSibling')] = g + W.ppr;
- RegionMatrix[rp(g, 'firstChild')] = -1;
- RegionMatrix[rp(g, 'mass')] = 0;
- RegionMatrix[rp(g, 'massCenterX')] = 0;
- RegionMatrix[rp(g, 'massCenterY')] = 0;
- // Bottom Left sub-region
- g += W.ppr;
- RegionMatrix[rp(g, 'node')] = -1;
- RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] - w;
- RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] + w;
- RegionMatrix[rp(g, 'size')] = w;
- RegionMatrix[rp(g, 'nextSibling')] = g + W.ppr;
- RegionMatrix[rp(g, 'firstChild')] = -1;
- RegionMatrix[rp(g, 'mass')] = 0;
- RegionMatrix[rp(g, 'massCenterX')] = 0;
- RegionMatrix[rp(g, 'massCenterY')] = 0;
- // Top Right sub-region
- g += W.ppr;
- RegionMatrix[rp(g, 'node')] = -1;
- RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] + w;
- RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] - w;
- RegionMatrix[rp(g, 'size')] = w;
- RegionMatrix[rp(g, 'nextSibling')] = g + W.ppr;
- RegionMatrix[rp(g, 'firstChild')] = -1;
- RegionMatrix[rp(g, 'mass')] = 0;
- RegionMatrix[rp(g, 'massCenterX')] = 0;
- RegionMatrix[rp(g, 'massCenterY')] = 0;
- // Bottom Right sub-region
- g += W.ppr;
- RegionMatrix[rp(g, 'node')] = -1;
- RegionMatrix[rp(g, 'centerX')] = RegionMatrix[rp(r, 'centerX')] + w;
- RegionMatrix[rp(g, 'centerY')] = RegionMatrix[rp(r, 'centerY')] + w;
- RegionMatrix[rp(g, 'size')] = w;
- RegionMatrix[rp(g, 'nextSibling')] = RegionMatrix[rp(r, 'nextSibling')];
- RegionMatrix[rp(g, 'firstChild')] = -1;
- RegionMatrix[rp(g, 'mass')] = 0;
- RegionMatrix[rp(g, 'massCenterX')] = 0;
- RegionMatrix[rp(g, 'massCenterY')] = 0;
- l += 4;
- // Now the goal is to find two different sub-regions
- // for the two nodes: the one previously recorded (r[0])
- // and the one we want to add (n)
- // Find the quadrant of the old node
- if (NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'x')] < RegionMatrix[rp(r, 'centerX')]) {
- if (NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')] < RegionMatrix[rp(r, 'centerY')]) {
- // Top Left quarter
- q = RegionMatrix[rp(r, 'firstChild')];
- }
- else {
- // Bottom Left quarter
- q = RegionMatrix[rp(r, 'firstChild')] + W.ppr;
- }
- }
- else {
- if (NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')] < RegionMatrix[rp(r, 'centerY')]) {
- // Top Right quarter
- q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 2;
- }
- else {
- // Bottom Right quarter
- q = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 3;
- }
- }
- // We remove r[0] from the region r, add its mass to r and record it in q
- RegionMatrix[rp(r, 'mass')] = NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')];
- RegionMatrix[rp(r, 'massCenterX')] = NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'x')];
- RegionMatrix[rp(r, 'massCenterY')] = NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')];
- RegionMatrix[rp(q, 'node')] = RegionMatrix[rp(r, 'node')];
- RegionMatrix[rp(r, 'node')] = -1;
- // Find the quadrant of n
- if (NodeMatrix[np(n, 'x')] < RegionMatrix[rp(r, 'centerX')]) {
- if (NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
- // Top Left quarter
- q2 = RegionMatrix[rp(r, 'firstChild')];
- }
- else {
- // Bottom Left quarter
- q2 = RegionMatrix[rp(r, 'firstChild')] + W.ppr;
- }
- }
- else {
- if(NodeMatrix[np(n, 'y')] < RegionMatrix[rp(r, 'centerY')]) {
- // Top Right quarter
- q2 = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 2;
- }
- else {
- // Bottom Right quarter
- q2 = RegionMatrix[rp(r, 'firstChild')] + W.ppr * 3;
- }
- }
- if (q === q2) {
- // If both nodes are in the same quadrant,
- // we have to try it again on this quadrant
- r = q;
- continue;
- }
- // If both quadrants are different, we record n
- // in its quadrant
- RegionMatrix[rp(q2, 'node')] = n;
- break;
- }
- }
- }
- }
- }
- // 2) Repulsion
- //--------------
- // NOTES: adjustSizes = antiCollision & scalingRatio = coefficient
- if (W.settings.barnesHutOptimize) {
- coefficient = W.settings.scalingRatio;
- // Applying repulsion through regions
- for (n = 0; n < W.nodesLength; n += W.ppn) {
- // Computing leaf quad nodes iteration
- r = 0; // Starting with root region
- while (true) {
- if (RegionMatrix[rp(r, 'firstChild')] >= 0) {
- // The region has sub-regions
- // We run the Barnes Hut test to see if we are at the right distance
- distance = Math.sqrt(
- (Math.pow(NodeMatrix[np(n, 'x')] - RegionMatrix[rp(r, 'massCenterX')], 2)) +
- (Math.pow(NodeMatrix[np(n, 'y')] - RegionMatrix[rp(r, 'massCenterY')], 2))
- );
- if (2 * RegionMatrix[rp(r, 'size')] / distance < W.settings.barnesHutTheta) {
- // We treat the region as a single body, and we repulse
- xDist = NodeMatrix[np(n, 'x')] - RegionMatrix[rp(r, 'massCenterX')];
- yDist = NodeMatrix[np(n, 'y')] - RegionMatrix[rp(r, 'massCenterY')];
- if (W.settings.adjustSizes) {
- //-- Linear Anti-collision Repulsion
- if (distance > 0) {
- factor = coefficient * NodeMatrix[np(n, 'mass')] *
- RegionMatrix[rp(r, 'mass')] / distance / distance;
- NodeMatrix[np(n, 'dx')] += xDist * factor;
- NodeMatrix[np(n, 'dy')] += yDist * factor;
- }
- else if (distance < 0) {
- factor = -coefficient * NodeMatrix[np(n, 'mass')] *
- RegionMatrix[rp(r, 'mass')] / distance;
- NodeMatrix[np(n, 'dx')] += xDist * factor;
- NodeMatrix[np(n, 'dy')] += yDist * factor;
- }
- }
- else {
- //-- Linear Repulsion
- if (distance > 0) {
- factor = coefficient * NodeMatrix[np(n, 'mass')] *
- RegionMatrix[rp(r, 'mass')] / distance / distance;
- NodeMatrix[np(n, 'dx')] += xDist * factor;
- NodeMatrix[np(n, 'dy')] += yDist * factor;
- }
- }
- // When this is done, we iterate. We have to look at the next sibling.
- if (RegionMatrix[rp(r, 'nextSibling')] < 0)
- break; // No next sibling: we have finished the tree
- r = RegionMatrix[rp(r, 'nextSibling')];
- continue;
- }
- else {
- // The region is too close and we have to look at sub-regions
- r = RegionMatrix[rp(r, 'firstChild')];
- continue;
- }
- }
- else {
- // The region has no sub-region
- // If there is a node r[0] and it is not n, then repulse
- if (RegionMatrix[rp(r, 'node')] >= 0 && RegionMatrix[rp(r, 'node')] !== n) {
- xDist = NodeMatrix[np(n, 'x')] - NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'x')];
- yDist = NodeMatrix[np(n, 'y')] - NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'y')];
- distance = Math.sqrt(xDist * xDist + yDist * yDist);
- if (W.settings.adjustSizes) {
- //-- Linear Anti-collision Repulsion
- if (distance > 0) {
- factor = coefficient * NodeMatrix[np(n, 'mass')] *
- NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')] / distance / distance;
- NodeMatrix[np(n, 'dx')] += xDist * factor;
- NodeMatrix[np(n, 'dy')] += yDist * factor;
- }
- else if (distance < 0) {
- factor = -coefficient * NodeMatrix[np(n, 'mass')] *
- NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')] / distance;
- NodeMatrix[np(n, 'dx')] += xDist * factor;
- NodeMatrix[np(n, 'dy')] += yDist * factor;
- }
- }
- else {
- //-- Linear Repulsion
- if (distance > 0) {
- factor = coefficient * NodeMatrix[np(n, 'mass')] *
- NodeMatrix[np(RegionMatrix[rp(r, 'node')], 'mass')] / distance / distance;
- NodeMatrix[np(n, 'dx')] += xDist * factor;
- NodeMatrix[np(n, 'dy')] += yDist * factor;
- }
- }
- }
- // When this is done, we iterate. We have to look at the next sibling.
- if (RegionMatrix[rp(r, 'nextSibling')] < 0)
- break; // No next sibling: we have finished the tree
- r = RegionMatrix[rp(r, 'nextSibling')];
- continue;
- }
- }
- }
- }
- else {
- coefficient = W.settings.scalingRatio;
- // Square iteration
- for (n1 = 0; n1 < W.nodesLength; n1 += W.ppn) {
- for (n2 = 0; n2 < n1; n2 += W.ppn) {
- // Common to both methods
- xDist = NodeMatrix[np(n1, 'x')] - NodeMatrix[np(n2, 'x')];
- yDist = NodeMatrix[np(n1, 'y')] - NodeMatrix[np(n2, 'y')];
- if (W.settings.adjustSizes) {
- //-- Anticollision Linear Repulsion
- distance = Math.sqrt(xDist * xDist + yDist * yDist) -
- NodeMatrix[np(n1, 'size')] -
- NodeMatrix[np(n2, 'size')];
- if (distance > 0) {
- factor = coefficient *
- NodeMatrix[np(n1, 'mass')] *
- NodeMatrix[np(n2, 'mass')] /
- distance / distance;
- // Updating nodes' dx and dy
- NodeMatrix[np(n1, 'dx')] += xDist * factor;
- NodeMatrix[np(n1, 'dy')] += yDist * factor;
- NodeMatrix[np(n2, 'dx')] += xDist * factor;
- NodeMatrix[np(n2, 'dy')] += yDist * factor;
- }
- else if (distance < 0) {
- factor = 100 * coefficient *
- NodeMatrix[np(n1, 'mass')] *
- NodeMatrix[np(n2, 'mass')];
- // Updating nodes' dx and dy
- NodeMatrix[np(n1, 'dx')] += xDist * factor;
- NodeMatrix[np(n1, 'dy')] += yDist * factor;
- NodeMatrix[np(n2, 'dx')] -= xDist * factor;
- NodeMatrix[np(n2, 'dy')] -= yDist * factor;
- }
- }
- else {
- //-- Linear Repulsion
- distance = Math.sqrt(xDist * xDist + yDist * yDist);
- if (distance > 0) {
- factor = coefficient *
- NodeMatrix[np(n1, 'mass')] *
- NodeMatrix[np(n2, 'mass')] /
- distance / distance;
- // Updating nodes' dx and dy
- NodeMatrix[np(n1, 'dx')] += xDist * factor;
- NodeMatrix[np(n1, 'dy')] += yDist * factor;
- NodeMatrix[np(n2, 'dx')] -= xDist * factor;
- NodeMatrix[np(n2, 'dy')] -= yDist * factor;
- }
- }
- }
- }
- }
- // 3) Gravity
- //------------
- g = W.settings.gravity / W.settings.scalingRatio;
- coefficient = W.settings.scalingRatio;
- for (n = 0; n < W.nodesLength; n += W.ppn) {
- factor = 0;
- // Common to both methods
- xDist = NodeMatrix[np(n, 'x')];
- yDist = NodeMatrix[np(n, 'y')];
- distance = Math.sqrt(
- Math.pow(xDist, 2) + Math.pow(yDist, 2)
- );
- if (W.settings.strongGravityMode) {
- //-- Strong gravity
- if (distance > 0)
- factor = coefficient * NodeMatrix[np(n, 'mass')] * g;
- }
- else {
- //-- Linear Anti-collision Repulsion n
- if (distance > 0)
- factor = coefficient * NodeMatrix[np(n, 'mass')] * g / distance;
- }
- // Updating node's dx and dy
- NodeMatrix[np(n, 'dx')] -= xDist * factor;
- NodeMatrix[np(n, 'dy')] -= yDist * factor;
- }
- // 4) Attraction
- //---------------
- coefficient = 1 *
- (W.settings.outboundAttractionDistribution ?
- outboundAttCompensation :
- 1);
- // TODO: simplify distance
- // TODO: coefficient is always used as -c --> optimize?
- for (e = 0; e < W.edgesLength; e += W.ppe) {
- n1 = EdgeMatrix[ep(e, 'source')];
- n2 = EdgeMatrix[ep(e, 'target')];
- w = EdgeMatrix[ep(e, 'weight')];
- // Edge weight influence
- ewc = Math.pow(w, W.settings.edgeWeightInfluence);
- // Common measures
- xDist = NodeMatrix[np(n1, 'x')] - NodeMatrix[np(n2, 'x')];
- yDist = NodeMatrix[np(n1, 'y')] - NodeMatrix[np(n2, 'y')];
- // Applying attraction to nodes
- if (W.settings.adjustSizes) {
- distance = Math.sqrt(
- (Math.pow(xDist, 2) + Math.pow(yDist, 2)) -
- NodeMatrix[np(n1, 'size')] -
- NodeMatrix[np(n2, 'size')]
- );
- if (W.settings.linLogMode) {
- if (W.settings.outboundAttractionDistribution) {
- //-- LinLog Degree Distributed Anti-collision Attraction
- if (distance > 0) {
- factor = -coefficient * ewc * Math.log(1 + distance) /
- distance /
- NodeMatrix[np(n1, 'mass')];
- }
- }
- else {
- //-- LinLog Anti-collision Attraction
- if (distance > 0) {
- factor = -coefficient * ewc * Math.log(1 + distance) / distance;
- }
- }
- }
- else {
- if (W.settings.outboundAttractionDistribution) {
- //-- Linear Degree Distributed Anti-collision Attraction
- if (distance > 0) {
- factor = -coefficient * ewc / NodeMatrix[np(n1, 'mass')];
- }
- }
- else {
- //-- Linear Anti-collision Attraction
- if (distance > 0) {
- factor = -coefficient * ewc;
- }
- }
- }
- }
- else {
- distance = Math.sqrt(
- Math.pow(xDist, 2) + Math.pow(yDist, 2)
- );
- if (W.settings.linLogMode) {
- if (W.settings.outboundAttractionDistribution) {
- //-- LinLog Degree Distributed Attraction
- if (distance > 0) {
- factor = -coefficient * ewc * Math.log(1 + distance) /
- distance /
- NodeMatrix[np(n1, 'mass')];
- }
- }
- else {
- //-- LinLog Attraction
- if (distance > 0)
- factor = -coefficient * ewc * Math.log(1 + distance) / distance;
- }
- }
- else {
- if (W.settings.outboundAttractionDistribution) {
- //-- Linear Attraction Mass Distributed
- // NOTE: Distance is set to 1 to override next condition
- distance = 1;
- factor = -coefficient * ewc / NodeMatrix[np(n1, 'mass')];
- }
- else {
- //-- Linear Attraction
- // NOTE: Distance is set to 1 to override next condition
- distance = 1;
- factor = -coefficient * ewc;
- }
- }
- }
- // Updating nodes' dx and dy
- // TODO: if condition or factor = 1?
- if (distance > 0) {
- // Updating nodes' dx and dy
- NodeMatrix[np(n1, 'dx')] += xDist * factor;
- NodeMatrix[np(n1, 'dy')] += yDist * factor;
- NodeMatrix[np(n2, 'dx')] -= xDist * factor;
- NodeMatrix[np(n2, 'dy')] -= yDist * factor;
- }
- }
- // 5) Apply Forces
- //-----------------
- var force,
- swinging,
- traction,
- nodespeed;
- // MATH: sqrt and square distances
- if (W.settings.adjustSizes) {
- for (n = 0; n < W.nodesLength; n += W.ppn) {
- if (!NodeMatrix[np(n, 'fixed')]) {
- force = Math.sqrt(
- Math.pow(NodeMatrix[np(n, 'dx')], 2) +
- Math.pow(NodeMatrix[np(n, 'dy')], 2)
- );
- if (force > W.maxForce) {
- NodeMatrix[np(n, 'dx')] =
- NodeMatrix[np(n, 'dx')] * W.maxForce / force;
- NodeMatrix[np(n, 'dy')] =
- NodeMatrix[np(n, 'dy')] * W.maxForce / force;
- }
- swinging = NodeMatrix[np(n, 'mass')] *
- Math.sqrt(
- (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) *
- (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) +
- (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')]) *
- (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')])
- );
- traction = Math.sqrt(
- (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) *
- (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) +
- (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')]) *
- (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')])
- ) / 2;
- nodespeed =
- 0.1 * Math.log(1 + traction) / (1 + Math.sqrt(swinging));
- // Updating node's positon
- NodeMatrix[np(n, 'x')] =
- NodeMatrix[np(n, 'x')] + NodeMatrix[np(n, 'dx')] *
- (nodespeed / W.settings.slowDown);
- NodeMatrix[np(n, 'y')] =
- NodeMatrix[np(n, 'y')] + NodeMatrix[np(n, 'dy')] *
- (nodespeed / W.settings.slowDown);
- }
- }
- }
- else {
- for (n = 0; n < W.nodesLength; n += W.ppn) {
- if (!NodeMatrix[np(n, 'fixed')]) {
- swinging = NodeMatrix[np(n, 'mass')] *
- Math.sqrt(
- (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) *
- (NodeMatrix[np(n, 'old_dx')] - NodeMatrix[np(n, 'dx')]) +
- (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')]) *
- (NodeMatrix[np(n, 'old_dy')] - NodeMatrix[np(n, 'dy')])
- );
- traction = Math.sqrt(
- (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) *
- (NodeMatrix[np(n, 'old_dx')] + NodeMatrix[np(n, 'dx')]) +
- (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')]) *
- (NodeMatrix[np(n, 'old_dy')] + NodeMatrix[np(n, 'dy')])
- ) / 2;
- nodespeed = NodeMatrix[np(n, 'convergence')] *
- Math.log(1 + traction) / (1 + Math.sqrt(swinging));
- // Updating node convergence
- NodeMatrix[np(n, 'convergence')] =
- Math.min(1, Math.sqrt(
- nodespeed *
- (Math.pow(NodeMatrix[np(n, 'dx')], 2) +
- Math.pow(NodeMatrix[np(n, 'dy')], 2)) /
- (1 + Math.sqrt(swinging))
- ));
- // Updating node's positon
- NodeMatrix[np(n, 'x')] =
- NodeMatrix[np(n, 'x')] + NodeMatrix[np(n, 'dx')] *
- (nodespeed / W.settings.slowDown);
- NodeMatrix[np(n, 'y')] =
- NodeMatrix[np(n, 'y')] + NodeMatrix[np(n, 'dy')] *
- (nodespeed / W.settings.slowDown);
- }
- }
- }
- // Counting one more iteration
- W.iterations++;
- }
- /**
- * Message reception & sending
- */
- // Sending data back to the supervisor
- var sendNewCoords;
- if (typeof window !== 'undefined' && window.document) {
- // From same document as sigma
- sendNewCoords = function() {
- var e;
- if (document.createEvent) {
- e = document.createEvent('Event');
- e.initEvent('newCoords', true, false);
- }
- else {
- e = document.createEventObject();
- e.eventType = 'newCoords';
- }
- e.eventName = 'newCoords';
- e.data = {
- nodes: NodeMatrix.buffer
- };
- requestAnimationFrame(function() {
- document.dispatchEvent(e);
- });
- };
- }
- else {
- // From a WebWorker
- sendNewCoords = function() {
- self.postMessage(
- {nodes: NodeMatrix.buffer},
- [NodeMatrix.buffer]
- );
- };
- }
- // Algorithm run
- function run(n) {
- for (var i = 0; i < n; i++)
- pass();
- sendNewCoords();
- }
- // On supervisor message
- var listener = function(e) {
- switch (e.data.action) {
- case 'start':
- init(
- new Float32Array(e.data.nodes),
- new Float32Array(e.data.edges),
- e.data.config
- );
- // First iteration(s)
- run(W.settings.startingIterations);
- break;
- case 'loop':
- NodeMatrix = new Float32Array(e.data.nodes);
- run(W.settings.iterationsPerRender);
- break;
- case 'config':
- // Merging new settings
- configure(e.data.config);
- break;
- case 'kill':
- // Deleting context for garbage collection
- __emptyObject(W);
- NodeMatrix = null;
- EdgeMatrix = null;
- RegionMatrix = null;
- self.removeEventListener('message', listener);
- break;
- default:
- }
- };
- // Adding event listener
- self.addEventListener('message', listener);
- };
- /**
- * Exporting
- * ----------
- *
- * Crush the worker function and make it accessible by sigma's instances so
- * the supervisor can call it.
- */
- function crush(fnString) {
- var pattern,
- i,
- l;
- var np = [
- 'x',
- 'y',
- 'dx',
- 'dy',
- 'old_dx',
- 'old_dy',
- 'mass',
- 'convergence',
- 'size',
- 'fixed'
- ];
- var ep = [
- 'source',
- 'target',
- 'weight'
- ];
- var rp = [
- 'node',
- 'centerX',
- 'centerY',
- 'size',
- 'nextSibling',
- 'firstChild',
- 'mass',
- 'massCenterX',
- 'massCenterY'
- ];
- // rp
- // NOTE: Must go first
- for (i = 0, l = rp.length; i < l; i++) {
- pattern = new RegExp('rp\\(([^,]*), \'' + rp[i] + '\'\\)', 'g');
- fnString = fnString.replace(
- pattern,
- (i === 0) ? '$1' : '$1 + ' + i
- );
- }
- // np
- for (i = 0, l = np.length; i < l; i++) {
- pattern = new RegExp('np\\(([^,]*), \'' + np[i] + '\'\\)', 'g');
- fnString = fnString.replace(
- pattern,
- (i === 0) ? '$1' : '$1 + ' + i
- );
- }
- // ep
- for (i = 0, l = ep.length; i < l; i++) {
- pattern = new RegExp('ep\\(([^,]*), \'' + ep[i] + '\'\\)', 'g');
- fnString = fnString.replace(
- pattern,
- (i === 0) ? '$1' : '$1 + ' + i
- );
- }
- return fnString;
- }
- // Exporting
- function getWorkerFn() {
- var fnString = crush ? crush(Worker.toString()) : Worker.toString();
- return ';(' + fnString + ').call(this);';
- }
- if (inWebWorker) {
- // We are in a webworker, so we launch the Worker function
- eval(getWorkerFn());
- }
- else {
- // We are requesting the worker from sigma, we retrieve it therefore
- if (typeof sigma === 'undefined')
- throw 'sigma is not declared';
- sigma.prototype.getForceAtlas2Worker = getWorkerFn;
- }
- }).call(this);
|