Some simple, frequently used functions for creating a map with D3.js and Topojson.
This block also uses the libraries chroma and jeezy.
centerZoom
- Automatically centers and scales your map to its container, and returns your map's outer boundaries in case you want to draw them.drawOuterBoundary
- Uses the boundary returned from centerZoom
to draw a boundary around your whole map.drawPlaces
- Draws place names, if your topojson has places.drawSubunits
- Draws subunits.fillSubUnits
- Fills subunits according to specified limits and color scale.drawTip
- Draws and positions and tooltip based on the data of the selected subunit.drawLegend
- Draws a legend according to specified limits and color scale.See this tutorial for more on making maps with D3. I got most of the centerZoom
function from this block.
xxxxxxxxxx
<html>
<head>
<style>
body {
margin: 0;
font-family: "Helvetica Neue", sans-serif;
}
#map .subunit {
fill: #ddd;
stroke: #fff;
stroke-width: 1px;
}
#map .subunit.selected {
stroke: #000;
stroke-width: 2px;
}
#map .subunit-boundary {
fill: none;
stroke: #3a403d;
}
#map .place,
.place-label {
pointer-events: none;
}
#map .place-label {
font-size: .7em;
text-shadow: 0px 0px 2px #fff;
}
#tip {
position: absolute;
background: #fff;
opacity: .9;
padding: 10px;
}
#legend {
position: absolute;
left: 10px;
top: 10px;
}
#legend .legend-title {
margin-bottom: 4px;
font-weight: bold;
}
#legend .legend-item {
margin-bottom: 4px;
font-size: .8em;
}
#legend .legend-swatch,
#legend .legend-value {
display: inline-block;
vertical-align: bottom;
}
#legend .legend-swatch {
width: 16px;
height: 16px;
margin-right: 4px;
}
</style>
</head>
<body>
<div id="tip"></div>
<div id="legend">
<div class="legend-title">Legend</div>
</div>
<div id="map"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-moveto@0.0.3/build/d3-moveto.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.min.js"></script>
<script src="https://unpkg.com/jeezy@1.12.0/lib/jeezy.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.4/chroma.min.js"></script>
<script>
var match_property = "dist",
value_property = "value",
colors = ["#edf8fb", "#b2e2e2", "#66c2a4", "#2ca25f", "#006d2c"],
breakType = "e";
var width = window.innerWidth,
height = window.innerHeight;
var projection = d3.geoMercator();
var path = d3.geoPath()
.projection(projection)
.pointRadius(2);
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
d3.queue()
.defer(d3.json, "map.json")
.defer(d3.csv, "data.csv")
.await(ready);
function ready(error, geo, data) {
if (error) throw error;
centerZoom(geo, width, height);
drawSubUnits(geo);
drawPlaces(geo);
drawOuterBoundary(geo);
fillSubUnits(data);
drawTip(data);
drawLegend(data);
window.addEventListener("resize", resize);
function resize(){
var width = window.innerWidth,
height = window.innerHeight;
svg.attr("width", width).attr("height", height);
centerZoom(geo, width, height);
d3.selectAll("path").attr("d", path);
d3.selectAll("text").attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; });
}
}
function drawLegend(data, breakType, breakCount) {
var breakCount = colors.length;
var limits = chroma.limits(data.map(function(d) {
return +d[value_property];
}), breakType, breakCount);
colors.forEach(function(color, color_index) {
d3.select("#legend").append("div")
.attr("class", "legend-item")
.html("<div class='legend-swatch' style='background: " + color + "'></div><div class='legend-value'>" + jz.str.numberLakhs(limits[color_index].toFixed(1)) + " - " + jz.str.numberLakhs(limits[color_index + 1].toFixed(1)) + "</div>");
});
}
function fillSubUnits(data, breakType, breakCount) {
var breakCount = colors.length;
var limits = chroma.limits(data.map(function(d) {
return +d[value_property];
}), breakType, breakCount);
svg.selectAll(".subunit")
.style("fill", function(d, i) {
var match = matchData(d, data);
var return_color = [];
limits.forEach(function(limit, limit_index) {
if (+match[value_property] >= limit && +match[value_property] <= limits[limit_index + 1]) {
return_color.push(colors[limit_index]);
}
});
return return_color[0];
});
}
function drawTip(data) {
svg.selectAll(".subunit")
.on("mouseover", function(d) {
d3.select("#tip").style("display", "block");
var match = matchData(d, data);
// make the content
var keys = Object.keys(match);
var content = keys.map(function(key) {
return "<b>" + key + "</b>: " + match[key];
}).join("<br />");
d3.select("#tip").html(content);
d3.select(".subunit." + jz.str.toSlugCase(d.properties[match_property])).classed("selected", true).moveToFront();
d3.selectAll(".place").moveToFront();
d3.selectAll(".place-label").moveToFront();
})
.on("mousemove", function() {
// tip positioning
var coordinates = [0, 0];
coordinates = d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
d3.select("#tip")
.style("left", x + 20)
.style("top", y - 20);
})
.on("mouseout", function() {
d3.select("#tip").style("display", "none");
d3.selectAll(".subunit").classed("selected", false);
d3.select(".subunit-boundary").moveToFront();
});
}
function matchData(d, data) {
return data.filter(function(e) {
return d.properties[match_property] == e[match_property];
})[0];
}
function centerZoom(data, width, height) {
projection.fitSize([width, height], topojson.feature(data, data.objects.polygons));
}
function drawOuterBoundary(data) {
var boundary = topojson.mesh(data, data.objects.polygons, function(a, b) { return a === b; });
svg.append("path")
.datum(boundary)
.attr("d", path)
.attr("class", "subunit-boundary");
}
function drawPlaces(data) {
svg.append("path")
.datum(topojson.feature(data, data.objects.places))
.attr("d", path)
.attr("class", "place");
svg.selectAll(".place-label")
.data(topojson.feature(data, data.objects.places).features)
.enter().append("text")
.attr("class", "place-label")
.attr("transform", function(d) {
return "translate(" + projection(d.geometry.coordinates) + ")";
})
.attr("dy", ".35em")
.attr("x", function(d) {
return projection(d.geometry.coordinates)[0] <= width / 2 ? -6 : 6;
})
.style("text-anchor", function(d) {
return projection(d.geometry.coordinates)[0] <= width / 2 ? "end" : "start";
})
.text(function(d) {
return d.properties.name;
});
}
function drawSubUnits(data) {
svg.selectAll(".subunit")
.data(topojson.feature(data, data.objects.polygons).features)
.enter().append("path")
.attr("class", function(d) {
return "subunit " + jz.str.toSlugCase(d.properties[match_property]);
})
.attr("d", path);
}
</script>
</body>
</html>
https://d3js.org/d3.v4.min.js
https://unpkg.com/d3-moveto@0.0.3/build/d3-moveto.js
https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.min.js
https://unpkg.com/jeezy@1.12.0/lib/jeezy.min.js
https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.4/chroma.min.js