gephi.team team mailing list archive
-
gephi.team team
-
Mailing list archive
-
Message #02185
[Merge] lp:~mathieu-jacomy/gephi/forceatlas2 into lp:gephi
Mathieu Bastian has proposed merging lp:~mathieu-jacomy/gephi/forceatlas2 into lp:gephi.
Requested reviews:
Mathieu Bastian (mathieu.bastian)
For more details, see:
https://code.launchpad.net/~mathieu-jacomy/gephi/forceatlas2/+merge/63504
New awesome ForceAtlas2 algorithm
--
https://code.launchpad.net/~mathieu-jacomy/gephi/forceatlas2/+merge/63504
Your team Gephi Team is subscribed to branch lp:gephi.
=== added directory 'ForceAtlas2'
=== added file 'ForceAtlas2/build.xml'
--- ForceAtlas2/build.xml 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/build.xml 2011-06-05 20:50:57 +0000
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- You may freely edit this file. See harness/README in the NetBeans platform -->
+<!-- for some information on what you could do (e.g. targets to override). -->
+<!-- If you delete this file and reopen the project it will be recreated. -->
+<project name="org.webatlas.forceatlas2" default="netbeans" basedir=".">
+ <description>Builds, tests, and runs the project org.webatlas.forceatlas2.</description>
+ <import file="nbproject/build-impl.xml"/>
+</project>
=== added file 'ForceAtlas2/manifest.mf'
--- ForceAtlas2/manifest.mf 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/manifest.mf 2011-06-05 20:50:57 +0000
@@ -0,0 +1,5 @@
+Manifest-Version: 1.0
+OpenIDE-Module: org.webatlas.forceatlas2
+OpenIDE-Module-Localizing-Bundle: org/webatlas/forceatlas2/Bundle.properties
+OpenIDE-Module-Specification-Version: 1.6
+
=== added directory 'ForceAtlas2/nbproject'
=== added file 'ForceAtlas2/nbproject/build-impl.xml'
--- ForceAtlas2/nbproject/build-impl.xml 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/nbproject/build-impl.xml 2011-06-05 20:50:57 +0000
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+*** GENERATED FROM project.xml - DO NOT EDIT ***
+*** EDIT ../build.xml INSTEAD ***
+-->
+<project name="org.webatlas.forceatlas2-impl" basedir="..">
+ <fail message="Please build using Ant 1.7.1 or higher.">
+ <condition>
+ <not>
+ <antversion atleast="1.7.1"/>
+ </not>
+ </condition>
+ </fail>
+ <property file="nbproject/private/suite-private.properties"/>
+ <property file="nbproject/suite.properties"/>
+ <fail unless="suite.dir">You must set 'suite.dir' to point to your containing module suite</fail>
+ <property file="${suite.dir}/nbproject/private/platform-private.properties"/>
+ <property file="${suite.dir}/nbproject/platform.properties"/>
+ <macrodef name="property" uri="http://www.netbeans.org/ns/nb-module-project/2">
+ <attribute name="name"/>
+ <attribute name="value"/>
+ <sequential>
+ <property name="@{name}" value="${@{value}}"/>
+ </sequential>
+ </macrodef>
+ <macrodef name="evalprops" uri="http://www.netbeans.org/ns/nb-module-project/2">
+ <attribute name="property"/>
+ <attribute name="value"/>
+ <sequential>
+ <property name="@{property}" value="@{value}"/>
+ </sequential>
+ </macrodef>
+ <property file="${user.properties.file}"/>
+ <nbmproject2:property name="harness.dir" value="nbplatform.${nbplatform.active}.harness.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
+ <nbmproject2:property name="nbplatform.active.dir" value="nbplatform.${nbplatform.active}.netbeans.dest.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
+ <nbmproject2:evalprops property="cluster.path.evaluated" value="${cluster.path}" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
+ <fail message="Path to 'platform' cluster missing in $${cluster.path} property or using corrupt Netbeans Platform (missing harness).">
+ <condition>
+ <not>
+ <contains string="${cluster.path.evaluated}" substring="platform"/>
+ </not>
+ </condition>
+ </fail>
+ <import file="${harness.dir}/build.xml"/>
+</project>
=== added file 'ForceAtlas2/nbproject/genfiles.properties'
--- ForceAtlas2/nbproject/genfiles.properties 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/nbproject/genfiles.properties 2011-06-05 20:50:57 +0000
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=0a8bc1d8
+build.xml.script.CRC32=7eaa9dd0
+build.xml.stylesheet.CRC32=a56c6a5b@1.45.1
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=0a8bc1d8
+nbproject/build-impl.xml.script.CRC32=eb5b8ec7
+nbproject/build-impl.xml.stylesheet.CRC32=238281d1@1.45.1
=== added file 'ForceAtlas2/nbproject/project.properties'
--- ForceAtlas2/nbproject/project.properties 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/nbproject/project.properties 2011-06-05 20:50:57 +0000
@@ -0,0 +1,2 @@
+javac.source=1.6
+javac.compilerargs=-Xlint -Xlint:-serial
=== added file 'ForceAtlas2/nbproject/project.xml'
--- ForceAtlas2/nbproject/project.xml 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/nbproject/project.xml 2011-06-05 20:50:57 +0000
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+ <type>org.netbeans.modules.apisupport.project</type>
+ <configuration>
+ <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
+ <code-name-base>org.webatlas.forceatlas2</code-name-base>
+ <suite-component/>
+ <module-dependencies>
+ <dependency>
+ <code-name-base>org.gephi.data.attributes.api</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>0.8</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.gephi.dynamic.api</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>0.8</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.gephi.graph.api</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>0.8</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.gephi.layout.api</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>0.8.0.1</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.gephi.project.api</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>0.8</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.openide.util</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>8.14.1</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
+ <code-name-base>org.openide.util.lookup</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <specification-version>8.6.1</specification-version>
+ </run-dependency>
+ </dependency>
+ </module-dependencies>
+ <public-packages/>
+ </data>
+ </configuration>
+</project>
=== added file 'ForceAtlas2/nbproject/suite.properties'
--- ForceAtlas2/nbproject/suite.properties 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/nbproject/suite.properties 2011-06-05 20:50:57 +0000
@@ -0,0 +1,1 @@
+suite.dir=${basedir}/..
=== added directory 'ForceAtlas2/src'
=== added directory 'ForceAtlas2/src/org'
=== added directory 'ForceAtlas2/src/org/webatlas'
=== added directory 'ForceAtlas2/src/org/webatlas/forceatlas2'
=== added file 'ForceAtlas2/src/org/webatlas/forceatlas2/Bundle.properties'
--- ForceAtlas2/src/org/webatlas/forceatlas2/Bundle.properties 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/src/org/webatlas/forceatlas2/Bundle.properties 2011-06-05 20:50:57 +0000
@@ -0,0 +1,33 @@
+OpenIDE-Module-Display-Category=Plugin
+OpenIDE-Module-Long-Description=Quality Layout, used in SNA. \
+ A linear-attraction linear-repulsion model with few approximations (BarnesHut). \
+ Features an optimal speed vs. precision balance and degree-driven repulsion for a high readability. \
+ Works on medium-sized graphs: 10 to 10000 nodes.
+OpenIDE-Module-Name=ForceAtlas 2, Quality Layout
+OpenIDE-Module-Short-Description=Quality Layout
+
+ForceAtlas2.name=ForceAtlas 2
+ForceAtlas2.description=Quality layout: a linear-attraction linear-repulsion model with few approximations (BarnesHut). Speed automatically computed.
+
+ForceAtlas2.tuning=Tuning
+ForceAtlas2.behavior=Behavior Alternatives
+ForceAtlas2.performance=Performance
+
+ForceAtlas2.scalingRatio.name=Scaling
+ForceAtlas2.scalingRatio.desc=How much repulsion you want. More makes a more sparse graph.
+ForceAtlas2.gravity.name=Gravity
+ForceAtlas2.gravity.desc=Attracts nodes to the center. Prevents islands from drifting away.
+ForceAtlas2.distributedAttraction.name=Dissuade Hubs
+ForceAtlas2.distributedAttraction.desc=Distributes attraction along outbound edges. Hubs attract less and thus are pushed to the borders.
+ForceAtlas2.linLogMode.name=LinLog mode
+ForceAtlas2.linLogMode.desc=Switch ForceAtlas' model from lin-lin to lin-log (tribute to Andreas Noack). Makes clusters more tight.
+ForceAtlas2.adjustSizes.name=Prevent Overlap
+ForceAtlas2.adjustSizes.desc=Use only when spatialized. Should not be used with "Approximate Repulsion"
+ForceAtlas2.jitterTolerance.name=Tolerance (speed)
+ForceAtlas2.jitterTolerance.desc=How much swinging you allow. Above 1 discouraged. Lower gives less speed and more precision.
+ForceAtlas2.barnesHutOptimization.name=Approximate Repulsion
+ForceAtlas2.barnesHutOptimization.desc=Barnes Hut optimization: n\u00b2 complexity to n.ln(n) ; allows larger graphs.
+ForceAtlas2.barnesHutTheta.name=Approximation
+ForceAtlas2.barnesHutTheta.desc=Theta of the Barnes Hut optimization.
+ForceAtlas2.edgeWeightInfluence.name=Edge Weight Influence
+ForceAtlas2.edgeWeightInfluence.desc=How much influence you give to the edges weight. 0 is "no influence" and 1 is "normal".
=== added file 'ForceAtlas2/src/org/webatlas/forceatlas2/Bundle_fr.properties'
--- ForceAtlas2/src/org/webatlas/forceatlas2/Bundle_fr.properties 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/src/org/webatlas/forceatlas2/Bundle_fr.properties 2011-06-05 20:50:57 +0000
@@ -0,0 +1,33 @@
+OpenIDE-Module-Display-Category=Plugin
+OpenIDE-Module-Long-Description=Spatialisation qualitative, utilis\u00e9e dans l'analyse des r\u00e9seaux sociaux. \
+ Attraction et r\u00e9pulsion lin\u00e9aires avec quelques approximations (Barnes Hut)\
+ Comprend un \u00e9quilibrage dynamique de la vitesse contre la pr\u00e9cision, et une r\u00e9pulsion par degree qui rend le graphe plus lisible.\
+ Fonctionne sur des graphes de 10 \u00e0 10000 noeuds.
+OpenIDE-Module-Name=ForceAtlas 2, Spatialisation qualitative
+OpenIDE-Module-Short-Description=Spatialisation qualitative
+
+ForceAtlas2.name=ForceAtlas 2
+ForceAtlas2.description=Spatialisation qualitative: mod\u00e8le \u00e0 attraction et r\u00e9pulsion lin\u00e9aires avec quelques optimisations (Barnes Hut). Param\u00e9trage automatis\u00e9.
+
+ForceAtlas2.tuning=R\u00e9glages fins
+ForceAtlas2.behavior=Options de comportement
+ForceAtlas2.performance=Performances
+
+ForceAtlas2.scalingRatio.name=Dimensionnement
+ForceAtlas2.scalingRatio.desc=Quantit\u00e9 de r\u00e9pulsion, par rapport \u00e0 l'attraction. Rend le graphe plus \u00e9tal\u00e9.
+ForceAtlas2.gravity.name=Gravit\u00e9
+ForceAtlas2.gravity.desc=Attire les noeuds vers le centre. Emp\u00eache les \u00eelots de d\u00e9river \u00e0 l'infini.
+ForceAtlas2.distributedAttraction.name=Dissuader les Hubs
+ForceAtlas2.distributedAttraction.desc=Distribue l'attraction dans les liens sortants. Les Hubs attirent moins et sont donc repouss\u00e9s en p\u00e9riph\u00e9rie.
+ForceAtlas2.linLogMode.name=Mode LinLog
+ForceAtlas2.linLogMode.desc=Passer le mod\u00e8le de ForceAtlas de lin-lin \u00e0 lin-log (hommage \u00e0 Andreas Noack). Rend les concentrations de noeuds sont plus resserr\u00e9es.
+ForceAtlas2.adjustSizes.name=Emp\u00eacher Recouvrement
+ForceAtlas2.adjustSizes.desc=Utilisez lorsque le graphe a d\u00e9j\u00e0 converg\u00e9. Permet d'emp\u00eacher les noeuds de se recouvrir. Utilisation d\u00e9conseill\u00e9e avec l'approximation de la r\u00e9pulsion.
+ForceAtlas2.jitterTolerance.name=Tol\u00e9rance (vitesse)
+ForceAtlas2.jitterTolerance.desc=Quantit\u00e9 de vibration autoris\u00e9e (valeurs > 1 d\u00e9conseill\u00e9es). Baisser la valeur apporte plus de pr\u00e9cision et moins de vitesse.
+ForceAtlas2.barnesHutOptimization.name=Approximer R\u00e9pulsion
+ForceAtlas2.barnesHutOptimization.desc=L'optimisation de Barnes Hut: permet une r\u00e9duction de la complexit\u00e9 du calcul de n\u00b2 \u00e0 n.ln(n). Permet de spatialiser de plus gros graphes.
+ForceAtlas2.barnesHutTheta.name=Approximation
+ForceAtlas2.barnesHutTheta.desc=param\u00e8tre Theta de l'optimisation de Barnes Hut.
+ForceAtlas2.edgeWeightInfluence.name=Influence Poids des liens
+ForceAtlas2.edgeWeightInfluence.desc=L'influence du poides des liens. 0 c'est aucune influence, 1 c'est une influence normale et au-del\u00e0 \u00e7a accentue l'importance du poids des liens dans la spatialisation.
=== added file 'ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2.java'
--- ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2.java 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2.java 2011-06-05 20:50:57 +0000
@@ -0,0 +1,450 @@
+/*
+Copyright 2008-2011 Gephi
+Authors : Mathieu Jacomy <Mathieu.Jacomy@xxxxxxxxx>
+Website : http://www.webatlas.fr
+
+You should have received a copy of the GNU Affero General Public License
+along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>.
+*/
+package org.webatlas.forceatlas2;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.gephi.data.attributes.type.TimeInterval;
+import org.gephi.dynamic.DynamicUtilities;
+import org.gephi.dynamic.api.DynamicController;
+import org.gephi.dynamic.api.DynamicModel;
+import org.gephi.graph.api.Edge;
+import org.gephi.graph.api.GraphModel;
+import org.gephi.graph.api.HierarchicalGraph;
+import org.gephi.graph.api.Node;
+import org.gephi.graph.api.NodeData;
+import org.gephi.layout.spi.Layout;
+import org.gephi.layout.spi.LayoutBuilder;
+import org.gephi.layout.spi.LayoutProperty;
+import org.gephi.project.api.Workspace;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.webatlas.forceatlas2.ForceFactory.AttractionForce;
+import org.webatlas.forceatlas2.ForceFactory.RepulsionForce;
+
+/**
+ * ForceAtlas 2 Layout, manages each step of the computations.
+ * @author Mathieu Jacomy
+ */
+public class ForceAtlas2 implements Layout{
+ private GraphModel graphModel;
+ private HierarchicalGraph graph;
+ private ForceAtlas2Builder layoutBuilder;
+ private DynamicModel dynamicModel;
+
+ private double edgeWeightInfluence;
+ private double jitterTolerance;
+ private double scalingRatio;
+ private double gravity;
+ private double speed;
+ private boolean outboundAttractionDistribution;
+ private boolean adjustSizes;
+ private boolean barnesHutOptimize;
+ private double barnesHutTheta;
+ private boolean linLogMode;
+ private Region rootRegion;
+
+ double outboundAttCompensation = 1;
+
+ //Dynamic Weight
+ private TimeInterval timeInterval;
+
+ public ForceAtlas2(ForceAtlas2Builder layoutBuilder) {
+ this.layoutBuilder = layoutBuilder;
+ }
+
+ @Override
+ public void initAlgo() {
+ speed = 1.;
+
+ graph = graphModel.getHierarchicalGraphVisible();
+ this.timeInterval = DynamicUtilities.getVisibleInterval(dynamicModel);
+
+ graph.readLock();
+ Node[] nodes = graph.getNodes().toArray();
+
+ // Initialise layout data
+ for (Node n : nodes) {
+ if (n.getNodeData().getLayoutData() == null || !(n.getNodeData().getLayoutData() instanceof ForceAtlas2LayoutData)) {
+ ForceAtlas2LayoutData nLayout = new ForceAtlas2LayoutData();
+ n.getNodeData().setLayoutData(nLayout);
+ }
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+ nLayout.mass = 1 + graph.getDegree(n);
+ nLayout.old_dx = 0;
+ nLayout.old_dy = 0;
+ nLayout.dx = 0;
+ nLayout.dy = 0;
+ }
+ }
+
+ @Override
+ public void goAlgo() {
+ // Initialize graph data
+ if (graphModel == null) {
+ return;
+ }
+ graph = graphModel.getHierarchicalGraphVisible();
+ this.timeInterval = DynamicUtilities.getVisibleInterval(dynamicModel);
+
+ graph.readLock();
+ Node[] nodes = graph.getNodes().toArray();
+ Edge[] edges = graph.getEdgesAndMetaEdges().toArray();
+
+ // Initialise layout data
+ for (Node n : nodes) {
+ if (n.getNodeData().getLayoutData() == null || !(n.getNodeData().getLayoutData() instanceof ForceAtlas2LayoutData)) {
+ ForceAtlas2LayoutData nLayout = new ForceAtlas2LayoutData();
+ n.getNodeData().setLayoutData(nLayout);
+ }
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+ nLayout.mass = 1 + graph.getDegree(n);
+ nLayout.old_dx = nLayout.dx;
+ nLayout.old_dy = nLayout.dy;
+ nLayout.dx = 0;
+ nLayout.dy = 0;
+ }
+
+ // If Barnes Hut active, initialize root region
+ if(isBarnesHutOptimize()){
+ rootRegion = new Region(nodes);
+ rootRegion.buildSubRegions();
+ }
+
+ // If outboundAttractionDistribution active, compensate.
+ if(isOutboundAttractionDistribution()){
+ outboundAttCompensation = 0;
+ for(Node n : nodes){
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+ outboundAttCompensation += nLayout.mass;
+ }
+ outboundAttCompensation /= nodes.length;
+ }
+
+ // Repulsion
+ RepulsionForce Repulsion = ForceFactory.builder.buildRepulsion(isAdjustSizes(), getScalingRatio());
+ if(isBarnesHutOptimize()){
+ for(Node n : nodes){
+ rootRegion.applyForce(n, Repulsion, getBarnesHutTheta());
+ }
+ } else {
+ for (int n1Index = 0; n1Index<nodes.length; n1Index++) {
+ Node n1 = nodes[n1Index];
+ for (int n2Index = 0; n2Index<n1Index; n2Index++) {
+ Node n2 = nodes[n2Index];
+ Repulsion.apply(n1, n2);
+ }
+ }
+ }
+
+ // Attraction
+ AttractionForce Attraction = ForceFactory.builder.buildAttraction(isLinLogMode(), isOutboundAttractionDistribution(), isAdjustSizes(), 1*((isOutboundAttractionDistribution())?(outboundAttCompensation):(1)));
+ for (Edge e : edges) {
+ Attraction.apply(e.getSource(), e.getTarget(), Math.pow(getWeight(e),getEdgeWeightInfluence()));
+ }
+
+ // Gravity
+ for (Node n : nodes) {
+ Repulsion.apply(n, getGravity()/getScalingRatio());
+ }
+
+ // Auto adjust speed
+ double totalSwinging = 0d; // How much irregular movement
+ double totalEffectiveTraction = 0d; // Hom much useful movement
+ for (Node n : nodes) {
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+ if (!nData.isFixed()) {
+ double swinging = Math.sqrt(Math.pow(nLayout.old_dx - nLayout.dx,2) + Math.pow(nLayout.old_dy - nLayout.dy,2));
+ totalSwinging += nLayout.mass * swinging; // If the node has a burst change of direction, then it's not converging.
+ totalEffectiveTraction += nLayout.mass * 0.5 * Math.sqrt(Math.pow(nLayout.old_dx + nLayout.dx,2) + Math.pow(nLayout.old_dy + nLayout.dy,2));
+ }
+ }
+ // We want that swingingMovement < tolerance * convergenceMovement
+ double targetSpeed = getJitterTolerance() * getJitterTolerance() * totalEffectiveTraction / totalSwinging;
+
+ // But the speed shoudn't rise too much too quickly, since it would make the convergence drop dramatically.
+ double maxRise = 0.5; // Max rise: 50%
+ speed = speed + Math.min(targetSpeed - speed, maxRise * speed);
+
+ // Apply forces
+ if(isAdjustSizes()){
+ // If nodes overlap prevention is active, it's not possible to trust the swinging mesure.
+ for (Node n : nodes) {
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+ if (!nData.isFixed()) {
+
+ // Adaptive auto-speed: the speed of each node is lowered
+ // when the node swings.
+ double swinging = Math.sqrt((nLayout.old_dx - nLayout.dx) * (nLayout.old_dx - nLayout.dx) + (nLayout.old_dy - nLayout.dy) * (nLayout.old_dy - nLayout.dy));
+ double factor = 0.1 * speed / (1f + speed * Math.sqrt(swinging));
+
+ double df = Math.sqrt(Math.pow(nLayout.dx, 2)+Math.pow(nLayout.dy, 2));
+ factor = Math.min(factor*df, 10.)/df;
+
+ double x = nData.x() + nLayout.dx*factor;
+ double y = nData.y() + nLayout.dy*factor;
+
+ nData.setX((float) x);
+ nData.setY((float) y);
+ }
+ }
+ } else {
+ for (Node n : nodes) {
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+ if (!nData.isFixed()) {
+
+ // Adaptive auto-speed: the speed of each node is lowered
+ // when the node swings.
+ double swinging = Math.sqrt((nLayout.old_dx - nLayout.dx) * (nLayout.old_dx - nLayout.dx) + (nLayout.old_dy - nLayout.dy) * (nLayout.old_dy - nLayout.dy));
+ //double factor = speed / (1f + Math.sqrt(speed * swinging));
+ double factor = speed / (1f + speed * Math.sqrt(swinging));
+
+ double x = nData.x() + nLayout.dx*factor;
+ double y = nData.y() + nLayout.dy*factor;
+
+ nData.setX((float) x);
+ nData.setY((float) y);
+ }
+ }
+ }
+ graph.readUnlock();
+ }
+
+ @Override
+ public boolean canAlgo() {
+ return graphModel != null;
+ }
+
+ @Override
+ public void endAlgo() {
+ for (Node n : graph.getNodes()) {
+ n.getNodeData().setLayoutData(null);
+ }
+ graph.readUnlock();
+ }
+
+ @Override
+ public LayoutProperty[] getProperties() {
+ List<LayoutProperty> properties = new ArrayList<LayoutProperty>();
+ final String FORCEATLAS2_TUNING = NbBundle.getMessage(getClass(), "ForceAtlas2.tuning");
+ final String FORCEATLAS2_BEHAVIOR = NbBundle.getMessage(getClass(), "ForceAtlas2.behavior");
+ final String FORCEATLAS2_PERFORMANCE = NbBundle.getMessage(getClass(), "ForceAtlas2.performance");
+
+ try {
+ properties.add(LayoutProperty.createProperty(
+ this, Double.class,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.scalingRatio.name"),
+ FORCEATLAS2_TUNING,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.scalingRatio.desc"),
+ "getScalingRatio", "setScalingRatio"));
+
+ properties.add(LayoutProperty.createProperty(
+ this, Double.class,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.gravity.name"),
+ FORCEATLAS2_TUNING,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.gravity.desc"),
+ "getGravity", "setGravity"));
+
+ properties.add(LayoutProperty.createProperty(
+ this, Boolean.class,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.distributedAttraction.name"),
+ FORCEATLAS2_BEHAVIOR,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.distributedAttraction.desc"),
+ "isOutboundAttractionDistribution", "setOutboundAttractionDistribution"));
+
+ properties.add(LayoutProperty.createProperty(
+ this, Boolean.class,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.linLogMode.name"),
+ FORCEATLAS2_BEHAVIOR,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.linLogMode.desc"),
+ "isLinLogMode", "setLinLogMode"));
+
+ properties.add(LayoutProperty.createProperty(
+ this, Boolean.class,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.adjustSizes.name"),
+ FORCEATLAS2_BEHAVIOR,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.adjustSizes.desc"),
+ "isAdjustSizes", "setAdjustSizes"));
+
+ properties.add(LayoutProperty.createProperty(
+ this, Double.class,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.edgeWeightInfluence.name"),
+ FORCEATLAS2_BEHAVIOR,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.edgeWeightInfluence.desc"),
+ "getEdgeWeightInfluence", "setEdgeWeightInfluence"));
+
+ properties.add(LayoutProperty.createProperty(
+ this, Double.class,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.jitterTolerance.name"),
+ FORCEATLAS2_PERFORMANCE,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.jitterTolerance.desc"),
+ "getJitterTolerance", "setJitterTolerance"));
+
+ properties.add(LayoutProperty.createProperty(
+ this, Boolean.class,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.barnesHutOptimization.name"),
+ FORCEATLAS2_PERFORMANCE,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.barnesHutOptimization.desc"),
+ "isBarnesHutOptimize", "setBarnesHutOptimize"));
+
+ properties.add(LayoutProperty.createProperty(
+ this, Double.class,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.barnesHutTheta.name"),
+ FORCEATLAS2_PERFORMANCE,
+ NbBundle.getMessage(getClass(), "ForceAtlas2.barnesHutTheta.desc"),
+ "getBarnesHutTheta", "setBarnesHutTheta"));
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return properties.toArray(new LayoutProperty[0]);
+ }
+
+ @Override
+ public void resetPropertiesValues() {
+ int nodesCount = 0;
+
+ if(graphModel != null){
+ nodesCount = graphModel.getHierarchicalGraphVisible().getNodeCount();
+ }
+
+ // Tuning
+ if(nodesCount>=100){
+ setScalingRatio(2.0);
+ } else {
+ setScalingRatio(10.0);
+ }
+ setGravity(1.);
+
+ // Behavior
+ setOutboundAttractionDistribution(false);
+ setLinLogMode(false);
+ setAdjustSizes(false);
+ setEdgeWeightInfluence(1.);
+
+ // Performance
+ if(nodesCount>=50000){
+ setJitterTolerance(10d);
+ } else if(nodesCount>=5000){
+ setJitterTolerance(1d);
+ } else {
+ setJitterTolerance(0.1d);
+ }
+ if(nodesCount>=1000){
+ setBarnesHutOptimize(true);
+ } else {
+ setBarnesHutOptimize(false);
+ }
+ setBarnesHutTheta(1.2);
+ }
+
+ @Override
+ public LayoutBuilder getBuilder() {
+ return layoutBuilder;
+ }
+
+ @Override
+ public void setGraphModel(GraphModel graphModel) {
+ this.graphModel = graphModel;
+ Workspace workspace = graphModel.getWorkspace();
+ DynamicController dynamicController = Lookup.getDefault().lookup(DynamicController.class);
+ if (dynamicController != null && workspace != null) {
+ dynamicModel = dynamicController.getModel(workspace);
+ }
+ // Trick: reset here to take the profile of the graph in account for default values
+ resetPropertiesValues();
+ }
+
+ public Double getBarnesHutTheta() {
+ return barnesHutTheta;
+ }
+
+ public void setBarnesHutTheta(Double barnesHutTheta) {
+ this.barnesHutTheta = barnesHutTheta;
+ }
+
+ public Double getEdgeWeightInfluence() {
+ return edgeWeightInfluence;
+ }
+
+ public void setEdgeWeightInfluence(Double edgeWeightInfluence) {
+ this.edgeWeightInfluence = edgeWeightInfluence;
+ }
+
+ public Double getJitterTolerance() {
+ return jitterTolerance;
+ }
+
+ public void setJitterTolerance(Double jitterTolerance) {
+ this.jitterTolerance = jitterTolerance;
+ }
+
+ public Boolean isLinLogMode() {
+ return linLogMode;
+ }
+
+ public void setLinLogMode(Boolean linLogMode) {
+ this.linLogMode = linLogMode;
+ }
+
+ public Double getScalingRatio() {
+ return scalingRatio;
+ }
+
+ public void setScalingRatio(Double scalingRatio) {
+ this.scalingRatio = scalingRatio;
+ }
+
+ public Double getGravity() {
+ return gravity;
+ }
+
+ public void setGravity(Double gravity) {
+ this.gravity = gravity;
+ }
+
+ public Boolean isOutboundAttractionDistribution() {
+ return outboundAttractionDistribution;
+ }
+
+ public void setOutboundAttractionDistribution(Boolean outboundAttractionDistribution) {
+ this.outboundAttractionDistribution = outboundAttractionDistribution;
+ }
+
+ public Boolean isAdjustSizes() {
+ return adjustSizes;
+ }
+
+ public void setAdjustSizes(Boolean adjustSizes) {
+ this.adjustSizes = adjustSizes;
+ }
+
+ public Boolean isBarnesHutOptimize() {
+ return barnesHutOptimize;
+ }
+
+ public void setBarnesHutOptimize(Boolean barnesHutOptimize) {
+ this.barnesHutOptimize = barnesHutOptimize;
+ }
+
+ private float getWeight(Edge edge) {
+ if(timeInterval!=null) {
+ return edge.getWeight(timeInterval.getLow(), timeInterval.getHigh());
+ } else {
+ return edge.getWeight();
+ }
+ }
+}
=== added file 'ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2Builder.java'
--- ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2Builder.java 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2Builder.java 2011-06-05 20:50:57 +0000
@@ -0,0 +1,75 @@
+/*
+Copyright 2008-2011 Gephi
+Authors : Mathieu Jacomy <Mathieu.Jacomy@xxxxxxxxx>
+Website : http://www.webatlas.fr
+
+You should have received a copy of the GNU Affero General Public License
+along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package org.webatlas.forceatlas2;
+
+import javax.swing.Icon;
+import javax.swing.JPanel;
+import org.gephi.layout.spi.Layout;
+import org.gephi.layout.spi.LayoutBuilder;
+import org.gephi.layout.spi.LayoutUI;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ * Layout Builder
+ * @author Mathieu Jacomy
+ */
+@ServiceProvider(service = LayoutBuilder.class)
+public class ForceAtlas2Builder implements LayoutBuilder{
+
+ private ForceAtlas2UI ui = new ForceAtlas2UI();
+
+ @Override
+ public String getName() {
+ return NbBundle.getMessage(ForceAtlas2.class, "ForceAtlas2.name");
+ }
+
+ @Override
+ public LayoutUI getUI() {
+ return ui;
+ }
+
+ @Override
+ public ForceAtlas2 buildLayout() {
+ ForceAtlas2 layout = new ForceAtlas2(this);
+ return layout;
+ }
+
+
+ private class ForceAtlas2UI implements LayoutUI{
+
+ @Override
+ public String getDescription() {
+ return NbBundle.getMessage(ForceAtlas2.class, "ForceAtlas2.description");
+ }
+
+ @Override
+ public Icon getIcon() {
+ return null;
+ }
+
+ @Override
+ public JPanel getSimplePanel(Layout layout) {
+ return null;
+ }
+
+ @Override
+ public int getQualityRank() {
+ return 4;
+ }
+
+ @Override
+ public int getSpeedRank() {
+ return 4;
+ }
+
+ }
+
+}
\ No newline at end of file
=== added file 'ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2LayoutData.java'
--- ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2LayoutData.java 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2LayoutData.java 2011-06-05 20:50:57 +0000
@@ -0,0 +1,24 @@
+/*
+Copyright 2008-2011 Gephi
+Authors : Mathieu Jacomy <Mathieu.Jacomy@xxxxxxxxx>
+Website : http://www.webatlas.fr
+
+You should have received a copy of the GNU Affero General Public License
+along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>.
+*/
+package org.webatlas.forceatlas2;
+
+import org.gephi.graph.spi.LayoutData;
+
+/**
+ * Data stored in Nodes and used by ForceAtlas2
+ * @author Mathieu Jacomy
+ */
+public class ForceAtlas2LayoutData implements LayoutData{
+ //Data
+ public double dx = 0;
+ public double dy = 0;
+ public double old_dx = 0;
+ public double old_dy = 0;
+ public double mass = 1;
+}
=== added file 'ForceAtlas2/src/org/webatlas/forceatlas2/ForceFactory.java'
--- ForceAtlas2/src/org/webatlas/forceatlas2/ForceFactory.java 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/src/org/webatlas/forceatlas2/ForceFactory.java 2011-06-05 20:50:57 +0000
@@ -0,0 +1,510 @@
+/*
+Copyright 2008-2011 Gephi
+Authors : Mathieu Jacomy <Mathieu.Jacomy@xxxxxxxxx>
+Website : http://www.webatlas.fr
+
+You should have received a copy of the GNU Affero General Public License
+along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>.
+*/
+package org.webatlas.forceatlas2;
+
+import org.gephi.graph.api.Node;
+import org.gephi.graph.api.NodeData;
+
+/**
+ * Generates the forces on demand, here are all the formulas for attraction and repulsion.
+ * @author Mathieu Jacomy
+ */
+public class ForceFactory {
+ public static ForceFactory builder = new ForceFactory();
+
+ private ForceFactory(){};
+
+ public RepulsionForce buildRepulsion(boolean adjustBySize, double coefficient){
+ if(adjustBySize){
+ return new linRepulsion_antiCollision(coefficient);
+ } else {
+ return new linRepulsion(coefficient);
+ }
+ }
+
+ public AttractionForce buildAttraction(boolean logAttraction, boolean distributedAttraction, boolean adjustBySize, double coefficient){
+ if(adjustBySize){
+ if(logAttraction){
+ if(distributedAttraction){
+ return new logAttraction_degreeDistributed_antiCollision(coefficient);
+ } else {
+ return new logAttraction_antiCollision(coefficient);
+ }
+ } else {
+ if(distributedAttraction){
+ return new linAttraction_degreeDistributed_antiCollision(coefficient);
+ } else {
+ return new linAttraction_antiCollision(coefficient);
+ }
+ }
+ } else {
+ if(logAttraction){
+ if(distributedAttraction){
+ return new logAttraction_degreeDistributed(coefficient);
+ } else {
+ return new logAttraction(coefficient);
+ }
+ } else {
+ if(distributedAttraction){
+ return new linAttraction_massDistributed(coefficient);
+ } else {
+ return new linAttraction(coefficient);
+ }
+ }
+ }
+ }
+
+ public abstract class AttractionForce {
+ public abstract void apply(Node n1, Node n2, double e); // Model for node-node attraction (e is for edge weight if needed)
+ }
+
+ public abstract class RepulsionForce {
+ public abstract void apply(Node n1, Node n2); // Model for node-node repulsion
+ public abstract void apply(Node n, Region r); // Model for Barnes Hut approximation
+ public abstract void apply(Node n, double g); // Model for gravitation (anti-repulsion)
+ }
+
+ /*
+ * Repulsion force: Linear
+ */
+ private class linRepulsion extends RepulsionForce{
+ private double coefficient;
+
+ public linRepulsion(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+ double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist);
+
+ if (distance > 0) {
+ // NB: factor = force / distance
+ double factor = coefficient * n1Layout.mass * n2Layout.mass / distance / distance;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+
+ @Override
+ public void apply(Node n, Region r) {
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+
+ // Get the distance
+ double xDist = nData.x() - r.getMassCenterX();
+ double yDist = nData.y() - r.getMassCenterY();
+ double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist);
+
+ if (distance > 0) {
+ // NB: factor = force / distance
+ double factor = coefficient * nLayout.mass * r.getMass() / distance / distance;
+
+ nLayout.dx += xDist * factor;
+ nLayout.dy += yDist * factor;
+ }
+ }
+
+ @Override
+ public void apply(Node n, double g) {
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+
+ // Get the distance
+ double xDist = nData.x();
+ double yDist = nData.y();
+ double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist);
+
+ if (distance > 0) {
+ // NB: factor = force / distance
+ double factor = coefficient * nLayout.mass * g / distance;
+
+ nLayout.dx -= xDist * factor;
+ nLayout.dy -= yDist * factor;
+ }
+ }
+ }
+
+ /*
+ * Repulsion force: Linear with Anti-collision
+ */
+ private class linRepulsion_antiCollision extends RepulsionForce{
+ private double coefficient;
+
+ public linRepulsion_antiCollision(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+ double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize();
+
+ if (distance > 0) {
+ // NB: factor = force / distance
+ double factor = coefficient * n1Layout.mass * n2Layout.mass / distance / distance;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+
+ } else if(distance < 0){
+ double factor = 100 * coefficient * n1Layout.mass * n2Layout.mass;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+
+ @Override
+ public void apply(Node n, Region r) {
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+
+ // Get the distance
+ double xDist = nData.x() - r.getMassCenterX();
+ double yDist = nData.y() - r.getMassCenterY();
+ double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist);
+
+ if (distance > 0) {
+ // NB: factor = force / distance
+ double factor = coefficient * nLayout.mass * r.getMass() / distance / distance;
+
+ nLayout.dx += xDist * factor;
+ nLayout.dy += yDist * factor;
+ } else if(distance < 0){
+ double factor = -coefficient * nLayout.mass * r.getMass() / distance;
+
+ nLayout.dx += xDist * factor;
+ nLayout.dy += yDist * factor;
+ }
+ }
+
+ @Override
+ public void apply(Node n, double g) {
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+
+ // Get the distance
+ double xDist = nData.x();
+ double yDist = nData.y();
+ double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist);
+
+ if (distance > 0) {
+ // NB: factor = force / distance
+ double factor = coefficient * nLayout.mass * g / distance;
+
+ nLayout.dx -= xDist * factor;
+ nLayout.dy -= yDist * factor;
+ }
+ }
+ }
+
+ /*
+ * Attraction force: Linear
+ */
+ private class linAttraction extends AttractionForce{
+ private double coefficient;
+
+ public linAttraction(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2, double e) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+
+ // NB: factor = force / distance
+ double factor = -coefficient * e;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+
+ /*
+ * Attraction force: Linear, distributed by mass (typically, degree)
+ */
+ private class linAttraction_massDistributed extends AttractionForce{
+ private double coefficient;
+
+ public linAttraction_massDistributed(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2, double e) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+
+ // NB: factor = force / distance
+ double factor = -coefficient * e / n1Layout.mass;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+
+ /*
+ * Attraction force: Logarithmic
+ */
+ private class logAttraction extends AttractionForce{
+ private double coefficient;
+
+ public logAttraction(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2, double e) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+ double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist);
+
+ if (distance > 0) {
+
+ // NB: factor = force / distance
+ double factor = -coefficient * e * Math.log(1+distance) / distance;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+ }
+
+ /*
+ * Attraction force: Linear, distributed by Degree
+ */
+ private class logAttraction_degreeDistributed extends AttractionForce{
+ private double coefficient;
+
+ public logAttraction_degreeDistributed(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2, double e) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+ double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist);
+
+ if (distance > 0) {
+
+ // NB: factor = force / distance
+ double factor = -coefficient * e * Math.log(1+distance) / distance / n1Layout.mass;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+ }
+
+ /*
+ * Attraction force: Linear, with Anti-Collision
+ */
+ private class linAttraction_antiCollision extends AttractionForce{
+ private double coefficient;
+
+ public linAttraction_antiCollision(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2, double e) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+ double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize();
+
+ if(distance>0){
+ // NB: factor = force / distance
+ double factor = -coefficient * e;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+ }
+
+ /*
+ * Attraction force: Linear, distributed by Degree, with Anti-Collision
+ */
+ private class linAttraction_degreeDistributed_antiCollision extends AttractionForce{
+ private double coefficient;
+
+ public linAttraction_degreeDistributed_antiCollision(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2, double e) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+ double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize();
+
+ if(distance>0){
+ // NB: factor = force / distance
+ double factor = -coefficient * e / n1Layout.mass;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+ }
+
+ /*
+ * Attraction force: Logarithmic, with Anti-Collision
+ */
+ private class logAttraction_antiCollision extends AttractionForce{
+ private double coefficient;
+
+ public logAttraction_antiCollision(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2, double e) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+ double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize();
+
+ if(distance>0){
+
+ // NB: factor = force / distance
+ double factor = -coefficient * e * Math.log(1+distance) / distance;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+ }
+
+ /*
+ * Attraction force: Linear, distributed by Degree, with Anti-Collision
+ */
+ private class logAttraction_degreeDistributed_antiCollision extends AttractionForce{
+ private double coefficient;
+
+ public logAttraction_degreeDistributed_antiCollision(double c){
+ coefficient = c;
+ }
+
+ @Override
+ public void apply(Node n1, Node n2, double e) {
+ NodeData n1Data = n1.getNodeData();
+ ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData();
+ NodeData n2Data = n2.getNodeData();
+ ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData();
+
+ // Get the distance
+ double xDist = n1Data.x() - n2Data.x();
+ double yDist = n1Data.y() - n2Data.y();
+ double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize();
+
+ if(distance>0){
+
+ // NB: factor = force / distance
+ double factor = -coefficient * e * Math.log(1+distance) / distance / n1Layout.mass;
+
+ n1Layout.dx += xDist * factor;
+ n1Layout.dy += yDist * factor;
+
+ n2Layout.dx -= xDist * factor;
+ n2Layout.dy -= yDist * factor;
+ }
+ }
+ }
+}
=== added file 'ForceAtlas2/src/org/webatlas/forceatlas2/Region.java'
--- ForceAtlas2/src/org/webatlas/forceatlas2/Region.java 1970-01-01 00:00:00 +0000
+++ ForceAtlas2/src/org/webatlas/forceatlas2/Region.java 2011-06-05 20:50:57 +0000
@@ -0,0 +1,197 @@
+/*
+Copyright 2008-2011 Gephi
+Authors : Mathieu Jacomy <Mathieu.Jacomy@xxxxxxxxx>
+Website : http://www.webatlas.fr
+
+You should have received a copy of the GNU Affero General Public License
+along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>.
+*/
+package org.webatlas.forceatlas2;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.gephi.graph.api.Node;
+import org.gephi.graph.api.NodeData;
+import org.webatlas.forceatlas2.ForceFactory.RepulsionForce;
+
+/**
+ * Barnes Hut optimization
+ * @author Mathieu Jacomy
+ */
+public class Region{
+
+ private double mass;
+
+ private double massCenterX;
+ private double massCenterY;
+ private double size;
+
+ private List<Node> nodes;
+ private List<Region> subregions = new ArrayList<Region>();
+
+ public Region(Node[] nodes){
+ this.nodes = new ArrayList<Node>();
+ this.nodes.addAll(Arrays.asList(nodes));
+ updateMassAndGeometry();
+ }
+
+ public Region(ArrayList<Node> nodes){
+ this.nodes = new ArrayList<Node>(nodes);
+ updateMassAndGeometry();
+ }
+
+ public void updateMassAndGeometry(){
+ if(nodes.size()>1){
+ // Compute Mass
+ mass = 0;
+ double massSumX = 0;
+ double massSumY = 0;
+ for(Node n : nodes){
+ NodeData nData = n.getNodeData();
+ ForceAtlas2LayoutData nLayout = nData.getLayoutData();
+ mass += nLayout.mass;
+ massSumX += nData.x() * nLayout.mass;
+ massSumY += nData.y() * nLayout.mass;
+ }
+ massCenterX = massSumX / mass;
+ massCenterY = massSumY / mass;
+
+ // Compute size
+ size = Double.MIN_VALUE;
+ for(Node n : nodes){
+ NodeData nData = n.getNodeData();
+ double distance = Math.sqrt((nData.x()-massCenterX)*(nData.x()-massCenterX) + (nData.y()-massCenterY)*(nData.y()-massCenterY));
+ size = Math.max(size, 2*distance);
+ }
+ }
+ }
+
+ public void buildSubRegions(){
+ if(nodes.size()>1){
+ ArrayList<Node> leftNodes = new ArrayList<Node>();
+ ArrayList<Node> rightNodes = new ArrayList<Node>();
+ for(Node n : nodes){
+ NodeData nData = n.getNodeData();
+ ArrayList<Node> nodesColumn = (nData.x()<massCenterX)?(leftNodes):(rightNodes);
+ nodesColumn.add(n);
+ }
+
+ ArrayList<Node> topleftNodes = new ArrayList<Node>();
+ ArrayList<Node> bottomleftNodes = new ArrayList<Node>();
+ for(Node n : leftNodes){
+ NodeData nData = n.getNodeData();
+ ArrayList<Node> nodesLine = (nData.y()<massCenterY)?(topleftNodes):(bottomleftNodes);
+ nodesLine.add(n);
+ }
+
+ ArrayList<Node> bottomrightNodes = new ArrayList<Node>();
+ ArrayList<Node> toprightNodes = new ArrayList<Node>();
+ for(Node n : rightNodes){
+ NodeData nData = n.getNodeData();
+ ArrayList<Node> nodesLine = (nData.y()<massCenterY)?(toprightNodes):(bottomrightNodes);
+ nodesLine.add(n);
+ }
+
+ if(topleftNodes.size()>0){
+ if(topleftNodes.size()<nodes.size()){
+ Region subregion = new Region(topleftNodes);
+ subregions.add(subregion);
+ } else {
+ for(Node n : topleftNodes){
+ ArrayList<Node> oneNodeList = new ArrayList<Node>();
+ oneNodeList.add(n);
+ Region subregion = new Region(oneNodeList);
+ subregions.add(subregion);
+ }
+ }
+ }
+ if(bottomleftNodes.size()>0){
+ if(bottomleftNodes.size()<nodes.size()){
+ Region subregion = new Region(bottomleftNodes);
+ subregions.add(subregion);
+ } else {
+ for(Node n : bottomleftNodes){
+ ArrayList<Node> oneNodeList = new ArrayList<Node>();
+ oneNodeList.add(n);
+ Region subregion = new Region(oneNodeList);
+ subregions.add(subregion);
+ }
+ }
+ }
+ if(bottomrightNodes.size()>0){
+ if(bottomrightNodes.size()<nodes.size()){
+ Region subregion = new Region(bottomrightNodes);
+ subregions.add(subregion);
+ } else {
+ for(Node n : bottomrightNodes){
+ ArrayList<Node> oneNodeList = new ArrayList<Node>();
+ oneNodeList.add(n);
+ Region subregion = new Region(oneNodeList);
+ subregions.add(subregion);
+ }
+ }
+ }
+ if(toprightNodes.size()>0){
+ if(toprightNodes.size()<nodes.size()){
+ Region subregion = new Region(toprightNodes);
+ subregions.add(subregion);
+ } else {
+ for(Node n : toprightNodes){
+ ArrayList<Node> oneNodeList = new ArrayList<Node>();
+ oneNodeList.add(n);
+ Region subregion = new Region(oneNodeList);
+ subregions.add(subregion);
+ }
+ }
+ }
+
+ for(Region subregion : subregions){
+ subregion.buildSubRegions();
+ }
+ }
+ }
+
+ public void applyForce(Node n, RepulsionForce Force, double theta){
+ NodeData nData = n.getNodeData();
+ if(nodes.size() < 2){
+ Node regionNode = nodes.get(0);
+ Force.apply(n, regionNode);
+ } else {
+ double distance = Math.sqrt((nData.x()-massCenterX)*(nData.x()-massCenterX) + (nData.y() - massCenterY)*(nData.y()-massCenterY));
+ if(distance * theta > size){
+ Force.apply(n, this);
+ } else {
+ for (Region subregion : subregions) {
+ subregion.applyForce(n, Force, theta);
+ }
+ }
+ }
+ }
+
+ public double getMass() {
+ return mass;
+ }
+
+ public void setMass(double mass) {
+ this.mass = mass;
+ }
+
+ public double getMassCenterX() {
+ return massCenterX;
+ }
+
+ public void setMassCenterX(double massCenterX) {
+ this.massCenterX = massCenterX;
+ }
+
+ public double getMassCenterY() {
+ return massCenterY;
+ }
+
+ public void setMassCenterY(double massCenterY) {
+ this.massCenterY = massCenterY;
+ }
+
+
+}
=== added directory 'ForceAtlas2/test'
=== added directory 'ForceAtlas2/test/unit'
=== added directory 'ForceAtlas2/test/unit/src'
Follow ups