Transform a collection of geojson features into squares and back again.
This block uses several functions from d3-geo and MapBox's polylabel algorithm to intelligently position the labels.
For more map tweening, see this block and this block.
xxxxxxxxxx
<html>
<head>
<style>
body {
margin: 0;
font-family: "Helvetica Neue", sans-serif;
}
.state-path {
fill: #ccc;
stroke: #fff;
stroke-width: 1px;
}
.state-label {
font-size: .5em;
}
</style>
</head>
<body step="0">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="polylabel.js"></script>
<script>
var width = window.innerWidth,
height = window.innerHeight,
duration = 800;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("us.json", function(map) {
var projection = centerZoom(map);
var polygons = [];
map.features.forEach(function(feature){
polygons.push({id: feature.properties.state, geom: feature.geometry})
});
var init = parse(polygons, projection).sort(function(a, b){
return b.area - a.area;
});
init.forEach(function(d){
var path = drawPath(d).attr("d", d.d0)
drawLabels(d, 0, 0);
});
d3.interval(function(){
var steps = stepUpdate();
stepChange(steps[0], steps[1], init);
}, duration * 2);
});
// This function "centers" and "zooms" a map by setting its projection's scale and translate according to its outer boundary
function centerZoom(data){
// create a first guess for the projection
var scale = 1;
var offset = [width / 2, height / 2];
var projection = d3.geoAlbersUsa().scale(scale).translate(offset);
// get bounds
var bounds = d3.geoPath().projection(projection).bounds(data);
// calculate the scale and offset
var hscale = scale * width / (bounds[1][0] - bounds[0][0]);
var vscale = scale * height / (bounds[1][1] - bounds[0][1]);
var scale = (hscale < vscale) ? hscale : vscale;
var offset = [width - (bounds[0][0] + bounds[1][0]) / 2, height - (bounds[0][1] + bounds[1][1]) / 2];
// new projection
projection = d3.geoAlbersUsa()
.scale(scale)
.translate(offset);
return projection;
}
function drawLabels(obj,oldStep,newStep){
var pOld = polylabel([obj["coordinates" + oldStep]], 1);
var pNew = polylabel([obj["coordinates" + newStep]], 1);
svg.append("text")
.attr("class", "state state-label")
.attr("x", pOld[0])
.attr("y", pOld[1])
.attr("dy", 5)
.attr("text-anchor", "middle")
.text(obj.id)
.transition().duration(duration)
.attr("x", pNew[0])
.attr("y", pNew[1])
}
function drawPath(obj){
var path = svg.append("path")
.attr("class", "state state-path")
.attr("id", obj.id)
return path;
}
function parse(polygons, projection) {
var arr = [];
polygons.forEach(function(state){
var obj = {};
obj.id = state.id;
obj.coordinates0 = state.geom.coordinates[0].map(projection);
obj.coordinates1 = square(obj.coordinates0)[0];
obj.d0 = "M" + obj.coordinates0.join("L") + "Z";
obj.d1 = "M" + obj.coordinates1.join("L") + "Z";
obj.area = square(obj.coordinates0)[1];
arr.push(obj);
});
return arr;
}
function square(coordinates){
var area = d3.polygonArea(coordinates);
area < 0 ? area = area * -1 : area = area;
var r = Math.sqrt(area) / 2.5;
var centroid = d3.polygonCentroid(coordinates);
var x = centroid[0];
var y = centroid[1];
var len = coordinates.length;
var square = squareCoords(x, y, r, len);
return [square, area];
}
function squareCoords(x, y, r, len){
var square = [];
var topLf = [x - r, y - r];
var topRt = [x + r, y - r];
var botRt = [x + r, y + r];
var botLf = [x - r, y + r];
for (var i = 0; i < len / 4; i++){
square.push(botRt);
}
for (var i = 0; i < len / 4; i++){
square.push(botLf);
}
for (var i = 0; i < len / 4; i++){
square.push(topLf);
}
for (var i = 0; i < len / 4; i++){
square.push(topRt);
}
return square;
}
function stepChange(oldStep, newStep, obj){
d3.selectAll(".state").remove();
obj.forEach( function(d){
transitionPath(drawPath(d), d["d" + oldStep], d["d" + newStep], duration);
drawLabels(d, oldStep, newStep);
} );
}
function stepUpdate(){
var currStep = +d3.select("body").attr("step"), newStep;
var newStep = currStep == 0 ? 1 : 0;
d3.select("body").attr("step", newStep);
return [currStep, newStep];
}
function transitionPath(path, d0, d1, duration){
path
.attr("d", d0)
.transition().duration(duration)
.attr("d", d1);
}
</script>
</body>
</html>
https://d3js.org/d3.v4.min.js