Skip to content

Instantly share code, notes, and snippets.

@scotthmurray
Last active August 29, 2015 13:57
Show Gist options
  • Save scotthmurray/9471486 to your computer and use it in GitHub Desktop.
Save scotthmurray/9471486 to your computer and use it in GitHub Desktop.
The United States of Bouncy Balls

Takes mbostock’s shape tweening example as a starting point, then devolves into ridiculousness. Try mousing over paths, and also clicking! Never use this for a real mapping project, except perhaps as inspiration for creating a legitimate outlines-to-Dorling transition.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The United States of Bouncy Balls</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<style type="text/css">
path {
stroke: white;
stroke-width: 0.5;
}
</style>
</head>
<body>
<script>
var width = 960;
var height = 500;
var dorlingView = false;
var projection = d3.geo.albersUsa()
.scale(1000);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("click", function() {
if (!dorlingView) {
//Make all into circles
d3.selectAll("path")
.classed("dropped", false)
.transition()
.duration(500)
.attr("transform", "translate(0,0)")
.attr("d", function(d) {
return d.endPath;
});
} else {
//Restore all original paths
d3.selectAll("path")
.classed("dropped", false)
.transition()
.duration(500)
.attr("transform", "translate(0,0)")
.attr("d", function(d) {
return d.startPath;
});
}
dorlingView = !dorlingView;
});
//Load JSON data
d3.json("us-states.json", function(json) {
//We're going to extract some coordinates
//and calculate some new path values,
//to be stored here:
var derivedPathData = [];
//Function to calc start/end paths for each polygon
var calcCoords = function(polygon) {
var startCoords = polygon.map(projection);
var endCoords = circle(startCoords);
var start = "M" + startCoords.join("L") + "Z";
var end = "M" + endCoords.join("L") + "Z";
derivedPathData.push({ startPath: start, endPath: end });
};
//Loop through once for each feature
for (var i = 0; i < json.features.length; i++) {
//Extract the geometry from this feature
var geometry = json.features[i].geometry;
//If this is a normal polygon
if (geometry.type == "Polygon") {
//Just calc the coordinate for its single polygon
calcCoords(geometry.coordinates[0]);
} else if (geometry.type == "MultiPolygon") {
//Otheriwse, calc the coordinates for its multiple polygons
for (var j = 0; j < geometry.coordinates.length; j++) {
calcCoords(geometry.coordinates[j][0]);
}
}
}
//Use this derived data to make new path elements
var paths = d3.select("svg")
.selectAll("path")
.data(derivedPathData)
.enter()
.append("path")
.attr("transform", "translate(0,0)")
.style("fill", "#ccc")
.attr("d", function(d) {
return d.startPath;
})
.on("mouseover", function(d) {
//If this path has already been 'dropped'
if (!d3.select(this).classed("dropped")) {
//Move to front and drop it down
d3.select(this)
.each(moveToFront)
.transition()
.duration(500)
.style("fill", "#f13")
.attr("d", function(d) {
return d.endPath;
})
.each("end", function() {
var bbox = d3.select(this).node().getBBox();
var newY = -bbox.y + (height - bbox.height);
d3.select(this)
.classed("dropped", true)
.transition()
.duration(750)
.ease("bounce")
.attr("transform", "translate(0," + newY + ")");
});
} else {
//Otherwise, restore its original position
d3.select(this)
.classed("dropped", false)
.transition()
.duration(500)
.style("fill", "#ccc")
.attr("transform", "translate(0,0)")
.attr("d", function(d) {
return d.startPath;
});
}
});
});
//Function to take coordinates and calculate a circular
//path using the same number of points. (Thanks, @mbostock!)
//In this example, we use the circle as our 'endPath'.
var circle = function(coordinates) {
var circle = [],
length = 0,
lengths = [length],
polygon = d3.geom.polygon(coordinates),
p0 = coordinates[0],
p1,
x,
y,
i = 0,
n = coordinates.length;
// Compute the distances of each coordinate.
while (++i < n) {
p1 = coordinates[i];
//console.log(p1);
x = p1[0] - p0[0];
y = p1[1] - p0[1];
lengths.push(length += Math.sqrt(x * x + y * y));
p0 = p1;
}
var area = polygon.area(),
radius = Math.sqrt(Math.abs(area) / Math.PI),
centroid = polygon.centroid(-1 / (6 * area)),
angleOffset = -Math.PI / 2, // TODO compute automatically
angle,
i = -1,
k = 2 * Math.PI / lengths[lengths.length - 1];
// Compute points along the circle’s circumference at equivalent distances.
while (++i < n) {
angle = angleOffset + lengths[i] * k;
circle.push([
centroid[0] + radius * Math.cos(angle),
centroid[1] + radius * Math.sin(angle)
]);
}
return circle;
};
//Move SVG elements to the end of their container,
//so they appear "on top".
var moveToFront = function() {
this.parentNode.appendChild(this);
}
</script>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment