This map uses D3.js v.5 and includes:
- Dynamic loading of data from Worldometer
- The use of topojson to load the shape file
- The addition of a legend with Susie Lu's legend
- Some degree of responsiveness
- Some css work for a smoother experience
This map uses D3.js v.5 and includes:
// Define size related variables | |
const dimension = d3.select(".visualisation") | |
.node() | |
.parentNode | |
.getBoundingClientRect(); | |
const margin = 90; | |
const width = dimension.width; | |
const height = 500; | |
const aspect = width / (height - margin); | |
const rotate = -9.9; | |
const zoom = d3.zoom() | |
.scaleExtent([1, 30]) | |
.translateExtent([[0,0],[width, height]]) | |
.on('zoom', function () { | |
d3.select('g').attr('transform', d3.event.transform) | |
}); | |
// Add the core svg block | |
const svg = d3.select(".visualisation") | |
.append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.attr("preserveAspectRatio", "xMinYMin meet") | |
.attr("viewBox", `0 0 ${width} ${height}`) | |
.call(zoom); | |
const globe = svg.append("g"); | |
const tooltip = d3.select('.visualisation').append('div') | |
.attr('class', 'hidden tooltip'); | |
const projection = d3.geoMercator() | |
.rotate([rotate,0]) | |
.scale(height / (1.4 * Math.PI)) | |
.translate([width / 2, (height - margin) / 1.2]); | |
const geoPath = d3.geoPath() | |
.projection(projection); | |
const colorScale = d3.scaleSqrt(["#ddd", "#777" ,"#000"]); | |
const map = {}; | |
Promise.all([ | |
d3.json('./world.topojson'), | |
d3.json('https://corona.lmao.ninja/v3/covid-19/countries'), | |
]).then(function([shapes, data]) { | |
var shapes = topojson.feature(shapes, "world"); | |
// save in a global context and remove antarctic. | |
map.features = shapes.features.filter((d) => d.properties.ISO_A3 !== "ATA"); | |
map.data = data; | |
map.metric = d3.select("#metrics").property("value"); | |
selectData(); | |
colorScale.domain([0, | |
d3.median(map.features, d => d.properties.dataPoint), | |
d3.max(map.features, d => d.properties.dataPoint)]); | |
draw(); | |
drawLegend(); | |
d3.select("#metrics").on("change",change); | |
}); | |
function selectData() { | |
map.features.forEach((d) => { | |
var entry1 = map.data.filter(t => t.countryInfo.iso3 == d.properties.ISO_A3)[0]; | |
if(entry1) { | |
d.properties.dataPoint = entry1[map.metric]; | |
d.properties.country = entry1.country; | |
} else { | |
d.properties.dataPoint = 0; | |
d.properties.country = "Unknown"; | |
} | |
}) | |
}; | |
function draw() { | |
globe.selectAll("path.country").remove(); | |
globe.selectAll("path.country") | |
.data(map.features) | |
.enter() | |
.append("path") | |
.attr("class","country") | |
.attr('d', geoPath) | |
.style("fill", d => colorScale(d.properties.dataPoint)) | |
.on('mousemove', function (d) { | |
tooltip.classed('hidden', false) | |
.html("<h6>" + d.properties.country + ": " + d.properties.dataPoint + "</h6>") | |
.attr('style', 'left:' + (d3.event.pageX + 15) + 'px; top:' + (d3.event.pageY + 20) + 'px'); | |
}) | |
.on('mouseout', function () { | |
tooltip.classed('hidden', true); | |
}); | |
}; | |
function drawLegend() { | |
svg.select(".legendLinear").remove(); | |
svg.append("g") | |
.attr("class", "legendLinear") | |
.attr("transform", "translate(10," + (height - margin) + ")"); | |
var shapeWidth = 40, | |
cellCount = 5, | |
shapePadding = 2, | |
legendTitle = map.metric.replace("PerOneMillion", "") + " per million population: "; | |
var legendLinear = d3.legendColor() | |
.title(legendTitle) | |
.shape("rect") | |
.shapeWidth(shapeWidth) | |
.cells(cellCount) | |
.labelFormat(d3.format(".3s")) | |
.orient('horizontal') | |
.shapePadding(shapePadding) | |
.scale(colorScale); | |
svg.select(".legendLinear") | |
.append("rect") | |
.attr("class", "legendBackground") | |
.attr("x", -5) | |
.attr("y", -22) | |
.attr("opacity", 0.9) | |
.attr("rx", 8) | |
.attr("ry", 8) | |
.attr("width", legendTitle.length*7.4) | |
.attr("height", margin); | |
svg.select(".legendLinear") | |
.call(legendLinear); | |
}; | |
function change() { | |
map.metric = d3.select("#metrics").property("value") | |
selectData(); | |
colorScale.domain([0, | |
d3.median(map.features, d => d.properties.dataPoint), | |
d3.max(map.features, d => d.properties.dataPoint)]); | |
draw(); | |
drawLegend(); | |
} | |
d3.select(window) | |
.on("resize", function() { | |
var targetWidth = d3.select(".visualisation").node().parentNode.getBoundingClientRect(); | |
svg.attr("width", targetWidth); | |
svg.attr("height", targetWidth / aspect); | |
svg.attr("viewBox", `0 0 ${width} ${height}`) | |
}); |
<!doctype html> | |
<html> | |
<head> | |
<title>Covid-19 world-wide totals per million population maps</title> | |
<style> | |
p, .legendTitle, .label { | |
font-family: sans-serif; | |
} | |
.country { | |
-webkit-transition: all 0.5s ease-out 0s; | |
-moz-transition: all 0.5s ease-out 0s; | |
-ms-transition: all 0.5s ease-out 0s; | |
-o-transition: all 0.5s ease-out 0s; | |
transition: all 0.5s ease-out 0s; | |
} | |
.country:hover { | |
fill: #D90429!important; | |
} | |
.hidden { | |
display: none; | |
} | |
div.tooltip { | |
background-color: #fff; | |
padding: 5px 5px; | |
text-shadow: #f5f5f5 0 1px 0; | |
font: 14pt sans-serif; | |
border: 1px solid; | |
border-color: grey; | |
border-radius: 8px; | |
opacity: 0.95; | |
position: absolute; | |
box-shadow: rgba(0, 0, 0, 0.3) 0 2px 5px; | |
-webkit-transition: all 0.5s ease-out 0s; | |
-moz-transition: all 0.5s ease-out 0s; | |
-ms-transition: all 0.5s ease-out 0s; | |
-o-transition: all 0.5s ease-out 0s; | |
transition: all 0.5s ease-out 0s; | |
} | |
div.tooltip h6 { | |
margin: 0; | |
color: #212121; | |
} | |
.legendTitle { | |
font-size: 12pt; | |
opacity: 0.8; | |
text-transform: capitalize; | |
} | |
.label { | |
font-size: 10pt; | |
opacity: 0.8; | |
} | |
.legendBackground { | |
fill: #ffffff!important; | |
} | |
</style> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
<script src="https://unpkg.com/topojson@3"></script> | |
<script src="https://unpkg.com/topojson-client@3"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.6/d3-legend.min.js"></script> | |
</head> | |
<body> | |
<p> | |
Choose your metric from the list: | |
<select id="metrics"> | |
<option value="casesPerOneMillion" selected="selected">Cases</option> | |
<option value="deathsPerOneMillion">Deaths</option> | |
<option value="testsPerOneMillion">Tests</option> | |
<option value="activePerOneMillion">Active</option> | |
<option value="recoveredPerOneMillion">Recovered</option> | |
<option value="criticalPerOneMillion">Critical</option> | |
</select> | |
</p> | |
<div class="visualisation"> </div> | |
<script src = "./covid_cases.js"></script> | |
</body> | |
</html> |