Public
Edited
May 9
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,
recipient: row.recipient,
amount: +row.commitment_amount_usd_constant,
purpose: row.coalesced_purpose_name,
}));
data.columns = Object.keys(data[0]);
return data;
}
Insert cell
Insert cell

byCountryYear = {
const donated = d3.rollup(
aiddata,
v => d3.sum(v, d => d.amount),
d => d.donor,
d => d.yearInt
);

const received = d3.rollup(
aiddata,
v => d3.sum(v, d => d.amount),
d => d.recipient,
d => d.yearInt
);

const out = [];
const countries = new Set([...donated.keys(), ...received.keys()]);
countries.forEach(country => {
const years = new Set([
...((donated.get(country) || new Map()).keys()),
...((received.get(country) || new Map()).keys())
]);
years.forEach(year => {
out.push({
country,
year,
donated : donated.get(country)?.get(year) ?? 0,
received: received.get(country)?.get(year) ?? 0,
net : (donated.get(country)?.get(year) ?? 0)
- (received.get(country)?.get(year) ?? 0)
});
});
});
return out;
}

Insert cell
years = d3.sort(Array.from(new Set(byCountryYear.map(d => d.year))));
Insert cell
countries = d3.sort(Array.from(new Set(byCountryYear.map(d => d.country))));

Insert cell
extent = d3.extent(byCountryYear, d => d.net);
Insert cell
absMax = Math.max(Math.abs(extent[0]), Math.abs(extent[1]));
Insert cell
color1 = d3.scaleDiverging()
.domain([-absMax, 0, absMax])
.interpolator(d3.interpolateRdBu)
Insert cell
Insert cell
// tu código aquí
{
const margin = {top: 60, right: 20, bottom: 40, left: 120};
const cellSize = 12;
const width = margin.left + margin.right + cellSize * years.length;
const height = margin.top + margin.bottom + cellSize * countries.length;

const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

// escalas
const x = d3.scaleBand()
.domain(years)
.range([margin.left, width - margin.right]);

const y = d3.scaleBand()
.domain(countries)
.range([margin.top, height - margin.bottom]);

// celdas
svg.append('g')
.selectAll('rect')
.data(byCountryYear)
.join('rect')
.attr('x', d => x(d.year))
.attr('y', d => y(d.country))
.attr('width', x.bandwidth())
.attr('height', y.bandwidth())
.attr('fill', d => color1(d.net))
.append('title')
.text(d => `${d.country} • ${d.year}
Donó: $${d3.format(",.0f")(d.donated)}
Recibió: $${d3.format(",.0f")(d.received)}
Balance: ${d3.format("+,.0f")(d.net)}`);

// ejes
svg.append('g')
.attr('transform', `translate(0, ${margin.top})`)
.call(d3.axisTop(x).tickValues(years.filter(y => y % 5 === 0)))
.call(g => g.select('.domain').remove());

svg.append('g')
.attr('transform', `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y).tickSize(0))
.call(g => g.select('.domain').remove());

// leyenda
const legendWidth = 160;
const lgScale = d3.scaleLinear()
.domain(color1.domain())
.range([0, legendWidth]);

const lg = svg.append('g')
.attr('transform', `translate(${width - legendWidth - 25}, ${margin.top - 40})`);

lg.selectAll('rect')
.data(d3.range(legendWidth))
.join('rect')
.attr('x', d => d)
.attr('width', 1)
.attr('height', 8)
.attr('fill', d => color1(lgScale.invert(d)));

lg.append('g')
.attr('transform', 'translate(0, 8)')
.call(d3.axisBottom(lgScale).ticks(3).tickFormat(d3.format("+~s")))
.call(g => g.select('.domain').remove());

return svg.node();
}

Insert cell
Insert cell

{
const margin = {top: 40, right: 140, bottom: 40, left: 60};
const width = 900;
const height = 500;
const clean = aiddata
.filter(d => d.purpose !== "Sectors not specified")
.map(d => ({
year : +d.yearInt,
purpose: d.purpose.trim(),
amount : +d.amount
}));

const totals = d3.rollup(
clean,
v => d3.sum(v, d => d.amount),
d => d.purpose
);
const top10 = d3.sort(totals, (a,b) => d3.descending(a[1], b[1]))
.slice(0,10)
.map(d => d[0]);


const byYearPurpose = d3.rollups(
clean.filter(d => top10.includes(d.purpose)),
v => d3.sum(v, d => d.amount),
d => d.year,
d => d.purpose
);

const series = byYearPurpose.map(([year, arr]) => {
const purposeMap = new Map(arr);
const row = { year };
top10.forEach(p => row[p] = purposeMap.get(p) ?? 0);
return row;
}).sort((a, b) => d3.ascending(a.year, b.year));


const stack = d3.stack().keys(top10);
const layers = stack(series);


const x = d3.scaleLinear()
.domain(d3.extent(series, d => d.year))
.range([margin.left, width - margin.right]);

const y = d3.scaleLinear()
.domain([0, d3.max(layers, L => d3.max(L, d => d[1]))])
.nice()
.range([height - margin.bottom, margin.top]);

const color = d3.scaleOrdinal()
.domain(top10)
.range(d3.schemeTableau10);

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

const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

svg.append('g')
.selectAll('path')
.data(layers)
.join('path')
.attr('fill', d => color(d.key))
.attr('d', area)
.append('title')
.text(d => d.key);

svg.append('g')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(10).tickFormat(d3.format('d')))
.call(g => g.select('.domain').remove());

// eje Y
svg.append('g')
.attr('transform', `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y).ticks(5, '~s'))
.call(g => g.select('.domain').remove());

// leyenda
const legend = svg.append('g')
.attr('font-size', 10)
.attr('transform', `translate(${width - margin.right + 10}, ${margin.top})`);

top10.forEach((p, i) => {
legend.append('rect')
.attr('x', 0)
.attr('y', i * 14)
.attr('width', 10)
.attr('height', 10)
.attr('fill', color(p));
legend.append('text')
.attr('x', 14)
.attr('y', i * 14 + 9)
.text(p);
});

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