1 | // A rudimentary force layout using Gauss-Seidel. |
---|
2 | d3.layout.force = function() { |
---|
3 | var force = {}, |
---|
4 | event = d3.dispatch("tick"), |
---|
5 | size = [1, 1], |
---|
6 | alpha, |
---|
7 | friction = .9, |
---|
8 | linkDistance = d3_layout_forceLinkDistance, |
---|
9 | linkStrength = d3_layout_forceLinkStrength, |
---|
10 | charge = -30, |
---|
11 | gravity = .1, |
---|
12 | theta = .8, |
---|
13 | interval, |
---|
14 | nodes = [], |
---|
15 | links = [], |
---|
16 | distances, |
---|
17 | strengths; |
---|
18 | |
---|
19 | function repulse(node, kc) { |
---|
20 | return function(quad, x1, y1, x2, y2) { |
---|
21 | if (quad.point !== node) { |
---|
22 | var dx = quad.cx - node.x, |
---|
23 | dy = quad.cy - node.y, |
---|
24 | dn = 1 / Math.sqrt(dx * dx + dy * dy); |
---|
25 | |
---|
26 | /* Barnes-Hut criterion. */ |
---|
27 | if ((x2 - x1) * dn < theta) { |
---|
28 | var k = kc * quad.count * dn * dn; |
---|
29 | node.x += dx * k; |
---|
30 | node.y += dy * k; |
---|
31 | return true; |
---|
32 | } |
---|
33 | |
---|
34 | if (quad.point && isFinite(dn)) { |
---|
35 | var k = kc * dn * dn; |
---|
36 | node.x += dx * k; |
---|
37 | node.y += dy * k; |
---|
38 | } |
---|
39 | } |
---|
40 | }; |
---|
41 | } |
---|
42 | |
---|
43 | function tick() { |
---|
44 | var n = nodes.length, |
---|
45 | m = links.length, |
---|
46 | q = d3.geom.quadtree(nodes), |
---|
47 | i, // current index |
---|
48 | o, // current object |
---|
49 | s, // current source |
---|
50 | t, // current target |
---|
51 | l, // current distance |
---|
52 | x, // x-distance |
---|
53 | y; // y-distance |
---|
54 | |
---|
55 | // gauss-seidel relaxation for links |
---|
56 | for (i = 0; i < m; ++i) { |
---|
57 | o = links[i]; |
---|
58 | s = o.source; |
---|
59 | t = o.target; |
---|
60 | x = t.x - s.x; |
---|
61 | y = t.y - s.y; |
---|
62 | if (l = (x * x + y * y)) { |
---|
63 | l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; |
---|
64 | x *= l; |
---|
65 | y *= l; |
---|
66 | t.x -= x; |
---|
67 | t.y -= y; |
---|
68 | s.x += x; |
---|
69 | s.y += y; |
---|
70 | } |
---|
71 | } |
---|
72 | |
---|
73 | // apply gravity forces |
---|
74 | var kg = alpha * gravity; |
---|
75 | x = size[0] / 2; |
---|
76 | y = size[1] / 2; |
---|
77 | i = -1; while (++i < n) { |
---|
78 | o = nodes[i]; |
---|
79 | o.x += (x - o.x) * kg; |
---|
80 | o.y += (y - o.y) * kg; |
---|
81 | } |
---|
82 | |
---|
83 | // compute quadtree center of mass |
---|
84 | d3_layout_forceAccumulate(q); |
---|
85 | |
---|
86 | // apply charge forces |
---|
87 | var kc = alpha * charge; |
---|
88 | i = -1; while (++i < n) { |
---|
89 | q.visit(repulse(nodes[i], kc)); |
---|
90 | } |
---|
91 | |
---|
92 | // position verlet integration |
---|
93 | i = -1; while (++i < n) { |
---|
94 | o = nodes[i]; |
---|
95 | if (o.fixed) { |
---|
96 | o.x = o.px; |
---|
97 | o.y = o.py; |
---|
98 | } else { |
---|
99 | o.x -= (o.px - (o.px = o.x)) * friction; |
---|
100 | o.y -= (o.py - (o.py = o.y)) * friction; |
---|
101 | } |
---|
102 | } |
---|
103 | |
---|
104 | event.tick.dispatch({type: "tick", alpha: alpha}); |
---|
105 | |
---|
106 | // simulated annealing, basically |
---|
107 | return (alpha *= .99) < .005; |
---|
108 | } |
---|
109 | |
---|
110 | force.on = function(type, listener) { |
---|
111 | event[type].add(listener); |
---|
112 | return force; |
---|
113 | }; |
---|
114 | |
---|
115 | force.nodes = function(x) { |
---|
116 | if (!arguments.length) return nodes; |
---|
117 | nodes = x; |
---|
118 | return force; |
---|
119 | }; |
---|
120 | |
---|
121 | force.links = function(x) { |
---|
122 | if (!arguments.length) return links; |
---|
123 | links = x; |
---|
124 | return force; |
---|
125 | }; |
---|
126 | |
---|
127 | force.size = function(x) { |
---|
128 | if (!arguments.length) return size; |
---|
129 | size = x; |
---|
130 | return force; |
---|
131 | }; |
---|
132 | |
---|
133 | force.linkDistance = function(x) { |
---|
134 | if (!arguments.length) return linkDistance; |
---|
135 | linkDistance = d3.functor(x); |
---|
136 | return force; |
---|
137 | }; |
---|
138 | |
---|
139 | // For backwards-compatibility. |
---|
140 | force.distance = force.linkDistance; |
---|
141 | |
---|
142 | force.linkStrength = function(x) { |
---|
143 | if (!arguments.length) return linkStrength; |
---|
144 | linkStrength = d3.functor(x); |
---|
145 | return force; |
---|
146 | }; |
---|
147 | |
---|
148 | force.friction = function(x) { |
---|
149 | if (!arguments.length) return friction; |
---|
150 | friction = x; |
---|
151 | return force; |
---|
152 | }; |
---|
153 | |
---|
154 | force.charge = function(x) { |
---|
155 | if (!arguments.length) return charge; |
---|
156 | charge = x; |
---|
157 | return force; |
---|
158 | }; |
---|
159 | |
---|
160 | force.gravity = function(x) { |
---|
161 | if (!arguments.length) return gravity; |
---|
162 | gravity = x; |
---|
163 | return force; |
---|
164 | }; |
---|
165 | |
---|
166 | force.theta = function(x) { |
---|
167 | if (!arguments.length) return theta; |
---|
168 | theta = x; |
---|
169 | return force; |
---|
170 | }; |
---|
171 | |
---|
172 | force.start = function() { |
---|
173 | var i, |
---|
174 | j, |
---|
175 | n = nodes.length, |
---|
176 | m = links.length, |
---|
177 | w = size[0], |
---|
178 | h = size[1], |
---|
179 | neighbors, |
---|
180 | o; |
---|
181 | |
---|
182 | for (i = 0; i < n; ++i) { |
---|
183 | (o = nodes[i]).index = i; |
---|
184 | } |
---|
185 | |
---|
186 | distances = []; |
---|
187 | strengths = []; |
---|
188 | for (i = 0; i < m; ++i) { |
---|
189 | o = links[i]; |
---|
190 | if (typeof o.source == "number") o.source = nodes[o.source]; |
---|
191 | if (typeof o.target == "number") o.target = nodes[o.target]; |
---|
192 | distances[i] = linkDistance.call(this, o, i); |
---|
193 | strengths[i] = linkStrength.call(this, o, i); |
---|
194 | } |
---|
195 | |
---|
196 | for (i = 0; i < n; ++i) { |
---|
197 | o = nodes[i]; |
---|
198 | if (isNaN(o.x)) o.x = position("x", w); |
---|
199 | if (isNaN(o.y)) o.y = position("y", h); |
---|
200 | if (isNaN(o.px)) o.px = o.x; |
---|
201 | if (isNaN(o.py)) o.py = o.y; |
---|
202 | } |
---|
203 | |
---|
204 | // initialize node position based on first neighbor |
---|
205 | function position(dimension, size) { |
---|
206 | var neighbors = neighbor(i), |
---|
207 | j = -1, |
---|
208 | m = neighbors.length, |
---|
209 | x; |
---|
210 | while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; |
---|
211 | return Math.random() * size; |
---|
212 | } |
---|
213 | |
---|
214 | // initialize neighbors lazily |
---|
215 | function neighbor() { |
---|
216 | if (!neighbors) { |
---|
217 | neighbors = []; |
---|
218 | for (j = 0; j < n; ++j) { |
---|
219 | neighbors[j] = []; |
---|
220 | } |
---|
221 | for (j = 0; j < m; ++j) { |
---|
222 | var o = links[j]; |
---|
223 | neighbors[o.source.index].push(o.target); |
---|
224 | neighbors[o.target.index].push(o.source); |
---|
225 | } |
---|
226 | } |
---|
227 | return neighbors[i]; |
---|
228 | } |
---|
229 | |
---|
230 | return force.resume(); |
---|
231 | }; |
---|
232 | |
---|
233 | force.resume = function() { |
---|
234 | alpha = .1; |
---|
235 | d3.timer(tick); |
---|
236 | return force; |
---|
237 | }; |
---|
238 | |
---|
239 | force.stop = function() { |
---|
240 | alpha = 0; |
---|
241 | return force; |
---|
242 | }; |
---|
243 | |
---|
244 | // use `node.call(force.drag)` to make nodes draggable |
---|
245 | force.drag = function() { |
---|
246 | |
---|
247 | this |
---|
248 | .on("mouseover.force", d3_layout_forceDragOver) |
---|
249 | .on("mouseout.force", d3_layout_forceDragOut) |
---|
250 | .on("mousedown.force", dragdown) |
---|
251 | .on("touchstart.force", dragdown); |
---|
252 | |
---|
253 | d3.select(window) |
---|
254 | .on("mousemove.force", d3_layout_forceDragMove) |
---|
255 | .on("touchmove.force", d3_layout_forceDragMove) |
---|
256 | .on("mouseup.force", d3_layout_forceDragUp, true) |
---|
257 | .on("touchend.force", d3_layout_forceDragUp, true) |
---|
258 | .on("click.force", d3_layout_forceDragClick, true); |
---|
259 | |
---|
260 | return force; |
---|
261 | }; |
---|
262 | |
---|
263 | function dragdown(d, i) { |
---|
264 | var m = d3_layout_forcePoint(this.parentNode); |
---|
265 | (d3_layout_forceDragNode = d).fixed = true; |
---|
266 | d3_layout_forceDragMoved = false; |
---|
267 | d3_layout_forceDragElement = this; |
---|
268 | d3_layout_forceDragForce = force; |
---|
269 | d3_layout_forceDragOffset = [m[0] - d.x, m[1] - d.y]; |
---|
270 | d3_layout_forceCancel(); |
---|
271 | } |
---|
272 | |
---|
273 | return force; |
---|
274 | }; |
---|
275 | |
---|
276 | var d3_layout_forceDragForce, |
---|
277 | d3_layout_forceDragNode, |
---|
278 | d3_layout_forceDragMoved, |
---|
279 | d3_layout_forceDragOffset, |
---|
280 | d3_layout_forceStopClick, |
---|
281 | d3_layout_forceDragElement; |
---|
282 | |
---|
283 | function d3_layout_forceDragOver(d) { |
---|
284 | d.fixed = true; |
---|
285 | } |
---|
286 | |
---|
287 | function d3_layout_forceDragOut(d) { |
---|
288 | if (d !== d3_layout_forceDragNode) { |
---|
289 | d.fixed = false; |
---|
290 | } |
---|
291 | } |
---|
292 | |
---|
293 | function d3_layout_forcePoint(container) { |
---|
294 | return d3.event.touches |
---|
295 | ? d3.svg.touches(container)[0] |
---|
296 | : d3.svg.mouse(container); |
---|
297 | } |
---|
298 | |
---|
299 | function d3_layout_forceDragMove() { |
---|
300 | if (!d3_layout_forceDragNode) return; |
---|
301 | var parent = d3_layout_forceDragElement.parentNode; |
---|
302 | |
---|
303 | // O NOES! The drag element was removed from the DOM. |
---|
304 | if (!parent) { |
---|
305 | d3_layout_forceDragNode.fixed = false; |
---|
306 | d3_layout_forceDragOffset = d3_layout_forceDragNode = d3_layout_forceDragElement = null; |
---|
307 | return; |
---|
308 | } |
---|
309 | |
---|
310 | var m = d3_layout_forcePoint(parent); |
---|
311 | d3_layout_forceDragMoved = true; |
---|
312 | d3_layout_forceDragNode.px = m[0] - d3_layout_forceDragOffset[0]; |
---|
313 | d3_layout_forceDragNode.py = m[1] - d3_layout_forceDragOffset[1]; |
---|
314 | d3_layout_forceCancel(); |
---|
315 | d3_layout_forceDragForce.resume(); // restart annealing |
---|
316 | } |
---|
317 | |
---|
318 | function d3_layout_forceDragUp() { |
---|
319 | if (!d3_layout_forceDragNode) return; |
---|
320 | |
---|
321 | // If the node was moved, prevent the mouseup from propagating. |
---|
322 | // Also prevent the subsequent click from propagating (e.g., for anchors). |
---|
323 | if (d3_layout_forceDragMoved) { |
---|
324 | d3_layout_forceStopClick = true; |
---|
325 | d3_layout_forceCancel(); |
---|
326 | } |
---|
327 | |
---|
328 | // Don't trigger this for touchend. |
---|
329 | if (d3.event.type === "mouseup") { |
---|
330 | d3_layout_forceDragMove(); |
---|
331 | } |
---|
332 | |
---|
333 | d3_layout_forceDragNode.fixed = false; |
---|
334 | d3_layout_forceDragForce = |
---|
335 | d3_layout_forceDragOffset = |
---|
336 | d3_layout_forceDragNode = |
---|
337 | d3_layout_forceDragElement = null; |
---|
338 | } |
---|
339 | |
---|
340 | function d3_layout_forceDragClick() { |
---|
341 | if (d3_layout_forceStopClick) { |
---|
342 | d3_layout_forceCancel(); |
---|
343 | d3_layout_forceStopClick = false; |
---|
344 | } |
---|
345 | } |
---|
346 | |
---|
347 | function d3_layout_forceCancel() { |
---|
348 | d3.event.stopPropagation(); |
---|
349 | d3.event.preventDefault(); |
---|
350 | } |
---|
351 | |
---|
352 | function d3_layout_forceAccumulate(quad) { |
---|
353 | var cx = 0, |
---|
354 | cy = 0; |
---|
355 | quad.count = 0; |
---|
356 | if (!quad.leaf) { |
---|
357 | var nodes = quad.nodes, |
---|
358 | n = nodes.length, |
---|
359 | i = -1, |
---|
360 | c; |
---|
361 | while (++i < n) { |
---|
362 | c = nodes[i]; |
---|
363 | if (c == null) continue; |
---|
364 | d3_layout_forceAccumulate(c); |
---|
365 | quad.count += c.count; |
---|
366 | cx += c.count * c.cx; |
---|
367 | cy += c.count * c.cy; |
---|
368 | } |
---|
369 | } |
---|
370 | if (quad.point) { |
---|
371 | // jitter internal nodes that are coincident |
---|
372 | if (!quad.leaf) { |
---|
373 | quad.point.x += Math.random() - .5; |
---|
374 | quad.point.y += Math.random() - .5; |
---|
375 | } |
---|
376 | quad.count++; |
---|
377 | cx += quad.point.x; |
---|
378 | cy += quad.point.y; |
---|
379 | } |
---|
380 | quad.cx = cx / quad.count; |
---|
381 | quad.cy = cy / quad.count; |
---|
382 | } |
---|
383 | |
---|
384 | function d3_layout_forceLinkDistance(link) { |
---|
385 | return 20; |
---|
386 | } |
---|
387 | |
---|
388 | function d3_layout_forceLinkStrength(link) { |
---|
389 | return 1; |
---|
390 | } |
---|