Public
Edited
Nov 12
1 star
Insert cell
Insert cell
descripton = md`A recreation of [Gapminder visualization](http://gapminder.org/world/) by [Hans Rosling](https://www.ted.com/talks/hans_rosling_the_best_stats_you_ve_ever_seen) showing Tanzania regions' per-capita income (*x*) in TZS, life expectancy (*y*) over a period of 10 years. Colors reflect zones.

Data: [NBS - National Accounts Statistics](https://www.nbs.go.tz/index.php/en/census-surveys/national-accounts-statistics/na-publications), [NBS- 2012 Census Mortality & Health Monograph ](https://www.nbs.go.tz/index.php/en/census-surveys/population-and-housing-census)`
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);

svg.append("g")
.call(grid);

const circle = svg.append("g")
.attr("stroke", "black")
.selectAll("circle")
.data(dataAt(2002), d => d.name)
.join("circle")
.sort((a, b) => d3.descending(a.population, b.population))
.attr("cx", d => x(d.income))
.attr("cy", d => y(d.lifeExpectancy))
.attr("r", d => radius(d.population))
.attr("fill", d => color(d.region))
.call(circle => circle.append("title")
.text(d => [d.name, d.region].join("\n")));

return Object.assign(svg.node(), {
update(data) {
circle.data(data, d => d.name)
.sort((a, b) => d3.descending(a.population, b.population))
.attr("cx", d => x(d.income))
.attr("cy", d => y(d.lifeExpectancy))
.attr("r", d => radius(d.population));
}
});
}
Insert cell
update = chart.update(currentData)
Insert cell
currentData = dataAt(date)
Insert cell
x = d3.scaleLog([100000, 8e6], [margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear([30, 86], [height - margin.bottom, margin.top])
Insert cell
radius = d3.scaleSqrt([0, 1e7], [0, 25])
Insert cell
color = d3.scaleOrdinal(data.map(d => d.region), d3.schemeCategory10).unknown("black")
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80, ","))
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", width)
.attr("y", margin.bottom - 4)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text("Income per capita (TZS) →"))
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", -margin.left)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("↑ Life expectancy (years)"))
Insert cell
grid = g => g
.attr("stroke", "currentColor")
.attr("stroke-opacity", 0.1)
.call(g => g.append("g")
.selectAll("line")
.data(x.ticks())
.join("line")
.attr("x1", d => 0.5 + x(d))
.attr("x2", d => 0.5 + x(d))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom))
.call(g => g.append("g")
.selectAll("line")
.data(y.ticks())
.join("line")
.attr("y1", d => 0.5 + y(d))
.attr("y2", d => 0.5 + y(d))
.attr("x1", margin.left)
.attr("x2", width - margin.right));
Insert cell
function dataAt(date) {
return data.map(d => ({
name: d.name,
region: d.region,
income: valueAt(d.income, date),
population: valueAt(d.population, date),
lifeExpectancy: valueAt(d.lifeExpectancy, date)
}));
}
Insert cell
function valueAt(values, date) {
const i = bisectDate(values, date, 0, values.length - 1);
const a = values[i];
if (i > 0) {
const b = values[i - 1];
const t = (date - a[0]) / (b[0] - a[0]);
return a[1] * (1 - t) + b[1] * t;
}
return a[1];
}
Insert cell
data = (await FileAttachment("mikoa@4.json").json())
.map(({name, region, income, population, lifeExpectancy}) => ({
name,
region,
income: parseSeries(income),
population: parseSeries(population),
lifeExpectancy: parseSeries(lifeExpectancy)
}))
Insert cell
interval = d3.utcMonth // interval between animation frames
Insert cell
dates = interval.range(
d3.min(data, d => {
return d3.min([
d.income[0],
d.population[0],
d.lifeExpectancy[0]
], ([date]) => date);
}),
d3.min(data, d => {
return d3.max([
d.income[d.income.length - 1],
d.population[d.population.length - 1],
d.lifeExpectancy[d.lifeExpectancy.length - 1]
], ([date]) => date);
})
)
Insert cell
function parseSeries(series) {
return series.map(([year, value]) => [new Date(Date.UTC(year, 0, 1)), value]);
}
Insert cell
bisectDate = d3.bisector(([date]) => date).left
Insert cell
margin = ({top: 20, right: 20, bottom: 35, left: 40})
Insert cell
height = 560
Insert cell
d3 = require("d3@6.7.0/dist/d3.min.js")
Insert cell
import {Scrubber} from "@mbostock/scrubber"
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