Public
Edited
Oct 30, 2024
Insert cell
Insert cell
Mini Project 1 (Geo)
Data

Here we load a simplified and reduced version of the AidData dataset. You are free to change this code as you wish. If you don't want to preprocess your data on Observable, then you can do the preprocessing using whatever languages or tools you'd like and then upload your preprocessed data to Observable.

The dataset contains the following columns:

yearDate: year of the commitment as a Date object
yearInt: year of the commitment as an integer
donor: country providing the financial resource
recipient: country receiving the financial resource
amount: the total amount of financial resources provided
purpose: the purpose of the transaction

Questions

Your goal is to create 3 independent visualizations of the same data set, each one with the intent of answering the questions stated below. For each numbered visualization, you should be able to create a data visualization that answers all of the questions specified. These are the 3 visualizations you should create:

Visualization 1: How do the countries compare in terms of how much they receive and donate? Are there countries that donate much more than they receive or receive much more than they donate?
Visualization 2: Do the countries that mostly receive or mostly donate tend to cluster around specific geographical areas of the world? Are there neighboring countries that have radically different patterns in terms of how much they receive vs. how much they donate?
[OPTIONAL / EXTRA POINTS] Visualization 3: Are there any major differences in how the top 5 most frequent purposes of disbursements (across all countries) distribute geographically in terms of countries that receive donations? Are there countries that tend to receive more of certain types of donations than others? (Note: it would be more interesting to look at the top 5 purposes locally, for each individual country but visualizing the results may be even harder. If you want to try out a solution for this harder case it would be nice to see it).

For each visualization add a description of your thought process and why you think your solution is appropriate and effective.

Note it is okay to submit more than one solution for a given visualization problem if you think there are some competing solutions that work well. When you do that, make sure to compare the solutions by commenting on their advantages and disadvantages.
Remember

The questions above are a very useful tool for you to verify whether you are producing a good solution or not. As you go about solving this exercise you can use the questions above as an evaluation tool in two main ways:

Verify that you can indeed answer the questions with your visualization. If you can’t fully answer the questions, it means your solution has some problems.
Compare multiple solutions to the same problem and ask yourself: “which one makes it easier for me to answer these questions?”, where easier may mean, more accurately, with less effort or faster.

Instructions

Your coded solutions must use D3. Other visualization libraries are not allowed.
You should feel free to process and transform the data any way you deem relevant and use any tool you prefer for data processing. Data transformation does not necessarily have to be in JavaScript nor within Observable. It’s ok to pre-process the data and then load it in Observable if you prefer.

Insert cell
// for parsing dates

dateFormat =function(date) {
var year = date.getUTCFullYear()
return year
}
Insert cell
tParser = d3.utcParse("%Y")
Insert cell
AidData = (await FileAttachment("AidData@1.csv").csv())
.map(aid => ({
'donorCountry': aid.donor,
'recipientCountry' : aid.recipient,
'year' : dateFormat(tParser(aid.year)),
'amountDonated' : Number(aid.amount_commited),
'numDonations' : Number(aid.number_commitments)
}))
Insert cell
constrastAidData = (await FileAttachment("AidData@2.csv").csv()).map(aid => ({ // aidData@2 is the original Aiddata
'donorCountry': aid.donor,
'recipientCountry' : aid.recipient,
'year' : dateFormat(tParser(aid.year)),
'amountDonated' : Number(aid.number_commitmented),
'numDonations' : Number(aid.number_commitments)
}))
Insert cell
typeof(AidData[3].year)
Insert cell
worldGeoJSON = FileAttachment("countries-50m.json").json()
Insert cell
// set of all the unique countries in the dataset
countries = new Set(worldGeoJSON.features.map(d => d.properties.NAME_EN))
Insert cell
//probably won't need this
regionColor = d3.scaleOrdinal()
.domain(countries)
.range(d3.schemeTableau10)
Insert cell
//maxDonation = d3.max(maxDonation => AidData.amountDonated)
Donations = Array.from(new Set(AidData.map(d => d['amountDonated'])))
Insert cell
//for scale -- maybe move this later.
maxDonation =d3.max(Donations)
Insert cell
color = d3.scaleSequential()
.domain([0, maxDonation])
.interpolator(d3.interpolateYlGnBu)
Insert cell
AidGivenRecieved = {
// create a rolled up group for total donations to each country
var DonationCountry = d3.rollup(AidData,
v => d3.sum(v, d => d.amountDonated),
d => d.donorCountry
)
// create a rollup for the total amount of aid recieved for weach country
var AidGivenCountry = d3.rollup(AidData,
v => d3.sum(v, d => d.amountDonated),
d => d.recipientCountry
)
return [DonationCountry, AidGivenCountry]
}
Insert cell
// get uniqe donators -- checking these are the same as the grouped donations.
//d3.map(AidData, function(d) {return d.donorCountry;}).keys()

