Public
Edited
Dec 12, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
IME-Water-Consumption-Food-Production - Sheet1.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
// https://www.theguardian.com/news/datablog/2013/jan/10/how-much-water-food-production-waste
waterConsumptionDataset = await FileAttachment("IME-Water-Consumption-Food-Production - Sheet1.csv").csv()
Insert cell
waterConsumptionViz = {
const margin = 75;
const width = 1000 - margin * 2;
const height = 500;

const svgW = d3.select(DOM.svg(width + margin * 2 + 200, height + margin * 2 + 25));
const svg = svgW.append('g').attr('transform', `translate(${margin + 100},${margin})`);

const y = d3.scaleBand().domain(waterConsumptionDataset.map(d => d.Food)).range([0, height]).padding(0.1);
const yAxis = d3.axisBottom(y);

const x = d3.scaleLinear().domain([0, d3.max(waterConsumptionDataset, d => parseInt(d["Water consumption, litres"]))]).range([0, width - 200]);
const xAxis = d3.axisBottom(x);

svg.append('text')
.attr('x', width / 2.6)
.attr('y', -margin / 2)
.attr('text-anchor', 'middle')
.style('font-size', '32px')
.style('font-weight', 'bold')
.text('Water Consumption for Food Production (per 1kg)');

// x-axis label
svg.append('text')
.attr('transform', `translate(${width / 2.6},${height + margin/1.5})`)
.attr('text-anchor', 'middle')
.text('Water consumption, litres');

// y-axis label
svg.append('g')
.selectAll('.bar-label')
.data(waterConsumptionDataset)
.enter().append('text')
.attr('class', 'bar-label')
.attr('x', -10)
.attr('y', d => y(d.Food) + y.bandwidth() / 2)
.attr('dy', '0.32em')
.attr('text-anchor', 'end')
.text(d => d.Food);

// draw each bar
svg.append('g')
.selectAll('rect')
.data(waterConsumptionDataset)
.enter().append('rect')
.attr('y', d => y(d.Food))
.attr('x', 0)
.attr('height', y.bandwidth())
.attr('width', d => x(d["Water consumption, litres"]))
.attr('fill', '#0e87cc');

// label each bar with totalEmissions value
svg.append('g')
.selectAll('.bar-label')
.data(waterConsumptionDataset)
.enter().append('text')
.attr('class', 'bar-label')
.attr('x', d => x(d["Water consumption, litres"]) + 10)
.attr('y', d => y(d.Food) + y.bandwidth() / 2)
.attr('dy', '0.32em')
.attr('text-anchor', 'start')
.text(d => d["Water consumption, litres"]);

svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(xAxis);

return svgW.node();
};
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
foodWaste
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
foodWasteMap = {
const width = 1150;
const height = 550;
const projection = d3.geoEqualEarth();
const path = d3.geoPath(projection);

// add zooming
const zoom = d3.zoom()
.scaleExtent([1, 8])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);

// create the SVG container
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, 1200, 570])
.attr("style", "max-width: 100%; height: auto;")
.call(zoom);

// create a child container of svg
const g = svg.append("g");

function zoomed(event) {
g.attr("transform", event.transform);
}

// draw the countries
const countries = g
.selectAll("path")
.data(countriesData)
.join("path")
.attr("d", path)
.attr("transform", "translate(0, 50)")
.attr("stroke", "black")
.attr("stroke-width", 0.5)
// lookup the corresponding country and apply the color scale for its food waste
.attr("fill", d => color(lookup.get(d.properties.name)) || "#ccc");

// add a tooltip - country's name: its food waste
countries
.append("title")
.text((d) => {
if (lookup.get(d.properties.name)) {
return `${d.properties.name}: ${lookup.get(d.properties.name)} kg`;
} else {
return d.properties.name;
}
});

