-
-
Save sheymann/5057611 to your computer and use it in GitHub Desktop.
| /** | |
| * Linkurious 2012, all rights reserved. | |
| * Sébastien Heymann <[email protected]>, | |
| * Romain Yon <[email protected]> | |
| * | |
| * Please use http://jsbeautifier.org/ and indent with 2 spaces. | |
| * | |
| * Lib docs: | |
| * http://twitter.github.com/bootstrap/ | |
| * http://docs.jquery.com/ | |
| * http://addyosmani.github.com/jquery-ui-bootstrap/ | |
| * http://sigmajs.org/ + well documented source code | |
| */ | |
| ; | |
| sigma.fruchtermanReingold = sigma.fruchtermanReingold || {}; | |
| sigma.fruchtermanReingold.FruchtermanReingold = function (graph, fixedNodes) { | |
| sigma.classes.Cascade.call(this); | |
| var self = this; | |
| this.graph = graph; | |
| this.stop = false; | |
| this.p = { | |
| area: 0.1, | |
| gravity: 10, | |
| speed: 0.1 | |
| }; | |
| var oldSumDist = Number.POSITIVE_INFINITY; | |
| this.init = function () { | |
| self.p.area = self.graph.nodes.length * self.graph.nodes.length; | |
| self.graph.iterNodes(function (n) { | |
| n.fr = { | |
| dx: 0, | |
| dy: 0 | |
| }; | |
| }); | |
| return self; | |
| }; | |
| this.go = function () { | |
| while (self.atomicGo()) {}; | |
| }; | |
| this.atomicGo = function () { | |
| self.p.area = self.graph.nodes.length * self.graph.nodes.length; | |
| var graph = self.graph; | |
| var nodes = graph.nodes.filter(function (n) { return !n.hidden; }); | |
| var edges = graph.edges.filter(function (e) { | |
| return !e.source.hidden && !e.target.hidden; | |
| }); | |
| var maxDisplace = Math.sqrt(self.p.area) / 10; | |
| var k = Math.sqrt(self.p.area / (1 + nodes.length)); | |
| nodes.forEach(function (n1) { | |
| if (!n1.hasOwnProperty('fr')) { | |
| n1.fr = { | |
| dx: 0, | |
| dy: 0 | |
| }; | |
| } | |
| // Repulsion force | |
| nodes.forEach(function (n2) { | |
| if (n1 != n2) { | |
| var xDist = n1.x - n2.x; | |
| var yDist = n1.y - n2.y; | |
| var dist = Math.sqrt(xDist * xDist + yDist * yDist) + 0.05; | |
| // var dist = Math.sqrt(xDist * xDist + yDist * yDist) - n1.size - n2.size; | |
| if (dist > 0) { | |
| var repulsiveF = k * k / dist; | |
| n1.fr.dx += xDist / dist * repulsiveF; | |
| n1.fr.dy += yDist / dist * repulsiveF; | |
| } | |
| } | |
| }); | |
| }); | |
| edges.forEach(function (e) { | |
| // Attraction force | |
| var nf = e.source; | |
| var nt = e.target; | |
| var xDist = nf.x - nt.x; | |
| var yDist = nf.y - nt.y; | |
| var dist = Math.sqrt(xDist * xDist + yDist * yDist) + 0.05; | |
| // var dist = Math.sqrt(xDist * xDist + yDist * yDist) - nf.size - nt.size; | |
| var attractiveF = dist * dist / k; | |
| if (dist > 0) { | |
| nf.fr.dx -= xDist / dist * attractiveF; | |
| nf.fr.dy -= yDist / dist * attractiveF; | |
| nt.fr.dx += xDist / dist * attractiveF; | |
| nt.fr.dy += yDist / dist * attractiveF; | |
| } | |
| }); | |
| nodes.forEach(function (n) { | |
| // Gravity | |
| var d = Math.sqrt(n.x * n.x + n.y * n.y); | |
| var gf = 0.01 * k * self.p.gravity * d; | |
| n.fr.dx -= gf * n.x / d; | |
| n.fr.dy -= gf * n.y / d; | |
| }); | |
| nodes.forEach(function (n) { | |
| // Speed | |
| n.fr.dx *= self.p.speed; | |
| n.fr.dy *= self.p.speed; | |
| }); | |
| var sumDist = 0; | |
| nodes.forEach(function (n) { | |
| // Apply computed displacement | |
| if (-1 == $.inArray(n.id+'', fixedNodes)) { | |
| var xDist = n.fr.dx; | |
| var yDist = n.fr.dy; | |
| var dist = Math.sqrt(xDist * xDist + yDist * yDist); | |
| if (dist > 0) { | |
| sumDist += dist; | |
| var limitedDist = Math.min(maxDisplace * self.p.speed, dist); | |
| n.x += xDist / dist * limitedDist; | |
| n.y += yDist / dist * limitedDist; | |
| } | |
| } | |
| }); | |
| // Global cooling (homebrew) | |
| sumDist = (sumDist * self.p.speed) / nodes.length; | |
| var diffDist = Math.abs(sumDist - oldSumDist); | |
| //console.log(sumDist, diffDist); | |
| if (sumDist < (Math.sqrt(nodes.length) / 100) && diffDist < (Math.sqrt(nodes.length) / 100)) { | |
| self.p.speed *= 0.995; | |
| if (self.p.speed < 0.01) { | |
| self.stop = true; | |
| } | |
| } | |
| oldSumDist = sumDist; | |
| return false; | |
| }; | |
| this.end = function () { | |
| self.stop = true; | |
| self.graph.iterNodes(function (n) { | |
| n.fr = null; | |
| }); | |
| }; | |
| } | |
| sigma.publicPrototype.startFruchtermanReingold = function () { | |
| this.FruchtermanReingold = new sigma.fruchtermanReingold.FruchtermanReingold(this._core.graph); | |
| this.FruchtermanReingold.init(); | |
| this.addGenerator('FruchtermanReingold', this.FruchtermanReingold.atomicGo, function () { | |
| return true; | |
| }); | |
| }; | |
| sigma.publicPrototype.stopFruchtermanReingold = function () { | |
| this.fruchtermanReingold.end(); | |
| this.removeGenerator('FruchtermanReingold'); | |
| }; |
Hi,
Thanks for creating this good plugin.
I am using it for my sigma graph. But when I call stopFruchtermanReingold, it give following error and layout does not stop.
Error : TypeError: Cannot call method 'end' of undefined.
I am calling the stopFruchtermanReingold in following manner.
objSigma.stopFruchtermanReingold();
where objSigma is sigma graph object and I called startFruchtermanReingold with same object.
Please help me to resolve this issue.
Thanks
Harpreet
Hi,
there's a typo @ line 156:
Replace this:
this.fruchtermanReingold.end();
With this:
this.FruchtermanReingold.end();
:)
@drml , Thanks
It worked.
This doesn't work with the new sigma.js version 1.0. Do you have a working version?
+1 ~ that would be cool.
Could you please share an example using Fruchterman-Reingold
This example does not work with Sigma 1.x but the algorithm is quite straightforward to adapt.
Hello folks, I've finally made a plugin for Sigma v1.x:
nice ! but how it is supposed to stop automatically ?