4
4
Implements a force-directed layout, the algorithm is based on Fruchterman and Reingold and
5
5
the JUNG implementation.
6
6
7
- Needs the following graph data structure:
7
+ Needs the graph data structure Graph.js :
8
8
https://github.com/davidpiegza/Graph-Visualization/blob/master/Graph.js
9
9
10
10
Parameters:
20
20
positionUpdated: <function>, called when the position of the node has been updated
21
21
}
22
22
23
- Examples
23
+ Examples:
24
24
25
- create:
25
+ create:
26
26
layout = new Layout.ForceDirected(graph, {width: 2000, height: 2000, iterations: 1000, layout: "3d"});
27
27
28
- call init when graph is loaded (and for reset or when new nodes has been added to graph):
28
+ call init when graph is loaded (and for reset or when new nodes has been added to the graph):
29
29
layout.init();
30
30
31
31
call generate in a render method, returns true if it's still calculating and false if it's finished
@@ -65,6 +65,9 @@ Layout.ForceDirected = function(graph, options) {
65
65
// performance test
66
66
var mean_time = 0 ;
67
67
68
+ /**
69
+ * Initialize parameters used by the algorithm.
70
+ */
68
71
this . init = function ( ) {
69
72
this . finished = false ;
70
73
temperature = this . width / 10.0 ;
@@ -75,8 +78,13 @@ Layout.ForceDirected = function(graph, options) {
75
78
repulsion_constant = this . repulsion_multiplier * forceConstant ;
76
79
} ;
77
80
81
+ /**
82
+ * Generates the force-directed layout.
83
+ *
84
+ * It finishes when the number of max_iterations has been reached or when
85
+ * the temperature is nearly zero.
86
+ */
78
87
this . generate = function ( ) {
79
- // TODO: stop if total force reached 0
80
88
if ( layout_iterations < this . max_iterations && temperature > 0.000001 ) {
81
89
var start = new Date ( ) . getTime ( ) ;
82
90
@@ -101,7 +109,6 @@ Layout.ForceDirected = function(graph, options) {
101
109
102
110
for ( var j = i + 1 ; j < nodes_length ; j ++ ) {
103
111
var node_u = graph . nodes [ j ] ;
104
- // if(node_v.id != node_u.id) {
105
112
if ( i != j ) {
106
113
node_u . layout = node_u . layout || { } ;
107
114
node_u . layout . tmp_pos_x = node_u . layout . tmp_pos_x || node_u . position . x ;
@@ -150,7 +157,7 @@ Layout.ForceDirected = function(graph, options) {
150
157
}
151
158
}
152
159
153
- // calc attraction
160
+ // calculate attraction
154
161
for ( var i = 0 ; i < edges_length ; i ++ ) {
155
162
var edge = graph . edges [ i ] ;
156
163
var delta_x = edge . source . layout . tmp_pos_x - edge . target . layout . tmp_pos_x ;
@@ -184,14 +191,13 @@ Layout.ForceDirected = function(graph, options) {
184
191
}
185
192
}
186
193
187
- // calc positions
194
+ // calculate positions
188
195
for ( var i = 0 ; i < nodes_length ; i ++ ) {
189
196
var node = graph . nodes [ i ] ;
190
197
var delta_length = Math . max ( EPSILON , Math . sqrt ( node . layout . offset_x * node . layout . offset_x + node . layout . offset_y * node . layout . offset_y ) ) ;
191
198
if ( this . layout === "3d" ) {
192
199
var delta_length_z = Math . max ( EPSILON , Math . sqrt ( node . layout . offset_z * node . layout . offset_z + node . layout . offset_y * node . layout . offset_y ) ) ;
193
200
}
194
- // alert(delta_length_z + " " + this.layout);
195
201
196
202
node . layout . tmp_pos_x += ( node . layout . offset_x / delta_length ) * Math . min ( delta_length , temperature ) ;
197
203
node . layout . tmp_pos_y += ( node . layout . offset_y / delta_length ) * Math . min ( delta_length , temperature ) ;
@@ -206,36 +212,17 @@ Layout.ForceDirected = function(graph, options) {
206
212
if ( this . layout === "3d" ) {
207
213
node . position . z -= ( node . position . z - node . layout . tmp_pos_z ) / 10 ;
208
214
}
209
-
210
- // var c = 200;
211
- // var updated = false;
212
- // if(node.position.x < (node.layout.tmp_pos_x - c) || node.position.x > (node.layout.tmp_pos_x + c)) {
213
- // node.position.x -= (node.position.x-node.layout.tmp_pos_x)/10;
214
- // updated = true;
215
- // }
216
- // if(node.position.y < (node.layout.tmp_pos_y - c) || node.position.y > (node.layout.tmp_pos_y + c)) {
217
- // node.position.y -= (node.position.y-node.layout.tmp_pos_y)/10;
218
- // updated = true;
219
- // }
220
- // if(this.layout === "3d") {
221
- // if(node.position.z < (node.layout.tmp_pos_z - c) || node.position.z > (node.layout.tmp_pos_z + c)) {
222
- // node.position.z -= (node.position.z-node.layout.tmp_pos_z)/10;
223
- // updated = true;
224
- // }
225
- // }
226
-
215
+
216
+ // execute callback function if positions has been updated
227
217
if ( updated && typeof callback_positionUpdated === 'function' ) {
228
218
callback_positionUpdated ( node ) ;
229
219
}
230
220
}
231
- var end = new Date ( ) . getTime ( ) ;
232
- mean_time += end - start ;
233
- // info.innerHTML = "node_force: " + parseInt(node_force) + "<br>edge_force: " + edge_force + "<br>div: " + (node_force-edge_force);
234
-
235
221
temperature *= ( 1 - ( layout_iterations / this . max_iterations ) ) ;
236
- // temperature -= 1/100;
237
- // console.log(temperature);
238
222
layout_iterations ++ ;
223
+
224
+ var end = new Date ( ) . getTime ( ) ;
225
+ mean_time += end - start ;
239
226
} else {
240
227
if ( ! this . finished ) {
241
228
console . log ( "Average time: " + ( mean_time / layout_iterations ) + " ms" ) ;
@@ -245,7 +232,10 @@ Layout.ForceDirected = function(graph, options) {
245
232
}
246
233
return true ;
247
234
} ;
248
-
235
+
236
+ /**
237
+ * Stops the calculation by setting the current_iterations to max_iterations.
238
+ */
249
239
this . stop_calculating = function ( ) {
250
240
layout_iterations = this . max_iterations ;
251
241
}
0 commit comments