Public
Edited
Nov 20, 2024
Insert cell
Insert cell
Insert cell
rawWorld = FileAttachment("countries-50m.json").json()
Insert cell
rawScholarData = FileAttachment("intl-scholars@2.csv").csv()
Insert cell
Insert cell
cleanedScholarData = rawScholarData
.map((d) => {
let newObj = {};

for (let prop in d) {
if (prop.substring(0, 1) == "2") {
newObj[prop] = parseFloat(d[prop]) ? parseFloat(d[prop]) : 0;
} else {
newObj[prop] = d[prop];
}
}
return newObj;
})
.filter((d) => d.entity == "country")
Insert cell
Insert cell
Insert cell
scholarWorldData = rawWorld.features.map((d) => ({
...d,
scholars: cleanedScholarData.find((e) => e.name == d.properties.SOVEREIGNT)
? cleanedScholarData.find((e) => e.name == d.properties.SOVEREIGNT)
: cleanedScholarData.find((e) => e.name == d.properties.NAME)
}))
Insert cell
Insert cell
scholarWorldData.filter((d) => !d.scholars).map((d) => d.properties.NAME)
Insert cell
Insert cell
margin = ({ top: 20, bottom: 100, sides: 20 })
Insert cell
worldProjection = d3
.geoEqualEarth()
.fitSize([width - margin.sides * 2, width / 2], worldOutline)
Insert cell
Insert cell
worldPath = d3.geoPath().projection(worldProjection)
Insert cell
Insert cell
worldOutline = d3.geoGraticule().outline()
Insert cell
graticules = d3.geoGraticule().step([15, 15])
Insert cell
Insert cell
colorScale = d3
.scaleLinear()
.domain(
d3.extent(scholarWorldData, (d) =>
d.scholars ? d.scholars[year] : 0
)
)
.range([0, 1])
Insert cell
Insert cell
//Inputs.select is a dropdown selection UI!
viewof year = Inputs.select(
Object.keys(scholarWorldData[0].scholars)
.filter((d) => d.substring(0, 1) == "2")
.filter((d) => d.split("-")[1] == "Scholars"),
{ value: "2019/20-Scholars" }
)
Insert cell
Insert cell
//find "Nigeria" in the list of countries, and get the user-selected year scholar count.
scholarWorldData.find((d) => d.properties.NAME == "Nigeria").scholars[year]
Insert cell
Insert cell
{
// create SVG artboard
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", width / 2 + margin.bottom);

// create background color rectangle
svg
.append("rect")
.attr("width", width)
.attr("height", width / 2 + 100)
.attr("fill", "#f9f9fe");

// add group for holding everything
const countryGroup = svg
.append("g")
.attr("transform", "translate(" + margin.sides + " " + margin.top + ")");

// fill world outline for ocean coloring
countryGroup
.append("path")
.attr("d", worldPath(worldOutline))
.attr("fill", "#4488cc");

// draw countries with choropleth coloring
countryGroup
.selectAll(".countries")
.data(scholarWorldData)
.join("path")
.attr("d", worldPath)
.attr("class", "countries")
.attr("fill", (d) => {
if (d.scholars) {
return d3.interpolateViridis(colorScale(d.scholars[year]));
} else {
return "gray";
}
})
.attr("stroke", "white")
.attr("stroke-width", 0.5)

// attach event listeners
.on("mouseover", (e, d) => {
d3.select(e.target).transition().attr("stroke-width", 2);
d3.select("#dataHolder").text(
`${d.properties.NAME} had ${
d.scholars[year]
} international scholars in school year ${year.split("-")[0]}`
);
})
.on("mouseout", (e, d) => {
d3.select(e.target).transition().attr("stroke-width", 0.5);
d3.select("#dataHolder").text("Mouseover a country for details");
});

// add the graticule lines
countryGroup
.append("path")
.attr("d", worldPath(graticules()))
.attr("stroke", "#dcdcdc")
.attr("stroke-width", 0.5)
.attr("fill", "none");

// create area for text to be displayed
svg
.append("text")
.attr("x", width / 2)
.attr("y", width / 2 + margin.bottom / 2)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("id", "dataHolder")
.attr("font-family", "Futura")
.text("Mouseover a country for details");

return svg.node();
}
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