Public
Edited
May 7
Insert cell
Insert cell
Insert cell
Inputs.table(aiddata)
Insert cell
aiddata = {
const data = await d3.csv(googleSheetCsvUrl, row => ({
yearDate: d3.timeParse("%Y")(row.year),
yearInt: +row.year,
donor: row.donor?.trim(),
recipient: row.recipient?.trim(),
amount: +row.commitment_amount_usd_constant,
purpose: row.coalesced_purpose_name?.trim()
}));

const filtered = data.filter(d =>
d.yearDate && !isNaN(d.amount) && d.purpose && d.purpose !== "UNSPECIFIED"
);

filtered.columns = Object.keys(filtered[0]);
return filtered;
}
Insert cell
Insert cell
balancesPaisAnio = {
const nested = d3.rollups(
aiddata,
v => {
const donadoPorPais = d3.rollup(v, v2 => d3.sum(v2, d => d.amount), d => d.donor);
const recibidoPorPais = d3.rollup(v, v2 => d3.sum(v2, d => d.amount), d => d.recipient);
const paises = new Set([...donadoPorPais.keys(), ...recibidoPorPais.keys()]);
return Array.from(paises, pais => ({
country: pais,
donado: donadoPorPais.get(pais) || 0,
recibido: recibidoPorPais.get(pais) || 0
}));
},
d => d.yearInt
);

const balances = [];
for (const [year, paises] of nested) {
for (const p of paises) {
balances.push({
year,
country: p.country,
donado: p.donado,
recibido: p.recibido,
neto: p.recibido - p.donado
});
}
}
return balances;
}

Insert cell
Insert cell
chartBalance = {
const dataPais = balancesPaisAnio.filter(d => d.country === paisSeleccionado);
dataPais.sort((a, b) => a.year - b.year);

const margin = { top: 30, right: 40, bottom: 40, left: 80 },
width = 700 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;

const svg = d3.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const x = d3.scaleLinear()
.domain(d3.extent(dataPais, d => d.year))
.range([0, width]);

const y = d3.scaleLinear()
.domain([
d3.min(dataPais, d => Math.min(d.donado, d.recibido)),
d3.max(dataPais, d => Math.max(d.donado, d.recibido))
])
.nice()
.range([height, 0]);

const lineDonado = d3.line()
.x(d => x(d.year))
.y(d => y(d.donado));

const lineRecibido = d3.line()
.x(d => x(d.year))
.y(d => y(d.recibido));

g.append("path")
.datum(dataPais)
.attr("fill", "none")
.attr("stroke", "#d62728")
.attr("stroke-width", 2)
.attr("d", lineDonado);

g.append("path")
.datum(dataPais)
.attr("fill", "none")
.attr("stroke", "#1f77b4")
.attr("stroke-width", 2)
.attr("d", lineRecibido);

g.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x).tickFormat(d3.format("d")));

g.append("g")
.call(d3.axisLeft(y));

g.append("text")
.attr("x", width / 2)
.attr("y", -10)
.attr("text-anchor", "middle")
.style("font-weight", "bold")
.text(`Balance de donaciones y recepciones para ${paisSeleccionado}`);

const legend = svg.append("g").attr("transform", `translate(${width - 50},${margin.top})`);
legend.append("rect").attr("x", -20).attr("width", 12).attr("height", 12).attr("fill", "#d62728");
legend.append("text").attr("x", -5).attr("y", 10).text("Donado").attr("font-size", "12px");
legend.append("rect").attr("x", -20).attr("y", 20).attr("width", 12).attr("height", 12).attr("fill", "#1f77b4");
legend.append("text").attr("x", -5).attr("y", 30).text("Recibido").attr("font-size", "12px");

return svg.node();
}

Insert cell
top10PorAno = {
const nested = d3.rollups(
aiddata,
v => d3.sum(v, d => d.amount),
d => d.purpose
);

const top10 = nested.sort((a, b) => d3.descending(a[1], b[1])).slice(0, 10).map(d => d[0]);

const filtered = aiddata.filter(d => top10.includes(d.purpose));

const data = Array.from(
d3.group(filtered, d => d.yearInt),
([year, values]) => {
const result = { year };
for (const purpose of top10) {
result[purpose] = d3.sum(values.filter(d => d.purpose === purpose), d => d.amount);
}
return result;
}
).sort((a, b) => a.year - b.year); // AÑADIR ORDENAMIENTO POR AÑO

data.columns = ["year", ...top10];
return data;
}
Insert cell
stackedAreaChart = {
const margin = { top: 30, right: 100, bottom: 40, left: 80 },
width = 800 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;

const svg = d3.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

const x = d3.scaleLinear()
.domain(d3.extent(top10PorAno, d => d.year))
.range([0, width]);

const y = d3.scaleLinear()
.domain([0, d3.max(top10PorAno, d => d3.sum(Object.values(d).slice(1)))])
.nice()
.range([height, 0]);

const color = d3.scaleOrdinal()
.domain(top10PorAno.columns.slice(1))
.range(d3.schemeCategory10);

const stack = d3.stack()
.keys(top10PorAno.columns.slice(1));

const area = d3.area()
.x(d => x(d.data.year))
.y0(d => y(d[0]))
.y1(d => y(d[1]));

g.selectAll("path")
.data(stack(top10PorAno))
.join("path")
.attr("fill", d => color(d.key))
.attr("d", area);

g.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x).tickFormat(d3.format("d")));

g.append("g")
.call(d3.axisLeft(y));

const legend = svg.append("g")
.attr("transform", `translate(${width + margin.left + 10},${margin.top})`);

top10PorAno.columns.slice(1).forEach((key, i) => {
legend.append("rect")
.attr("x", 0)
.attr("y", i * 20)
.attr("width", 12)
.attr("height", 12)
.attr("fill", color(key));

legend.append("text")
.attr("x", 18)
.attr("y", i * 20 + 10)
.text(key)
.attr("font-size", "11px");
});

return svg.node();
}

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require('d3@7')
Insert cell
googleSheetCsvUrl = 'https://docs.google.com/spreadsheets/d/1YiuHdfZv_JZ-igOemKJMRaU8dkucfmHxOP6Od3FraW8/gviz/tq?tqx=out:csv'
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