Insert cell
Insert cell
chart = htl.html`
${titleOnly}
${legendOnly}
${chartOnly}
`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
country = "brazil"
Insert cell
height = width * 0.625
Insert cell
width
Insert cell
keyCol = "trase_id"
Insert cell
Insert cell
Insert cell
Insert cell
valueYear = parseInt(valueYearSelect)
Insert cell
pct_select = [100, 95, 50, {
total : "Total",
quase : "95%",
metade : "50%"
}]
Insert cell
munSelected = SelectedDataMap?.length
Insert cell
munSelectedProduction = d3.sum(SelectedDataMap, (d) => d["cattle_raw_production"])
Insert cell
munSelectedProductionPct = munSelectedProduction /
d3.sum(selectedData, (d) => d["Cattle total production (tonnes)"])
Insert cell
munSelectedExports = d3.sum(SelectedDataMap, (d) => d["cattle_tn"])
Insert cell
munSelectedExportsPct = munSelectedExports /
d3.sum(selectedData, (d) => d["Beef exports (tonnes)"])
Insert cell
valuePct = pct_select[Object.values(pct_select[3]).indexOf(valuePctSelect)]
Insert cell
valueCol = valueColSelect?.column
Insert cell
valueTitle = valueColSelect?.name
Insert cell
valueUnits = valueColSelect?.units
Insert cell
columnsToDisplay = {
const selected = possibleColumns.find((d) => d.column === valueCol);
const other = possibleColumns.filter((d) => d.column !== valueCol);
return [selected, ...other];
}
Insert cell
possibleColumns = [
{
column: "absolute_sum_cattle_def",
name: "Cattle deforestation and conversion - exports",
units: "(ha)",
selectable: true,
format: ",.0~f"
},
{
column: "relative_cattle_def_ha_tn",
name: "Relative cattle deforestation and conversion",
units: "(ha/tonnes)",
selectable: true,
format: ",.0~f"
},
{
column: "cattle_tn",
name: "Beef exports",
units: "(tonnes)",
selectable: false,
format: ",.0~f"
},
{
column: "cattle_raw_production",
name: "Cattle total production",
units: "(tonnes)",
selectable: false,
format: ",.0~f"
}
// {
// column: "pasture_deforestation_full",
// name: "Pasture deforestation and conversion - total",
// units: "(ha)",
// selectable: false,
// format: ",.0~f"
// },
// {
// column: "pasture_total_area",
// name: "Total pasture area",
// units: "(ha)",
// selectable: false,
// format: ",.0~f"
// }
]
Insert cell
tableVis
Insert cell
showLand = true
Insert cell
showBiomes = true
Insert cell
selectedData = data
.sort((a, b) => d3.descending(a[valueCol], b[valueCol]))
.map((d) => {
const obj = {
Municipality: `${d["mun_name"]}, ${d["state_name"]}`
// "Soy deforestation (ha)": d[valueCol]
};
for (const c of possibleColumns) {
obj[`${c.name} ${c.units}`] = d[c.column];
}
if (d["year"] === valueYear) {
return obj;
}
else{
return null
}
})
.filter(element => {
return element !== null;
})
Insert cell
data[0]["absolute_sum_cattle_def_cpct"]
Insert cell
SelectedDataMap = data
.map((d) => {
if (d["year"] === valueYear) {
if (d["absolute_sum_cattle_def_cpct"] <= valuePct) {
return d;
}
}
return null;
})
.filter((element) => {
return element !== null;
})
Insert cell
// data = configs.data
data = await FileAttachment("general_results_MatoGrosso@1.csv").csv({
typed: true
})
Insert cell
data[0]
Insert cell
configs.data[0]
Insert cell
configs = FileAttachment("95pct_cattledef_map@7.json").json()
Insert cell
dataMap = new Map(
SelectedDataMap
.filter((d) => {
const regionId = String(d[keyCol]).includes("-")
? String(d[keyCol]).split("-")[1]
: String(d[keyCol]);
// ignore regions with XXXXXXX id, which are domestic consumption
if (regionId !== "XXXXXXX" && regionId !== "XX") {
return d;
}
})
.map((d) => {
const regionId = String(d[keyCol]).includes("-")
? String(d[keyCol]).split("-")[1]
: String(d[keyCol]);

// return [regionId, +d[valueCol]];
return [regionId, d];
})
)
Insert cell
boundaries = d3.json(
`https://unpkg.com/@trase/trase-atlas@1.1.1/files/${country.toLowerCase()}.json`
)
Insert cell
// world = d3.json("https://unpkg.com/visionscarto-world-atlas/world/50m.json")
world = FileAttachment("world-50m.json").json()
Insert cell
// biomes = FileAttachment("biomes_ecoregions_filtered_no_caatinga.json").json()
biomes = FileAttachment("biome@3.topo.json").json()
Insert cell
features = topojson.feature(boundaries, boundaries.objects[`level${level}`])
.features
Insert cell
// levels of data resolution - will need updating over time
levels = new Map([
["argentina", 3],
["bolivia", 3],
["brazil", 3],
["colombia", 2],
["cote_divoire", 3],
["ecuador", 3],
["paraguay", 2],
["indonesia", 3]
])
Insert cell
countries = Array.from(levels.keys())
Insert cell
level = levels.get(country.toLowerCase())
Insert cell
toTitleCase = (str) => {
return str?.toLowerCase()?.replace(/(^|\s)\S/g, (t) => t?.toUpperCase());
}
Insert cell
projections = new Map([
[
"argentina",
d3
.geoTransverseMercator()
.rotate([69, 0])
.fitSize(
[width, height],
topojson.mesh(boundaries, boundaries.objects.level1)
)
],
[
"bolivia",
d3
.geoTransverseMercator()
.rotate([50, 55])
.fitSize(
[width, height],
topojson.mesh(boundaries, boundaries.objects.level1)
)
],
[
"brazil",
d3
.geoPolyconic()
.rotate([54, 0])
.fitSize(
[width, height],
topojson.mesh(boundaries, boundaries.objects.level2)
)
],
[
"colombia",
d3
.geoTransverseMercator()
.rotate([68, 4.6])
.fitSize(
[width, height],
topojson.mesh(boundaries, boundaries.objects.level1)
)
],
[
"cote_divoire",
d3
.geoTransverseMercator()
// .rotate([68, 4.6])
.fitSize(
[width, height],
topojson.mesh(boundaries, boundaries.objects.level1)
)
],
[
"ecuador",
d3
.geoTransverseMercator()
.rotate([50, 55])
.fitSize(
[width, height],
topojson.mesh(boundaries, boundaries.objects.level1)
)
],
[
"paraguay",
d3
.geoTransverseMercator()
.rotate([63, 0])
.fitSize(
[width, height],
topojson.mesh(boundaries, boundaries.objects.level1)
)
],
[
"indonesia",
d3
.geoTransverseMercator()
.rotate([-139.5, 0])
.fitSize(
[width, height],
topojson.mesh(boundaries, boundaries.objects.level1)
)
]
])
Insert cell
projection = projections.get(country.toLowerCase())
Insert cell
colourScheme = traseReds
Insert cell
ninetiethPercentile = d3.quantile(
data.map((d) => d[valueCol]),
0.995
)
Insert cell
tenthPercentile = d3.quantile(data.map(d => d[valueCol]), 0.85);
Insert cell
extent = [tenthPercentile, ninetiethPercentile];
Insert cell
colour = d3
.scaleSequential(
d3.interpolateRgbBasis([
"#f7f0ee",
"#fac3be",
"#fc978f",
"#fd7167",
"#f6665c",
"#df5d55",
"#ba4e49"
])
)
.domain(extent)
Insert cell
// colour = d3.scaleSequential(
// d3.extent(dataMap, ([, v]) => v[valueCol]),
// d3.interpolateRgbBasis(colourScheme[9])
// )
Insert cell
numberFormat = ",.0~f"
Insert cell
format = (value) =>
Math.abs(value) < 10
? d3.format(".2r")(value)
: d3.format(numberFormat)(value)
Insert cell
annotate = g => {}
Insert cell
function hover(canvas, svg) {
const path = d3.geoPath(projection);
const highlight = svg
.append("path")
.attr("stroke", "#666")
.attr("stroke-width", 1.5)
.attr("fill", "none");
const tooltip = new Tooltip();
svg.append(() => tooltip.node);
canvas
.style("cursor", "pointer")
.on("touchstart", (event) => event.preventDefault())
.on("pointerenter pointermove", (event) => {
// get feature
let [cx, cy] = d3.pointer(event);
const [x, y] = projection.invert([cx, cy]);
const candidates = index.search(x, y, x, y).map((i) => features[i]);
const f = candidates.find((f) => turf.booleanPointInPolygon([x, y], f));
// define interaction
if (f && dataMap.has(f.id)) {
(cx += 10), (cy += 10);
if (cx + tooltip.width > width) cx -= tooltip.width + 20;
if (cy + tooltip.height > height) cy -= tooltip.height;
tooltip.show(
f.properties.name,
// tooltipKeyValue(tooltipKey, format(dataMap.get(f.id)), f)
generateTooltip(f)
);
tooltip.position(cx, cy);
highlight.attr("d", path(f));
} else {
highlight.attr("d", null);
tooltip.hide();
}
})
.on("pointerout", () => {
highlight.attr("d", null);
tooltip.hide();
});
}
Insert cell
generateTooltip = (d) => {
const spacing = 4.25;
const template = (valueTitle, valueUnits, value, i) => svg`
<text>
<tspan font-size="12px" fill="#839095" x="0" dy="${
spacing * i
}em" font-weight="${
i === 0 ? 700 : 400
}" style="text-transform:uppercase">${valueTitle} ${valueUnits}</tspan>
<tspan font-family="var(--trase-sans-serif)" font-size="14px" fill="#31464e" x="0" dy="1.4em">
<tspan font-weight="${i === 0 ? 700 : 400}">${format(value)}</tspan>
</tspan>
`;
return svg`
<line x1="0" y1="30" x2="250" y2="30" stroke="#839095" />
${columnsToDisplay.map((x, i) =>
template(x.name, x.units, dataMap.get(d.id)[x.column], i)
)}
`;
}
Insert cell
tooltipKey = data.c || `${valueTitle} ${valueUnits ? valueUnits : ""}`
Insert cell
// https://observablehq.com/@rdmurphy/combining-html-canvas-svg-flatbush-for-super-efficient-hov
script = {
const blob = new Blob([`
importScripts("${await require.resolve("flatbush@3.2")}");

onmessage = (event) => {
const { data } = event.data;
const index = new Flatbush(data.length);
data.forEach(bbox => { index.add(...bbox) });
index.finish();
postMessage(index.data);
close();
};
`], {type: "text/javascript"});
const script = URL.createObjectURL(blob);
invalidation.then(() => URL.revokeObjectURL(script));
return script;
}
Insert cell
index = Generators.queue(notify => {
const worker = new Worker(script);
invalidation.then(() => worker.terminate());
worker.addEventListener("message", ({data}) => notify(Flatbush.from(data)))
worker.postMessage({ data: features.map(d => turf.bbox(d)) });
});
Insert cell
import { traseColours, traseReds, fonts } from "@trase/visual-id@1366"
Insert cell
import { legend as legendTemplate } from "@trase/legends@376"
Insert cell
import { titleCard } from "@trase/title-card"
Insert cell
import { Tooltip, tooltipKeyValue, tooltipOffset } from "@trase/tooltip@440"
Insert cell
import { tableVis } with { selectedData as data } from "@trase/table-visualization"
Insert cell
d3 = require("d3@6", "d3-geo-projection@2.9")
Insert cell
topojson = require("topojson-client@3")
Insert cell
Flatbush = require("flatbush@3.2")
Insert cell
turf = require("@turf/turf@5")
Insert cell
simple = require("simple-statistics@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