|
<html> |
|
<head> |
|
<script src="http://d3js.org/d3.v3.js"></script> |
|
</head> |
|
<body> |
|
<script> |
|
var w = 960, |
|
h = 960, |
|
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, 0]); |
|
|
|
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(.01) |
|
.charge(-2) |
|
.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; }) |
|
.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"]); |
|
}); |
|
} |
|
|
|
// how this works I have only a vague idea |
|
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> |