Public
Edited
Aug 21, 2022
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
worldCountry = geoJSON.features
Insert cell
Insert cell
Insert cell
Insert cell
import {legend, swatches} from "@d3/color-legend"
Insert cell
Insert cell
Insert cell
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?
Insert cell
Data Question Q1:
Group the countries based on the year, donation recieved and donation done.
Filter the countries that have more receipts than donations
Insert cell
donateAid = d3.rollup(aiddata, g => d3.sum(g, x => x.amount), d => d.donor)
Insert cell
donateAid.get('Italy')
Insert cell
maxAid = d3.max([...donateAid.values()])
Insert cell
donatingCountry = [...donateAid.keys()]
Insert cell
worldCountry.map(d => d.properties.SUBUNIT)
Insert cell
worldCountry[16].properties
Insert cell
receiveCountry = receiveAid.map(d => d[0])
Insert cell
viewof selectDonor = Inputs.select(donatingCountry,{label:"Donating Country",value:"United States"})
Insert cell
viewof selectReciept = Inputs.select(receiveCountry,{label:"Recieving Country",value:"India"})
Insert cell
txnYear = [... new Set(aiddata.map(d => d.yearInt))]
Insert cell
receiveAid = d3.rollups(aiddata, g => d3.sum(g, x => x.amount), d => d.recipient)
Insert cell
receiveAidCountry = d3.rollup(aiddata, g => d3.sum(g, x => x.amount), d => d.recipient)
Insert cell
aidFormat = d3.format('s')
Insert cell
givOrTake = receiveAid.map((d) =>({
country: d[0],
aidrecieved : d[1],
aidgiven: donateAid.get(d[0]) === undefined ? 0 : donateAid.get(d[0])
}))
.sort((a,b) => d3.descending(a.aidgiven,b.aidgiven))
Insert cell
Insert cell
width = 700
Insert cell
height = 700
Insert cell
margin = ({top:20, left:60, right:20, bottom:40})
Insert cell
visWidth = width - margin.top - margin.bottom
Insert cell
visHeight = height - margin.left - margin.right
Insert cell
worldCountry.filter(d => d.properties.SUBUNIT == "India")
Insert cell
worldProjection = d3.geoEquirectangular()
.fitSize([visWidth, visHeight], geoJSON)
Insert cell
worldPath = d3.geoPath().projection(worldProjection)
Insert cell
worldPath(worldCountry[0])
Insert cell
color = d3.scaleSequential()
.domain([0, maxAid])
.interpolator(d3.interpolateCividis)
Insert cell
//Creating the grouped bar for each country, showing their aid Given or Taken with horizontal bars.
gOTBar = {
const svg = d3.create('svg').attr('width',width).attr('height',height)
const plot = svg.append('g')
.attr('transform',`translate(${margin.left},0)`)

const countryBand = d3.scaleBand()
.domain(receiveCountry) //there is more of recieving countries, so using that as domain
.range([0, height - margin.top])
.paddingInner(0.2)

const yAxis = svg.append('g')
.attr('transform',`translate(${margin.left},0)`)
.call(d3.axisLeft(countryBand))

const aidScale = d3.scaleLinear()
.domain([0,d3.max(givOrTake.map(d => d.aidrecieved))])
.range([margin.left, width - margin.right])

const xAxis = svg.append('g')
.attr('transform',`translate(0,${height - margin.top})`)
.call(d3.axisBottom(aidScale).tickFormat(d3.format(".0s")))//need to add the tickformat here
const group = d3.scaleBand()
.domain(['donor','recipient'])
.range([0,countryBand.bandwidth()])

//Bringing in the bars for donors first

plot.selectAll('.donor')
.data(givOrTake)
.join('rect')
.attr('x',0)
.attr('width',d => aidScale(d.aidgiven))
.attr('y',d => countryBand(d.country) - countryBand.bandwidth()/2)
.attr('height', countryBand.bandwidth() / 2)
.attr('fill','blue')
.attr('class','donor')

plot.selectAll('.recep')
.data(givOrTake)
.join('rect')
.attr('x',0)
.attr('width',d => aidScale(d.aidrecieved))
.attr('y',d => countryBand(d.country))
.attr('height', countryBand.bandwidth()/2)
.attr('fill','red')
.attr('class','recep')
return svg.node()
}
Insert cell
geoWorld = {
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);

const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// Showing countries recieving Aid
const geoMap = g.selectAll('path')
.data(worldCountry)
.join('path')
.attr('d', worldPath)
.attr('fill', d => receiveAidCountry.get(d.properties.SUBUNIT) !== undefined? color(receiveAidCountry.get(d.properties.SUBUNIT)) : "green")
.attr('stroke','white')
return svg.node();
}
Insert cell
donatingWorld = {
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);

