Public
Edited
Feb 16, 2023
1 fork
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// From researchers affiliated with the Climate Impact Lab via Human Climate Horizons
// https://www.dropbox.com/s/qpxg51eoxg4lizy/IMPACTS%20country-level.zip?dl=0
data_rcp45 = FileAttachment("unit_change_in_deathrate_geography_country_level_years_averaged_rcp45_SSP3_quantiles_mean@1.csv").csv({ typed: true })
Insert cell
// From researchers affiliated with the Climate Impact Lab via Human Climate Horizons
// https://www.dropbox.com/s/qpxg51eoxg4lizy/IMPACTS%20country-level.zip?dl=0
data_rcp85 = FileAttachment("unit_change_in_deathrate_geography_country_level_years_averaged_rcp85_SSP3_quantiles_mean@1.csv").csv({ typed: true })
Insert cell
// From the United Nations, Department of Economic and Social Affairs, Population Division (2022). World Population Prospects 2022, Online Edition.
// via https://population.un.org/wpp/Download/Standard/Population/
un = FileAttachment("WPP2022_Demographic_Indicators_Medium@1.csv").csv()
Insert cell
// GDP data through 2021 from the World Bank
// via https://data.worldbank.org/indicator/NY.GDP.MKTP.CD
worldbank = FileAttachment("API_NY@1.GDP.MKTP.CD_DS2_en_csv_v2_4770391.csv").text()
Insert cell
// TIMESERIES CRU (Observed) data by year and country
// from the World Bank's Climate Change Knowledge Portal
// via https://climateknowledgeportal.worldbank.org/download-data
// accessed Feb. 2, 2023
temps = FileAttachment("country-annual-temperatures@1.csv").csv()
Insert cell
// A lookup table for country names
countryLookup = FileAttachment("country-lookup@2.csv").csv()
Insert cell
// Countries to annotate on the chart
annotations = [
{
code: "AUS",
label: "Australia",
textAnchor: "start",
angle: 0,
dy: 5,
dx: 2,
},
{
code: "CAN",
label: "Canada",
textAnchor: "start",
angle: 0,
dy: 5,
dx: 2,
},
{
code: "CHN",
label: "China",
textAnchor: "start",
angle: 45,
dy: 7,
dx: 2
},
{
code: "FIN",
label: "Finland",
textAnchor: "middle",
angle: 270,
dy: -2,
dx: 0,
},
{
code: "GRL",
label: "Greenland",
textAnchor: "middle",
angle: 270,
dy: -2,
dx: 0,
},
{
code: "IND",
label: "India",
textAnchor: "start",
angle: 0,
dy: 5,
dx: 2,
},
{
code: "NER",
label: "Niger",
textAnchor: "middle",
angle: 90,
dy: 11,
dx: 0
},
{
code: "USA",
label: "United States",
textAnchor: "start",
angle: 45,
dy: 7,
dx: 2
},
]
.map(d => Object.assign(d, data.find(f => f.iso === d.code)))
Insert cell
Insert cell
// Year to use for GDP per capita
year = "2020"
Insert cell
unParsed = un
.filter(d => d.Time === year && d.ISO3_code)
.map(d => {
return {
country: d.Location,
iso: d.ISO3_code,
year,
pop: +d.TPopulation1Jan * 1e3
}
})
Insert cell
// Parse the World Bank data, transforming it from wide to narrow
worldbankParsed = d3.csvParse(worldbank
.split("\n")
.filter((d, i) => i > 3)
.join("\n"))
.map(d => {
return {
country: d["Country Name"],
iso: d["Country Code"],
year,
gdp: +d[year]
}
})
Insert cell
// Parse the temperature data, calculating the 10-year average from 2011-2020
tempsParsed = d3.groups(clone(temps), d => d.country)
.map(([country, entries]) => {
const last10 = entries.filter(d => +d.year <= +year && +d.year >= (+year - 9));
const temp_c = d3.mean(last10, d => +d.temp);
const temp_f = ((9 / 5) * temp_c) + 32;
return {
country,
iso: entries[0].iso3,
temp_c,
temp_f
}
})
Insert cell
// Combine all the data, using the projection for 2080-2099
data = {
return clone(data_rcp45)
.map(d => {
// Match with other datasets
const worldbankMatch = worldbankParsed.find(d0 => d.ISO_code === d0.iso)
const unMatch = unParsed.find(d0 => d.ISO_code === d0.iso);
const tempsMatch = tempsParsed.find(d0 => d.ISO_code === d0.iso)

// Remove countries for which there is no matching data
// This will yield 201 countries and territories
if (!worldbankMatch || !unMatch || !tempsMatch){
return false;
}

const o = {};
o.country = countryLookup.find(d0 => d0.iso === d.ISO_code).country;
o.iso = d.ISO_code;
o.rcp45 = d.years_2080_2099;
o.rcp85 = data_rcp85.find(d0 => d0.ISO_code === d.ISO_code).years_2080_2099;

o.temp_f = tempsMatch.temp_f;
o.temp_c = tempsMatch.temp_c;
const gdp = worldbankMatch.gdp;
const pop = unMatch.pop;
o.gdp_percap = gdp / pop;

return o;
})
.filter(d => d.rcp45 && d.gdp_percap && d.temp_f)
.sort((a, b) => d3.descending(a.gdp_percap, b.gdp_percap))
}
Insert cell
Insert cell
xAxis = g => {
const tickValues = scenario === "rcp45" ? [-300, -150, 0, 150] : [-400, -200, 0, 200, 400];
const axis = g.append("g")
.attr("transform", `translate(0, ${chartheight})`)
.call(
d3.axisBottom(x)
.tickValues(tickValues)
.tickSizeOuter(0)
.tickSizeInner(10)
);
axis.select(".domain").remove()
axis.selectAll("text")
.attr("font-size", 12)
.attr("font-family", franklinLight);
axis.selectAll("line")
.attr("stroke-width", 0.5)
.attr("shape-rendering", "crispEdges")
.attr("stroke", d => d ? "#c2c2c2" : "#000");

const grid = g.append("g")
.call(
d3.axisBottom(x)
.tickValues(tickValues)
.tickSize(chartheight)
)

grid.select(".domain").remove();
grid.selectAll("text").remove();
grid.selectAll("line")
.attr("stroke-width", 0.5)
.attr("shape-rendering", "crispEdges")
.attr("stroke", d => d ? "#c2c2c2" : "#000");

g.append("text")
.attr("transform", `translate(${x(0)}, ${chartheight})`)
.attr("text-anchor", "middle")
.attr("dy", margin.bottom - 24)
.html("<tspan y=-4>⟵ </tspan><tspan dy=4>Change in mortality linked to temperature</tspan><tspan dy=-4> ⟶</tspan>");

g.append("text")
.attr("transform", `translate(${x(0)}, ${chartheight})`)
.attr("text-anchor", "middle")
.attr("dy", margin.bottom - 4)
.attr("font-size", 14)
.text("Annual deaths per 100,000 people")
return g;
}
Insert cell
yAxis = g => {
const tickValues = unit === "temp_f" ? [0, 20, 40, 60, 80] : [-20, -10, 0, 10, 20];
const axis = g.append("g")
.call(
d3.axisLeft(y)
.tickFormat(d => `${d}°`)
.tickValues(tickValues)
.tickSizeOuter(0)
.tickSizeInner(10)
);

axis.select(".domain").remove()
const ticks = axis.selectAll(".tick");
ticks.select("line")
.attr("stroke-width", 0.5)
.attr("shape-rendering", "crispEdges")
.attr("stroke", d => d ? "#c2c2c2" : "#000");
ticks.select("text")
.attr("font-size", 12)
.attr("font-family", franklinLight);
const grid = g.append("g")
.attr("transform", `translate(${chartwidth})`)
.call(
d3.axisLeft(y)
.tickValues(tickValues)
.tickSize(chartwidth)
);
grid.select(".domain").remove();
const gridTicks = grid.selectAll(".tick");
gridTicks.select("text").remove();
gridTicks.select("line")
.attr("stroke-width", 0.5)
.attr("shape-rendering", "crispEdges")
.attr("stroke", d => d ? "#c2c2c2" : "#000");
g.append("text")
.attr("transform", `translate(0, 16)`)
.attr("dx", -margin.left)
.attr("dy", -margin.top)
.html(`<tspan dy=-28>↑ </tspan><tspan dy=2>Average temperature (${unit.replace("temp_", "").toUpperCase()})</tspan>`)

}
Insert cell
annotate = g => {
const annos = g.selectAll(".anno")
.data(annotations)
.enter().append("g")
.attr("class", "anno")
.attr("transform", d => `translate(${[x(d[scenario]), y(d[unit])]})`);
annos.append("polyline")
.attr("points", d => {
const l = useR ? r(d.gdp_percap) : 4;
const start = geometric.pointTranslate([0, 0], d.angle, l);
const end = geometric.pointTranslate(start, d.angle, 5);
return [start, end];
})
.attr("stroke", "#222");
annos.append("text")
.attr("transform", d => {
const l = useR ? r(d.gdp_percap) : 4;
return `translate(${geometric.pointTranslate([0, 0], d.angle, l + 5)})`
})
.attr("text-anchor", d => d.textAnchor)
.attr("font-size", 14)
.attr("dy", d => d.dy)
.attr("dx", d => d.dx)
.attr("stroke", "white")
.attr("paint-order", "stroke fill")
.attr("stroke-width", 4)
.attr("stroke-linejoin", "round")
.attr("stroke-opacity", 0.7)
.attr("pointer-events", "none")
.text(d => d.label);
}
Insert cell
Insert cell
x = d3.scaleLinear(d3.extent(data, d => d[scenario]), [ 0, chartwidth ]).nice()
Insert cell
y = d3.scaleLinear(d3.extent(data, d => d[unit]), [ chartheight, 0] ).nice()
Insert cell
color = d3.scaleThreshold(
[-400, -200, -150, -100, -50, -10, 0, 10, 50, 100, 150, 200, 400],
[
"#007a00", "#1c8d25", "#34a241", "#4db55c", "#69c978", "#88dc96", "#aaeeb4",
"#dcd0ff", "#c8b2ff", "#b294ff", "#9a76ff", "#7e58ff", "#5936ff", "#0000ff"
]
);
Insert cell
r = d3.scaleSqrt(d3.extent(data, d => d.gdp_percap), [3, 20])
Insert cell
Insert cell
margin = ({left: unit === "temp_f" ? 31 : 35, right: 10, top: 24, bottom: 66})
Insert cell
size = Math.min(width, 640)
Insert cell
chartwidth = size - margin.left - margin.right
Insert cell
chartheight = size * 1.2 - margin.top - margin.bottom
Insert cell
Insert cell
Insert cell
clone = data => JSON.parse(JSON.stringify(data))
Insert cell
Insert cell
import {
franklinLight,
franklinBold
} from "1dec0e3505bd3624"
Insert cell
import { toc } from "@harrystevens/toc"
Insert cell
geometric = require("geometric@2")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more