Published
Edited
Oct 18, 2021
1 star
Insert cell
Insert cell
viewof commodity = select({
options: ["Soy", "Beef", "Palm oil"],
description: "Select a commodity"
})
Insert cell
chartTitle1 = titleCard({
title: "Soy deforestation risk highly concentrated",
subtitle:
"Concentration of tropical deforestation and conversion embedded in soy (2016)"
})
Insert cell
chartTitle2 = titleCard({
title: "Beef deforestation risk highly concentrated",
subtitle:
"Concentration of tropical deforestation and conversion embedded in beef (2017)"
})
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
viewof indicator = select({
options: [
{ value: "SUM(volume)", label: "Volume (tonnes)" },
{ value: "SUM(commodity_deforestation)", label: "Commodity deforestation risk (ha)" },
{ value: "SUM(commodity_emissions)", label: "GHG emissions from commodity deforestation risk (tonnes)" },
{
value: "(SUM(COALESCE(commodity_deforestation, 0)) / SUM(volume)) * 1000",
label: "Relative deforestation risk (ha / 1000 tonnes)"
}
],
description: "Select an indicator"
})
Insert cell
chart = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.call((svg) =>
svg.append("defs").html(`<style>
@import url("https://fonts.googleapis.com/css2?family=DM+Sans&family=Space+Mono&display=swap");
:root {
--trase-mono: "Space Mono", --mono_fonts;
--trase-sans-serif: "DM Sans", --sans-serif;
}
</style>`)
);

// graticule
svg
.append("g")
.datum(d3.geoGraticule10())
.append("path")
.attr("stroke", "#e3ebe8")
.attr("stroke-width", 0.5)
.attr("fill", "none")
.attr("d", path);

// countries
svg
.append("g")
.selectAll("path")
.data(topojson.feature(world, world.objects.land).features)
.join("path")
.attr("fill", "#e3ebe8")
.attr("d", path);

// regions
svg
.append("g")
.selectAll("path")
.data(boundaries)
.join("path")
.attr("stroke", (d) => (data.has(d.id) ? "white" : "none"))
.attr("stroke-width", 0.4)
.attr("fill", (d) => colour(data.get(d.id)) || "none")
.attr("d", path);

// biomes stroke and text
if (!palm) {
svg
.append("g")
.datum(
topojson.mesh(
biomes_ecoregions,
biomes_ecoregions.objects.biomes_ecoregions
)
)
.append("path")
.attr("stroke", "#748f7e")
.attr("stroke-width", 0.75)
.attr("fill", "none")
.attr("d", path);

svg
.append("g")
.selectAll("text")
.data(
topojson.feature(
biomes_ecoregions,
biomes_ecoregions.objects.biomes_ecoregions
).features
)
.join("text")
.style("font", "bold 14px var(--trase-sans-serif), sans-serif")
.style("letter-spacing", "0.03em")
.style("text-anchor", "middle")
.attr("transform", (d) => {
let [x, y] = path.centroid(d);
if (d.properties.name === "Atlantic Forest") (x -= 50), (y += 60);
return `translate(${x}, ${y})`;
})
.text((d) => d.properties.name);
}

// paraguay, just checking
svg
.append("g")
.selectAll("path")
.data(boundaries.filter((d) => d.id.includes("PY")))
.join("path")
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1)
.attr("d", path);

// legend
svg
.append("g")
.attr("transform", `translate(20, ${height - 60})`)
.append(() =>
legend({
colour: colour,
title: indicatorLabel,
tickFormat: d3.format("~s"),
titleFont: "bold 14px var(--trase-sans-serif), sans-serif",
upperCase: false,
width: 380,
marginRight: 120
})
);

return svg.node();
}
Insert cell
Insert cell
data = splitgraph`
SELECT region_production_1_trase_id AS id, ${indicator} AS value
FROM "trase/supply-chains"."supply-chains"
WHERE scale = 'SUBNATIONAL'
AND economic_bloc = 'EUROPEAN UNION'
AND commodity = '${commodity.toUpperCase()}'
AND year = ${year}
GROUP BY region_production_1_trase_id
`.then(res =>
d3.rollup(
res.filter(d => d.value),
v => d3.sum(v, d => d.value),
d => microregions.get(d.id) || d.id
)
)
Insert cell
years = new Map([["Soy", 2016], ["Beef", 2017], ["Palm oil", 2015]])
Insert cell
year = years.get(commodity)
Insert cell
palm = commodity === "Palm oil"
Insert cell
height = palm ? 500 : 800
Insert cell
projection = palm ?
d3.geoOrthographic()
.rotate([242, 0])
.fitExtent([[10, 10], [width - 10, height - 10]], outline) :
d3.geoOrthographic()
.rotate([56, 4])
.fitExtent([[10, 20], [width - 10, height - 130]], outline)
Insert cell
colourScheme = indicator === "SUM(volume)" ?
d3.interpolateRgbBasis(trasePurples) :
d3.interpolateRgbBasis(traseOranges)
Insert cell
colour = d3.scaleSequential(d3.extent(data.values()), colourScheme)
Insert cell
path = d3.geoPath(projection)
Insert cell
world = d3.json("https://unpkg.com/visionscarto-world-atlas/world/50m.json")
Insert cell
biomes_ecoregions = FileAttachment("biomes_ecoregions_filtered_no_caatinga.json").json()
Insert cell
latam = FileAttachment("trase-atlas-latam-micro.json").json()
Insert cell
microregions = new Map(d3.dsvFormat(";").parse(await FileAttachment("MICROREGION_BRA.csv").text())
.map(d => ["BR-" + d.GEOCODE, "BR-" + d.GEOCODE_MICRO]))
Insert cell
indonesia = d3.json("https://unpkg.com/@bayre/trase-atlas@1.1/files/indonesia.json")
Insert cell
boundaries = commodity === "Palm oil" ?
topojson.feature(indonesia, indonesia.objects.level3).features.map(d => ({...d, id: "ID-" + d.id})) :
topojson.feature(latam, latam.objects.jurisdictions).features
Insert cell
outline = commodity === "Palm oil" ?
topojson.mesh(indonesia) :
topojson.mesh(biomes_ecoregions)
Insert cell
indicatorLabel = new Map([
{ value: "SUM(volume)", label: "Volume (tonnes)" },
{ value: "SUM(commodity_deforestation)", label: "Commodity deforestation risk (ha)" },
{ value: "SUM(commodity_emissions)", label: "GHG emissions from commodity deforestation risk (tonnes)" },
{
value: "(SUM(COALESCE(commodity_deforestation, 0)) / SUM(volume)) * 1000",
label: "Relative deforestation risk (ha / 1000 tonnes)"
}
].map(d => [d.value, d.label])).get(indicator)
Insert cell
import { select } from "@jashkenas/inputs"
Insert cell
import { splitgraph } from "@trase/splitgraph"
Insert cell
import { legend } from "@trase/legends@376"
Insert cell
import { titleCard } from "@trase/title-card@646"
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@6")
Insert cell
// new (experimental) colour schemes
traseOranges = ["#fff1c3", "#fdd396", "#fab270", "#f98661", "#f55c57", "#e8474b", "#d63940", "#ba383d", "#9c3a3d"]
Insert cell
// trasePurples = ["#f8f4e9", "#ecd0eb", "#dfabec", "#cf8cee", "#b087f6", "#8e84f9", "#6c73e1", "#4b5faa", "#2a4a10"]
Insert cell
import { trasePurples as trasePurplesAll } from "@trase/visual-id"
Insert cell
trasePurples = trasePurplesAll[trasePurplesAll.length - 1]
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