Published
Edited
Mar 14, 2022
Insert cell
Insert cell
euScatter = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

svg
.append("rect")
.attr("x", vaxScale(0))
.attr("y", gdpY.range()[1])
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom)
.attr("fill", "#f9f9f9");

svg
.append("text")
.attr("x", width / 2)
.attr("y", 20)
.attr("opacity", 0.8)
.attr("font-size", 22)
.attr("text-anchor", "middle")
.text(title);

svg
.append("foreignObject")
.attr("font-size", 14)
.attr("opacity", 0.9)
.attr("width", width - margin.right - margin.left)
.attr("height", 100)
.attr("x", vaxScale(0))
.attr("y", 30)
.text(explanation);

svg
.append("path")
.attr("fill", "#ccc")
.attr("fill-opacity", 0.15)
.attr("stroke", "none")
.attr("d", area(intervalData));

svg
.append("path")
.datum(correlationLineData)
.attr("stroke", "#333")
.attr("opacity", 0.4)
.attr("stroke-dasharray", "8,8")
.attr("stroke-width", 1.5)
.attr("d", corrLine);

svg
.append("rect")
.attr("x", 0)
.attr("y", height - margin.bottom)
.attr("width", width)
.attr("height", 200)
.attr("fill", "white");

svg.append("g").call(xGrid);
svg.append("g").call(yGrid);
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);

svg.append("path").datum();

svg
.selectAll(".flags")
.data(combinedData)
.enter()
.append("text")
.attr("font-size", (d) => populationScale(d.population))
.attr("opacity", 0.8)
.attr("x", (d) => vaxScale(d.vaxRate))
.attr("y", (d) => gdpY(d.gdp))
.text((d) => emojiMap.get(d.code));

return svg.node();
}
Insert cell
title = "Why are vaccination rates correlated with economic prosperity?"
Insert cell
explanation = "The chart shows EU countries scattered according to vaccination rates and median equalised net income. Flag size correlates to respective population sizes. The dotted line represents regression, with a grey area indicating the 95% confidence interval. One can notice a significant correlation between the observed variables and the clustering of countries along the 'iron curtain' lines."
Insert cell
Insert cell
correlationLineData = [
[vaxScale.domain()[0], predict(vaxScale.domain()[0])],
[vaxScale.domain()[1], predict(vaxScale.domain()[1])]
]
Insert cell
yAxis = (g) =>
g
.attr("transform", `translate(${margin.left},0)`)
.style("stroke-width", 1)
.call(d3.axisLeft(gdpY))
.call((g) => g.select(".domain").remove())
.call((g) => g.select(".tick:last-of-type text").append("tspan").text(" €"))
.call((g) =>
g
.append("text")
.attr("x", 5)
.attr("y", margin.top + 15)
.attr("fill", "#000")
.attr("opacity", 0.5)
.attr("font-size", 12)
// .attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Median equivalised net income")
)
Insert cell
xAxis = (g) =>
g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(vaxScale).ticks(width / 100))
.call((g) => g.select(".domain").remove())
.call((g) => g.select(".tick:last-of-type text").append("tspan").text("%"))
.call((g) =>
g
.append("text")
.attr("x", width - margin.right - 5)
.attr("y", -10)
.attr("fill", "#000")
.attr("opacity", 0.5)
.attr("font-size", 12)
// .attr("font-weight", "bold")
.attr("text-anchor", "end")
.text("Percentage of population vaccinated with at least one dose")
)
Insert cell
yGrid = (g) =>
g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(gdpY).tickSize(-width + margin.left + margin.right))
.call((g) => g.select(".domain").remove())
.call((g) => g.selectAll("text").remove())
.call((g) => g.selectAll("line").attr("stroke", "white"))
Insert cell
xGrid = (g) =>
g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(
d3.axisBottom(vaxScale).tickSize(-height + margin.top + margin.bottom)
)
.call((g) => g.select(".domain").remove())
.call((g) => g.selectAll("text").remove())
.call((g) => g.selectAll("line").attr("stroke", "white"))
Insert cell
Insert cell
Insert cell
Insert cell
predict = linearRegression(confidenceData)
Insert cell
interval = confidenceInterval(confidenceData, predict)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
emojiMap = new Map(countryEmoji.map((key) => [key.alpha3, key.flag]))
Insert cell
Insert cell
gdpDataMap = new Map(gdpData.map((key) => [key.code, key.gdp]))
Insert cell
countryNameMap = new Map(gdpData.map((key) => [key.code, key.country]))
Insert cell
countryEmojiMap = countryEmoji.map
Insert cell
format = d3.format(".1%")
Insert cell
gdpY = d3
.scaleLinear()
.domain([0, 40000])
.range([height - margin.bottom, margin.top])
Insert cell
austriaRed = "#ED2939"
Insert cell
inactiveOpacity = 0.2
Insert cell
inactiveColor = 'grey'
Insert cell
height = 800
Insert cell
wrangled = {
let retval = [];

euCountries
.map((d) => d["iso-3"])
.forEach((country) => {
const countryData = grouped.get(country);

const lastNonNull = countryData
.filter((x) => x["people_vaccinated"] != null)
.slice(-1)
.pop();

var countryName = country;
// if (countryName == "OWID_EUN") countryName = euAutName;

retval.push({
name: countryName,
value: lastNonNull["people_vaccinated"] / lastNonNull["population"],
population: lastNonNull["population"]
});
});

return retval.sort((a, b) => a.value - b.value);
}
Insert cell
findWithAttr = (array, attr, value) => {
for (var i = 0; i < array.length; i += 1) {
if (array[i][attr] === value) {
return i;
}
}
return -1;
}
Insert cell
vaxScale = d3
.scaleLinear()
.domain([0, 100])
.range([margin.left, width - margin.right])
Insert cell
euCountries = [
...d3.csvParse(
await FileAttachment("countryCodes@1.csv").text(),
d3.autoType
),
{ country: "EU", "iso-3": "OWID_EUN" }
]
Insert cell
gdpData = d3.csvParse(
await FileAttachment("gdp-per-capita-worldbank@2.csv").text(),
d3.autoType
)
Insert cell
grouped = d3.group(data, d => d.iso_code)
Insert cell
data = d3.csvParse(
await FileAttachment("owid-covid-data (1)@1.csv").text(),
d3.autoType
)
Insert cell
margin = ({ top: 100, right: 100, bottom: 30, left: 80 })
Insert cell
import { countryEmoji } from "@mattdzugan/making-maps-out-of-emojis";
Insert cell
d3 = require("d3@6", "d3-regression")
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more