Public
Edited
Nov 9, 2023
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
geoJSON = FileAttachment("countries-50m.json").json()
Insert cell
Insert cell
Insert cell
Insert cell
import {legend, Swatches} from "@d3/color-legend"
Insert cell
Insert cell
Insert cell
// add cells here
Insert cell
donateData = d3.rollups(
aiddata,
v => d3.sum(v, d => d.amount),
d => d.donor,
d => d.yearDate,
);

Insert cell
modifiedDonateData = donateData.map(countryData => {
return {
country: countryData[0],
data: countryData[1]
.map(item => {
return {
date: new Date(item[0]),
amount: item[1]
};
})
.sort((a, b) => a.date - b.date)
};
});

Insert cell
receiveData = d3.rollups(
aiddata,
v => d3.sum(v, d => d.amount),
d => d.recipient,
d => d.yearDate,
);
Insert cell
modifiedReceiveData = receiveData.map(countryData => {
return {
country: countryData[0],
data: countryData[1]
.map(item => {
return {
date: new Date(item[0]),
amount: item[1]
};
})
.sort((a, b) => a.date - b.date)
};
});

Insert cell
countries = new Set([...d3.map(aiddata, d => d.donor), ...d3.map(aiddata, d => d.recipient)]);
Insert cell
max_received = Math.max(...receiveData.map(data => Math.max(...data[1].map(item => item[1]))));

Insert cell
median_received = d3.median(receiveData.map(data => d3.median(data[1].map(item => item[1]))));

Insert cell
max_donated = Math.max(...donateData.map(data => Math.max(...data[1].map(item => item[1]))));
Insert cell
median_donated = d3.median(donateData.map(data => d3.median(data[1].map(item => item[1]))));

