sigma-rails 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +37 -0
- data/lib/sigma/rails.rb +8 -0
- data/lib/sigma/rails/version.rb +5 -0
- data/vendor/assets/javascripts/sigma.js +8723 -0
- data/vendor/assets/javascripts/sigma.layout.forceAtlas2/README.md +12 -0
- data/vendor/assets/javascripts/sigma.layout.forceAtlas2/sigma.layout.forceAtlas2.js +1051 -0
- data/vendor/assets/javascripts/sigma.parsers.gexf/README.md +29 -0
- data/vendor/assets/javascripts/sigma.parsers.gexf/gexf-parser.js +548 -0
- data/vendor/assets/javascripts/sigma.parsers.gexf/sigma.parsers.gexf.js +108 -0
- data/vendor/assets/javascripts/sigma.parsers.json/README.md +29 -0
- data/vendor/assets/javascripts/sigma.parsers.json/sigma.parsers.json.js +88 -0
- data/vendor/assets/javascripts/sigma.plugins.animate/README.md +8 -0
- data/vendor/assets/javascripts/sigma.plugins.animate/sigma.plugins.animate.js +165 -0
- data/vendor/assets/javascripts/sigma.plugins.dragNodes/README.md +8 -0
- data/vendor/assets/javascripts/sigma.plugins.dragNodes/sigma.plugins.dragNodes.js +135 -0
- data/vendor/assets/javascripts/sigma.plugins.neighborhoods/README.md +24 -0
- data/vendor/assets/javascripts/sigma.plugins.neighborhoods/sigma.plugins.neighborhoods.js +186 -0
- data/vendor/assets/javascripts/sigma.renderers.customShapes/README.md +55 -0
- data/vendor/assets/javascripts/sigma.renderers.customShapes/shape-library.js +145 -0
- data/vendor/assets/javascripts/sigma.renderers.customShapes/sigma.renderers.customShapes.js +112 -0
- metadata +121 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
sigma.layout.forceAtlas2
|
2
|
+
========================
|
3
|
+
|
4
|
+
Plugin developed by [Mathieu Jacomy](https://github.com/jacomyma).
|
5
|
+
|
6
|
+
---
|
7
|
+
|
8
|
+
This plugin adds two methods to every sigma instances, `startForceAtlas2` and `stopForceAtlas2`. It implements [ForceAtlas2](http://webatlas.fr/tempshare/ForceAtlas2_Paper.pdf), a force-directed layout algorithm.
|
9
|
+
|
10
|
+
Basically, `myInstance.startForceAtlas2()` will start running the layout: It will spawn an atomic job in [conrad](http://jacomyal.github.io/conrad.js/) that will compute the next step. When the job is done, the `refresh` method of the instance will be called, and everything starts again.
|
11
|
+
|
12
|
+
The layout will not stop by itself, and it is necessary to call `myInstance.stopForceAtlas2()`.
|
@@ -0,0 +1,1051 @@
|
|
1
|
+
/**
|
2
|
+
* Mathieu Jacomy @ Sciences Po Médialab & WebAtlas
|
3
|
+
* (requires sigma.js to be loaded)
|
4
|
+
*/
|
5
|
+
;(function(undefined) {
|
6
|
+
'use strict';
|
7
|
+
|
8
|
+
if (typeof sigma === 'undefined')
|
9
|
+
throw 'sigma is not declared';
|
10
|
+
|
11
|
+
var forceatlas2 = sigma.utils.pkg('sigma.layout.forceatlas2');
|
12
|
+
forceatlas2.ForceAtlas2 = function(graph, options) {
|
13
|
+
var self = this;
|
14
|
+
this.graph = graph;
|
15
|
+
|
16
|
+
this.p = sigma.utils.extend(
|
17
|
+
options || {},
|
18
|
+
forceatlas2.defaultSettings
|
19
|
+
);
|
20
|
+
|
21
|
+
// The state tracked from one atomic "go" to another
|
22
|
+
this.state = {step: 0, index: 0};
|
23
|
+
|
24
|
+
// Runtime (the ForceAtlas2 itself)
|
25
|
+
this.init = function() {
|
26
|
+
self.state = {
|
27
|
+
step: 0,
|
28
|
+
index: 0
|
29
|
+
};
|
30
|
+
|
31
|
+
self.graph.nodes().forEach(function(n) {
|
32
|
+
n.fa2 = {
|
33
|
+
mass: 1 + self.graph.degree(n.id),
|
34
|
+
old_dx: 0,
|
35
|
+
old_dy: 0,
|
36
|
+
dx: 0,
|
37
|
+
dy: 0
|
38
|
+
};
|
39
|
+
});
|
40
|
+
|
41
|
+
return self;
|
42
|
+
};
|
43
|
+
|
44
|
+
this.go = function() {
|
45
|
+
while (self.atomicGo()) {}
|
46
|
+
};
|
47
|
+
|
48
|
+
this.atomicGo = function() {
|
49
|
+
var i,
|
50
|
+
n,
|
51
|
+
e,
|
52
|
+
l,
|
53
|
+
fixed,
|
54
|
+
swinging,
|
55
|
+
factor,
|
56
|
+
graph = self.graph,
|
57
|
+
nIndex = graph.nodes,
|
58
|
+
eIndex = graph.edges,
|
59
|
+
nodes = nIndex(),
|
60
|
+
edges = eIndex(),
|
61
|
+
cInt = self.p.complexIntervals,
|
62
|
+
sInt = self.p.simpleIntervals;
|
63
|
+
|
64
|
+
switch (self.state.step) {
|
65
|
+
// Pass init
|
66
|
+
case 0:
|
67
|
+
// Initialise layout data
|
68
|
+
for (i = 0, l = nodes.length; i < l; i++) {
|
69
|
+
n = nodes[i];
|
70
|
+
if (n.fa2)
|
71
|
+
n.fa2 = {
|
72
|
+
mass: 1 + self.graph.degree(n.id),
|
73
|
+
old_dx: n.fa2.dx || 0,
|
74
|
+
old_dy: n.fa2.dy || 0,
|
75
|
+
dx: 0,
|
76
|
+
dy: 0
|
77
|
+
};
|
78
|
+
else
|
79
|
+
n.fa2 = {
|
80
|
+
mass: 1 + self.graph.degree(n.id),
|
81
|
+
old_dx: 0,
|
82
|
+
old_dy: 0,
|
83
|
+
dx: 0,
|
84
|
+
dy: 0
|
85
|
+
};
|
86
|
+
}
|
87
|
+
|
88
|
+
// If Barnes Hut is active, initialize root region
|
89
|
+
if (self.p.barnesHutOptimize) {
|
90
|
+
self.rootRegion = new forceatlas2.Region(nodes, 0);
|
91
|
+
self.rootRegion.buildSubRegions();
|
92
|
+
}
|
93
|
+
|
94
|
+
// If outboundAttractionDistribution active, compensate.
|
95
|
+
if (self.p.outboundAttractionDistribution) {
|
96
|
+
self.p.outboundAttCompensation = 0;
|
97
|
+
for (i = 0, l = nodes.length; i < l; i++) {
|
98
|
+
n = nodes[i];
|
99
|
+
self.p.outboundAttCompensation += n.fa2.mass;
|
100
|
+
}
|
101
|
+
self.p.outboundAttCompensation /= nodes.length;
|
102
|
+
}
|
103
|
+
|
104
|
+
self.state.step = 1;
|
105
|
+
self.state.index = 0;
|
106
|
+
return true;
|
107
|
+
|
108
|
+
// Repulsion
|
109
|
+
case 1:
|
110
|
+
var n1,
|
111
|
+
n2,
|
112
|
+
i1,
|
113
|
+
i2,
|
114
|
+
rootRegion,
|
115
|
+
barnesHutTheta,
|
116
|
+
Repulsion = self.ForceFactory.buildRepulsion(
|
117
|
+
self.p.adjustSizes,
|
118
|
+
self.p.scalingRatio
|
119
|
+
);
|
120
|
+
|
121
|
+
if (self.p.barnesHutOptimize) {
|
122
|
+
rootRegion = self.rootRegion;
|
123
|
+
|
124
|
+
// Pass to the scope of forEach
|
125
|
+
barnesHutTheta = self.p.barnesHutTheta;
|
126
|
+
i = self.state.index;
|
127
|
+
|
128
|
+
while (i < nodes.length && i < self.state.index + cInt)
|
129
|
+
if ((n = nodes[i++]).fa2)
|
130
|
+
rootRegion.applyForce(n, Repulsion, barnesHutTheta);
|
131
|
+
|
132
|
+
if (i === nodes.length)
|
133
|
+
self.state = {
|
134
|
+
step: 2,
|
135
|
+
index: 0
|
136
|
+
};
|
137
|
+
else
|
138
|
+
self.state.index = i;
|
139
|
+
} else {
|
140
|
+
i1 = self.state.index;
|
141
|
+
|
142
|
+
while (i1 < nodes.length && i1 < self.state.index + cInt)
|
143
|
+
if ((n1 = nodes[i1++]).fa2)
|
144
|
+
for (i2 = 0; i2 < i1; i2++)
|
145
|
+
if ((n2 = nodes[i2]).fa2)
|
146
|
+
Repulsion.apply_nn(n1, n2);
|
147
|
+
|
148
|
+
if (i1 === nodes.length)
|
149
|
+
self.state = {
|
150
|
+
step: 2,
|
151
|
+
index: 0
|
152
|
+
};
|
153
|
+
else
|
154
|
+
self.state.index = i1;
|
155
|
+
}
|
156
|
+
return true;
|
157
|
+
|
158
|
+
// Gravity
|
159
|
+
case 2:
|
160
|
+
var Gravity =
|
161
|
+
self.p.strongGravityMode ?
|
162
|
+
self.ForceFactory.getStrongGravity(
|
163
|
+
self.p.scalingRatio
|
164
|
+
) :
|
165
|
+
self.ForceFactory.buildRepulsion(
|
166
|
+
self.p.adjustSizes,
|
167
|
+
self.p.scalingRatio
|
168
|
+
),
|
169
|
+
// Pass gravity and scalingRatio to the scope of the function
|
170
|
+
gravity = self.p.gravity,
|
171
|
+
scalingRatio = self.p.scalingRatio;
|
172
|
+
|
173
|
+
i = self.state.index;
|
174
|
+
while (i < nodes.length && i < self.state.index + sInt) {
|
175
|
+
n = nodes[i++];
|
176
|
+
if (n.fa2)
|
177
|
+
Gravity.apply_g(n, gravity / scalingRatio);
|
178
|
+
}
|
179
|
+
|
180
|
+
if (i === nodes.length)
|
181
|
+
self.state = {
|
182
|
+
step: 3,
|
183
|
+
index: 0
|
184
|
+
};
|
185
|
+
else
|
186
|
+
self.state.index = i;
|
187
|
+
return true;
|
188
|
+
|
189
|
+
// Attraction
|
190
|
+
case 3:
|
191
|
+
var Attraction = self.ForceFactory.buildAttraction(
|
192
|
+
self.p.linLogMode,
|
193
|
+
self.p.outboundAttractionDistribution,
|
194
|
+
self.p.adjustSizes,
|
195
|
+
self.p.outboundAttractionDistribution ?
|
196
|
+
self.p.outboundAttCompensation :
|
197
|
+
1
|
198
|
+
);
|
199
|
+
|
200
|
+
i = self.state.index;
|
201
|
+
if (self.p.edgeWeightInfluence === 0)
|
202
|
+
while (i < edges.length && i < self.state.index + cInt) {
|
203
|
+
e = edges[i++];
|
204
|
+
Attraction.apply_nn(nIndex(e.source), nIndex(e.target), 1);
|
205
|
+
}
|
206
|
+
else if (self.p.edgeWeightInfluence === 1)
|
207
|
+
while (i < edges.length && i < self.state.index + cInt) {
|
208
|
+
e = edges[i++];
|
209
|
+
Attraction.apply_nn(
|
210
|
+
nIndex(e.source),
|
211
|
+
nIndex(e.target),
|
212
|
+
e.weight || 1
|
213
|
+
);
|
214
|
+
}
|
215
|
+
else
|
216
|
+
while (i < edges.length && i < self.state.index + cInt) {
|
217
|
+
e = edges[i++];
|
218
|
+
Attraction.apply_nn(
|
219
|
+
nIndex(e.source), nIndex(e.target),
|
220
|
+
Math.pow(e.weight || 1, self.p.edgeWeightInfluence)
|
221
|
+
);
|
222
|
+
}
|
223
|
+
|
224
|
+
if (i === edges.length)
|
225
|
+
self.state = {
|
226
|
+
step: 4,
|
227
|
+
index: 0
|
228
|
+
};
|
229
|
+
else
|
230
|
+
self.state.index = i;
|
231
|
+
|
232
|
+
return true;
|
233
|
+
|
234
|
+
// Auto adjust speed
|
235
|
+
case 4:
|
236
|
+
var maxRise,
|
237
|
+
targetSpeed,
|
238
|
+
totalSwinging = 0, // How much irregular movement
|
239
|
+
totalEffectiveTraction = 0; // Hom much useful movement
|
240
|
+
|
241
|
+
for (i = 0, l = nodes.length; i < l; i++) {
|
242
|
+
n = nodes[i];
|
243
|
+
|
244
|
+
fixed = n.fixed || false;
|
245
|
+
if (!fixed && n.fa2) {
|
246
|
+
swinging =
|
247
|
+
Math.sqrt(Math.pow(n.fa2.old_dx - n.fa2.dx, 2) +
|
248
|
+
Math.pow(n.fa2.old_dy - n.fa2.dy, 2));
|
249
|
+
|
250
|
+
// If the node has a burst change of direction,
|
251
|
+
// then it's not converging.
|
252
|
+
totalSwinging += n.fa2.mass * swinging;
|
253
|
+
totalEffectiveTraction += n.fa2.mass *
|
254
|
+
0.5 *
|
255
|
+
Math.sqrt(
|
256
|
+
Math.pow(n.fa2.old_dx + n.fa2.dx, 2) +
|
257
|
+
Math.pow(n.fa2.old_dy + n.fa2.dy, 2)
|
258
|
+
);
|
259
|
+
}
|
260
|
+
}
|
261
|
+
|
262
|
+
self.p.totalSwinging = totalSwinging;
|
263
|
+
self.p.totalEffectiveTraction = totalEffectiveTraction;
|
264
|
+
|
265
|
+
// We want that swingingMovement < tolerance * convergenceMovement
|
266
|
+
targetSpeed =
|
267
|
+
Math.pow(self.p.jitterTolerance, 2) *
|
268
|
+
self.p.totalEffectiveTraction /
|
269
|
+
self.p.totalSwinging;
|
270
|
+
|
271
|
+
// But the speed shoudn't rise too much too quickly,
|
272
|
+
// since it would make the convergence drop dramatically.
|
273
|
+
// Max rise: 50%
|
274
|
+
maxRise = 0.5;
|
275
|
+
self.p.speed =
|
276
|
+
self.p.speed +
|
277
|
+
Math.min(
|
278
|
+
targetSpeed - self.p.speed,
|
279
|
+
maxRise * self.p.speed
|
280
|
+
);
|
281
|
+
|
282
|
+
// Save old coordinates
|
283
|
+
for (i = 0, l = nodes.length; i < l; i++) {
|
284
|
+
n = nodes[i];
|
285
|
+
n.old_x = +n.x;
|
286
|
+
n.old_y = +n.y;
|
287
|
+
}
|
288
|
+
|
289
|
+
self.state.step = 5;
|
290
|
+
return true;
|
291
|
+
|
292
|
+
// Apply forces
|
293
|
+
case 5:
|
294
|
+
var df,
|
295
|
+
speed;
|
296
|
+
|
297
|
+
i = self.state.index;
|
298
|
+
if (self.p.adjustSizes) {
|
299
|
+
speed = self.p.speed;
|
300
|
+
// If nodes overlap prevention is active,
|
301
|
+
// it's not possible to trust the swinging mesure.
|
302
|
+
while (i < nodes.length && i < self.state.index + sInt) {
|
303
|
+
n = nodes[i++];
|
304
|
+
fixed = n.fixed || false;
|
305
|
+
if (!fixed && n.fa2) {
|
306
|
+
// Adaptive auto-speed: the speed of each node is lowered
|
307
|
+
// when the node swings.
|
308
|
+
swinging = Math.sqrt(
|
309
|
+
(n.fa2.old_dx - n.fa2.dx) *
|
310
|
+
(n.fa2.old_dx - n.fa2.dx) +
|
311
|
+
(n.fa2.old_dy - n.fa2.dy) *
|
312
|
+
(n.fa2.old_dy - n.fa2.dy)
|
313
|
+
);
|
314
|
+
factor = 0.1 * speed / (1 + speed * Math.sqrt(swinging));
|
315
|
+
|
316
|
+
df =
|
317
|
+
Math.sqrt(Math.pow(n.fa2.dx, 2) +
|
318
|
+
Math.pow(n.fa2.dy, 2));
|
319
|
+
|
320
|
+
factor = Math.min(factor * df, 10) / df;
|
321
|
+
|
322
|
+
n.x += n.fa2.dx * factor;
|
323
|
+
n.y += n.fa2.dy * factor;
|
324
|
+
}
|
325
|
+
}
|
326
|
+
} else {
|
327
|
+
speed = self.p.speed;
|
328
|
+
while (i < nodes.length && i < self.state.index + sInt) {
|
329
|
+
n = nodes[i++];
|
330
|
+
fixed = n.fixed || false;
|
331
|
+
if (!fixed && n.fa2) {
|
332
|
+
// Adaptive auto-speed: the speed of each node is lowered
|
333
|
+
// when the node swings.
|
334
|
+
swinging = Math.sqrt(
|
335
|
+
(n.fa2.old_dx - n.fa2.dx) *
|
336
|
+
(n.fa2.old_dx - n.fa2.dx) +
|
337
|
+
(n.fa2.old_dy - n.fa2.dy) *
|
338
|
+
(n.fa2.old_dy - n.fa2.dy)
|
339
|
+
);
|
340
|
+
|
341
|
+
factor = speed / (1 + speed * Math.sqrt(swinging));
|
342
|
+
|
343
|
+
n.x += n.fa2.dx * factor;
|
344
|
+
n.y += n.fa2.dy * factor;
|
345
|
+
}
|
346
|
+
}
|
347
|
+
}
|
348
|
+
|
349
|
+
if (i === nodes.length) {
|
350
|
+
self.state = {
|
351
|
+
step: 0,
|
352
|
+
index: 0
|
353
|
+
};
|
354
|
+
return false;
|
355
|
+
} else {
|
356
|
+
self.state.index = i;
|
357
|
+
return true;
|
358
|
+
}
|
359
|
+
break;
|
360
|
+
|
361
|
+
default:
|
362
|
+
throw new Error('ForceAtlas2 - atomic state error');
|
363
|
+
}
|
364
|
+
};
|
365
|
+
|
366
|
+
this.clean = function() {
|
367
|
+
var a = this.graph.nodes(),
|
368
|
+
l = a.length,
|
369
|
+
i;
|
370
|
+
|
371
|
+
for (i = 0; i < l; i++)
|
372
|
+
delete a[i].fa2;
|
373
|
+
};
|
374
|
+
|
375
|
+
// Auto Settings
|
376
|
+
this.setAutoSettings = function() {
|
377
|
+
var graph = this.graph;
|
378
|
+
|
379
|
+
// Tuning
|
380
|
+
if (graph.nodes().length >= 100)
|
381
|
+
this.p.scalingRatio = 2.0;
|
382
|
+
else
|
383
|
+
this.p.scalingRatio = 10.0;
|
384
|
+
|
385
|
+
this.p.strongGravityMode = false;
|
386
|
+
this.p.gravity = 1;
|
387
|
+
|
388
|
+
// Behavior
|
389
|
+
this.p.outboundAttractionDistribution = false;
|
390
|
+
this.p.linLogMode = false;
|
391
|
+
this.p.adjustSizes = false;
|
392
|
+
this.p.edgeWeightInfluence = 1;
|
393
|
+
|
394
|
+
// Performance
|
395
|
+
if (graph.nodes().length >= 50000)
|
396
|
+
this.p.jitterTolerance = 10;
|
397
|
+
else if (graph.nodes().length >= 5000)
|
398
|
+
this.p.jitterTolerance = 1;
|
399
|
+
else
|
400
|
+
this.p.jitterTolerance = 0.1;
|
401
|
+
|
402
|
+
if (graph.nodes().length >= 1000)
|
403
|
+
this.p.barnesHutOptimize = true;
|
404
|
+
else
|
405
|
+
this.p.barnesHutOptimize = false;
|
406
|
+
|
407
|
+
this.p.barnesHutTheta = 1.2;
|
408
|
+
|
409
|
+
return this;
|
410
|
+
};
|
411
|
+
|
412
|
+
this.kill = function() {
|
413
|
+
// TODO
|
414
|
+
};
|
415
|
+
|
416
|
+
// All the different forces
|
417
|
+
this.ForceFactory = {
|
418
|
+
buildRepulsion: function(adjustBySize, coefficient) {
|
419
|
+
if (adjustBySize) {
|
420
|
+
return new this.linRepulsion_antiCollision(coefficient);
|
421
|
+
} else {
|
422
|
+
return new this.linRepulsion(coefficient);
|
423
|
+
}
|
424
|
+
},
|
425
|
+
|
426
|
+
getStrongGravity: function(coefficient) {
|
427
|
+
return new this.strongGravity(coefficient);
|
428
|
+
},
|
429
|
+
|
430
|
+
buildAttraction: function(logAttr, distributedAttr, adjustBySize, c) {
|
431
|
+
if (adjustBySize) {
|
432
|
+
if (logAttr) {
|
433
|
+
if (distributedAttr) {
|
434
|
+
return new this.logAttraction_degreeDistributed_antiCollision(c);
|
435
|
+
} else {
|
436
|
+
return new this.logAttraction_antiCollision(c);
|
437
|
+
}
|
438
|
+
} else {
|
439
|
+
if (distributedAttr) {
|
440
|
+
return new this.linAttraction_degreeDistributed_antiCollision(c);
|
441
|
+
} else {
|
442
|
+
return new this.linAttraction_antiCollision(c);
|
443
|
+
}
|
444
|
+
}
|
445
|
+
} else {
|
446
|
+
if (logAttr) {
|
447
|
+
if (distributedAttr) {
|
448
|
+
return new this.logAttraction_degreeDistributed(c);
|
449
|
+
} else {
|
450
|
+
return new this.logAttraction(c);
|
451
|
+
}
|
452
|
+
} else {
|
453
|
+
if (distributedAttr) {
|
454
|
+
return new this.linAttraction_massDistributed(c);
|
455
|
+
} else {
|
456
|
+
return new this.linAttraction(c);
|
457
|
+
}
|
458
|
+
}
|
459
|
+
}
|
460
|
+
},
|
461
|
+
|
462
|
+
// Repulsion force: Linear
|
463
|
+
linRepulsion: function(c) {
|
464
|
+
this.coefficient = c;
|
465
|
+
this.apply_nn = function(n1, n2) {
|
466
|
+
if (n1.fa2 && n2.fa2)
|
467
|
+
{
|
468
|
+
// Get the distance
|
469
|
+
var xDist = n1.x - n2.x;
|
470
|
+
var yDist = n1.y - n2.y;
|
471
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
472
|
+
|
473
|
+
if (distance > 0) {
|
474
|
+
// NB: factor = force / distance
|
475
|
+
var factor = this.coefficient *
|
476
|
+
n1.fa2.mass *
|
477
|
+
n2.fa2.mass /
|
478
|
+
Math.pow(distance, 2);
|
479
|
+
|
480
|
+
n1.fa2.dx += xDist * factor;
|
481
|
+
n1.fa2.dy += yDist * factor;
|
482
|
+
|
483
|
+
n2.fa2.dx -= xDist * factor;
|
484
|
+
n2.fa2.dy -= yDist * factor;
|
485
|
+
}
|
486
|
+
}
|
487
|
+
};
|
488
|
+
|
489
|
+
this.apply_nr = function(n, r) {
|
490
|
+
// Get the distance
|
491
|
+
var xDist = n.x - r.p.massCenterX;
|
492
|
+
var yDist = n.y - r.p.massCenterY;
|
493
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
494
|
+
|
495
|
+
if (distance > 0) {
|
496
|
+
// NB: factor = force / distance
|
497
|
+
var factor = this.coefficient *
|
498
|
+
n.fa2.mass *
|
499
|
+
r.p.mass /
|
500
|
+
Math.pow(distance, 2);
|
501
|
+
|
502
|
+
n.fa2.dx += xDist * factor;
|
503
|
+
n.fa2.dy += yDist * factor;
|
504
|
+
}
|
505
|
+
};
|
506
|
+
|
507
|
+
this.apply_g = function(n, g) {
|
508
|
+
// Get the distance
|
509
|
+
var xDist = n.x;
|
510
|
+
var yDist = n.y;
|
511
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
512
|
+
|
513
|
+
if (distance > 0) {
|
514
|
+
// NB: factor = force / distance
|
515
|
+
var factor = this.coefficient * n.fa2.mass * g / distance;
|
516
|
+
|
517
|
+
n.fa2.dx -= xDist * factor;
|
518
|
+
n.fa2.dy -= yDist * factor;
|
519
|
+
}
|
520
|
+
};
|
521
|
+
},
|
522
|
+
|
523
|
+
linRepulsion_antiCollision: function(c) {
|
524
|
+
this.coefficient = c;
|
525
|
+
this.apply_nn = function(n1, n2) {
|
526
|
+
var factor;
|
527
|
+
|
528
|
+
if (n1.fa2 && n2.fa2) {
|
529
|
+
// Get the distance
|
530
|
+
var xDist = n1.x - n2.x;
|
531
|
+
var yDist = n1.y - n2.y;
|
532
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist) -
|
533
|
+
n1.size -
|
534
|
+
n2.size;
|
535
|
+
|
536
|
+
if (distance > 0) {
|
537
|
+
// NB: factor = force / distance
|
538
|
+
factor = this.coefficient *
|
539
|
+
n1.fa2.mass *
|
540
|
+
n2.fa2.mass /
|
541
|
+
Math.pow(distance, 2);
|
542
|
+
|
543
|
+
n1.fa2.dx += xDist * factor;
|
544
|
+
n1.fa2.dy += yDist * factor;
|
545
|
+
|
546
|
+
n2.fa2.dx -= xDist * factor;
|
547
|
+
n2.fa2.dy -= yDist * factor;
|
548
|
+
|
549
|
+
} else if (distance < 0) {
|
550
|
+
factor = 100 * this.coefficient * n1.fa2.mass * n2.fa2.mass;
|
551
|
+
|
552
|
+
n1.fa2.dx += xDist * factor;
|
553
|
+
n1.fa2.dy += yDist * factor;
|
554
|
+
|
555
|
+
n2.fa2.dx -= xDist * factor;
|
556
|
+
n2.fa2.dy -= yDist * factor;
|
557
|
+
}
|
558
|
+
}
|
559
|
+
};
|
560
|
+
|
561
|
+
this.apply_nr = function(n, r) {
|
562
|
+
// Get the distance
|
563
|
+
var factor,
|
564
|
+
xDist = n.fa2.x() - r.getMassCenterX(),
|
565
|
+
yDist = n.fa2.y() - r.getMassCenterY(),
|
566
|
+
distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
567
|
+
|
568
|
+
if (distance > 0) {
|
569
|
+
// NB: factor = force / distance
|
570
|
+
factor = this.coefficient *
|
571
|
+
n.fa2.mass *
|
572
|
+
r.getMass() /
|
573
|
+
Math.pow(distance, 2);
|
574
|
+
|
575
|
+
n.fa2.dx += xDist * factor;
|
576
|
+
n.fa2.dy += yDist * factor;
|
577
|
+
} else if (distance < 0) {
|
578
|
+
factor = -this.coefficient * n.fa2.mass * r.getMass() / distance;
|
579
|
+
|
580
|
+
n.fa2.dx += xDist * factor;
|
581
|
+
n.fa2.dy += yDist * factor;
|
582
|
+
}
|
583
|
+
};
|
584
|
+
|
585
|
+
this.apply_g = function(n, g) {
|
586
|
+
// Get the distance
|
587
|
+
var xDist = n.x;
|
588
|
+
var yDist = n.y;
|
589
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
590
|
+
|
591
|
+
if (distance > 0) {
|
592
|
+
// NB: factor = force / distance
|
593
|
+
var factor = this.coefficient * n.fa2.mass * g / distance;
|
594
|
+
|
595
|
+
n.fa2.dx -= xDist * factor;
|
596
|
+
n.fa2.dy -= yDist * factor;
|
597
|
+
}
|
598
|
+
};
|
599
|
+
},
|
600
|
+
|
601
|
+
// Repulsion force: Strong Gravity
|
602
|
+
// (as a Repulsion Force because it is easier)
|
603
|
+
strongGravity: function(c) {
|
604
|
+
this.coefficient = c;
|
605
|
+
this.apply_nn = function(n1, n2) {
|
606
|
+
// Not Relevant
|
607
|
+
};
|
608
|
+
this.apply_nr = function(n, r) {
|
609
|
+
// Not Relevant
|
610
|
+
};
|
611
|
+
|
612
|
+
this.apply_g = function(n, g) {
|
613
|
+
// Get the distance
|
614
|
+
var xDist = n.x;
|
615
|
+
var yDist = n.y;
|
616
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
617
|
+
|
618
|
+
if (distance > 0) {
|
619
|
+
// NB: factor = force / distance
|
620
|
+
var factor = this.coefficient * n.fa2.mass * g;
|
621
|
+
|
622
|
+
n.fa2.dx -= xDist * factor;
|
623
|
+
n.fa2.dy -= yDist * factor;
|
624
|
+
}
|
625
|
+
};
|
626
|
+
},
|
627
|
+
|
628
|
+
// Attraction force: Linear
|
629
|
+
linAttraction: function(c) {
|
630
|
+
this.coefficient = c;
|
631
|
+
|
632
|
+
this.apply_nn = function(n1, n2, e) {
|
633
|
+
if (n1.fa2 && n2.fa2)
|
634
|
+
{
|
635
|
+
// Get the distance
|
636
|
+
var xDist = n1.x - n2.x;
|
637
|
+
var yDist = n1.y - n2.y;
|
638
|
+
|
639
|
+
// NB: factor = force / distance
|
640
|
+
var factor = -this.coefficient * e;
|
641
|
+
|
642
|
+
n1.fa2.dx += xDist * factor;
|
643
|
+
n1.fa2.dy += yDist * factor;
|
644
|
+
|
645
|
+
n2.fa2.dx -= xDist * factor;
|
646
|
+
n2.fa2.dy -= yDist * factor;
|
647
|
+
}
|
648
|
+
};
|
649
|
+
},
|
650
|
+
|
651
|
+
|
652
|
+
// Attraction force: Linear, distributed by mass (typically, degree)
|
653
|
+
linAttraction_massDistributed: function(c) {
|
654
|
+
this.coefficient = c;
|
655
|
+
|
656
|
+
this.apply_nn = function(n1, n2, e) {
|
657
|
+
if (n1.fa2 && n2.fa2)
|
658
|
+
{
|
659
|
+
// Get the distance
|
660
|
+
var xDist = n1.x - n2.x;
|
661
|
+
var yDist = n1.y - n2.y;
|
662
|
+
|
663
|
+
// NB: factor = force / distance
|
664
|
+
var factor = -this.coefficient * e / n1.fa2.mass;
|
665
|
+
|
666
|
+
n1.fa2.dx += xDist * factor;
|
667
|
+
n1.fa2.dy += yDist * factor;
|
668
|
+
|
669
|
+
n2.fa2.dx -= xDist * factor;
|
670
|
+
n2.fa2.dy -= yDist * factor;
|
671
|
+
}
|
672
|
+
};
|
673
|
+
},
|
674
|
+
|
675
|
+
|
676
|
+
// Attraction force: Logarithmic
|
677
|
+
logAttraction: function(c) {
|
678
|
+
this.coefficient = c;
|
679
|
+
|
680
|
+
this.apply_nn = function(n1, n2, e) {
|
681
|
+
if (n1.fa2 && n2.fa2)
|
682
|
+
{
|
683
|
+
// Get the distance
|
684
|
+
var xDist = n1.x - n2.x;
|
685
|
+
var yDist = n1.y - n2.y;
|
686
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
687
|
+
|
688
|
+
if (distance > 0) {
|
689
|
+
// NB: factor = force / distance
|
690
|
+
var factor = -this.coefficient *
|
691
|
+
e *
|
692
|
+
Math.log(1 + distance) /
|
693
|
+
distance;
|
694
|
+
|
695
|
+
n1.fa2.dx += xDist * factor;
|
696
|
+
n1.fa2.dy += yDist * factor;
|
697
|
+
|
698
|
+
n2.fa2.dx -= xDist * factor;
|
699
|
+
n2.fa2.dy -= yDist * factor;
|
700
|
+
}
|
701
|
+
}
|
702
|
+
};
|
703
|
+
},
|
704
|
+
|
705
|
+
|
706
|
+
// Attraction force: Linear, distributed by Degree
|
707
|
+
logAttraction_degreeDistributed: function(c) {
|
708
|
+
this.coefficient = c;
|
709
|
+
|
710
|
+
this.apply_nn = function(n1, n2, e) {
|
711
|
+
if (n1.fa2 && n2.fa2)
|
712
|
+
{
|
713
|
+
// Get the distance
|
714
|
+
var xDist = n1.x - n2.x;
|
715
|
+
var yDist = n1.y - n2.y;
|
716
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
717
|
+
|
718
|
+
if (distance > 0) {
|
719
|
+
// NB: factor = force / distance
|
720
|
+
var factor = -this.coefficient *
|
721
|
+
e *
|
722
|
+
Math.log(1 + distance) /
|
723
|
+
distance /
|
724
|
+
n1.fa2.mass;
|
725
|
+
|
726
|
+
n1.fa2.dx += xDist * factor;
|
727
|
+
n1.fa2.dy += yDist * factor;
|
728
|
+
|
729
|
+
n2.fa2.dx -= xDist * factor;
|
730
|
+
n2.fa2.dy -= yDist * factor;
|
731
|
+
}
|
732
|
+
}
|
733
|
+
};
|
734
|
+
},
|
735
|
+
|
736
|
+
|
737
|
+
// Attraction force: Linear, with Anti-Collision
|
738
|
+
linAttraction_antiCollision: function(c) {
|
739
|
+
this.coefficient = c;
|
740
|
+
|
741
|
+
this.apply_nn = function(n1, n2, e) {
|
742
|
+
if (n1.fa2 && n2.fa2)
|
743
|
+
{
|
744
|
+
// Get the distance
|
745
|
+
var xDist = n1.x - n2.x;
|
746
|
+
var yDist = n1.y - n2.y;
|
747
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
748
|
+
|
749
|
+
if (distance > 0) {
|
750
|
+
// NB: factor = force / distance
|
751
|
+
var factor = -this.coefficient * e;
|
752
|
+
|
753
|
+
n1.fa2.dx += xDist * factor;
|
754
|
+
n1.fa2.dy += yDist * factor;
|
755
|
+
|
756
|
+
n2.fa2.dx -= xDist * factor;
|
757
|
+
n2.fa2.dy -= yDist * factor;
|
758
|
+
}
|
759
|
+
}
|
760
|
+
};
|
761
|
+
},
|
762
|
+
|
763
|
+
|
764
|
+
// Attraction force: Linear, distributed by Degree, with Anti-Collision
|
765
|
+
linAttraction_degreeDistributed_antiCollision: function(c) {
|
766
|
+
this.coefficient = c;
|
767
|
+
|
768
|
+
this.apply_nn = function(n1, n2, e) {
|
769
|
+
if (n1.fa2 && n2.fa2)
|
770
|
+
{
|
771
|
+
// Get the distance
|
772
|
+
var xDist = n1.x - n2.x;
|
773
|
+
var yDist = n1.y - n2.y;
|
774
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
775
|
+
|
776
|
+
if (distance > 0) {
|
777
|
+
// NB: factor = force / distance
|
778
|
+
var factor = -this.coefficient * e / n1.fa2.mass;
|
779
|
+
|
780
|
+
n1.fa2.dx += xDist * factor;
|
781
|
+
n1.fa2.dy += yDist * factor;
|
782
|
+
|
783
|
+
n2.fa2.dx -= xDist * factor;
|
784
|
+
n2.fa2.dy -= yDist * factor;
|
785
|
+
}
|
786
|
+
}
|
787
|
+
};
|
788
|
+
},
|
789
|
+
|
790
|
+
|
791
|
+
// Attraction force: Logarithmic, with Anti-Collision
|
792
|
+
logAttraction_antiCollision: function(c) {
|
793
|
+
this.coefficient = c;
|
794
|
+
|
795
|
+
this.apply_nn = function(n1, n2, e) {
|
796
|
+
if (n1.fa2 && n2.fa2)
|
797
|
+
{
|
798
|
+
// Get the distance
|
799
|
+
var xDist = n1.x - n2.x;
|
800
|
+
var yDist = n1.y - n2.y;
|
801
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
802
|
+
|
803
|
+
if (distance > 0) {
|
804
|
+
// NB: factor = force / distance
|
805
|
+
var factor = -this.coefficient *
|
806
|
+
e *
|
807
|
+
Math.log(1 + distance) /
|
808
|
+
distance;
|
809
|
+
|
810
|
+
n1.fa2.dx += xDist * factor;
|
811
|
+
n1.fa2.dy += yDist * factor;
|
812
|
+
|
813
|
+
n2.fa2.dx -= xDist * factor;
|
814
|
+
n2.fa2.dy -= yDist * factor;
|
815
|
+
}
|
816
|
+
}
|
817
|
+
};
|
818
|
+
},
|
819
|
+
|
820
|
+
// Attraction force: Linear, distributed by Degree, with Anti-Collision
|
821
|
+
logAttraction_degreeDistributed_antiCollision: function(c) {
|
822
|
+
this.coefficient = c;
|
823
|
+
|
824
|
+
this.apply_nn = function(n1, n2, e) {
|
825
|
+
if (n1.fa2 && n2.fa2)
|
826
|
+
{
|
827
|
+
// Get the distance
|
828
|
+
var xDist = n1.x - n2.x;
|
829
|
+
var yDist = n1.y - n2.y;
|
830
|
+
var distance = Math.sqrt(xDist * xDist + yDist * yDist);
|
831
|
+
|
832
|
+
if (distance > 0) {
|
833
|
+
// NB: factor = force / distance
|
834
|
+
var factor = -this.coefficient *
|
835
|
+
e *
|
836
|
+
Math.log(1 + distance) /
|
837
|
+
distance /
|
838
|
+
n1.fa2.mass;
|
839
|
+
|
840
|
+
n1.fa2.dx += xDist * factor;
|
841
|
+
n1.fa2.dy += yDist * factor;
|
842
|
+
|
843
|
+
n2.fa2.dx -= xDist * factor;
|
844
|
+
n2.fa2.dy -= yDist * factor;
|
845
|
+
}
|
846
|
+
}
|
847
|
+
};
|
848
|
+
}
|
849
|
+
};
|
850
|
+
};
|
851
|
+
|
852
|
+
// The Region class, as used by the Barnes Hut optimization
|
853
|
+
forceatlas2.Region = function(nodes, depth) {
|
854
|
+
this.depthLimit = 20;
|
855
|
+
this.size = 0;
|
856
|
+
this.nodes = nodes;
|
857
|
+
this.subregions = [];
|
858
|
+
this.depth = depth;
|
859
|
+
|
860
|
+
this.p = {
|
861
|
+
mass: 0,
|
862
|
+
massCenterX: 0,
|
863
|
+
massCenterY: 0
|
864
|
+
};
|
865
|
+
|
866
|
+
this.updateMassAndGeometry();
|
867
|
+
};
|
868
|
+
|
869
|
+
forceatlas2.Region.prototype.updateMassAndGeometry = function() {
|
870
|
+
if (this.nodes.length > 1) {
|
871
|
+
// Compute Mass
|
872
|
+
var mass = 0;
|
873
|
+
var massSumX = 0;
|
874
|
+
var massSumY = 0;
|
875
|
+
this.nodes.forEach(function(n) {
|
876
|
+
mass += n.fa2.mass;
|
877
|
+
massSumX += n.x * n.fa2.mass;
|
878
|
+
massSumY += n.y * n.fa2.mass;
|
879
|
+
});
|
880
|
+
var massCenterX = massSumX / mass,
|
881
|
+
massCenterY = massSumY / mass;
|
882
|
+
|
883
|
+
// Compute size
|
884
|
+
var size;
|
885
|
+
this.nodes.forEach(function(n) {
|
886
|
+
var distance = Math.sqrt(
|
887
|
+
(n.x - massCenterX) *
|
888
|
+
(n.x - massCenterX) +
|
889
|
+
(n.y - massCenterY) *
|
890
|
+
(n.y - massCenterY)
|
891
|
+
);
|
892
|
+
size = Math.max(size || (2 * distance), 2 * distance);
|
893
|
+
});
|
894
|
+
|
895
|
+
this.p.mass = mass;
|
896
|
+
this.p.massCenterX = massCenterX;
|
897
|
+
this.p.massCenterY = massCenterY;
|
898
|
+
this.size = size;
|
899
|
+
}
|
900
|
+
};
|
901
|
+
|
902
|
+
|
903
|
+
forceatlas2.Region.prototype.buildSubRegions = function() {
|
904
|
+
if (this.nodes.length > 1) {
|
905
|
+
var leftNodes = [],
|
906
|
+
rightNodes = [],
|
907
|
+
subregions = [],
|
908
|
+
massCenterX = this.p.massCenterX,
|
909
|
+
massCenterY = this.p.massCenterY,
|
910
|
+
nextDepth = this.depth + 1,
|
911
|
+
self = this,
|
912
|
+
tl = [],
|
913
|
+
bl = [],
|
914
|
+
br = [],
|
915
|
+
tr = [],
|
916
|
+
nodesColumn,
|
917
|
+
nodesLine,
|
918
|
+
oneNodeList,
|
919
|
+
subregion;
|
920
|
+
|
921
|
+
this.nodes.forEach(function(n) {
|
922
|
+
nodesColumn = (n.x < massCenterX) ? (leftNodes) : (rightNodes);
|
923
|
+
nodesColumn.push(n);
|
924
|
+
});
|
925
|
+
|
926
|
+
leftNodes.forEach(function(n) {
|
927
|
+
nodesLine = (n.y < massCenterY) ? (tl) : (bl);
|
928
|
+
nodesLine.push(n);
|
929
|
+
});
|
930
|
+
|
931
|
+
rightNodes.forEach(function(n) {
|
932
|
+
nodesLine = (n.y < massCenterY) ? (tr) : (br);
|
933
|
+
nodesLine.push(n);
|
934
|
+
});
|
935
|
+
|
936
|
+
[tl, bl, br, tr].filter(function(a) {
|
937
|
+
return a.length;
|
938
|
+
}).forEach(function(a) {
|
939
|
+
if (nextDepth <= self.depthLimit && a.length < self.nodes.length) {
|
940
|
+
subregion = new forceatlas2.Region(a, nextDepth);
|
941
|
+
subregions.push(subregion);
|
942
|
+
} else {
|
943
|
+
a.forEach(function(n) {
|
944
|
+
oneNodeList = [n];
|
945
|
+
subregion = new forceatlas2.Region(oneNodeList, nextDepth);
|
946
|
+
subregions.push(subregion);
|
947
|
+
});
|
948
|
+
}
|
949
|
+
});
|
950
|
+
|
951
|
+
this.subregions = subregions;
|
952
|
+
|
953
|
+
subregions.forEach(function(subregion) {
|
954
|
+
subregion.buildSubRegions();
|
955
|
+
});
|
956
|
+
}
|
957
|
+
};
|
958
|
+
|
959
|
+
forceatlas2.Region.prototype.applyForce = function(n, Force, theta) {
|
960
|
+
if (this.nodes.length < 2) {
|
961
|
+
var regionNode = this.nodes[0];
|
962
|
+
Force.apply_nn(n, regionNode);
|
963
|
+
} else {
|
964
|
+
var distance = Math.sqrt(
|
965
|
+
(n.x - this.p.massCenterX) *
|
966
|
+
(n.x - this.p.massCenterX) +
|
967
|
+
(n.y - this.p.massCenterY) *
|
968
|
+
(n.y - this.p.massCenterY)
|
969
|
+
);
|
970
|
+
|
971
|
+
if (distance * theta > this.size) {
|
972
|
+
Force.apply_nr(n, this);
|
973
|
+
} else {
|
974
|
+
this.subregions.forEach(function(subregion) {
|
975
|
+
subregion.applyForce(n, Force, theta);
|
976
|
+
});
|
977
|
+
}
|
978
|
+
}
|
979
|
+
};
|
980
|
+
|
981
|
+
sigma.prototype.startForceAtlas2 = function(options) {
|
982
|
+
if ((this.forceatlas2 || {}).isRunning)
|
983
|
+
return this;
|
984
|
+
|
985
|
+
if (!this.forceatlas2) {
|
986
|
+
this.forceatlas2 = new forceatlas2.ForceAtlas2(this.graph, options || {});
|
987
|
+
|
988
|
+
if (this.forceatlas2.p.autoSettings)
|
989
|
+
this.forceatlas2.setAutoSettings();
|
990
|
+
|
991
|
+
this.forceatlas2.init();
|
992
|
+
}
|
993
|
+
|
994
|
+
this.forceatlas2.isRunning = true;
|
995
|
+
|
996
|
+
var self = this;
|
997
|
+
|
998
|
+
function addJob() {
|
999
|
+
if (!conrad.hasJob('forceatlas2_' + self.id))
|
1000
|
+
conrad.addJob({
|
1001
|
+
id: 'forceatlas2_' + self.id,
|
1002
|
+
job: self.forceatlas2.atomicGo,
|
1003
|
+
end: function() {
|
1004
|
+
self.refresh();
|
1005
|
+
if (self.forceatlas2.isRunning)
|
1006
|
+
addJob();
|
1007
|
+
}
|
1008
|
+
});
|
1009
|
+
}
|
1010
|
+
|
1011
|
+
addJob();
|
1012
|
+
|
1013
|
+
return this;
|
1014
|
+
};
|
1015
|
+
|
1016
|
+
sigma.prototype.stopForceAtlas2 = function() {
|
1017
|
+
if ((this.forceatlas2 || {}).isRunning) {
|
1018
|
+
this.forceatlas2.state = {
|
1019
|
+
step: 0,
|
1020
|
+
index: 0
|
1021
|
+
};
|
1022
|
+
this.forceatlas2.isRunning = false;
|
1023
|
+
this.forceatlas2.clean();
|
1024
|
+
}
|
1025
|
+
|
1026
|
+
if (conrad.hasJob('forceatlas2_' + this.id))
|
1027
|
+
conrad.killJob('forceatlas2_' + this.id);
|
1028
|
+
|
1029
|
+
return this;
|
1030
|
+
};
|
1031
|
+
|
1032
|
+
forceatlas2.defaultSettings = {
|
1033
|
+
autoSettings: true,
|
1034
|
+
linLogMode: false,
|
1035
|
+
outboundAttractionDistribution: false,
|
1036
|
+
adjustSizes: false,
|
1037
|
+
edgeWeightInfluence: 0,
|
1038
|
+
scalingRatio: 1,
|
1039
|
+
strongGravityMode: false,
|
1040
|
+
gravity: 1,
|
1041
|
+
jitterTolerance: 1,
|
1042
|
+
barnesHutOptimize: false,
|
1043
|
+
barnesHutTheta: 1.2,
|
1044
|
+
speed: 1,
|
1045
|
+
outboundAttCompensation: 1,
|
1046
|
+
totalSwinging: 0,
|
1047
|
+
totalEffectiveTraction: 0,
|
1048
|
+
complexIntervals: 500,
|
1049
|
+
simpleIntervals: 1000
|
1050
|
+
};
|
1051
|
+
})();
|