@@ -155,42 +155,127 @@ d3.layout.force = function() {
155155 var force = { } ,
156156 event = d3 . dispatch ( "tick" ) ,
157157 size = [ 1 , 1 ] ,
158- alpha = .5 ,
158+ alpha = .1 ,
159+ drag = .9 ,
159160 distance = 30 ,
161+ charge = - 60 ,
162+ gravity = 180 ,
163+ theta = .8 ,
160164 interval ,
161165 nodes ,
162166 links ,
163167 distances ;
164168
169+ function accumulate ( n ) {
170+ var cx = 0 ,
171+ cy = 0 ;
172+ n . count = 0 ;
173+ if ( ! n . leaf ) {
174+ n . nodes . forEach ( function ( c ) {
175+ accumulate ( c ) ;
176+ n . count += c . count ;
177+ cx += c . count * c . cx ;
178+ cy += c . count * c . cy ;
179+ } ) ;
180+ }
181+ if ( n . point ) {
182+ n . count ++ ;
183+ cx += n . point . x ;
184+ cy += n . point . y ;
185+ }
186+ n . cx = cx / n . count ;
187+ n . cy = cy / n . count ;
188+ }
189+
190+ function repulse ( p ) {
191+ return function ( n , x1 , y1 , x2 , y2 ) {
192+ var dx = n . cx - p . x ,
193+ dy = n . cy - p . y ,
194+ dn = 1 / Math . sqrt ( dx * dx + dy * dy ) ;
195+
196+ /* Barnes-Hut criterion. */
197+ if ( ( x2 - x1 ) * dn < theta ) {
198+ var k = alpha * charge * n . count * dn * dn ;
199+ p . fx += dx * k ;
200+ p . fy += dy * k ;
201+ return true ;
202+ }
203+
204+ if ( n . point && ( n . point != p ) ) {
205+ var k = alpha * charge * dn * dn ;
206+ p . fx += dx * k ;
207+ p . fy += dy * k ;
208+ }
209+ } ;
210+ }
211+
165212 function tick ( ) {
166- var n = distances . length ,
213+ var n = nodes . length ,
214+ m = links . length ,
215+ q = d3 . geom . quadtree ( nodes ) ,
167216 i , // current index
168- o , // current link
217+ o , // current object
169218 s , // current source
170219 t , // current target
171220 l , // current distance
172221 x , // x-distance
173222 y ; // y-distance
174223
175- // gauss-seidel relaxation
176- for ( i = 0 ; i < n ; ++ i ) {
177- o = distances [ i ] ;
224+ // reset forces
225+ i = - 1 ; while ( ++ i < n ) {
226+ ( o = nodes [ i ] ) . fx = o . fy = 0 ;
227+ }
228+
229+ // gauss-seidel relaxation for links
230+ for ( i = 0 ; i < m ; ++ i ) {
231+ o = links [ i ] ;
178232 s = o . source ;
179233 t = o . target ;
180234 x = t . x - s . x ;
181235 y = t . y - s . y ;
182236 if ( l = Math . sqrt ( x * x + y * y ) ) {
183- l = alpha / ( o . distance * o . distance ) * ( l - distance * o . distance ) / l ;
237+ l = alpha * ( l - distance ) / l ;
184238 x *= l ;
185239 y *= l ;
186- if ( ! t . fixed ) {
187- t . x -= x ;
188- t . y -= y ;
189- }
190- if ( ! s . fixed ) {
191- s . x += x ;
192- s . y += y ;
193- }
240+ t . x -= x ;
241+ t . y -= y ;
242+ s . x += x ;
243+ s . y += y ;
244+ }
245+ }
246+
247+ // compute quadtree center of mass
248+ accumulate ( q ) ;
249+
250+ // apply gravity forces
251+ x = size [ 0 ] / 2 - q . cx ;
252+ y = size [ 1 ] / 2 - q . cy ;
253+ l = Math . min ( .02 , 1 / Math . sqrt ( x * x + y * y ) ) ;
254+ l = alpha * gravity * l * l ;
255+ x *= l ;
256+ y *= l ;
257+ i = - 1 ; while ( ++ i < n ) {
258+ o = nodes [ i ] ;
259+ o . fx += x ;
260+ o . fy += y ;
261+ }
262+
263+ // apply charge forces
264+ i = - 1 ; while ( ++ i < n ) {
265+ q . visit ( repulse ( nodes [ i ] ) ) ;
266+ }
267+
268+ // position verlet integration
269+ i = - 1 ; while ( ++ i < n ) {
270+ o = nodes [ i ] ;
271+ if ( o . fixed ) {
272+ o . x = o . px ;
273+ o . y = o . py ;
274+ } else {
275+ x = o . px - ( o . px = o . x ) ;
276+ y = o . py - ( o . py = o . y ) ;
277+ o . x += o . fx - x * drag ;
278+ o . y += o . fy - y * drag ;
194279 }
195280 }
196281
@@ -231,59 +316,27 @@ d3.layout.force = function() {
231316
232317 force . start = function ( ) {
233318 var i ,
234- j ,
235- k ,
236319 n = nodes . length ,
237320 m = links . length ,
238321 w = size [ 0 ] ,
239322 h = size [ 1 ] ,
240323 o ;
241324
242- var paths = [ ] ;
325+ // TODO initialize positions of new nodes using constraints (links)
243326 for ( i = 0 ; i < n ; ++ i ) {
244327 o = nodes [ i ] ;
245- o . x = o . x || Math . random ( ) * w ;
246- o . y = o . y || Math . random ( ) * h ;
247- o . fixed = 0 ;
248- paths [ i ] = [ ] ;
249- for ( j = 0 ; j < n ; ++ j ) {
250- paths [ i ] [ j ] = Infinity ;
251- }
252- paths [ i ] [ i ] = 0 ;
328+ if ( isNaN ( o . x ) ) o . x = Math . random ( ) * w ;
329+ if ( isNaN ( o . y ) ) o . y = Math . random ( ) * h ;
330+ if ( isNaN ( o . px ) ) o . px = o . x ;
331+ if ( isNaN ( o . py ) ) o . py = o . y ;
253332 }
254333
255334 for ( i = 0 ; i < m ; ++ i ) {
256335 o = links [ i ] ;
257- paths [ o . source ] [ o . target ] = 1 ;
258- paths [ o . target ] [ o . source ] = 1 ;
259- o . source = nodes [ o . source ] ;
260- o . target = nodes [ o . target ] ;
336+ if ( typeof o . source == "number" ) o . source = nodes [ o . source ] ;
337+ if ( typeof o . target == "number" ) o . target = nodes [ o . target ] ;
261338 }
262339
263- // Floyd-Warshall
264- for ( k = 0 ; k < n ; ++ k ) {
265- for ( i = 0 ; i < n ; ++ i ) {
266- for ( j = 0 ; j < n ; ++ j ) {
267- paths [ i ] [ j ] = Math . min ( paths [ i ] [ j ] , paths [ i ] [ k ] + paths [ k ] [ j ] ) ;
268- }
269- }
270- }
271-
272- distances = [ ] ;
273- for ( i = 0 ; i < n ; ++ i ) {
274- for ( j = i + 1 ; j < n ; ++ j ) {
275- distances . push ( {
276- source : nodes [ i ] ,
277- target : nodes [ j ] ,
278- distance : paths [ i ] [ j ] * paths [ i ] [ j ]
279- } ) ;
280- }
281- }
282-
283- distances . sort ( function ( a , b ) {
284- return a . distance - b . distance ;
285- } ) ;
286-
287340 d3 . timer ( tick ) ;
288341 return force ;
289342 } ;
@@ -321,8 +374,8 @@ d3.layout.force = function() {
321374 function mousemove ( ) {
322375 if ( ! node ) return ;
323376 var m = d3 . svg . mouse ( element ) ;
324- node . x = m [ 0 ] ;
325- node . y = m [ 1 ] ;
377+ node . px = m [ 0 ] ;
378+ node . py = m [ 1 ] ;
326379 force . resume ( ) ; // restart annealing
327380 }
328381
0 commit comments