const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// Showing countries donating Aid
const geoMap = g.selectAll('path')
.data(worldCountry)
.join('path')
.attr('d', worldPath)
.attr('fill', d => donateAid.get(d.properties.SUBUNIT) !== undefined? color(donateAid.get(d.properties.SUBUNIT)): "green")
.attr('stroke','white')
return svg.node();
}
Insert cell
moreReceipts = givOrTake.filter(d => d.aidrecieved > d.aidgiven)
Insert cell
noDonation = givOrTake.filter(d => d.aidgiven == 0)
Insert cell
noReciept = givOrTake.filter(d => d.aidrecieved == 0)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
recieptsPerYrPerCountryVer1.filter(d => d.year === selectYear)
Insert cell
viewof selectYear = Inputs.select(txnYear, {label:"Transaction Year", value:2013})
Insert cell
freqReceiptBar = {
const svg = d3.create('svg').attr('width',width).attr('height',height)
const plot = svg.append('g')
.attr('transform',`translate(${margin.left},0)`)

const countryBand = d3.scaleBand()
.range([0, height - margin.top])
.paddingInner(0.2)
const countries = recieptsPerYrPerCountryVer1.filter(d => d.year === selectYear).map(d => d.country)
const yAxis = svg.append('g')
.attr('transform',`translate(${margin.left},0)`)
.call(d3.axisLeft(countryBand
.domain(countries))) //there is more of recieving countries, so using that as domain

const maxFreq = d3.max(recieptsPerYrPerCountryVer1.filter(d => d.year === selectYear).map(d => d.frequency))
const aidScale = d3.scaleLinear()
.domain([0,maxFreq])
.range([margin.left, width - margin.right])

const xAxis = svg.append('g')
.attr('transform',`translate(0,${height - margin.top})`)
.call(d3.axisBottom(aidScale))//need to add the tickformat here
//Bringing in the bars for donors first
const t = plot.transition().duration(1000);
plot.selectAll('rect')
.data(recieptsPerYrPerCountryVer1
.filter(d => d.year === selectYear)
.sort((a,b) => d3.descending(a.frequency, b.frequency))
)
.join(
enter => enter
.append('rect')
.attr('x',0)
.attr('y',d => countryBand(d.country))
.attr('fill','red')
.call(e => e.transition(t)
.attr('height', countryBand.bandwidth())
.attr('width',d => aidScale(d.frequency))
.attr('fill','blue')
.attr('class','donor')
),
update => update
.call(u =>u
.transition(t)
.attr('fill',"green")),
exit => exit
.attr('fill',"black")
.call(e => e.transition(t)
.attr('opacity',0)
.remove()
)
)
return svg.node()
}
Insert cell
Insert cell
Insert cell
frequentPurpose = d3.rollups(aiddata, g => g.length, d => d.purpose).sort((a,b) => d3.descending(a[1],b[1]))
Insert cell
Are there countries that tend to receive more of certain types of donations than others?
Insert cell
freqCntryPurp = d3.rollups(aiddata, g => g.length, d => d.recipient, d => d.purpose)
Insert cell
freqCntryPurpMap = {
const cntryPurp =[];
freqCntryPurp.forEach(([country, purpFreq]) => {
purpFreq.forEach(([purpose, freq]) => {
cntryPurp.push({
country : country,
purpose : purpose,
frequency : freq
})
})
})
return cntryPurp
}
Insert cell
Insert cell
countryToAbbr = {
const countryAbbr = [];
worldCountry.forEach(d =>{
countryAbbr.push([d.properties.SUBUNIT,d.properties.BRK_A3])
})
return countryAbbr
}
Insert cell
mapCountryAbbr = new Map(countryToAbbr)
Insert cell
topFivePurposesReceipt = {
const sortedPurpTopFive =[];
freqCntryPurp.forEach(([recep, purpose]) => {
const sortedPurpose = purpose.sort((a, b) => d3.descending(a[1],b[1])).slice(0,5) //take the top 5
sortedPurpose.forEach(([purp, freq]) => {
sortedPurpTopFive.push({
country: recep,
purpose: purp,
frequency: freq,
abbr: mapCountryAbbr.get(recep),
purpAbbr: purp.split(' ').map(d => d[0].toUpperCase()).join('')
})
})
})
return sortedPurpTopFive
}
Insert cell
top5PurpSmlMul = freqCntryPurp.map(([recep,purpose]) => {
const country = recep;
const purpSlice = purpose.sort((a,b) =>d3.descending(a[1],b[1])).slice(0,5);
const purp = purpSlice.map(p => p[0]);
const freq = purpSlice.map(f => f[1]);
const purposeX = d3.scaleBand().domain(purpose.map(p => p[0]))
.range([cols.bandwidth(),0]);
const purpAxis = d3.axisBottom(purposeX);
const freqY = d3.scaleLinear().domain(d3.extent(purpose.map(f => f[1]))).range([0,rows.bandwidth()])
const freqAxis = d3.axisLeft(freqY);
return {country, purp,purposeX, freq,freqY,purpAxis, freqAxis}
})
Insert cell
//array of Objects, containing the
- Country
- Purpose Abbr arry
- XScale
- Row
- Col
- Purpose freq Array
Insert cell
freqCntryDonationPurpose = d3.rollups(aiddata, g => g.length, d => d.donor, d => d.purpose)
Insert cell
topFivePurposesDonated = {
const sortedPurpTopFive =[];
freqCntryDonationPurpose.forEach(([donor, purpose]) => {
const sortedPurpose = purpose.sort((a, b) => d3.descending(a[1],b[1])).slice(0,5) //take the top 5
sortedPurpose.forEach(([purp, freq]) => {
sortedPurpTopFive.push({
country: donor,
purpose: purp,
frequency: freq
})
})
})
return sortedPurpTopFive
}
Insert cell
dimensions = {
const margin = { top: 30, bottom: 20, right: 10, left: 30 };
const visWidth = 1000 - margin.left - margin.right;
const visHeight = 800 - margin.top - margin.bottom;
return { margin, visWidth, visHeight };
}
Insert cell
numRows = 6
Insert cell
numCols = 8
Insert cell
rows = d3.scaleBand()
.domain(d3.range(numRows))
.range([0,dimensions.visHeight])
.padding(0.01)
Insert cell
cols = d3.scaleBand()
.domain(d3.range(numCols))
.range([0,dimensions.visWidth])
.padding(0.05)
Insert cell
maxFreq = d3.max(topFivePurposesReceipt, d => d.frequency)
Insert cell
yAxis = d3.scaleLinear()
.domain([maxFreq,0])
.range([0,rows.bandwidth()])
Insert cell
xAxis = d3.scaleBand()
.domain(d3.range(5))
.range([0,cols.bandwidth()])
Insert cell
area = d3.area()
.x((d,i) => xAxis(i))
.y1(d => yAxis(d))
.y0(d => yAxis(0))
Insert cell
bars = d3.selectAll('rect')
.attr('x',(d,i) => xAxis(i))
.attr('width',xAxis.bandwidth())
.attr('y',(d,i) => yAxis(0))
.attr('height',(d,i) => yAxis(d))
Insert cell
fivePurpSmallMultbar = {
const {visWidth, visHeight, margin} = dimensions;
const svg = d3.create('svg')
.attr('width',visWidth + margin.left + margin.right)
.attr('height',visHeight + margin.top + margin.bottom)
const g = svg.append('g')
.attr('font-family', 'sans-serif')
.attr('transform',`translate(${margin.left},${margin.top})`);

g.append('text')
.text('Top Five Purposes of Donations')

const cells = g.selectAll('g')
.data(top5PurpSmlMul)
.join('g')
.attr('transform',(d,i) => {
//i will be 0 to 44
const r = Math.floor(i / numCols);
const c = i % numCols;

return `translate(${cols(c)},${rows(r)})`
});

cells.each(function(d) {
// select the group for this cell
const group = d3.select(this);

// get the y-scale for this industry
const axis = d3.axisLeft(d.freqY)
.ticks(3)
.tickSizeOuter(0);
group.call(axis)
.call(g => g.select('.domain').remove())
});

cells.each(function (d) {
const bars = d3.select(this);
const rects = d3.selectAll('rect')
.data(d, e => e.freq)
.join('rect')
.attr('x',(r,i) => xAxis(i))
.attr('width',xAxis.bandwidth())
.attr('height',(r,i) =>d.freqY(r))
.attr('y',d.freqY(0))
})
const xAxes = cells.append('g')
.attr('transform', d => `translate(0,${rows.bandwidth()})`)
.call(d3.axisBottom(xAxis))
.call(g => g.select('.domain').remove())
return svg.node()
}
Insert cell
fivePurpSmallMult = {
const {visWidth, visHeight, margin} = dimensions;
const svg = d3.create('svg')
.attr('width',visWidth + margin.left + margin.right)
.attr('height',visHeight + margin.top + margin.bottom)
const g = svg.append('g')
.attr('font-family', 'sans-serif')
.attr('transform',`translate(${margin.left},${margin.top})`);

g.append('text')
.text('Top Five Purposes of Donations')

const cells = g.selectAll('g')
.data(top5PurpSmlMul)
.join('g')
.attr('transform',(d,i) => {
//i will be 0 to 44
const r = Math.floor(i / numCols);
const c = i % numCols;

return `translate(${cols(c)},${rows(r)})`
});

const countryText = cells
.append('path')
.attr('d', d => area(d.freq))
const xAxes = cells.append('g')
.attr('transform', d => `translate(0,${rows.bandwidth()})`)
.call(d3.axisBottom(xAxis))
const yAxes = cells.append('g')
.call(d3.axisLeft(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