Published
Edited
Oct 18, 2021
1 fork
Insert cell
Insert cell
chartTitle = titleCard({
title: "Imported deforestation and GHG emissions",
subtitle:
"Though deforestation associated with EU imports fell by around 40% between 2005 and 2017, the EU was responsible for 16% of deforestation linked to international trade in 2017.",
extra: md`([Pendrill et al, 2020](https://zenodo.org/record/4250532#.YHVY3OhKhdg)).`
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
csv = d3.dsvFormat(";").parse(await FileAttachment("Table_Figure1.csv").text(), d3.autoType)
Insert cell
data = Object.assign(csv.map(d => ({date: d3.utcParse("%Y")(d.Year), key: d["Importing country"], value: d[indicator]})), { y: indicator === "DEF_HA" ? "Deforestation (ha)" : "GHG emissions (tonnes)"})
Insert cell
margin = ({ top: 50, right: 30, bottom: 30, left: 40 })
Insert cell
height = width * 0.6
Insert cell
keys = d3.rollups(data, v => d3.sum(v, d => d.value), d => d.key)
.sort(([,a], [,b]) => b - a)
.map(([key]) => key)
Insert cell
values = d3.rollup(data, ([d]) => d.value, d => d.key, d => +d.date)
Insert cell
dates = [...new Set(data.map(d => d.date.getTime()))].map(d3.utcDay).sort((a, b) => a - b)
Insert cell
series = keys.map(k => ({
key: k,
values: dates.map(d => values.get(k).get(+d))
}))
Insert cell
x = d3.scaleUtc()
.domain(d3.extent(dates))
.range([margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear()
.domain(d3.extent(series.flatMap(d => d3.extent(d.values)))).nice()
.range([height - margin.bottom, margin.top])
Insert cell
line = d3.line()
.defined(d => !isNaN(d))
.x((d, i) => x(dates[i]))
.y(d => y(d))
Insert cell
xAxisTicks = Math.min(dates.length, width / 50)
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.style("font", "12px var(--trase-mono)")
.style("color", traseColours.get("moss"))
.call(
d3.axisBottom(x)
.ticks(xAxisTicks)
.tickFormat(formatX)
.tickSizeOuter(0)
)
.call(g => g.selectAll(".tick line").remove())
.call(g => g.select(".domain").attr("stroke-width", 1.5))
Insert cell
yAxis = g =>
g
.attr("transform", `translate(${margin.left},0)`)
.style("font", "12px var(--trase-mono)")
.style("color", traseColours.get("moss"))
.call(d3.axisLeft(y).ticks(Math.max(height / 100, 2), formatY))
.call(g => g.selectAll(".tick line").remove())
.call(g => g.select(".domain").remove())
.call(g => {
if(data.y) {
g.select(".tick:last-of-type text").clone()
.attr("x", 0)
.attr("text-anchor", "start")
.text(data.y.toUpperCase())
}
})
Insert cell
grid = g =>
g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).ticks(height / 100, formatY))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick text").remove())
.call(g =>
g
.selectAll(".tick line")
.attr("stroke", traseColours.get("lightgrey"))
.attr("x1", (d, i, g) =>
i === g.length - 1 && data.y
? getLabelLength(data.y, "font: 12px var(--trase-mono)") + 5
: 0
)
.attr("x2", width - margin.left - margin.right)
)
Insert cell
colour = d3.scaleOrdinal()
.domain(keys)
.range(traseCategory1)
Insert cell
formatX = d3.utcFormat("%Y")
Insert cell
formatY = d3.format("~s")
Insert cell
// adapted from https://observablehq.com/@d3/multi-line-chart
function hover(g, svg) {
// create dot
const dot = svg.append("g").attr("display", "none");
dot
.append("circle")
.attr("r", 6)
.attr("stroke-width", 3)
.attr("fill", "white");

// add tooltip
const tooltip = new Tooltip();
svg
.style("-webkit-tap-highlight-color", "transparent")
.style("cursor", "pointer")
.on("touchstart", event => event.preventDefault())
.on("pointerenter pointermove", enter)
.on("pointerout", exit);
svg.append(() => tooltip.node);

function enter(event) {
event.preventDefault();
const pointer = d3.pointer(event, this);
const xm = x.invert(pointer[0]);
const ym = y.invert(pointer[1]);
const i1 = d3.bisectLeft(dates, xm);
const i0 = i1 - 1;
const i = xm - dates[i0] > dates[i1] - xm ? i1 : i0;
const s = d3.least(series, d => Math.abs(d.values[i] - ym));
let xp = x(dates[i]);
let yp = y(s.values[i]);
g.attr("stroke", d =>
d === s ? colour(d.key) : traseColours.get("lightgrey")
)
.filter(d => d === s)
.raise();
dot
.attr("display", null)
.attr("stroke", colour(s.key))
.attr("transform", `translate(${xp},${yp})`);
tooltip.show(
s.key,
tooltipKeyValue(
formatTooltipKey(dates[i]),
formatTooltipValue(s.values[i])
)
);
xp += 10;
if (xp + tooltip.width > width) xp -= tooltip.width + 20;
if (yp + tooltip.height > height - margin.bottom)
yp = height - margin.bottom - tooltip.height - 10;
tooltip.position(xp, yp);
}

function exit() {
g.style("mix-blend-mode", "multiply").attr("stroke", ({ key }) =>
colour(key)
);
dot.attr("display", "none");
tooltip.hide();
}
}
Insert cell
formatTooltipKey = d3.utcFormat("%Y")
Insert cell
formatTooltipValue = d3.format("0.2s")
Insert cell
import { traseColours, traseCategory1, fonts } from "@trase/visual-id@1366"
Insert cell
import { select } from "@jashkenas/inputs"
Insert cell
import { Tooltip, tooltipKeyValue } from "@trase/tooltip@440"
Insert cell
import { swatches, getLabelLength } from "@trase/legends@376"
Insert cell
import { titleCard } from "@trase/title-card@646"
Insert cell
d3 = require("d3@7")
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