Permalink
Browse files

Better force-directed layout.

  • Loading branch information...
1 parent 7080602 commit a534c528f212995a0d2a3cf85ecc2455615780a0 @mbostock mbostock committed Feb 28, 2011
Showing with 142 additions and 124 deletions.
  1. +4 −2 _includes/force.js
  2. +16 −1 d3.js
  3. +44 −44 d3.min.js
  4. +3 −3 ex/force.css
  5. +4 −2 ex/force.js
  6. +71 −72 ex/force_layout.js
  7. BIN force.png
View
@@ -1,5 +1,6 @@
var w = 960,
- h = 500;
+ h = 500,
+ fill = d3.scale.category20();
var vis = d3.select("#chart")
.append("svg:svg")
@@ -28,7 +29,8 @@ d3.json("miserables.json", function(json) {
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
- .attr("r", 4.5);
+ .attr("r", 5)
+ .style("fill", function(d) { return fill(d.group); });
vis.attr("opacity", 0)
.transition()
View
17 d3.js
@@ -1,4 +1,4 @@
-(function(){d3 = {version: "1.2.1"}; // semver
+(function(){d3 = {version: "1.3.0"}; // semver
if (!Date.now) Date.now = function() {
return +new Date();
};
@@ -588,6 +588,21 @@ d3.interpolateRgb = function(a, b) {
};
};
+// interpolates HSL space, but outputs RGB string (for compatibility)
+d3.interpolateHsl = function(a, b) {
+ a = d3.hsl(a);
+ b = d3.hsl(b);
+ var h0 = a.h,
+ s0 = a.s,
+ l0 = a.l,
+ h1 = b.h - h0,
+ s1 = b.s - s0,
+ l1 = b.l - l0;
+ return function(t) {
+ return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t).toString();
+ };
+};
+
d3.interpolateArray = function(a, b) {
var x = [],
c = [],
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1,9 +1,9 @@
circle.node {
- fill: lightsteelblue;
- stroke: steelblue;
+ stroke: #fff;
stroke-width: 1.5px;
}
line.link {
- stroke: #333;
+ stroke: #999;
+ stroke-opacity: .6;
}
View
@@ -1,5 +1,6 @@
var w = 960,
- h = 500;
+ h = 500,
+ fill = d3.scale.category20();
var vis = d3.select("#chart")
.append("svg:svg")
@@ -28,7 +29,8 @@ d3.json("miserables.json", function(json) {
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
- .attr("r", 4.5);
+ .attr("r", 5)
+ .style("fill", function(d) { return fill(d.group); });
vis.attr("opacity", 0)
.transition()
View
@@ -3,85 +3,53 @@ function layout_force() {
var force = {},
event = d3.dispatch("tick"),
size = {x: 1, y: 1},
- alpha = .1,
- nodeDistance = 60,
- linkDistance = 30,
+ alpha = .5,
+ distance = 30,
interval,
nodes,
- links;
-
- // TODO
- // slow the interval as the graph stabilizes
- // allow the nodes to be dragged interactively
+ links,
+ distances;
function tick() {
- var n = nodes.length,
- m = links.length,
+ var n = distances.length,
i, // current index
- j, // current index
o, // current link
s, // current source
t, // current target
l, // current distance
- x,
- y;
+ x, // x-distance
+ y; // y-distance
- // repel nodes
+ // gauss-seidel relaxation
for (i = 0; i < n; ++i) {
- s = nodes[i];
- for (j = i + 1; j < n; ++j) {
- t = nodes[j];
- x = t.x - s.x;
- y = t.y - s.y;
- l = Math.sqrt(x * x + y * y);
- if (l < nodeDistance) {
- l = alpha * (l - nodeDistance) / l;
- x *= l;
- y *= l;
- if (s.fixed) {
- if (t.fixed) continue;
- t.x -= x;
- t.y -= y;
- } else if (t.fixed) {
- s.x += x;
- s.y += y;
- } else {
- s.x += x;
- s.y += y;
- t.x -= x;
- t.y -= y;
- }
- }
- }
- }
-
- // position constraint for links
- for (i = 0; i < m; ++i) {
- o = links[i];
+ o = distances[i];
s = o.source;
t = o.target;
x = t.x - s.x;
y = t.y - s.y;
- l = Math.sqrt(x * x + y * y);
- if (l <= 0) l = 0.01;
- l = alpha * (l - linkDistance) / l;
- x *= l;
- y *= l;
- if (s.fixed) {
- if (t.fixed) continue;
- t.x -= x;
- t.y -= y;
- } else if (t.fixed) {
- s.x += x;
- s.y += y;
- } else {
- s.x += x;
- s.y += y;
- t.x -= x;
- t.y -= y;
+ if (l = Math.sqrt(x * x + y * y)) {
+ l = alpha / (o.distance * o.distance) * (l - distance * o.distance) / l;
+ x *= l;
+ y *= l;
+ if (s.fixed) {
+ if (t.fixed) continue;
+ t.x -= x;
+ t.y -= y;
+ } else if (t.fixed) {
+ s.x += x;
+ s.y += y;
+ } else {
+ s.x += x;
+ s.y += y;
+ t.x -= x;
+ t.y -= y;
+ }
}
}
+ // simulated annealing, basically
+ if ((alpha *= .99) < 1e-6) force.stop();
+
event.tick.dispatch({type: "tick"});
}
@@ -108,44 +76,75 @@ function layout_force() {
return force;
};
- force.nodeDistance = function(d) {
- if (!arguments.length) return nodeDistance;
- nodeDistance = d;
- return force;
- };
-
- force.linkDistance = function(d) {
- if (!arguments.length) return linkDistance;
- linkDistance = d;
+ force.distance = function(d) {
+ if (!arguments.length) return distance;
+ distance = d;
return force;
};
force.start = function() {
var i,
+ j,
+ k,
n = nodes.length,
m = links.length,
w = size.x,
h = size.y,
o;
+
+ var paths = [];
for (i = 0; i < n; ++i) {
o = nodes[i];
o.x = o.x || Math.random() * w;
o.y = o.y || Math.random() * h;
o.fixed = 0;
+ paths[i] = [];
+ for (j = 0; j < n; ++j) {
+ paths[i][j] = Infinity;
+ }
+ paths[i][i] = 0;
}
+
for (i = 0; i < m; ++i) {
o = links[i];
+ paths[o.source][o.target] = 1;
+ paths[o.target][o.source] = 1;
o.source = nodes[o.source];
o.target = nodes[o.target];
}
+
+ // Floyd-Warshall
+ for (k = 0; k < n; ++k) {
+ for (i = 0; i < n; ++i) {
+ for (j = 0; j < n; ++j) {
+ paths[i][j] = Math.min(paths[i][j], paths[i][k] + paths[k][j]);
+ }
+ }
+ }
+
+ distances = [];
+ for (i = 0; i < n; ++i) {
+ for (j = i + 1; j < n; ++j) {
+ distances.push({
+ source: nodes[i],
+ target: nodes[j],
+ distance: paths[i][j] * paths[i][j]
+ });
+ }
+ }
+
+ distances.sort(function(a, b) {
+ return a.distance - b.distance;
+ });
+
if (interval) clearInterval(interval);
interval = setInterval(tick, 24);
return force;
};
force.resume = function() {
- if (interval) clearInterval(interval);
- interval = setInterval(tick, 24);
+ alpha = .1;
+ if (!interval) interval = setInterval(tick, 24);
return force;
};
View
BIN force.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit a534c52

Please sign in to comment.