Merge lp:~mathieu-jacomy/gephi/forceatlas2 into lp:~gephi.team/gephi/0.8
- forceatlas2
- Merge into 0.8
Proposed by
Mathieu Bastian
Status: | Merged |
---|---|
Merged at revision: | 2240 |
Proposed branch: | lp:~mathieu-jacomy/gephi/forceatlas2 |
Merge into: | lp:~gephi.team/gephi/0.8 |
Diff against target: |
1540 lines (+1460/-0) 14 files modified
ForceAtlas2/build.xml (+8/-0) ForceAtlas2/manifest.mf (+5/-0) ForceAtlas2/nbproject/build-impl.xml (+45/-0) ForceAtlas2/nbproject/genfiles.properties (+8/-0) ForceAtlas2/nbproject/project.properties (+2/-0) ForceAtlas2/nbproject/project.xml (+69/-0) ForceAtlas2/nbproject/suite.properties (+1/-0) ForceAtlas2/src/org/webatlas/forceatlas2/Bundle.properties (+33/-0) ForceAtlas2/src/org/webatlas/forceatlas2/Bundle_fr.properties (+33/-0) ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2.java (+450/-0) ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2Builder.java (+75/-0) ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2LayoutData.java (+24/-0) ForceAtlas2/src/org/webatlas/forceatlas2/ForceFactory.java (+510/-0) ForceAtlas2/src/org/webatlas/forceatlas2/Region.java (+197/-0) |
To merge this branch: | bzr merge lp:~mathieu-jacomy/gephi/forceatlas2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mathieu Bastian | Approve | ||
Review via email: mp+63504@code.launchpad.net |
Commit message
Description of the change
New awesome ForceAtlas2 algorithm
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'ForceAtlas2' |
2 | === added file 'ForceAtlas2/build.xml' |
3 | --- ForceAtlas2/build.xml 1970-01-01 00:00:00 +0000 |
4 | +++ ForceAtlas2/build.xml 2011-06-05 20:50:57 +0000 |
5 | @@ -0,0 +1,8 @@ |
6 | +<?xml version="1.0" encoding="UTF-8"?> |
7 | +<!-- You may freely edit this file. See harness/README in the NetBeans platform --> |
8 | +<!-- for some information on what you could do (e.g. targets to override). --> |
9 | +<!-- If you delete this file and reopen the project it will be recreated. --> |
10 | +<project name="org.webatlas.forceatlas2" default="netbeans" basedir="."> |
11 | + <description>Builds, tests, and runs the project org.webatlas.forceatlas2.</description> |
12 | + <import file="nbproject/build-impl.xml"/> |
13 | +</project> |
14 | |
15 | === added file 'ForceAtlas2/manifest.mf' |
16 | --- ForceAtlas2/manifest.mf 1970-01-01 00:00:00 +0000 |
17 | +++ ForceAtlas2/manifest.mf 2011-06-05 20:50:57 +0000 |
18 | @@ -0,0 +1,5 @@ |
19 | +Manifest-Version: 1.0 |
20 | +OpenIDE-Module: org.webatlas.forceatlas2 |
21 | +OpenIDE-Module-Localizing-Bundle: org/webatlas/forceatlas2/Bundle.properties |
22 | +OpenIDE-Module-Specification-Version: 1.6 |
23 | + |
24 | |
25 | === added directory 'ForceAtlas2/nbproject' |
26 | === added file 'ForceAtlas2/nbproject/build-impl.xml' |
27 | --- ForceAtlas2/nbproject/build-impl.xml 1970-01-01 00:00:00 +0000 |
28 | +++ ForceAtlas2/nbproject/build-impl.xml 2011-06-05 20:50:57 +0000 |
29 | @@ -0,0 +1,45 @@ |
30 | +<?xml version="1.0" encoding="UTF-8"?> |
31 | +<!-- |
32 | +*** GENERATED FROM project.xml - DO NOT EDIT *** |
33 | +*** EDIT ../build.xml INSTEAD *** |
34 | +--> |
35 | +<project name="org.webatlas.forceatlas2-impl" basedir=".."> |
36 | + <fail message="Please build using Ant 1.7.1 or higher."> |
37 | + <condition> |
38 | + <not> |
39 | + <antversion atleast="1.7.1"/> |
40 | + </not> |
41 | + </condition> |
42 | + </fail> |
43 | + <property file="nbproject/private/suite-private.properties"/> |
44 | + <property file="nbproject/suite.properties"/> |
45 | + <fail unless="suite.dir">You must set 'suite.dir' to point to your containing module suite</fail> |
46 | + <property file="${suite.dir}/nbproject/private/platform-private.properties"/> |
47 | + <property file="${suite.dir}/nbproject/platform.properties"/> |
48 | + <macrodef name="property" uri="http://www.netbeans.org/ns/nb-module-project/2"> |
49 | + <attribute name="name"/> |
50 | + <attribute name="value"/> |
51 | + <sequential> |
52 | + <property name="@{name}" value="${@{value}}"/> |
53 | + </sequential> |
54 | + </macrodef> |
55 | + <macrodef name="evalprops" uri="http://www.netbeans.org/ns/nb-module-project/2"> |
56 | + <attribute name="property"/> |
57 | + <attribute name="value"/> |
58 | + <sequential> |
59 | + <property name="@{property}" value="@{value}"/> |
60 | + </sequential> |
61 | + </macrodef> |
62 | + <property file="${user.properties.file}"/> |
63 | + <nbmproject2:property name="harness.dir" value="nbplatform.${nbplatform.active}.harness.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/> |
64 | + <nbmproject2:property name="nbplatform.active.dir" value="nbplatform.${nbplatform.active}.netbeans.dest.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/> |
65 | + <nbmproject2:evalprops property="cluster.path.evaluated" value="${cluster.path}" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/> |
66 | + <fail message="Path to 'platform' cluster missing in $${cluster.path} property or using corrupt Netbeans Platform (missing harness)."> |
67 | + <condition> |
68 | + <not> |
69 | + <contains string="${cluster.path.evaluated}" substring="platform"/> |
70 | + </not> |
71 | + </condition> |
72 | + </fail> |
73 | + <import file="${harness.dir}/build.xml"/> |
74 | +</project> |
75 | |
76 | === added file 'ForceAtlas2/nbproject/genfiles.properties' |
77 | --- ForceAtlas2/nbproject/genfiles.properties 1970-01-01 00:00:00 +0000 |
78 | +++ ForceAtlas2/nbproject/genfiles.properties 2011-06-05 20:50:57 +0000 |
79 | @@ -0,0 +1,8 @@ |
80 | +build.xml.data.CRC32=0a8bc1d8 |
81 | +build.xml.script.CRC32=7eaa9dd0 |
82 | +build.xml.stylesheet.CRC32=a56c6a5b@1.45.1 |
83 | +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. |
84 | +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. |
85 | +nbproject/build-impl.xml.data.CRC32=0a8bc1d8 |
86 | +nbproject/build-impl.xml.script.CRC32=eb5b8ec7 |
87 | +nbproject/build-impl.xml.stylesheet.CRC32=238281d1@1.45.1 |
88 | |
89 | === added file 'ForceAtlas2/nbproject/project.properties' |
90 | --- ForceAtlas2/nbproject/project.properties 1970-01-01 00:00:00 +0000 |
91 | +++ ForceAtlas2/nbproject/project.properties 2011-06-05 20:50:57 +0000 |
92 | @@ -0,0 +1,2 @@ |
93 | +javac.source=1.6 |
94 | +javac.compilerargs=-Xlint -Xlint:-serial |
95 | |
96 | === added file 'ForceAtlas2/nbproject/project.xml' |
97 | --- ForceAtlas2/nbproject/project.xml 1970-01-01 00:00:00 +0000 |
98 | +++ ForceAtlas2/nbproject/project.xml 2011-06-05 20:50:57 +0000 |
99 | @@ -0,0 +1,69 @@ |
100 | +<?xml version="1.0" encoding="UTF-8"?> |
101 | +<project xmlns="http://www.netbeans.org/ns/project/1"> |
102 | + <type>org.netbeans.modules.apisupport.project</type> |
103 | + <configuration> |
104 | + <data xmlns="http://www.netbeans.org/ns/nb-module-project/3"> |
105 | + <code-name-base>org.webatlas.forceatlas2</code-name-base> |
106 | + <suite-component/> |
107 | + <module-dependencies> |
108 | + <dependency> |
109 | + <code-name-base>org.gephi.data.attributes.api</code-name-base> |
110 | + <build-prerequisite/> |
111 | + <compile-dependency/> |
112 | + <run-dependency> |
113 | + <specification-version>0.8</specification-version> |
114 | + </run-dependency> |
115 | + </dependency> |
116 | + <dependency> |
117 | + <code-name-base>org.gephi.dynamic.api</code-name-base> |
118 | + <build-prerequisite/> |
119 | + <compile-dependency/> |
120 | + <run-dependency> |
121 | + <specification-version>0.8</specification-version> |
122 | + </run-dependency> |
123 | + </dependency> |
124 | + <dependency> |
125 | + <code-name-base>org.gephi.graph.api</code-name-base> |
126 | + <build-prerequisite/> |
127 | + <compile-dependency/> |
128 | + <run-dependency> |
129 | + <specification-version>0.8</specification-version> |
130 | + </run-dependency> |
131 | + </dependency> |
132 | + <dependency> |
133 | + <code-name-base>org.gephi.layout.api</code-name-base> |
134 | + <build-prerequisite/> |
135 | + <compile-dependency/> |
136 | + <run-dependency> |
137 | + <specification-version>0.8.0.1</specification-version> |
138 | + </run-dependency> |
139 | + </dependency> |
140 | + <dependency> |
141 | + <code-name-base>org.gephi.project.api</code-name-base> |
142 | + <build-prerequisite/> |
143 | + <compile-dependency/> |
144 | + <run-dependency> |
145 | + <specification-version>0.8</specification-version> |
146 | + </run-dependency> |
147 | + </dependency> |
148 | + <dependency> |
149 | + <code-name-base>org.openide.util</code-name-base> |
150 | + <build-prerequisite/> |
151 | + <compile-dependency/> |
152 | + <run-dependency> |
153 | + <specification-version>8.14.1</specification-version> |
154 | + </run-dependency> |
155 | + </dependency> |
156 | + <dependency> |
157 | + <code-name-base>org.openide.util.lookup</code-name-base> |
158 | + <build-prerequisite/> |
159 | + <compile-dependency/> |
160 | + <run-dependency> |
161 | + <specification-version>8.6.1</specification-version> |
162 | + </run-dependency> |
163 | + </dependency> |
164 | + </module-dependencies> |
165 | + <public-packages/> |
166 | + </data> |
167 | + </configuration> |
168 | +</project> |
169 | |
170 | === added file 'ForceAtlas2/nbproject/suite.properties' |
171 | --- ForceAtlas2/nbproject/suite.properties 1970-01-01 00:00:00 +0000 |
172 | +++ ForceAtlas2/nbproject/suite.properties 2011-06-05 20:50:57 +0000 |
173 | @@ -0,0 +1,1 @@ |
174 | +suite.dir=${basedir}/.. |
175 | |
176 | === added directory 'ForceAtlas2/src' |
177 | === added directory 'ForceAtlas2/src/org' |
178 | === added directory 'ForceAtlas2/src/org/webatlas' |
179 | === added directory 'ForceAtlas2/src/org/webatlas/forceatlas2' |
180 | === added file 'ForceAtlas2/src/org/webatlas/forceatlas2/Bundle.properties' |
181 | --- ForceAtlas2/src/org/webatlas/forceatlas2/Bundle.properties 1970-01-01 00:00:00 +0000 |
182 | +++ ForceAtlas2/src/org/webatlas/forceatlas2/Bundle.properties 2011-06-05 20:50:57 +0000 |
183 | @@ -0,0 +1,33 @@ |
184 | +OpenIDE-Module-Display-Category=Plugin |
185 | +OpenIDE-Module-Long-Description=Quality Layout, used in SNA. \ |
186 | + A linear-attraction linear-repulsion model with few approximations (BarnesHut). \ |
187 | + Features an optimal speed vs. precision balance and degree-driven repulsion for a high readability. \ |
188 | + Works on medium-sized graphs: 10 to 10000 nodes. |
189 | +OpenIDE-Module-Name=ForceAtlas 2, Quality Layout |
190 | +OpenIDE-Module-Short-Description=Quality Layout |
191 | + |
192 | +ForceAtlas2.name=ForceAtlas 2 |
193 | +ForceAtlas2.description=Quality layout: a linear-attraction linear-repulsion model with few approximations (BarnesHut). Speed automatically computed. |
194 | + |
195 | +ForceAtlas2.tuning=Tuning |
196 | +ForceAtlas2.behavior=Behavior Alternatives |
197 | +ForceAtlas2.performance=Performance |
198 | + |
199 | +ForceAtlas2.scalingRatio.name=Scaling |
200 | +ForceAtlas2.scalingRatio.desc=How much repulsion you want. More makes a more sparse graph. |
201 | +ForceAtlas2.gravity.name=Gravity |
202 | +ForceAtlas2.gravity.desc=Attracts nodes to the center. Prevents islands from drifting away. |
203 | +ForceAtlas2.distributedAttraction.name=Dissuade Hubs |
204 | +ForceAtlas2.distributedAttraction.desc=Distributes attraction along outbound edges. Hubs attract less and thus are pushed to the borders. |
205 | +ForceAtlas2.linLogMode.name=LinLog mode |
206 | +ForceAtlas2.linLogMode.desc=Switch ForceAtlas' model from lin-lin to lin-log (tribute to Andreas Noack). Makes clusters more tight. |
207 | +ForceAtlas2.adjustSizes.name=Prevent Overlap |
208 | +ForceAtlas2.adjustSizes.desc=Use only when spatialized. Should not be used with "Approximate Repulsion" |
209 | +ForceAtlas2.jitterTolerance.name=Tolerance (speed) |
210 | +ForceAtlas2.jitterTolerance.desc=How much swinging you allow. Above 1 discouraged. Lower gives less speed and more precision. |
211 | +ForceAtlas2.barnesHutOptimization.name=Approximate Repulsion |
212 | +ForceAtlas2.barnesHutOptimization.desc=Barnes Hut optimization: n\u00b2 complexity to n.ln(n) ; allows larger graphs. |
213 | +ForceAtlas2.barnesHutTheta.name=Approximation |
214 | +ForceAtlas2.barnesHutTheta.desc=Theta of the Barnes Hut optimization. |
215 | +ForceAtlas2.edgeWeightInfluence.name=Edge Weight Influence |
216 | +ForceAtlas2.edgeWeightInfluence.desc=How much influence you give to the edges weight. 0 is "no influence" and 1 is "normal". |
217 | |
218 | === added file 'ForceAtlas2/src/org/webatlas/forceatlas2/Bundle_fr.properties' |
219 | --- ForceAtlas2/src/org/webatlas/forceatlas2/Bundle_fr.properties 1970-01-01 00:00:00 +0000 |
220 | +++ ForceAtlas2/src/org/webatlas/forceatlas2/Bundle_fr.properties 2011-06-05 20:50:57 +0000 |
221 | @@ -0,0 +1,33 @@ |
222 | +OpenIDE-Module-Display-Category=Plugin |
223 | +OpenIDE-Module-Long-Description=Spatialisation qualitative, utilis\u00e9e dans l'analyse des r\u00e9seaux sociaux. \ |
224 | + Attraction et r\u00e9pulsion lin\u00e9aires avec quelques approximations (Barnes Hut)\ |
225 | + Comprend un \u00e9quilibrage dynamique de la vitesse contre la pr\u00e9cision, et une r\u00e9pulsion par degree qui rend le graphe plus lisible.\ |
226 | + Fonctionne sur des graphes de 10 \u00e0 10000 noeuds. |
227 | +OpenIDE-Module-Name=ForceAtlas 2, Spatialisation qualitative |
228 | +OpenIDE-Module-Short-Description=Spatialisation qualitative |
229 | + |
230 | +ForceAtlas2.name=ForceAtlas 2 |
231 | +ForceAtlas2.description=Spatialisation qualitative: mod\u00e8le \u00e0 attraction et r\u00e9pulsion lin\u00e9aires avec quelques optimisations (Barnes Hut). Param\u00e9trage automatis\u00e9. |
232 | + |
233 | +ForceAtlas2.tuning=R\u00e9glages fins |
234 | +ForceAtlas2.behavior=Options de comportement |
235 | +ForceAtlas2.performance=Performances |
236 | + |
237 | +ForceAtlas2.scalingRatio.name=Dimensionnement |
238 | +ForceAtlas2.scalingRatio.desc=Quantit\u00e9 de r\u00e9pulsion, par rapport \u00e0 l'attraction. Rend le graphe plus \u00e9tal\u00e9. |
239 | +ForceAtlas2.gravity.name=Gravit\u00e9 |
240 | +ForceAtlas2.gravity.desc=Attire les noeuds vers le centre. Emp\u00eache les \u00eelots de d\u00e9river \u00e0 l'infini. |
241 | +ForceAtlas2.distributedAttraction.name=Dissuader les Hubs |
242 | +ForceAtlas2.distributedAttraction.desc=Distribue l'attraction dans les liens sortants. Les Hubs attirent moins et sont donc repouss\u00e9s en p\u00e9riph\u00e9rie. |
243 | +ForceAtlas2.linLogMode.name=Mode LinLog |
244 | +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. |
245 | +ForceAtlas2.adjustSizes.name=Emp\u00eacher Recouvrement |
246 | +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. |
247 | +ForceAtlas2.jitterTolerance.name=Tol\u00e9rance (vitesse) |
248 | +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. |
249 | +ForceAtlas2.barnesHutOptimization.name=Approximer R\u00e9pulsion |
250 | +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. |
251 | +ForceAtlas2.barnesHutTheta.name=Approximation |
252 | +ForceAtlas2.barnesHutTheta.desc=param\u00e8tre Theta de l'optimisation de Barnes Hut. |
253 | +ForceAtlas2.edgeWeightInfluence.name=Influence Poids des liens |
254 | +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. |
255 | |
256 | === added file 'ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2.java' |
257 | --- ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2.java 1970-01-01 00:00:00 +0000 |
258 | +++ ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2.java 2011-06-05 20:50:57 +0000 |
259 | @@ -0,0 +1,450 @@ |
260 | +/* |
261 | +Copyright 2008-2011 Gephi |
262 | +Authors : Mathieu Jacomy <Mathieu.Jacomy@gmail.com> |
263 | +Website : http://www.webatlas.fr |
264 | + |
265 | +You should have received a copy of the GNU Affero General Public License |
266 | +along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>. |
267 | +*/ |
268 | +package org.webatlas.forceatlas2; |
269 | + |
270 | +import java.util.ArrayList; |
271 | +import java.util.List; |
272 | +import org.gephi.data.attributes.type.TimeInterval; |
273 | +import org.gephi.dynamic.DynamicUtilities; |
274 | +import org.gephi.dynamic.api.DynamicController; |
275 | +import org.gephi.dynamic.api.DynamicModel; |
276 | +import org.gephi.graph.api.Edge; |
277 | +import org.gephi.graph.api.GraphModel; |
278 | +import org.gephi.graph.api.HierarchicalGraph; |
279 | +import org.gephi.graph.api.Node; |
280 | +import org.gephi.graph.api.NodeData; |
281 | +import org.gephi.layout.spi.Layout; |
282 | +import org.gephi.layout.spi.LayoutBuilder; |
283 | +import org.gephi.layout.spi.LayoutProperty; |
284 | +import org.gephi.project.api.Workspace; |
285 | +import org.openide.util.Lookup; |
286 | +import org.openide.util.NbBundle; |
287 | +import org.webatlas.forceatlas2.ForceFactory.AttractionForce; |
288 | +import org.webatlas.forceatlas2.ForceFactory.RepulsionForce; |
289 | + |
290 | +/** |
291 | + * ForceAtlas 2 Layout, manages each step of the computations. |
292 | + * @author Mathieu Jacomy |
293 | + */ |
294 | +public class ForceAtlas2 implements Layout{ |
295 | + private GraphModel graphModel; |
296 | + private HierarchicalGraph graph; |
297 | + private ForceAtlas2Builder layoutBuilder; |
298 | + private DynamicModel dynamicModel; |
299 | + |
300 | + private double edgeWeightInfluence; |
301 | + private double jitterTolerance; |
302 | + private double scalingRatio; |
303 | + private double gravity; |
304 | + private double speed; |
305 | + private boolean outboundAttractionDistribution; |
306 | + private boolean adjustSizes; |
307 | + private boolean barnesHutOptimize; |
308 | + private double barnesHutTheta; |
309 | + private boolean linLogMode; |
310 | + private Region rootRegion; |
311 | + |
312 | + double outboundAttCompensation = 1; |
313 | + |
314 | + //Dynamic Weight |
315 | + private TimeInterval timeInterval; |
316 | + |
317 | + public ForceAtlas2(ForceAtlas2Builder layoutBuilder) { |
318 | + this.layoutBuilder = layoutBuilder; |
319 | + } |
320 | + |
321 | + @Override |
322 | + public void initAlgo() { |
323 | + speed = 1.; |
324 | + |
325 | + graph = graphModel.getHierarchicalGraphVisible(); |
326 | + this.timeInterval = DynamicUtilities.getVisibleInterval(dynamicModel); |
327 | + |
328 | + graph.readLock(); |
329 | + Node[] nodes = graph.getNodes().toArray(); |
330 | + |
331 | + // Initialise layout data |
332 | + for (Node n : nodes) { |
333 | + if (n.getNodeData().getLayoutData() == null || !(n.getNodeData().getLayoutData() instanceof ForceAtlas2LayoutData)) { |
334 | + ForceAtlas2LayoutData nLayout = new ForceAtlas2LayoutData(); |
335 | + n.getNodeData().setLayoutData(nLayout); |
336 | + } |
337 | + NodeData nData = n.getNodeData(); |
338 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
339 | + nLayout.mass = 1 + graph.getDegree(n); |
340 | + nLayout.old_dx = 0; |
341 | + nLayout.old_dy = 0; |
342 | + nLayout.dx = 0; |
343 | + nLayout.dy = 0; |
344 | + } |
345 | + } |
346 | + |
347 | + @Override |
348 | + public void goAlgo() { |
349 | + // Initialize graph data |
350 | + if (graphModel == null) { |
351 | + return; |
352 | + } |
353 | + graph = graphModel.getHierarchicalGraphVisible(); |
354 | + this.timeInterval = DynamicUtilities.getVisibleInterval(dynamicModel); |
355 | + |
356 | + graph.readLock(); |
357 | + Node[] nodes = graph.getNodes().toArray(); |
358 | + Edge[] edges = graph.getEdgesAndMetaEdges().toArray(); |
359 | + |
360 | + // Initialise layout data |
361 | + for (Node n : nodes) { |
362 | + if (n.getNodeData().getLayoutData() == null || !(n.getNodeData().getLayoutData() instanceof ForceAtlas2LayoutData)) { |
363 | + ForceAtlas2LayoutData nLayout = new ForceAtlas2LayoutData(); |
364 | + n.getNodeData().setLayoutData(nLayout); |
365 | + } |
366 | + NodeData nData = n.getNodeData(); |
367 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
368 | + nLayout.mass = 1 + graph.getDegree(n); |
369 | + nLayout.old_dx = nLayout.dx; |
370 | + nLayout.old_dy = nLayout.dy; |
371 | + nLayout.dx = 0; |
372 | + nLayout.dy = 0; |
373 | + } |
374 | + |
375 | + // If Barnes Hut active, initialize root region |
376 | + if(isBarnesHutOptimize()){ |
377 | + rootRegion = new Region(nodes); |
378 | + rootRegion.buildSubRegions(); |
379 | + } |
380 | + |
381 | + // If outboundAttractionDistribution active, compensate. |
382 | + if(isOutboundAttractionDistribution()){ |
383 | + outboundAttCompensation = 0; |
384 | + for(Node n : nodes){ |
385 | + NodeData nData = n.getNodeData(); |
386 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
387 | + outboundAttCompensation += nLayout.mass; |
388 | + } |
389 | + outboundAttCompensation /= nodes.length; |
390 | + } |
391 | + |
392 | + // Repulsion |
393 | + RepulsionForce Repulsion = ForceFactory.builder.buildRepulsion(isAdjustSizes(), getScalingRatio()); |
394 | + if(isBarnesHutOptimize()){ |
395 | + for(Node n : nodes){ |
396 | + rootRegion.applyForce(n, Repulsion, getBarnesHutTheta()); |
397 | + } |
398 | + } else { |
399 | + for (int n1Index = 0; n1Index<nodes.length; n1Index++) { |
400 | + Node n1 = nodes[n1Index]; |
401 | + for (int n2Index = 0; n2Index<n1Index; n2Index++) { |
402 | + Node n2 = nodes[n2Index]; |
403 | + Repulsion.apply(n1, n2); |
404 | + } |
405 | + } |
406 | + } |
407 | + |
408 | + // Attraction |
409 | + AttractionForce Attraction = ForceFactory.builder.buildAttraction(isLinLogMode(), isOutboundAttractionDistribution(), isAdjustSizes(), 1*((isOutboundAttractionDistribution())?(outboundAttCompensation):(1))); |
410 | + for (Edge e : edges) { |
411 | + Attraction.apply(e.getSource(), e.getTarget(), Math.pow(getWeight(e),getEdgeWeightInfluence())); |
412 | + } |
413 | + |
414 | + // Gravity |
415 | + for (Node n : nodes) { |
416 | + Repulsion.apply(n, getGravity()/getScalingRatio()); |
417 | + } |
418 | + |
419 | + // Auto adjust speed |
420 | + double totalSwinging = 0d; // How much irregular movement |
421 | + double totalEffectiveTraction = 0d; // Hom much useful movement |
422 | + for (Node n : nodes) { |
423 | + NodeData nData = n.getNodeData(); |
424 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
425 | + if (!nData.isFixed()) { |
426 | + double swinging = Math.sqrt(Math.pow(nLayout.old_dx - nLayout.dx,2) + Math.pow(nLayout.old_dy - nLayout.dy,2)); |
427 | + totalSwinging += nLayout.mass * swinging; // If the node has a burst change of direction, then it's not converging. |
428 | + totalEffectiveTraction += nLayout.mass * 0.5 * Math.sqrt(Math.pow(nLayout.old_dx + nLayout.dx,2) + Math.pow(nLayout.old_dy + nLayout.dy,2)); |
429 | + } |
430 | + } |
431 | + // We want that swingingMovement < tolerance * convergenceMovement |
432 | + double targetSpeed = getJitterTolerance() * getJitterTolerance() * totalEffectiveTraction / totalSwinging; |
433 | + |
434 | + // But the speed shoudn't rise too much too quickly, since it would make the convergence drop dramatically. |
435 | + double maxRise = 0.5; // Max rise: 50% |
436 | + speed = speed + Math.min(targetSpeed - speed, maxRise * speed); |
437 | + |
438 | + // Apply forces |
439 | + if(isAdjustSizes()){ |
440 | + // If nodes overlap prevention is active, it's not possible to trust the swinging mesure. |
441 | + for (Node n : nodes) { |
442 | + NodeData nData = n.getNodeData(); |
443 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
444 | + if (!nData.isFixed()) { |
445 | + |
446 | + // Adaptive auto-speed: the speed of each node is lowered |
447 | + // when the node swings. |
448 | + double swinging = Math.sqrt((nLayout.old_dx - nLayout.dx) * (nLayout.old_dx - nLayout.dx) + (nLayout.old_dy - nLayout.dy) * (nLayout.old_dy - nLayout.dy)); |
449 | + double factor = 0.1 * speed / (1f + speed * Math.sqrt(swinging)); |
450 | + |
451 | + double df = Math.sqrt(Math.pow(nLayout.dx, 2)+Math.pow(nLayout.dy, 2)); |
452 | + factor = Math.min(factor*df, 10.)/df; |
453 | + |
454 | + double x = nData.x() + nLayout.dx*factor; |
455 | + double y = nData.y() + nLayout.dy*factor; |
456 | + |
457 | + nData.setX((float) x); |
458 | + nData.setY((float) y); |
459 | + } |
460 | + } |
461 | + } else { |
462 | + for (Node n : nodes) { |
463 | + NodeData nData = n.getNodeData(); |
464 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
465 | + if (!nData.isFixed()) { |
466 | + |
467 | + // Adaptive auto-speed: the speed of each node is lowered |
468 | + // when the node swings. |
469 | + double swinging = Math.sqrt((nLayout.old_dx - nLayout.dx) * (nLayout.old_dx - nLayout.dx) + (nLayout.old_dy - nLayout.dy) * (nLayout.old_dy - nLayout.dy)); |
470 | + //double factor = speed / (1f + Math.sqrt(speed * swinging)); |
471 | + double factor = speed / (1f + speed * Math.sqrt(swinging)); |
472 | + |
473 | + double x = nData.x() + nLayout.dx*factor; |
474 | + double y = nData.y() + nLayout.dy*factor; |
475 | + |
476 | + nData.setX((float) x); |
477 | + nData.setY((float) y); |
478 | + } |
479 | + } |
480 | + } |
481 | + graph.readUnlock(); |
482 | + } |
483 | + |
484 | + @Override |
485 | + public boolean canAlgo() { |
486 | + return graphModel != null; |
487 | + } |
488 | + |
489 | + @Override |
490 | + public void endAlgo() { |
491 | + for (Node n : graph.getNodes()) { |
492 | + n.getNodeData().setLayoutData(null); |
493 | + } |
494 | + graph.readUnlock(); |
495 | + } |
496 | + |
497 | + @Override |
498 | + public LayoutProperty[] getProperties() { |
499 | + List<LayoutProperty> properties = new ArrayList<LayoutProperty>(); |
500 | + final String FORCEATLAS2_TUNING = NbBundle.getMessage(getClass(), "ForceAtlas2.tuning"); |
501 | + final String FORCEATLAS2_BEHAVIOR = NbBundle.getMessage(getClass(), "ForceAtlas2.behavior"); |
502 | + final String FORCEATLAS2_PERFORMANCE = NbBundle.getMessage(getClass(), "ForceAtlas2.performance"); |
503 | + |
504 | + try { |
505 | + properties.add(LayoutProperty.createProperty( |
506 | + this, Double.class, |
507 | + NbBundle.getMessage(getClass(), "ForceAtlas2.scalingRatio.name"), |
508 | + FORCEATLAS2_TUNING, |
509 | + NbBundle.getMessage(getClass(), "ForceAtlas2.scalingRatio.desc"), |
510 | + "getScalingRatio", "setScalingRatio")); |
511 | + |
512 | + properties.add(LayoutProperty.createProperty( |
513 | + this, Double.class, |
514 | + NbBundle.getMessage(getClass(), "ForceAtlas2.gravity.name"), |
515 | + FORCEATLAS2_TUNING, |
516 | + NbBundle.getMessage(getClass(), "ForceAtlas2.gravity.desc"), |
517 | + "getGravity", "setGravity")); |
518 | + |
519 | + properties.add(LayoutProperty.createProperty( |
520 | + this, Boolean.class, |
521 | + NbBundle.getMessage(getClass(), "ForceAtlas2.distributedAttraction.name"), |
522 | + FORCEATLAS2_BEHAVIOR, |
523 | + NbBundle.getMessage(getClass(), "ForceAtlas2.distributedAttraction.desc"), |
524 | + "isOutboundAttractionDistribution", "setOutboundAttractionDistribution")); |
525 | + |
526 | + properties.add(LayoutProperty.createProperty( |
527 | + this, Boolean.class, |
528 | + NbBundle.getMessage(getClass(), "ForceAtlas2.linLogMode.name"), |
529 | + FORCEATLAS2_BEHAVIOR, |
530 | + NbBundle.getMessage(getClass(), "ForceAtlas2.linLogMode.desc"), |
531 | + "isLinLogMode", "setLinLogMode")); |
532 | + |
533 | + properties.add(LayoutProperty.createProperty( |
534 | + this, Boolean.class, |
535 | + NbBundle.getMessage(getClass(), "ForceAtlas2.adjustSizes.name"), |
536 | + FORCEATLAS2_BEHAVIOR, |
537 | + NbBundle.getMessage(getClass(), "ForceAtlas2.adjustSizes.desc"), |
538 | + "isAdjustSizes", "setAdjustSizes")); |
539 | + |
540 | + properties.add(LayoutProperty.createProperty( |
541 | + this, Double.class, |
542 | + NbBundle.getMessage(getClass(), "ForceAtlas2.edgeWeightInfluence.name"), |
543 | + FORCEATLAS2_BEHAVIOR, |
544 | + NbBundle.getMessage(getClass(), "ForceAtlas2.edgeWeightInfluence.desc"), |
545 | + "getEdgeWeightInfluence", "setEdgeWeightInfluence")); |
546 | + |
547 | + properties.add(LayoutProperty.createProperty( |
548 | + this, Double.class, |
549 | + NbBundle.getMessage(getClass(), "ForceAtlas2.jitterTolerance.name"), |
550 | + FORCEATLAS2_PERFORMANCE, |
551 | + NbBundle.getMessage(getClass(), "ForceAtlas2.jitterTolerance.desc"), |
552 | + "getJitterTolerance", "setJitterTolerance")); |
553 | + |
554 | + properties.add(LayoutProperty.createProperty( |
555 | + this, Boolean.class, |
556 | + NbBundle.getMessage(getClass(), "ForceAtlas2.barnesHutOptimization.name"), |
557 | + FORCEATLAS2_PERFORMANCE, |
558 | + NbBundle.getMessage(getClass(), "ForceAtlas2.barnesHutOptimization.desc"), |
559 | + "isBarnesHutOptimize", "setBarnesHutOptimize")); |
560 | + |
561 | + properties.add(LayoutProperty.createProperty( |
562 | + this, Double.class, |
563 | + NbBundle.getMessage(getClass(), "ForceAtlas2.barnesHutTheta.name"), |
564 | + FORCEATLAS2_PERFORMANCE, |
565 | + NbBundle.getMessage(getClass(), "ForceAtlas2.barnesHutTheta.desc"), |
566 | + "getBarnesHutTheta", "setBarnesHutTheta")); |
567 | + |
568 | + } catch (Exception e) { |
569 | + e.printStackTrace(); |
570 | + } |
571 | + |
572 | + return properties.toArray(new LayoutProperty[0]); |
573 | + } |
574 | + |
575 | + @Override |
576 | + public void resetPropertiesValues() { |
577 | + int nodesCount = 0; |
578 | + |
579 | + if(graphModel != null){ |
580 | + nodesCount = graphModel.getHierarchicalGraphVisible().getNodeCount(); |
581 | + } |
582 | + |
583 | + // Tuning |
584 | + if(nodesCount>=100){ |
585 | + setScalingRatio(2.0); |
586 | + } else { |
587 | + setScalingRatio(10.0); |
588 | + } |
589 | + setGravity(1.); |
590 | + |
591 | + // Behavior |
592 | + setOutboundAttractionDistribution(false); |
593 | + setLinLogMode(false); |
594 | + setAdjustSizes(false); |
595 | + setEdgeWeightInfluence(1.); |
596 | + |
597 | + // Performance |
598 | + if(nodesCount>=50000){ |
599 | + setJitterTolerance(10d); |
600 | + } else if(nodesCount>=5000){ |
601 | + setJitterTolerance(1d); |
602 | + } else { |
603 | + setJitterTolerance(0.1d); |
604 | + } |
605 | + if(nodesCount>=1000){ |
606 | + setBarnesHutOptimize(true); |
607 | + } else { |
608 | + setBarnesHutOptimize(false); |
609 | + } |
610 | + setBarnesHutTheta(1.2); |
611 | + } |
612 | + |
613 | + @Override |
614 | + public LayoutBuilder getBuilder() { |
615 | + return layoutBuilder; |
616 | + } |
617 | + |
618 | + @Override |
619 | + public void setGraphModel(GraphModel graphModel) { |
620 | + this.graphModel = graphModel; |
621 | + Workspace workspace = graphModel.getWorkspace(); |
622 | + DynamicController dynamicController = Lookup.getDefault().lookup(DynamicController.class); |
623 | + if (dynamicController != null && workspace != null) { |
624 | + dynamicModel = dynamicController.getModel(workspace); |
625 | + } |
626 | + // Trick: reset here to take the profile of the graph in account for default values |
627 | + resetPropertiesValues(); |
628 | + } |
629 | + |
630 | + public Double getBarnesHutTheta() { |
631 | + return barnesHutTheta; |
632 | + } |
633 | + |
634 | + public void setBarnesHutTheta(Double barnesHutTheta) { |
635 | + this.barnesHutTheta = barnesHutTheta; |
636 | + } |
637 | + |
638 | + public Double getEdgeWeightInfluence() { |
639 | + return edgeWeightInfluence; |
640 | + } |
641 | + |
642 | + public void setEdgeWeightInfluence(Double edgeWeightInfluence) { |
643 | + this.edgeWeightInfluence = edgeWeightInfluence; |
644 | + } |
645 | + |
646 | + public Double getJitterTolerance() { |
647 | + return jitterTolerance; |
648 | + } |
649 | + |
650 | + public void setJitterTolerance(Double jitterTolerance) { |
651 | + this.jitterTolerance = jitterTolerance; |
652 | + } |
653 | + |
654 | + public Boolean isLinLogMode() { |
655 | + return linLogMode; |
656 | + } |
657 | + |
658 | + public void setLinLogMode(Boolean linLogMode) { |
659 | + this.linLogMode = linLogMode; |
660 | + } |
661 | + |
662 | + public Double getScalingRatio() { |
663 | + return scalingRatio; |
664 | + } |
665 | + |
666 | + public void setScalingRatio(Double scalingRatio) { |
667 | + this.scalingRatio = scalingRatio; |
668 | + } |
669 | + |
670 | + public Double getGravity() { |
671 | + return gravity; |
672 | + } |
673 | + |
674 | + public void setGravity(Double gravity) { |
675 | + this.gravity = gravity; |
676 | + } |
677 | + |
678 | + public Boolean isOutboundAttractionDistribution() { |
679 | + return outboundAttractionDistribution; |
680 | + } |
681 | + |
682 | + public void setOutboundAttractionDistribution(Boolean outboundAttractionDistribution) { |
683 | + this.outboundAttractionDistribution = outboundAttractionDistribution; |
684 | + } |
685 | + |
686 | + public Boolean isAdjustSizes() { |
687 | + return adjustSizes; |
688 | + } |
689 | + |
690 | + public void setAdjustSizes(Boolean adjustSizes) { |
691 | + this.adjustSizes = adjustSizes; |
692 | + } |
693 | + |
694 | + public Boolean isBarnesHutOptimize() { |
695 | + return barnesHutOptimize; |
696 | + } |
697 | + |
698 | + public void setBarnesHutOptimize(Boolean barnesHutOptimize) { |
699 | + this.barnesHutOptimize = barnesHutOptimize; |
700 | + } |
701 | + |
702 | + private float getWeight(Edge edge) { |
703 | + if(timeInterval!=null) { |
704 | + return edge.getWeight(timeInterval.getLow(), timeInterval.getHigh()); |
705 | + } else { |
706 | + return edge.getWeight(); |
707 | + } |
708 | + } |
709 | +} |
710 | |
711 | === added file 'ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2Builder.java' |
712 | --- ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2Builder.java 1970-01-01 00:00:00 +0000 |
713 | +++ ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2Builder.java 2011-06-05 20:50:57 +0000 |
714 | @@ -0,0 +1,75 @@ |
715 | +/* |
716 | +Copyright 2008-2011 Gephi |
717 | +Authors : Mathieu Jacomy <Mathieu.Jacomy@gmail.com> |
718 | +Website : http://www.webatlas.fr |
719 | + |
720 | +You should have received a copy of the GNU Affero General Public License |
721 | +along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>. |
722 | +*/ |
723 | + |
724 | +package org.webatlas.forceatlas2; |
725 | + |
726 | +import javax.swing.Icon; |
727 | +import javax.swing.JPanel; |
728 | +import org.gephi.layout.spi.Layout; |
729 | +import org.gephi.layout.spi.LayoutBuilder; |
730 | +import org.gephi.layout.spi.LayoutUI; |
731 | +import org.openide.util.NbBundle; |
732 | +import org.openide.util.lookup.ServiceProvider; |
733 | + |
734 | +/** |
735 | + * Layout Builder |
736 | + * @author Mathieu Jacomy |
737 | + */ |
738 | +@ServiceProvider(service = LayoutBuilder.class) |
739 | +public class ForceAtlas2Builder implements LayoutBuilder{ |
740 | + |
741 | + private ForceAtlas2UI ui = new ForceAtlas2UI(); |
742 | + |
743 | + @Override |
744 | + public String getName() { |
745 | + return NbBundle.getMessage(ForceAtlas2.class, "ForceAtlas2.name"); |
746 | + } |
747 | + |
748 | + @Override |
749 | + public LayoutUI getUI() { |
750 | + return ui; |
751 | + } |
752 | + |
753 | + @Override |
754 | + public ForceAtlas2 buildLayout() { |
755 | + ForceAtlas2 layout = new ForceAtlas2(this); |
756 | + return layout; |
757 | + } |
758 | + |
759 | + |
760 | + private class ForceAtlas2UI implements LayoutUI{ |
761 | + |
762 | + @Override |
763 | + public String getDescription() { |
764 | + return NbBundle.getMessage(ForceAtlas2.class, "ForceAtlas2.description"); |
765 | + } |
766 | + |
767 | + @Override |
768 | + public Icon getIcon() { |
769 | + return null; |
770 | + } |
771 | + |
772 | + @Override |
773 | + public JPanel getSimplePanel(Layout layout) { |
774 | + return null; |
775 | + } |
776 | + |
777 | + @Override |
778 | + public int getQualityRank() { |
779 | + return 4; |
780 | + } |
781 | + |
782 | + @Override |
783 | + public int getSpeedRank() { |
784 | + return 4; |
785 | + } |
786 | + |
787 | + } |
788 | + |
789 | +} |
790 | \ No newline at end of file |
791 | |
792 | === added file 'ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2LayoutData.java' |
793 | --- ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2LayoutData.java 1970-01-01 00:00:00 +0000 |
794 | +++ ForceAtlas2/src/org/webatlas/forceatlas2/ForceAtlas2LayoutData.java 2011-06-05 20:50:57 +0000 |
795 | @@ -0,0 +1,24 @@ |
796 | +/* |
797 | +Copyright 2008-2011 Gephi |
798 | +Authors : Mathieu Jacomy <Mathieu.Jacomy@gmail.com> |
799 | +Website : http://www.webatlas.fr |
800 | + |
801 | +You should have received a copy of the GNU Affero General Public License |
802 | +along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>. |
803 | +*/ |
804 | +package org.webatlas.forceatlas2; |
805 | + |
806 | +import org.gephi.graph.spi.LayoutData; |
807 | + |
808 | +/** |
809 | + * Data stored in Nodes and used by ForceAtlas2 |
810 | + * @author Mathieu Jacomy |
811 | + */ |
812 | +public class ForceAtlas2LayoutData implements LayoutData{ |
813 | + //Data |
814 | + public double dx = 0; |
815 | + public double dy = 0; |
816 | + public double old_dx = 0; |
817 | + public double old_dy = 0; |
818 | + public double mass = 1; |
819 | +} |
820 | |
821 | === added file 'ForceAtlas2/src/org/webatlas/forceatlas2/ForceFactory.java' |
822 | --- ForceAtlas2/src/org/webatlas/forceatlas2/ForceFactory.java 1970-01-01 00:00:00 +0000 |
823 | +++ ForceAtlas2/src/org/webatlas/forceatlas2/ForceFactory.java 2011-06-05 20:50:57 +0000 |
824 | @@ -0,0 +1,510 @@ |
825 | +/* |
826 | +Copyright 2008-2011 Gephi |
827 | +Authors : Mathieu Jacomy <Mathieu.Jacomy@gmail.com> |
828 | +Website : http://www.webatlas.fr |
829 | + |
830 | +You should have received a copy of the GNU Affero General Public License |
831 | +along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>. |
832 | +*/ |
833 | +package org.webatlas.forceatlas2; |
834 | + |
835 | +import org.gephi.graph.api.Node; |
836 | +import org.gephi.graph.api.NodeData; |
837 | + |
838 | +/** |
839 | + * Generates the forces on demand, here are all the formulas for attraction and repulsion. |
840 | + * @author Mathieu Jacomy |
841 | + */ |
842 | +public class ForceFactory { |
843 | + public static ForceFactory builder = new ForceFactory(); |
844 | + |
845 | + private ForceFactory(){}; |
846 | + |
847 | + public RepulsionForce buildRepulsion(boolean adjustBySize, double coefficient){ |
848 | + if(adjustBySize){ |
849 | + return new linRepulsion_antiCollision(coefficient); |
850 | + } else { |
851 | + return new linRepulsion(coefficient); |
852 | + } |
853 | + } |
854 | + |
855 | + public AttractionForce buildAttraction(boolean logAttraction, boolean distributedAttraction, boolean adjustBySize, double coefficient){ |
856 | + if(adjustBySize){ |
857 | + if(logAttraction){ |
858 | + if(distributedAttraction){ |
859 | + return new logAttraction_degreeDistributed_antiCollision(coefficient); |
860 | + } else { |
861 | + return new logAttraction_antiCollision(coefficient); |
862 | + } |
863 | + } else { |
864 | + if(distributedAttraction){ |
865 | + return new linAttraction_degreeDistributed_antiCollision(coefficient); |
866 | + } else { |
867 | + return new linAttraction_antiCollision(coefficient); |
868 | + } |
869 | + } |
870 | + } else { |
871 | + if(logAttraction){ |
872 | + if(distributedAttraction){ |
873 | + return new logAttraction_degreeDistributed(coefficient); |
874 | + } else { |
875 | + return new logAttraction(coefficient); |
876 | + } |
877 | + } else { |
878 | + if(distributedAttraction){ |
879 | + return new linAttraction_massDistributed(coefficient); |
880 | + } else { |
881 | + return new linAttraction(coefficient); |
882 | + } |
883 | + } |
884 | + } |
885 | + } |
886 | + |
887 | + public abstract class AttractionForce { |
888 | + public abstract void apply(Node n1, Node n2, double e); // Model for node-node attraction (e is for edge weight if needed) |
889 | + } |
890 | + |
891 | + public abstract class RepulsionForce { |
892 | + public abstract void apply(Node n1, Node n2); // Model for node-node repulsion |
893 | + public abstract void apply(Node n, Region r); // Model for Barnes Hut approximation |
894 | + public abstract void apply(Node n, double g); // Model for gravitation (anti-repulsion) |
895 | + } |
896 | + |
897 | + /* |
898 | + * Repulsion force: Linear |
899 | + */ |
900 | + private class linRepulsion extends RepulsionForce{ |
901 | + private double coefficient; |
902 | + |
903 | + public linRepulsion(double c){ |
904 | + coefficient = c; |
905 | + } |
906 | + |
907 | + @Override |
908 | + public void apply(Node n1, Node n2) { |
909 | + NodeData n1Data = n1.getNodeData(); |
910 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
911 | + NodeData n2Data = n2.getNodeData(); |
912 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
913 | + |
914 | + // Get the distance |
915 | + double xDist = n1Data.x() - n2Data.x(); |
916 | + double yDist = n1Data.y() - n2Data.y(); |
917 | + double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist); |
918 | + |
919 | + if (distance > 0) { |
920 | + // NB: factor = force / distance |
921 | + double factor = coefficient * n1Layout.mass * n2Layout.mass / distance / distance; |
922 | + |
923 | + n1Layout.dx += xDist * factor; |
924 | + n1Layout.dy += yDist * factor; |
925 | + |
926 | + n2Layout.dx -= xDist * factor; |
927 | + n2Layout.dy -= yDist * factor; |
928 | + } |
929 | + } |
930 | + |
931 | + @Override |
932 | + public void apply(Node n, Region r) { |
933 | + NodeData nData = n.getNodeData(); |
934 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
935 | + |
936 | + // Get the distance |
937 | + double xDist = nData.x() - r.getMassCenterX(); |
938 | + double yDist = nData.y() - r.getMassCenterY(); |
939 | + double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist); |
940 | + |
941 | + if (distance > 0) { |
942 | + // NB: factor = force / distance |
943 | + double factor = coefficient * nLayout.mass * r.getMass() / distance / distance; |
944 | + |
945 | + nLayout.dx += xDist * factor; |
946 | + nLayout.dy += yDist * factor; |
947 | + } |
948 | + } |
949 | + |
950 | + @Override |
951 | + public void apply(Node n, double g) { |
952 | + NodeData nData = n.getNodeData(); |
953 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
954 | + |
955 | + // Get the distance |
956 | + double xDist = nData.x(); |
957 | + double yDist = nData.y(); |
958 | + double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist); |
959 | + |
960 | + if (distance > 0) { |
961 | + // NB: factor = force / distance |
962 | + double factor = coefficient * nLayout.mass * g / distance; |
963 | + |
964 | + nLayout.dx -= xDist * factor; |
965 | + nLayout.dy -= yDist * factor; |
966 | + } |
967 | + } |
968 | + } |
969 | + |
970 | + /* |
971 | + * Repulsion force: Linear with Anti-collision |
972 | + */ |
973 | + private class linRepulsion_antiCollision extends RepulsionForce{ |
974 | + private double coefficient; |
975 | + |
976 | + public linRepulsion_antiCollision(double c){ |
977 | + coefficient = c; |
978 | + } |
979 | + |
980 | + @Override |
981 | + public void apply(Node n1, Node n2) { |
982 | + NodeData n1Data = n1.getNodeData(); |
983 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
984 | + NodeData n2Data = n2.getNodeData(); |
985 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
986 | + |
987 | + // Get the distance |
988 | + double xDist = n1Data.x() - n2Data.x(); |
989 | + double yDist = n1Data.y() - n2Data.y(); |
990 | + double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize(); |
991 | + |
992 | + if (distance > 0) { |
993 | + // NB: factor = force / distance |
994 | + double factor = coefficient * n1Layout.mass * n2Layout.mass / distance / distance; |
995 | + |
996 | + n1Layout.dx += xDist * factor; |
997 | + n1Layout.dy += yDist * factor; |
998 | + |
999 | + n2Layout.dx -= xDist * factor; |
1000 | + n2Layout.dy -= yDist * factor; |
1001 | + |
1002 | + } else if(distance < 0){ |
1003 | + double factor = 100 * coefficient * n1Layout.mass * n2Layout.mass; |
1004 | + |
1005 | + n1Layout.dx += xDist * factor; |
1006 | + n1Layout.dy += yDist * factor; |
1007 | + |
1008 | + n2Layout.dx -= xDist * factor; |
1009 | + n2Layout.dy -= yDist * factor; |
1010 | + } |
1011 | + } |
1012 | + |
1013 | + @Override |
1014 | + public void apply(Node n, Region r) { |
1015 | + NodeData nData = n.getNodeData(); |
1016 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
1017 | + |
1018 | + // Get the distance |
1019 | + double xDist = nData.x() - r.getMassCenterX(); |
1020 | + double yDist = nData.y() - r.getMassCenterY(); |
1021 | + double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist); |
1022 | + |
1023 | + if (distance > 0) { |
1024 | + // NB: factor = force / distance |
1025 | + double factor = coefficient * nLayout.mass * r.getMass() / distance / distance; |
1026 | + |
1027 | + nLayout.dx += xDist * factor; |
1028 | + nLayout.dy += yDist * factor; |
1029 | + } else if(distance < 0){ |
1030 | + double factor = -coefficient * nLayout.mass * r.getMass() / distance; |
1031 | + |
1032 | + nLayout.dx += xDist * factor; |
1033 | + nLayout.dy += yDist * factor; |
1034 | + } |
1035 | + } |
1036 | + |
1037 | + @Override |
1038 | + public void apply(Node n, double g) { |
1039 | + NodeData nData = n.getNodeData(); |
1040 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
1041 | + |
1042 | + // Get the distance |
1043 | + double xDist = nData.x(); |
1044 | + double yDist = nData.y(); |
1045 | + double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist); |
1046 | + |
1047 | + if (distance > 0) { |
1048 | + // NB: factor = force / distance |
1049 | + double factor = coefficient * nLayout.mass * g / distance; |
1050 | + |
1051 | + nLayout.dx -= xDist * factor; |
1052 | + nLayout.dy -= yDist * factor; |
1053 | + } |
1054 | + } |
1055 | + } |
1056 | + |
1057 | + /* |
1058 | + * Attraction force: Linear |
1059 | + */ |
1060 | + private class linAttraction extends AttractionForce{ |
1061 | + private double coefficient; |
1062 | + |
1063 | + public linAttraction(double c){ |
1064 | + coefficient = c; |
1065 | + } |
1066 | + |
1067 | + @Override |
1068 | + public void apply(Node n1, Node n2, double e) { |
1069 | + NodeData n1Data = n1.getNodeData(); |
1070 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
1071 | + NodeData n2Data = n2.getNodeData(); |
1072 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
1073 | + |
1074 | + // Get the distance |
1075 | + double xDist = n1Data.x() - n2Data.x(); |
1076 | + double yDist = n1Data.y() - n2Data.y(); |
1077 | + |
1078 | + // NB: factor = force / distance |
1079 | + double factor = -coefficient * e; |
1080 | + |
1081 | + n1Layout.dx += xDist * factor; |
1082 | + n1Layout.dy += yDist * factor; |
1083 | + |
1084 | + n2Layout.dx -= xDist * factor; |
1085 | + n2Layout.dy -= yDist * factor; |
1086 | + } |
1087 | + } |
1088 | + |
1089 | + /* |
1090 | + * Attraction force: Linear, distributed by mass (typically, degree) |
1091 | + */ |
1092 | + private class linAttraction_massDistributed extends AttractionForce{ |
1093 | + private double coefficient; |
1094 | + |
1095 | + public linAttraction_massDistributed(double c){ |
1096 | + coefficient = c; |
1097 | + } |
1098 | + |
1099 | + @Override |
1100 | + public void apply(Node n1, Node n2, double e) { |
1101 | + NodeData n1Data = n1.getNodeData(); |
1102 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
1103 | + NodeData n2Data = n2.getNodeData(); |
1104 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
1105 | + |
1106 | + // Get the distance |
1107 | + double xDist = n1Data.x() - n2Data.x(); |
1108 | + double yDist = n1Data.y() - n2Data.y(); |
1109 | + |
1110 | + // NB: factor = force / distance |
1111 | + double factor = -coefficient * e / n1Layout.mass; |
1112 | + |
1113 | + n1Layout.dx += xDist * factor; |
1114 | + n1Layout.dy += yDist * factor; |
1115 | + |
1116 | + n2Layout.dx -= xDist * factor; |
1117 | + n2Layout.dy -= yDist * factor; |
1118 | + } |
1119 | + } |
1120 | + |
1121 | + /* |
1122 | + * Attraction force: Logarithmic |
1123 | + */ |
1124 | + private class logAttraction extends AttractionForce{ |
1125 | + private double coefficient; |
1126 | + |
1127 | + public logAttraction(double c){ |
1128 | + coefficient = c; |
1129 | + } |
1130 | + |
1131 | + @Override |
1132 | + public void apply(Node n1, Node n2, double e) { |
1133 | + NodeData n1Data = n1.getNodeData(); |
1134 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
1135 | + NodeData n2Data = n2.getNodeData(); |
1136 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
1137 | + |
1138 | + // Get the distance |
1139 | + double xDist = n1Data.x() - n2Data.x(); |
1140 | + double yDist = n1Data.y() - n2Data.y(); |
1141 | + double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist); |
1142 | + |
1143 | + if (distance > 0) { |
1144 | + |
1145 | + // NB: factor = force / distance |
1146 | + double factor = -coefficient * e * Math.log(1+distance) / distance; |
1147 | + |
1148 | + n1Layout.dx += xDist * factor; |
1149 | + n1Layout.dy += yDist * factor; |
1150 | + |
1151 | + n2Layout.dx -= xDist * factor; |
1152 | + n2Layout.dy -= yDist * factor; |
1153 | + } |
1154 | + } |
1155 | + } |
1156 | + |
1157 | + /* |
1158 | + * Attraction force: Linear, distributed by Degree |
1159 | + */ |
1160 | + private class logAttraction_degreeDistributed extends AttractionForce{ |
1161 | + private double coefficient; |
1162 | + |
1163 | + public logAttraction_degreeDistributed(double c){ |
1164 | + coefficient = c; |
1165 | + } |
1166 | + |
1167 | + @Override |
1168 | + public void apply(Node n1, Node n2, double e) { |
1169 | + NodeData n1Data = n1.getNodeData(); |
1170 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
1171 | + NodeData n2Data = n2.getNodeData(); |
1172 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
1173 | + |
1174 | + // Get the distance |
1175 | + double xDist = n1Data.x() - n2Data.x(); |
1176 | + double yDist = n1Data.y() - n2Data.y(); |
1177 | + double distance = (float) Math.sqrt(xDist * xDist + yDist * yDist); |
1178 | + |
1179 | + if (distance > 0) { |
1180 | + |
1181 | + // NB: factor = force / distance |
1182 | + double factor = -coefficient * e * Math.log(1+distance) / distance / n1Layout.mass; |
1183 | + |
1184 | + n1Layout.dx += xDist * factor; |
1185 | + n1Layout.dy += yDist * factor; |
1186 | + |
1187 | + n2Layout.dx -= xDist * factor; |
1188 | + n2Layout.dy -= yDist * factor; |
1189 | + } |
1190 | + } |
1191 | + } |
1192 | + |
1193 | + /* |
1194 | + * Attraction force: Linear, with Anti-Collision |
1195 | + */ |
1196 | + private class linAttraction_antiCollision extends AttractionForce{ |
1197 | + private double coefficient; |
1198 | + |
1199 | + public linAttraction_antiCollision(double c){ |
1200 | + coefficient = c; |
1201 | + } |
1202 | + |
1203 | + @Override |
1204 | + public void apply(Node n1, Node n2, double e) { |
1205 | + NodeData n1Data = n1.getNodeData(); |
1206 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
1207 | + NodeData n2Data = n2.getNodeData(); |
1208 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
1209 | + |
1210 | + // Get the distance |
1211 | + double xDist = n1Data.x() - n2Data.x(); |
1212 | + double yDist = n1Data.y() - n2Data.y(); |
1213 | + double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize(); |
1214 | + |
1215 | + if(distance>0){ |
1216 | + // NB: factor = force / distance |
1217 | + double factor = -coefficient * e; |
1218 | + |
1219 | + n1Layout.dx += xDist * factor; |
1220 | + n1Layout.dy += yDist * factor; |
1221 | + |
1222 | + n2Layout.dx -= xDist * factor; |
1223 | + n2Layout.dy -= yDist * factor; |
1224 | + } |
1225 | + } |
1226 | + } |
1227 | + |
1228 | + /* |
1229 | + * Attraction force: Linear, distributed by Degree, with Anti-Collision |
1230 | + */ |
1231 | + private class linAttraction_degreeDistributed_antiCollision extends AttractionForce{ |
1232 | + private double coefficient; |
1233 | + |
1234 | + public linAttraction_degreeDistributed_antiCollision(double c){ |
1235 | + coefficient = c; |
1236 | + } |
1237 | + |
1238 | + @Override |
1239 | + public void apply(Node n1, Node n2, double e) { |
1240 | + NodeData n1Data = n1.getNodeData(); |
1241 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
1242 | + NodeData n2Data = n2.getNodeData(); |
1243 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
1244 | + |
1245 | + // Get the distance |
1246 | + double xDist = n1Data.x() - n2Data.x(); |
1247 | + double yDist = n1Data.y() - n2Data.y(); |
1248 | + double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize(); |
1249 | + |
1250 | + if(distance>0){ |
1251 | + // NB: factor = force / distance |
1252 | + double factor = -coefficient * e / n1Layout.mass; |
1253 | + |
1254 | + n1Layout.dx += xDist * factor; |
1255 | + n1Layout.dy += yDist * factor; |
1256 | + |
1257 | + n2Layout.dx -= xDist * factor; |
1258 | + n2Layout.dy -= yDist * factor; |
1259 | + } |
1260 | + } |
1261 | + } |
1262 | + |
1263 | + /* |
1264 | + * Attraction force: Logarithmic, with Anti-Collision |
1265 | + */ |
1266 | + private class logAttraction_antiCollision extends AttractionForce{ |
1267 | + private double coefficient; |
1268 | + |
1269 | + public logAttraction_antiCollision(double c){ |
1270 | + coefficient = c; |
1271 | + } |
1272 | + |
1273 | + @Override |
1274 | + public void apply(Node n1, Node n2, double e) { |
1275 | + NodeData n1Data = n1.getNodeData(); |
1276 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
1277 | + NodeData n2Data = n2.getNodeData(); |
1278 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
1279 | + |
1280 | + // Get the distance |
1281 | + double xDist = n1Data.x() - n2Data.x(); |
1282 | + double yDist = n1Data.y() - n2Data.y(); |
1283 | + double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize(); |
1284 | + |
1285 | + if(distance>0){ |
1286 | + |
1287 | + // NB: factor = force / distance |
1288 | + double factor = -coefficient * e * Math.log(1+distance) / distance; |
1289 | + |
1290 | + n1Layout.dx += xDist * factor; |
1291 | + n1Layout.dy += yDist * factor; |
1292 | + |
1293 | + n2Layout.dx -= xDist * factor; |
1294 | + n2Layout.dy -= yDist * factor; |
1295 | + } |
1296 | + } |
1297 | + } |
1298 | + |
1299 | + /* |
1300 | + * Attraction force: Linear, distributed by Degree, with Anti-Collision |
1301 | + */ |
1302 | + private class logAttraction_degreeDistributed_antiCollision extends AttractionForce{ |
1303 | + private double coefficient; |
1304 | + |
1305 | + public logAttraction_degreeDistributed_antiCollision(double c){ |
1306 | + coefficient = c; |
1307 | + } |
1308 | + |
1309 | + @Override |
1310 | + public void apply(Node n1, Node n2, double e) { |
1311 | + NodeData n1Data = n1.getNodeData(); |
1312 | + ForceAtlas2LayoutData n1Layout = n1Data.getLayoutData(); |
1313 | + NodeData n2Data = n2.getNodeData(); |
1314 | + ForceAtlas2LayoutData n2Layout = n2Data.getLayoutData(); |
1315 | + |
1316 | + // Get the distance |
1317 | + double xDist = n1Data.x() - n2Data.x(); |
1318 | + double yDist = n1Data.y() - n2Data.y(); |
1319 | + double distance = Math.sqrt(xDist * xDist + yDist * yDist) - n1Data.getSize() - n2Data.getSize(); |
1320 | + |
1321 | + if(distance>0){ |
1322 | + |
1323 | + // NB: factor = force / distance |
1324 | + double factor = -coefficient * e * Math.log(1+distance) / distance / n1Layout.mass; |
1325 | + |
1326 | + n1Layout.dx += xDist * factor; |
1327 | + n1Layout.dy += yDist * factor; |
1328 | + |
1329 | + n2Layout.dx -= xDist * factor; |
1330 | + n2Layout.dy -= yDist * factor; |
1331 | + } |
1332 | + } |
1333 | + } |
1334 | +} |
1335 | |
1336 | === added file 'ForceAtlas2/src/org/webatlas/forceatlas2/Region.java' |
1337 | --- ForceAtlas2/src/org/webatlas/forceatlas2/Region.java 1970-01-01 00:00:00 +0000 |
1338 | +++ ForceAtlas2/src/org/webatlas/forceatlas2/Region.java 2011-06-05 20:50:57 +0000 |
1339 | @@ -0,0 +1,197 @@ |
1340 | +/* |
1341 | +Copyright 2008-2011 Gephi |
1342 | +Authors : Mathieu Jacomy <Mathieu.Jacomy@gmail.com> |
1343 | +Website : http://www.webatlas.fr |
1344 | + |
1345 | +You should have received a copy of the GNU Affero General Public License |
1346 | +along with ForceAtlas 2. If not, see <http://www.gnu.org/licenses/>. |
1347 | +*/ |
1348 | +package org.webatlas.forceatlas2; |
1349 | + |
1350 | +import java.util.ArrayList; |
1351 | +import java.util.Arrays; |
1352 | +import java.util.List; |
1353 | +import org.gephi.graph.api.Node; |
1354 | +import org.gephi.graph.api.NodeData; |
1355 | +import org.webatlas.forceatlas2.ForceFactory.RepulsionForce; |
1356 | + |
1357 | +/** |
1358 | + * Barnes Hut optimization |
1359 | + * @author Mathieu Jacomy |
1360 | + */ |
1361 | +public class Region{ |
1362 | + |
1363 | + private double mass; |
1364 | + |
1365 | + private double massCenterX; |
1366 | + private double massCenterY; |
1367 | + private double size; |
1368 | + |
1369 | + private List<Node> nodes; |
1370 | + private List<Region> subregions = new ArrayList<Region>(); |
1371 | + |
1372 | + public Region(Node[] nodes){ |
1373 | + this.nodes = new ArrayList<Node>(); |
1374 | + this.nodes.addAll(Arrays.asList(nodes)); |
1375 | + updateMassAndGeometry(); |
1376 | + } |
1377 | + |
1378 | + public Region(ArrayList<Node> nodes){ |
1379 | + this.nodes = new ArrayList<Node>(nodes); |
1380 | + updateMassAndGeometry(); |
1381 | + } |
1382 | + |
1383 | + public void updateMassAndGeometry(){ |
1384 | + if(nodes.size()>1){ |
1385 | + // Compute Mass |
1386 | + mass = 0; |
1387 | + double massSumX = 0; |
1388 | + double massSumY = 0; |
1389 | + for(Node n : nodes){ |
1390 | + NodeData nData = n.getNodeData(); |
1391 | + ForceAtlas2LayoutData nLayout = nData.getLayoutData(); |
1392 | + mass += nLayout.mass; |
1393 | + massSumX += nData.x() * nLayout.mass; |
1394 | + massSumY += nData.y() * nLayout.mass; |
1395 | + } |
1396 | + massCenterX = massSumX / mass; |
1397 | + massCenterY = massSumY / mass; |
1398 | + |
1399 | + // Compute size |
1400 | + size = Double.MIN_VALUE; |
1401 | + for(Node n : nodes){ |
1402 | + NodeData nData = n.getNodeData(); |
1403 | + double distance = Math.sqrt((nData.x()-massCenterX)*(nData.x()-massCenterX) + (nData.y()-massCenterY)*(nData.y()-massCenterY)); |
1404 | + size = Math.max(size, 2*distance); |
1405 | + } |
1406 | + } |
1407 | + } |
1408 | + |
1409 | + public void buildSubRegions(){ |
1410 | + if(nodes.size()>1){ |
1411 | + ArrayList<Node> leftNodes = new ArrayList<Node>(); |
1412 | + ArrayList<Node> rightNodes = new ArrayList<Node>(); |
1413 | + for(Node n : nodes){ |
1414 | + NodeData nData = n.getNodeData(); |
1415 | + ArrayList<Node> nodesColumn = (nData.x()<massCenterX)?(leftNodes):(rightNodes); |
1416 | + nodesColumn.add(n); |
1417 | + } |
1418 | + |
1419 | + ArrayList<Node> topleftNodes = new ArrayList<Node>(); |
1420 | + ArrayList<Node> bottomleftNodes = new ArrayList<Node>(); |
1421 | + for(Node n : leftNodes){ |
1422 | + NodeData nData = n.getNodeData(); |
1423 | + ArrayList<Node> nodesLine = (nData.y()<massCenterY)?(topleftNodes):(bottomleftNodes); |
1424 | + nodesLine.add(n); |
1425 | + } |
1426 | + |
1427 | + ArrayList<Node> bottomrightNodes = new ArrayList<Node>(); |
1428 | + ArrayList<Node> toprightNodes = new ArrayList<Node>(); |
1429 | + for(Node n : rightNodes){ |
1430 | + NodeData nData = n.getNodeData(); |
1431 | + ArrayList<Node> nodesLine = (nData.y()<massCenterY)?(toprightNodes):(bottomrightNodes); |
1432 | + nodesLine.add(n); |
1433 | + } |
1434 | + |
1435 | + if(topleftNodes.size()>0){ |
1436 | + if(topleftNodes.size()<nodes.size()){ |
1437 | + Region subregion = new Region(topleftNodes); |
1438 | + subregions.add(subregion); |
1439 | + } else { |
1440 | + for(Node n : topleftNodes){ |
1441 | + ArrayList<Node> oneNodeList = new ArrayList<Node>(); |
1442 | + oneNodeList.add(n); |
1443 | + Region subregion = new Region(oneNodeList); |
1444 | + subregions.add(subregion); |
1445 | + } |
1446 | + } |
1447 | + } |
1448 | + if(bottomleftNodes.size()>0){ |
1449 | + if(bottomleftNodes.size()<nodes.size()){ |
1450 | + Region subregion = new Region(bottomleftNodes); |
1451 | + subregions.add(subregion); |
1452 | + } else { |
1453 | + for(Node n : bottomleftNodes){ |
1454 | + ArrayList<Node> oneNodeList = new ArrayList<Node>(); |
1455 | + oneNodeList.add(n); |
1456 | + Region subregion = new Region(oneNodeList); |
1457 | + subregions.add(subregion); |
1458 | + } |
1459 | + } |
1460 | + } |
1461 | + if(bottomrightNodes.size()>0){ |
1462 | + if(bottomrightNodes.size()<nodes.size()){ |
1463 | + Region subregion = new Region(bottomrightNodes); |
1464 | + subregions.add(subregion); |
1465 | + } else { |
1466 | + for(Node n : bottomrightNodes){ |
1467 | + ArrayList<Node> oneNodeList = new ArrayList<Node>(); |
1468 | + oneNodeList.add(n); |
1469 | + Region subregion = new Region(oneNodeList); |
1470 | + subregions.add(subregion); |
1471 | + } |
1472 | + } |
1473 | + } |
1474 | + if(toprightNodes.size()>0){ |
1475 | + if(toprightNodes.size()<nodes.size()){ |
1476 | + Region subregion = new Region(toprightNodes); |
1477 | + subregions.add(subregion); |
1478 | + } else { |
1479 | + for(Node n : toprightNodes){ |
1480 | + ArrayList<Node> oneNodeList = new ArrayList<Node>(); |
1481 | + oneNodeList.add(n); |
1482 | + Region subregion = new Region(oneNodeList); |
1483 | + subregions.add(subregion); |
1484 | + } |
1485 | + } |
1486 | + } |
1487 | + |
1488 | + for(Region subregion : subregions){ |
1489 | + subregion.buildSubRegions(); |
1490 | + } |
1491 | + } |
1492 | + } |
1493 | + |
1494 | + public void applyForce(Node n, RepulsionForce Force, double theta){ |
1495 | + NodeData nData = n.getNodeData(); |
1496 | + if(nodes.size() < 2){ |
1497 | + Node regionNode = nodes.get(0); |
1498 | + Force.apply(n, regionNode); |
1499 | + } else { |
1500 | + double distance = Math.sqrt((nData.x()-massCenterX)*(nData.x()-massCenterX) + (nData.y() - massCenterY)*(nData.y()-massCenterY)); |
1501 | + if(distance * theta > size){ |
1502 | + Force.apply(n, this); |
1503 | + } else { |
1504 | + for (Region subregion : subregions) { |
1505 | + subregion.applyForce(n, Force, theta); |
1506 | + } |
1507 | + } |
1508 | + } |
1509 | + } |
1510 | + |
1511 | + public double getMass() { |
1512 | + return mass; |
1513 | + } |
1514 | + |
1515 | + public void setMass(double mass) { |
1516 | + this.mass = mass; |
1517 | + } |
1518 | + |
1519 | + public double getMassCenterX() { |
1520 | + return massCenterX; |
1521 | + } |
1522 | + |
1523 | + public void setMassCenterX(double massCenterX) { |
1524 | + this.massCenterX = massCenterX; |
1525 | + } |
1526 | + |
1527 | + public double getMassCenterY() { |
1528 | + return massCenterY; |
1529 | + } |
1530 | + |
1531 | + public void setMassCenterY(double massCenterY) { |
1532 | + this.massCenterY = massCenterY; |
1533 | + } |
1534 | + |
1535 | + |
1536 | +} |
1537 | |
1538 | === added directory 'ForceAtlas2/test' |
1539 | === added directory 'ForceAtlas2/test/unit' |
1540 | === added directory 'ForceAtlas2/test/unit/src' |
The algorithm classes will be moved to the LayoutPlugin module