donor_recipients = {
function onlyUnique(value, index, array) {
return array.indexOf(value) === index;
}
var donorCountries = Array.from(new Set(AidData.map(d => d['donorCountry']))).filter(onlyUnique);
var recpCountries = Array.from(new Set(AidData.map(d => d['recipientCountry']))).filter(onlyUnique);

return [donorCountries, recpCountries]
}
Insert cell
// convert both AidGivenRecived maps to arrays of arrays.
aidTwoDarrs = {
let donation = Array.from(AidGivenRecieved[0], ([country, amount]) => ({country, amount}));
let given = Array.from(AidGivenRecieved[1], ([country, amount]) => ({country, amount}));
return [donation, given]
}
Insert cell
netAid = {
const result = new Map([AidGivenRecieved[0]]);
AidGivenRecieved[1].forEach((value, key) => {
if (result.has(key)) {
result.set(key, result.get(key) - value);
} else {
result.set(key, value);
}
});
return result
}
Insert cell
// trying out to map to seperate example arays:
// delete this later
ex_arrs = {
const map1 = new Map([['India', 1], ['Usa', 2]]);
const map2 = new Map([['India', 3], ['Russia', 4]]);
const result = new Map([...map1]);

map2.forEach((value, key) => {
if (result.has(key)) {
result.set(key, result.get(key) + value);
} else {
result.set(key, value);
}
});
return result;
}
Insert cell
// get the size of the unique attr's from each to confirm is the correct size.

// get projection:
projection = d3.geoOrthographic()
Insert cell
path = geoCurvePath((d3.curveCatmullRom.alpha(0.5), projection));
Insert cell
//chloropleth mapping of donations - net donations.
chart = {
let worldMargin = ({top: 0, right: 0, bottom: 0, left: 0})
let worldWidth = 900 - worldMargin.top - worldMargin.bottom
let worldHeight = 500 - worldMargin.left - worldMargin.right
let worldOutline = d3.geoGraticule().outline()
let worldProjection = d3.geoEqualEarth()
.fitSize([worldWidth, worldHeight], worldOutline)
let worldPath = d3.geoPath().projection(worldProjection)
const svg = d3.create('svg')
.attr('width', worldWidth + worldMargin.left + worldMargin.right)
.attr('height', worldHeight + worldMargin.top + worldMargin.bottom);

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

g.selectAll('path')
.data(worldGeoJSON.features)
.join('path')
.attr('d', worldPath)
.attr('fill', country => color(AidData.donor)) // useless fxn
.attr('stroke', 'white');

g.append('path')
.attr('d', worldPath(worldOutline))
.attr('stroke', '#dcdcdc')
.attr('fill', 'none');

return svg.node();
}
Insert cell
sbstrtFrqArr = Array.from(AidData, ([Substrate, Frequency]) => ({Substrate, Frequency}))
Insert cell
//chloropleth mapping of recipients
{
const svg = d3.create('svg')
.attr('width', worldWidth + worldMargin.left + worldMargin.right)
.attr('height', worldHeight + worldMargin.top + worldMargin.bottom);

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

g.selectAll('path')
.data(worldGeoJSON.features)
.join('path')
.attr('d', worldPath)
.attr('fill', country => color(AidData.recipient))
.attr('stroke', 'white');
// draw outline

g.append('path')
.attr('d', worldPath(worldOutline))
.attr('stroke', '#dcdcdc')
.attr('fill', 'none');

return svg.node();
}
Insert cell
import {geoCurvePath} from "@d3/context-to-curve"
Insert cell
topojson = require("topojson@3")
Insert cell
import {Histogram} from "@d3/histogram"
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