Published
Edited
Apr 22, 2021
6 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dms = {
let dimensions = {
width: width,
heightTall: Math.max(width, 900),
heightShort: width * (9 / 16),
marginTop: 30,
marginLeft: 60
};

// dimensions.marginLeft = dimensions.marginTop;
dimensions.marginBottom = dimensions.marginTop;
dimensions.marginRight = dimensions.marginLeft;

dimensions.chartWidth =
dimensions.width - dimensions.marginLeft - dimensions.marginRight;
dimensions.chartHeightTall =
dimensions.heightTall - dimensions.marginTop - dimensions.marginBottom;
dimensions.chartHeightShort =
dimensions.heightShort - dimensions.marginTop - dimensions.marginBottom;

return dimensions;
}
Insert cell
Insert cell
timeParser = d3.timeParse("%Y-%0m-%0d")
Insert cell
timeFormatter = function(date, year = true) {
const months = [
"Jan.",
"Feb.",
"March",
"April",
"May",
"June",
"July",
"Aug.",
"Sept.",
"Oct.",
"Nov.",
"Dec."
];
const monthIndex = +d3.timeFormat('%m')(date) - 1;
const day = +d3.timeFormat('%e')(date);
let remainingDate = "";

year
? (remainingDate = d3.timeFormat(`${months[monthIndex]} %-e, %Y`)(date))
: (remainingDate = d3.timeFormat(`${months[monthIndex]} %-e`)(date));

return remainingDate;
}
Insert cell
Insert cell
Insert cell
crosswalk = {
const attachment = await FileAttachment("CrosswalkIncomeBracket.csv").text();

const incomeBracket = d3.rollup(
d3.csvParse(attachment).filter(d => d["Region"].length > 0),
v => v,
d => d["Country Code"]
);

const crosswalk = Array.from(dataGDP.values())
.map(d => {
return {
name: d["name"],
iso: d["iso"],
region:
incomeBracket.get(d["iso"]) == undefined
? undefined
: incomeBracket.get(d["iso"])[0]["Region"],
income:
incomeBracket.get(d["iso"]) == undefined
? undefined
: incomeBracket.get(d["iso"])[0]["IncomeGroup"]
};
})
.filter(d => d["region"] != undefined);

// return crosswalk;

return d3.rollup(crosswalk, v => v[0], d => d["iso"]);
}
Insert cell
dataGDP = {
const attachment = await FileAttachment("GDP@1.csv").text();

const attachmentProcessed = d3.csvParse(attachment).map(d => {
let gdpAll = [];
for (let i = 1961; i <= 2019; i++) {
+d[i] > 0 ? gdpAll.push(+d[i]) : null;
}

return {
name: d["Country Name"],
iso: d["Country Code"],
gdp: { 2019: d["2019"], 2018: d["2018"], 2017: d["2017"] },
gdpLatest: gdpAll[gdpAll.length - 1]
};
});

return d3.rollup(attachmentProcessed, v => v[0], d => d["iso"]);
}
Insert cell
dataPop = {
const attachment = await FileAttachment("Population-UN.csv").text();

const attachmentProcessed = d3
.csvParse(attachment)
.map(d => {
return {
name: d["entity"],
iso: d["iso_code"],
population: d["population"]
};
})
.filter(f => f["iso"].length > 0 && f["iso"].length <= 3);

return d3.rollup(attachmentProcessed, v => v[0], d => d["iso"]);
}
Insert cell
Insert cell
dataVaccinationsRAW = {
const attachment = await FileAttachment(
"country-vaccinations-historical-combined (2).csv"
).text();

const attachmentProcessed = d3.csvParse(attachment).map(d => {
d["date"] = timeParser(d["date"]);
return d;
});

return attachmentProcessed;
}
Insert cell
dataVaccinations = {
const input = dataVaccinationsRAW.sort((a, b) =>
d3.ascending(a["iso"], b["iso"])
);

return d3.rollup(
input,
v => {
const array = v.sort((a, b) => d3.ascending(a["date"], b["date"]));

const arrayFilled = array.map((d, i, arr) => {
// Filling in all the blanks in the vaccinated people data:
if (i > 0) {
d["vaccPeopleTotal"] =
+d["vaccPeopleTotal"] > 0
? +d["vaccPeopleTotal"]
: +arr[i - 1]["vaccPeopleTotal"];
} else {
d["vaccPeopleTotal"] = +d["vaccPeopleTotal"];
}

// Filling in all the blanks in the vaccine doses data:
if (i > 0) {
d["vaccDosesTotal"] =
+d["vaccDosesTotal"] > 0
? +d["vaccDosesTotal"]
: +arr[i - 1]["vaccDosesTotal"];
} else {
d["vaccDosesTotal"] = +d["vaccDosesTotal"];
}

d["dateFormatted"] = timeFormatter(d["date"]);
d["name"] = d["country"];
d["iso"] = d["countryIso"];
d["population"] = +d["populationTotal"];

return d;
});

return arrayFilled;
},
d => d["countryIso"]
);
}
Insert cell
Insert cell
dataCombined = {
// I had to filter the World Bank countries array so that I could
// match it to UN population data. I also had to filter out SARs
// in China because we lump that data in with China in the
// vaccination data.
const countriesAll = Array.from(crosswalk.keys())
.sort(d3.ascending)
.filter(f => dataPop.get(f) != undefined)
.filter(f => crosswalk.get(f)["name"].includes("SAR, China") == false);

// The filter on this data isn't really doing anything right now
// because it
const countriesWithVaccines = Array.from(dataVaccinations.keys())
.sort(d3.ascending)
.filter(f => countriesAll.includes(f));

return countriesAll.map(d => {
const obj = {
name: crosswalk.get(d)["name"],
iso: crosswalk.get(d)["iso"],
income: crosswalk.get(d)["income"],
gdp: dataGDP.get(d)["gdpLatest"],
population:
dataVaccinations.get(d) != undefined &&
dataVaccinations.get(d)["population"] > 0
? dataVaccinations.get(d)["population"]
: +dataPop.get(d)["population"],
vaccPeopleTotal:
dataVaccinations.get(d) == undefined
? 0
: dataVaccinations.get(d)[dataVaccinations.get(d).length - 1][
"vaccPeopleTotal"
],
vaccDosesTotal:
dataVaccinations.get(d) == undefined
? 0
: dataVaccinations.get(d)[dataVaccinations.get(d).length - 1][
"vaccDosesTotal"
]
};

obj["gdpPerCapita"] = obj["gdp"] / obj["population"];
obj["vaccPeoplePer100"] =
(obj["vaccPeopleTotal"] / obj["population"]) * 100;
obj["vaccDosesPer100k"] =
(obj["vaccDosesTotal"] / obj["population"]) * 100000;

return obj;
});
// .filter(f => f["population"] > 1000000);
// .filter(f => crosswalk.get(f["iso"])["income"] == "High income");
}
Insert cell
dataNodes = {
const nodeData = dataCombined;

return nodeData.map(d => {
d["x"] = dms.chartWidth / 2;

// d["x"] = dms.width / 2;
if (d["vaccDosesPer100k"] > 0) {
d["y"] = scaleDosesVacc(d["vaccDosesPer100k"]);
} else {
d["y"] = scaleDosesVacc(0) - (dms.chartHeightTall * 1) / 10;
}
return d;
});
}
Insert cell
Insert cell
scalePopVacc = d3
.scaleLinear()
.domain([0, d3.max(dataCombined.map(d => d["vaccPeoplePer100"]))])
// .domain([0, 100])
.range([0 + dms.chartHeightTall / 5, dms.chartHeightTall])
Insert cell
scaleDosesVacc = d3
.scaleLinear()
.domain([0, d3.max(dataCombined.map(d => d["vaccDosesPer100k"]))])
// .domain([0, 100])
.range([0 + dms.chartHeightTall / 5, dms.chartHeightTall])
Insert cell
scaleGDP = d3
.scaleLinear()
.domain([0, d3.max(dataCombined.map(d => d["gdp"]))])
.range([0, 20])
Insert cell
scaleGDPPerCapita = d3
.scaleLinear()
.domain([0, d3.max(dataCombined.map(d => d["gdpPerCapita"]))])
.range([0, 20])
Insert cell
scalePop = d3
.scaleLinear()
.domain([
d3.min(dataCombined.map(d => d["population"])),
d3.max(dataCombined.map(d => d["population"]))
])
.range([7, 700])
Insert cell
scaleIncome = d3
.scaleOrdinal()
.domain([
"Low income",
"Lower middle income",
"Upper middle income",
"High income"
])
.range(["#FBC654", "#D89D0F", "#A7CE95", "#64974B"])
Insert cell
Insert cell
scaleScatterGDP = d3
.scalePow()
.exponent(0.5)
.domain([0, d3.max(dataCombined.map(d => d["gdpPerCapita"]))])
.range([0, dms.chartWidth])
Insert cell
scaleScatterVacc = d3
.scaleLinear()
.domain([0, d3.max(dataCombined.map(d => d["vaccDosesPer100k"]))])
.range([dms.chartHeightShort, 0])
Insert cell
Insert cell
viewof ticks = Scrubber(d3.range(101), {
autoplay: false,
loop: false,
alternate: false,
initial: 100
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more