Insert cell
max_amount = Math.max(max_donated, max_received)
Insert cell
x = d3.scaleTime()
.range([0, width])
.domain(d3.extent(aiddata.map(d => d.yearDate)))
Insert cell
overlap = 2
Insert cell
h = 50
Insert cell
y = {
const max = max_amount
return d3.scaleLinear()
.range([ h * overlap, -overlap * h ])
.domain([-max, +max])
}
Insert cell
area = d3.area()
.curve(d3.curveBasis)
.x(d => x(d.date))
.y0(0)
.y1(d => y(d.amount))
Insert cell
color_received = i => d3['schemeBlues'][overlap * 2 + 1][i + (i >= 0) + overlap]
Insert cell
margin = ({top: 5, right: 1, bottom: 20, left: 40})
Insert cell
step = 50
Insert cell
overlaps = Array.from({length: overlap * 2} , (_, i) => Object.assign({index: i < overlap ? -i - 1: i - overlap}))
Insert cell
modifiedReceiveData[0].data
Insert cell
chart = {
const mode = 'offset';
const svg = d3.select(DOM.svg(width, h));
// Define Axes
const xAxis = d3.axisBottom().scale(x);
// Area Chart
const g = svg.append("g").attr("transform", `translate(0, 0)`)
g.append("clipPath")
.attr("id", "clipy")
.append("rect")
.attr("width", width)
.attr("height", step)
g.append("defs").append("path")
.attr("id", "path-def")
.datum(modifiedReceiveData[0].data)
.attr("d", area);
g.append("g")
.attr("clip-path", "url(#clipy)")
.selectAll("use")
.data(overlaps)
.enter().append("use")
.attr("fill", d => color_received(d.index))
.attr("transform", d => mode === "mirror" && d.index < 0
? `scale(1,-1) translate(0, ${d.index * step})`
: `translate(0,${(d.index + 1) * step})`)
.attr("href", "#path-def")
// Append Axes last
svg.append("g").call(xAxis);
return svg.node();
}
Insert cell
width
Insert cell
charts = {
const mode = 'offset';
const margin = { top: 20, right: 20, bottom: 30, left: 50 }; // Define the margin
const width = 928 - margin.left - margin.right; // Adjust the width
const height = 2000 - margin.top - margin.bottom; // Adjust the height

const svg = d3.select(DOM.svg(width + margin.left + margin.right, height + margin.top + margin.bottom));

// Define Axes
const xAxis = d3.axisBottom().scale(x);

// Loop through the modifiedReceiveData array to create charts for each country
modifiedReceiveData.forEach((countryData, index) => {
const g = svg.append("g").attr("transform", `translate(${margin.left}, ${index * 100 + margin.top})`); // Adjust the positioning

g.append("clipPath")
.attr("id", `clipy-${index}`)
.append("rect")
.attr("width", width)
.attr("height", step);

g.append("defs").append("path")
.attr("id", `path-def-${index}`)
.datum(countryData.data)
.attr("d", area);

g.append("g")
.attr("clip-path", `url(#clipy-${index})`)
.selectAll("use")
.data(overlaps)
.enter().append("use")
.attr("fill", d => color_received(d.index))
.attr("transform", d => mode === "mirror" && d.index < 0
? `scale(1,-1) translate(0, ${d.index * step})`
: `translate(0,${(d.index + 1) * step})`)
.attr("href", `#path-def-${index}`);

// Add country names
svg.append("text")
.attr("x", 0) // Adjust the x position for the country name
.attr("y", index * 100 + 30) // Adjust the y position for the country name
.text(countryData.country)
.style("font-size", "12px")
.style("font-family", "Calibri, sans-serif");
;

// Append Axes last
if (index === 0) {
svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`) // Translate the x-axis
.call(xAxis);
}
});


return svg.node();
}

Insert cell
top_10 = d3.rollups(
aiddata,
group => d3.sum(group, d => d.amount),
d => d.purpose
).sort((a, b) => d3.descending(a[1], b[1]))
.slice(0, 11);
Insert cell
top_10_cleaned = d3.map(top_10, d => d[0]).filter(d => d !== "Sectors not specified");
Insert cell
aiddata_cleaned = d3.map(aiddata,d => ({
date: d.yearDate,
key: d.purpose,
value: d.amount,
})).filter(d => top_10_cleaned.includes(d.key));
Insert cell
groupedData = Array.from(
d3.rollups(
aiddata_cleaned,
v => d3.sum(v, d => d.value),
d => d.date,
d => d.key
),
([date, data]) => data.map(([key, value]) => ({ date, key, value }))
).flat();
Insert cell
test = {
const newData = [];
const dates = [...new Set(groupedData.map(item => item.date))]; // Extract unique dates

dates.forEach(date => {
const filteredData = groupedData.filter(item => item.date === date);
const existingKeys = filteredData.map(item => item.key);
const missingKeys = top_10_cleaned.filter(key => !existingKeys.includes(key));

missingKeys.forEach(missingKey => {
newData.push({ date: date, key: missingKey, value: 0 });
});

filteredData.forEach(item => {
newData.push(item);
});
});
return newData
}
Insert cell
// sortedData = test.sort((a, b) => {
// if (a.date !== b.date) {
// return d3.ascending(a.date, b.date); // Sort by date
// }
// return d3.ascending(a.key, b.key); // Sort by key
// });

Insert cell
sortedData = test.sort((a, b) => {
if (a.date !== b.date) {
return d3.ascending(a.date, b.date); // Sort by date
}
const keyComparison = top_10_cleaned.indexOf(a.key) - top_10_cleaned.indexOf(b.key);
return keyComparison !== 0 ? keyComparison : d3.ascending(a.key, b.key); // Sort by key's position in top_10_cleaned, then by key
});

Insert cell
Insert cell
Insert cell
Swatches(d3.scaleOrdinal(top_10_cleaned, d3.schemeTableau10), {
columns: "180px"
})
Insert cell
chartBasic = {
const height = 500
const width = 900
const svg = d3.create('svg')
.attr('height', height)
.attr('width', width)
const keys = Array.from(d3.group(sortedData, d => d.key).keys())
const values = Array.from(d3.rollup(sortedData, ([d]) => d.value, d => +d.date, d => d.key))

console.log(values)
const series = d3.stack()
.keys(keys)
.value(([, values], key) => values.get(key))
.order(d3.stackOrderNone)
.offset(d3.stackOffsetExpand) // d3.stackOffsetExpand for normalized
// .offset(null)
(values)
const x = d3.scaleUtc()
.domain(d3.extent(sortedData, d => d.date))
.range([margin.left, width - margin.right])
const y = d3.scaleLinear()
.domain([0, d3.max(series, d => d3.max(d, d => d[1]))])
.range([height - margin.bottom, margin.top])
const c = d3.scaleOrdinal()
.domain(keys)
.range(d3.schemeTableau10)
// .range(d3.schemePastel2)
const area = d3.area()
.x(d => x(d.data[0]))
.y0(d => y(d[0]))
.y1(d => y(d[1]))

const xAxis = g => g
.attr('transform', `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
const yAxis = g => g
.attr('transform', `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select('.domain').remove())
.call(g => g.select('.tick:last-of-type text').clone()
.attr('x', 3)
.attr('text-anchor', 'start')
.attr('font-weight', 'bold')
.text(sortedData.y))
svg.append('g')
.selectAll('path')
.data(series)
.join('path')
.attr('fill', ({key}) => c(key))
.attr('d', area)
.append('title')
.text(({key}) => key)
svg.append('g')
.call(xAxis)
svg.append('g')
.call(yAxis)
return svg.node()
}
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