// add mouse hover interactions
countries
.on("mouseover", function(event, d) {
// only if we have data for that country
if (lookup.get(d.properties.name)) {
d3.select(this).attr("stroke-width", 2);
// add background behind pie
svg
.append("rect")
.attr("class", "background")
.attr("width", width/4.6)
.attr("height", height/2.1)
.attr("fill", "white")
.attr("opacity", 0.9)
.attr("transform", "translate(890, 35)");
// draw pie breakdown by sectors
const breakdown = [
{name:"Retail", value:Number(getRetail.get(d.properties.name))},
{name:"Households", value:Number(getHousehold.get(d.properties.name))},
{name:"Out of Home Consumption", value:Number(getOOH.get(d.properties.name))}
];
drawPie(svg, breakdown);
}
})
.on("mouseout", function() {
d3.select(this).attr("stroke-width", 0.5);
svg.select(".pie-chart").remove();
svg.select(".pie-legend").remove();
svg.select(".background").remove();
});

return svg.node();
}
Insert cell
Insert cell
FAOSTAT_data_en_12-3-2023.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
annualFoodLossData = processFoodLossData();
Insert cell
async function processFoodLossData() {
let data = await FileAttachment('FAOSTAT_data_en_12-3-2023.csv').csv({typed: true});
// Convert "Year" field to a Date object with a specific day and month
data.forEach(d => {
d.Year = new Date(`${d.Year}-01-01`);
});
// Filter based on the range specified by the slider
const [minYear, maxYear] = yearSlider;
const filteredData = data.filter(d => d.Year >= new Date(`${minYear}-01-01`) && d.Year <= new Date(`${maxYear}-12-31`));

// Calculate the sum of values for each food
const sumByItem = Array.from(d3.group(filteredData, d => d.Item), ([Item, values]) => ({
Item,
SumValue: d3.sum(values, d => d.Value),
}));
// Sort the items by sum of values in descending order
const sortedItems = sumByItem.sort((a, b) => b.SumValue - a.SumValue);
// Select the top 5 items
const topItems = sortedItems.slice(0, filterSlider);

// Filter the data to include only the top 5 items
const finalFilteredData = filteredData.filter(d => topItems.some(item => item.Item === d.Item));

return finalFilteredData;
}
Insert cell
viewof yearSlider = rangeSlider({
min: 2010,
max: 2021,
step: 1,
value: [2010, 2021],
title: 'Years',
})
Insert cell
viewof filterSlider = Inputs.range([1, 18], {step: 1, label: "Top K Contributors", value: 6});
Insert cell
foodWasteStack = Plot.plot({
width: 1000,
height: 600,
margin: 50,
title: "Annual Worldwide Food Waste by Category",
y: {grid: true, domain: [0, 700000], label: "Losses (1000t)"},
color: {
legend: true,
domain: [
"Cereals - Excluding Beer",
"Starchy Roots",
"Sugar Crops",
"Sugar & Sweeteners",
"Pulses",
"Treenuts",
"Oilcrops",
"Vegetable Oils",
"Vegetables",
"Fruits - Excluding Wine",
"Stimulants",
"Spices",
"Alcoholic Beverages",
"Meat",
"Offals",
"Animal Fats",
"Eggs",
"Milk - Excluding Butter"
],
range: [
"#e6194b",
"#3cb44b",
"#ffe119",
"#4363d8",
"#f58231",
"#911eb4",
"#46f0f0",
"#f032e6",
"#bcf60c",
"#fabebe",
"#008080",
"#e6beff",
"#9a6324",
"#fffac8",
"#800000",
"#aaffc3",
"#808000",
"#ffd8b1"
],
},
marks: [
Plot.areaY(annualFoodLossData, {x: "Year", y: "Value", fill: "Item", tip: {format: {Item: true, Value: true, Year: true, x: (dateString) => {
const date = new Date(dateString);
return date.getUTCFullYear().toString();
}}}}),
Plot.ruleY([0])
],
})
Insert cell
import {Plot} from "@mkfreeman/plot-tooltip"
Insert cell
import {addTooltips} from "@mkfreeman/plot-tooltip"
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import {rangeSlider} from '@mootari/range-slider'
Insert cell
Insert cell
import {Scrubber} from "@mbostock/scrubber"
Insert cell
import {Legend, Swatches} from "@d3/color-legend"
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