1
+ /**
2
+ @author David Piegza
3
+
4
+ Implements a force-directed layout, the algorithm is based on Fruchterman and Reingold and
5
+ the JUNG implementation.
6
+
7
+ Needs the following graph data structure:
8
+ https://github.com/davidpiegza/Graph-Visualization/blob/master/Graph.js
9
+
10
+ Parameters:
11
+ graph - data structure
12
+ options = {
13
+ layout: "2d" or "3d"
14
+ attraction: <float>, attraction value for force-directed layout
15
+ repulsion: <float>, repulsion value for force-directed layout
16
+ iterations: <int>, maximum number of iterations
17
+ width: <int>, width of the viewport
18
+ height: <int>, height of the viewport
19
+
20
+ positionUpdated: <function>, called when the position of the node has been updated
21
+ }
22
+
23
+ Example:
24
+
25
+ create:
26
+ layout = new Layout.ForceDirected(graph, {width: 2000, height: 2000, iterations: 1000, layout: "3d"});
27
+
28
+ call init when graph is loaded (and for reset or when new nodes has been added to graph):
29
+ layout.init();
30
+
31
+ call generate in a render method, returns true if it's still calculating and false if it's finished
32
+ layout.generate();
33
+
34
+
35
+ Feel free to contribute a new layout!
36
+
37
+ */
1
38
2
39
var Layout = Layout || { } ;
3
40
4
41
Layout . ForceDirected = function ( graph , options ) {
5
42
var options = options || { } ;
43
+ var layout = options . layout || "2d" ;
44
+ var callback_positionUpdated = options . positionUpdated ;
45
+
6
46
var EPSILON = 0.000001 ;
7
47
var attraction_multiplier = options . attraction || 5 ;
8
48
var attraction_constant ;
9
49
var repulsion_multiplier = options . repulsion || 0.75 ;
10
50
var repulsion_constant ;
11
- var max_dimension ;
12
51
var forceConstant ;
13
52
var layout_iterations = 0 ;
14
53
var max_iterations = options . iterations || 10000 ;
@@ -17,7 +56,10 @@ Layout.ForceDirected = function(graph, options) {
17
56
var width = options . width || 200 ;
18
57
var height = options . height || 200 ;
19
58
59
+ this . finished = false ;
60
+
20
61
this . init = function ( ) {
62
+ this . finished = false ;
21
63
temperature = width / 10.0 ;
22
64
forceConstant = Math . sqrt ( height * width / graph . nodes . length ) ;
23
65
attraction_constant = attraction_multiplier * forceConstant ;
@@ -41,9 +83,10 @@ Layout.ForceDirected = function(graph, options) {
41
83
42
84
// info.innerHTML = "node_force: " + parseInt(node_force) + "<br>edge_force: " + edge_force + "<br>div: " + (node_force-edge_force);
43
85
44
- temperature *= ( 1.0 - ( layout_iterations / max_iterations ) ) ;
86
+ // temperature *= (1.0 - (layout_iterations / max_iterations));
45
87
layout_iterations ++ ;
46
88
} else {
89
+ this . finished = true ;
47
90
return false ;
48
91
}
49
92
return true ;
@@ -53,30 +96,52 @@ Layout.ForceDirected = function(graph, options) {
53
96
node_v . layout = node_v . layout || { } ;
54
97
node_v . layout . offset_x = 0 ;
55
98
node_v . layout . offset_y = 0 ;
99
+ if ( layout === "3d" ) {
100
+ node_v . layout . offset_z = 0 ;
101
+ }
56
102
node_v . layout . force = 0 ;
57
103
node_v . layout . tmp_pos_x = node_v . layout . tmp_pos_x || node_v . position . x ;
58
104
node_v . layout . tmp_pos_y = node_v . layout . tmp_pos_y || node_v . position . y ;
105
+ if ( layout === "3d" ) {
106
+ node_v . layout . tmp_pos_z = node_v . layout . tmp_pos_z || node_v . position . z ;
107
+ }
108
+
59
109
60
110
61
111
graph . nodes . forEach ( function ( node_u ) {
62
112
if ( node_v . id != node_u . id ) {
63
113
node_u . layout = node_u . layout || { } ;
64
114
node_u . layout . tmp_pos_x = node_u . layout . tmp_pos_x || node_u . position . x ;
65
115
node_u . layout . tmp_pos_y = node_u . layout . tmp_pos_y || node_u . position . y ;
116
+ if ( layout === "3d" ) {
117
+ node_u . layout . tmp_pos_z = node_u . layout . tmp_pos_z || node_u . position . z ;
118
+ }
66
119
67
- // var delta_x = node_v.position.x - node_u.position.x;
68
- // var delta_y = node_v.position.y - node_u.position.y;
69
120
var delta_x = node_v . layout . tmp_pos_x - node_u . layout . tmp_pos_x ;
70
121
var delta_y = node_v . layout . tmp_pos_y - node_u . layout . tmp_pos_y ;
122
+ if ( layout === "3d" ) {
123
+ var delta_z = node_v . layout . tmp_pos_z - node_u . layout . tmp_pos_z ;
124
+ }
71
125
72
126
var delta_length = Math . max ( EPSILON , Math . sqrt ( ( delta_x * delta_x ) + ( delta_y * delta_y ) ) ) ;
127
+ if ( layout === "3d" ) {
128
+ var delta_length_z = Math . max ( EPSILON , Math . sqrt ( ( delta_z * delta_z ) + ( delta_y * delta_y ) ) ) ;
129
+ }
130
+
73
131
var force = ( repulsion_constant * repulsion_constant ) / delta_length ;
132
+ if ( layout === "3d" ) {
133
+ var force_z = ( repulsion_constant * repulsion_constant ) / delta_length_z ;
134
+ }
135
+
74
136
75
137
node_v . layout . force += force ;
76
138
node_u . layout . force += force ;
77
139
78
140
node_v . layout . offset_x += ( delta_x / delta_length ) * force ;
79
141
node_v . layout . offset_y += ( delta_y / delta_length ) * force ;
142
+ if ( layout === "3d" ) {
143
+ node_v . layout . offset_z += ( delta_z / delta_length_z ) * force_z ;
144
+ }
80
145
}
81
146
} ) ;
82
147
} ;
@@ -86,36 +151,75 @@ Layout.ForceDirected = function(graph, options) {
86
151
// var delta_y = edge.source.position.y - edge.target.position.y;
87
152
var delta_x = edge . source . layout . tmp_pos_x - edge . target . layout . tmp_pos_x ;
88
153
var delta_y = edge . source . layout . tmp_pos_y - edge . target . layout . tmp_pos_y ;
154
+ if ( layout === "3d" ) {
155
+ var delta_z = edge . source . layout . tmp_pos_z - edge . target . layout . tmp_pos_z ;
156
+ }
89
157
90
158
var delta_length = Math . max ( EPSILON , Math . sqrt ( ( delta_x * delta_x ) + ( delta_y * delta_y ) ) ) ;
159
+ if ( layout === "3d" ) {
160
+ var delta_length_z = Math . max ( EPSILON , Math . sqrt ( ( delta_z * delta_z ) + ( delta_y * delta_y ) ) ) ;
161
+ }
91
162
var force = ( delta_length * delta_length ) / attraction_constant ;
163
+ if ( layout === "3d" ) {
164
+ var force_z = ( delta_length_z * delta_length_z ) / attraction_constant ;
165
+ }
92
166
93
167
edge . source . layout . force -= force ;
94
168
edge . target . layout . force += force ;
95
169
96
170
edge . source . layout . offset_x -= ( delta_x / delta_length ) * force ;
97
171
edge . source . layout . offset_y -= ( delta_y / delta_length ) * force ;
172
+ if ( layout === "3d" ) {
173
+ edge . source . layout . offset_z -= ( delta_z / delta_length_z ) * force_z ;
174
+ }
175
+
98
176
edge . target . layout . offset_x += ( delta_x / delta_length ) * force ;
99
177
edge . target . layout . offset_y += ( delta_y / delta_length ) * force ;
178
+ if ( layout === "3d" ) {
179
+ edge . target . layout . offset_z += ( delta_z / delta_length_z ) * force_z ;
180
+ }
100
181
} ;
101
182
102
183
var calcPositions = function ( graph , node ) {
103
184
var delta_length = Math . max ( EPSILON , norm ( node ) ) ;
185
+ if ( layout === "3d" ) {
186
+ var delta_length_z = Math . max ( EPSILON , norm2 ( node ) ) ;
187
+ }
104
188
105
189
node . layout . tmp_pos_x += ( node . layout . offset_x / delta_length ) * Math . min ( delta_length , temperature ) ;
106
190
node . layout . tmp_pos_y += ( node . layout . offset_y / delta_length ) * Math . min ( delta_length , temperature ) ;
191
+ if ( layout === "3d" ) {
192
+ node . layout . tmp_pos_z += ( node . layout . offset_z / delta_length_z ) * Math . min ( delta_length_z , temperature ) ;
193
+ }
107
194
108
195
var c = 100 ;
196
+ var updated = false ;
109
197
if ( node . position . x < ( node . layout . tmp_pos_x - c ) || node . position . x > ( node . layout . tmp_pos_x + c ) ) {
110
198
node . position . x -= ( node . position . x - node . layout . tmp_pos_x ) / 10 ;
199
+ updated = true ;
111
200
}
112
201
if ( node . position . y < ( node . layout . tmp_pos_y - c ) || node . position . y > ( node . layout . tmp_pos_y + c ) ) {
113
202
node . position . y -= ( node . position . y - node . layout . tmp_pos_y ) / 10 ;
203
+ updated = true ;
204
+ }
205
+ if ( layout === "3d" ) {
206
+ if ( node . position . z < ( node . layout . tmp_pos_z - c ) || node . position . z > ( node . layout . tmp_pos_z + c ) ) {
207
+ node . position . z -= ( node . position . z - node . layout . tmp_pos_z ) / 10 ;
208
+ updated = true ;
209
+ }
210
+ }
211
+
212
+ if ( updated && typeof callback_positionUpdated === 'function' ) {
213
+ callback_positionUpdated ( node ) ;
114
214
}
115
215
} ;
116
216
117
217
var norm = function ( node ) {
118
218
return Math . sqrt ( node . layout . offset_x * node . layout . offset_x + node . layout . offset_y * node . layout . offset_y ) ;
119
219
} ;
120
220
221
+ var norm2 = function ( node ) {
222
+ return Math . sqrt ( node . layout . offset_z * node . layout . offset_z + node . layout . offset_y * node . layout . offset_y ) ;
223
+ } ;
224
+
121
225
} ;
0 commit comments