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.
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
+ })();