sigma-rails 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (23) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +37 -0
  4. data/lib/sigma/rails.rb +8 -0
  5. data/lib/sigma/rails/version.rb +5 -0
  6. data/vendor/assets/javascripts/sigma.js +8723 -0
  7. data/vendor/assets/javascripts/sigma.layout.forceAtlas2/README.md +12 -0
  8. data/vendor/assets/javascripts/sigma.layout.forceAtlas2/sigma.layout.forceAtlas2.js +1051 -0
  9. data/vendor/assets/javascripts/sigma.parsers.gexf/README.md +29 -0
  10. data/vendor/assets/javascripts/sigma.parsers.gexf/gexf-parser.js +548 -0
  11. data/vendor/assets/javascripts/sigma.parsers.gexf/sigma.parsers.gexf.js +108 -0
  12. data/vendor/assets/javascripts/sigma.parsers.json/README.md +29 -0
  13. data/vendor/assets/javascripts/sigma.parsers.json/sigma.parsers.json.js +88 -0
  14. data/vendor/assets/javascripts/sigma.plugins.animate/README.md +8 -0
  15. data/vendor/assets/javascripts/sigma.plugins.animate/sigma.plugins.animate.js +165 -0
  16. data/vendor/assets/javascripts/sigma.plugins.dragNodes/README.md +8 -0
  17. data/vendor/assets/javascripts/sigma.plugins.dragNodes/sigma.plugins.dragNodes.js +135 -0
  18. data/vendor/assets/javascripts/sigma.plugins.neighborhoods/README.md +24 -0
  19. data/vendor/assets/javascripts/sigma.plugins.neighborhoods/sigma.plugins.neighborhoods.js +186 -0
  20. data/vendor/assets/javascripts/sigma.renderers.customShapes/README.md +55 -0
  21. data/vendor/assets/javascripts/sigma.renderers.customShapes/shape-library.js +145 -0
  22. data/vendor/assets/javascripts/sigma.renderers.customShapes/sigma.renderers.customShapes.js +112 -0
  23. 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
+ })();