I've been playing around with d3.js for the first time and have managed to create a basic chloropleth map.
In essence there are 3 things remaining that i'd like to do, but am not familiar enough with d3 or Javascript to do them:
Make legend horizontal and move it below the coast of Africa
Add thin black border to all of the countries.
Perhaps automatically crop out antartica? This can be done in post processing if not possible
Not sure if these tasks are impossible or easy as I have not worked much with d3.js and wasn't making much headway.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
@import url(//fonts.googleapis.com/css?family=Times+New+Roman);
.countries {
fill: none;
stroke: #fff;
stroke-linejoin: round;
}
.legendThreshold {
font-size: 12px;
font-family: sans-serif;
}
.caption {
fill: #000;
text-anchor: start;
font-size: 14px;
}
/* font-weight: bold;*/
.anchorNode {
font-family: "Times New Roman";
font-size: 2px;
}
.legendLinear text.label {
fill: '#fff'
font-family: "Times New Roman";
font-size: 2px;
}
.legendThreshold text.label {
fill: '#fff'
font-family: "Times New Roman";
font-size: 12px;
}
* {
font-family: "Times New Roman", Times, serif;
}
/* font-size: 100%;*/
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.24.0/d3-legend.js"></script>
<script>
// The svg
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
// Map and projection
var path = d3.geoPath();
var projection = d3.geoMercator()
.scale(width / 2 / Math.PI)
.translate([width / 2, height / 2])
var path = d3.geoPath()
.projection(projection);
// Data and color scale
var data = d3.map();
var colorScheme = d3.schemeReds[6];
colorScheme.unshift("#eee")
var colorScale = d3.scaleThreshold()
.domain([1, 3, 5, 10, 20, 100])
.range(colorScheme);
// Legend
var g = svg.append("g")
.attr("class", "legendThreshold")
.attr("transform", "translate(190,300)");
g.append("text")
.attr("class", "caption")
.attr("x", 0)
.attr("y", -6)
.style("font-size","12px")
.text("Subscribers");
var labels = ['0', '1-3', '3-5', '5-10', '10-20', '20-100', '> 100'];
var legend = d3.legendColor()
.labels(function (d) { return labels[d.i]; })
.shapePadding(0)
.scale(colorScale);
svg.select(".legendThreshold")
.call(legend);
// Load external data and boot
d3.queue()
.defer(d3.json, "http://enjalot.github.io/wwsd/data/world/world-110m.geojson")
.defer(d3.csv, "https://gist.githubusercontent.com/palewire/d2906de347a160f38bc0b7ca57721328/raw/3429696a8d51ae43867633ffe438128f8c396998/mooc-countries.csv", function(d) { data.set(d.code, +d.total); })
.await(ready);
function ready(error, topo) {
if (error) throw error;
// Draw the map
svg.append("g")
.attr("class", "countries")
.selectAll("path")
.data(topo.features)
.enter().append("path")
.attr("fill", function (d){
// Pull data for this country
d.total = data.get(d.id) || 0;
// Set the color
return colorScale(d.total);
})
.attr("d", path);
}
</script>
The first 2 are trivial. 1 is just a matter of changing the translate position of the legend and making it horizontal is just from looking at the d3-legend doc (https://d3-legend.susielu.com/#color-linear):
var g = svg.append("g")
.attr("class", "legendThreshold")
.attr("transform", "translate(440,440)");
...and jump ahead for next bit...
var legend = d3.legendColor()
.labels(function (d) { return labels[d.i]; })
.shapePadding(0)
.orient('horizontal')
.shapeWidth(40)
.scale(colorScale);
2 you change the stroke colour to black (#000) for .countries in the css
.countries {
fill: none;
stroke: #000;
stroke-linejoin: round;
}
3 is a bit more involved, but in the data (after a bit of console.logging
the returned object) we know the key for Antarctica is "ATA", so we can filter that out of the returned topo.features data
function ready(error, topo) {
if (error) throw error;
topo.features = topo.features.filter (function(d) { return d.id !== "ATA"; });