Skip to content

Instantly share code, notes, and snippets.

@dhoboy
Last active October 26, 2015 02:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dhoboy/f3c154f3b949e68ecfd5 to your computer and use it in GitHub Desktop.
Save dhoboy/f3c154f3b949e68ecfd5 to your computer and use it in GitHub Desktop.
Force layout #2

Example #2 from learning the d3 Force Layout. I loosed the charge from Force Layout #1, but I still don't think the outlier bubbles are sufficiently separate from the pack here.

<html>
<head>
<script src="http://d3js.org/d3.v3.js"></script>
</head>
<body>
<script>
var w = 960,
h = 960,
padding = 3, // svg boundry padding... seems to be pointless
format = d3.format(".2%"),
maxRadius = 20;
// pull in csv data
d3.csv("/d/283008a26997e97e0490/Student_Weight_Status_Category_Reporting_Results__Beginning_2010.csv", function(error, rows) {
var dataset = [], counter = 0;
rows.forEach(function(entry){
if ((entry["GRADE LEVEL"] == "DISTRICT TOTAL") && (entry["PCT OVERWEIGHT OR OBESE"] != "")) {
var pctOverObese = /[\d]+\.[\d]/.exec(entry["PCT OVERWEIGHT OR OBESE"]);
if (pctOverObese != null) {
pctOverObese = pctOverObese[0] / 100; // form percent mathematically
entry["pctOverObese"] = pctOverObese;
entry["pos"] = counter;
counter += 1;
dataset.push(entry);
}
}
});
// desired data is loaded. form scales based on it.
var xScale = d3.scale.linear()
.domain([0, dataset.length])
.range([w/4, (3*w)/4]);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d){ return d["pctOverObese"]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d){ return d["pctOverObese"]; })])
.range([2, 20]);
var colorScale = d3.scale.quantize()
.domain([0, d3.max(dataset, function(d){ return d["pctOverObese"]; })])
.range(["#006837", "#1a9850", "#66bd63", "#fdae61",
"#f46d43", "#d73027", "#a50026"]);
// set node positions
dataset.forEach(function(node){
node["x"] = xScale(node["pos"]);
node["y"] = yScale(node["pctOverObese"]);
});
// set up force layout
var force = d3.layout.force()
.nodes(dataset)
.size([w, h])
.gravity(.02) // setting gravity too high was throwing my outliers back into the middle of the screen, which I did not want
.charge(-3)
.on("tick", tick)
.start();
// append the svg
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// create and draw nodes
var node = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("r", 0) // so the tween doesn't freak out
.style("fill", function(d) { return colorScale(d["pctOverObese"]); })
.style("stroke", "rgba(255,255,255,.5)");
node.transition()
.duration(750)
.delay(function(d, i) { return i * 2; }) // doesn't seem to matter what the delay is
.attrTween("r", function(d) {
var i = d3.interpolate(0, rScale(d["pctOverObese"]));
return function(t) { return d["r"] = i(t); };
});
function tick(e) {
node
.each(collide(.5)) // the collide parameter remains a mystery to me
.attr("cx", function(d){ return d["x"]; })
.attr("cy", function(d){ return d["y"]; })
.append("title")
.text(function(d) {
//return d["AREA NAME"] + ": " + format(d["pctOverObese"]);
return d["x"] + "," + d["y"];
});
}
function collide(alpha) {
var quadtree = d3.geom.quadtree(dataset);
return function(d) {
var r = d["r"] + maxRadius,
nx1 = d["x"] - r,
nx2 = d["x"] + r,
ny1 = d["y"] - r,
ny2 = d["y"] + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d["x"] - quad.point.x,
y = d["y"] - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d["r"] + quad.point.radius;
if (l < r) {
l = (l - r) / l * alpha;
d["x"] -= x *= l;
d["y"] -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
});
d3.select(self.frameElement).style("height", h + "px");
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment