library(ggrepel)
library(gganimate)
library(ggtext)
# Search the icons you want to use with fa_icons() and note their names:
fa_icons(query = "earth")
set.seed(42)
# ── Expand to yearly frames via interpolation ─────────────────────────────
v_years_key <- c(1970, 1980, 1990, 2000, 2010, 2020)
v_years_fine <- seq(1970, 2020, by = 1)
v_countries <- c(
"Nigeria", "Ethiopia", "South Africa", "Kenya",
"USA", "Brazil", "Mexico", "Chile",
"Germany", "Spain", "Poland", "UK",
"Japan", "China", "India", "South Korea"
)
v_continents <- c(
rep("Africa", 4),
rep("Americas", 4),
rep("Europe", 4),
rep("Asia", 4)
)
v_icons <- c(
rep("earth-africa", 4),
rep("earth-americas", 4),
rep("earth-europe", 4),
rep("earth-asia", 4)
)
v_life_exp <- c(
45, 46, 45, 46, 52, 55,
42, 43, 45, 50, 62, 67,
53, 58, 61, 55, 56, 64,
50, 56, 58, 52, 60, 66,
71, 74, 75, 77, 79, 79,
59, 63, 66, 70, 73, 76,
62, 67, 70, 74, 75, 76,
63, 68, 73, 77, 79, 80,
71, 73, 75, 78, 80, 81,
72, 76, 77, 79, 82, 83,
70, 71, 71, 74, 76, 78,
72, 74, 76, 78, 81, 81,
72, 76, 79, 81, 83, 84,
61, 67, 69, 71, 75, 77,
49, 54, 59, 63, 67, 70,
62, 66, 71, 76, 80, 83
)
v_gdp <- c(
1100, 1500, 1300, 1100, 2100, 2000,
200, 150, 180, 600, 1400, 2200,
3500, 5000, 5200, 5000, 8000, 6000,
1000, 1400, 1600, 1500, 1800, 4500,
23000, 28000, 36000, 45000, 48000, 55000,
4000, 8000, 8000, 9000, 14000, 14000,
6000, 9000, 9000, 10000, 13000, 10000,
4000, 5000, 8000, 12000, 15000, 13000,
18000, 24000, 30000, 36000, 40000, 45000,
9000, 15000, 20000, 26000, 30000, 28000,
5000, 7000, 6000, 11000, 20000, 32000,
15000, 19000, 24000, 32000, 36000, 40000,
10000, 18000, 28000, 35000, 38000, 40000,
400, 700, 1000, 3000, 9000, 17000,
600, 800, 1200, 1700, 3500, 6000,
2000, 5000, 10000, 17000, 24000, 31000
)
# ── Build keyframe df ─────────────────────────────────────────────────────
df_key <- data.frame(
country = rep(v_countries, each = length(v_years_key)),
year = rep(v_years_key, times = length(v_countries)),
continent = rep(v_continents, each = length(v_years_key)),
icon = rep(v_icons, each = length(v_years_key)),
life_exp = v_life_exp,
gdp_pc = v_gdp
)
# ── Interpolate each country to yearly resolution ─────────────────────────
df_world <- df_key %>%
group_by(country, continent, icon) %>%
reframe(
year = v_years_fine,
life_exp = approx(v_years_key, life_exp, xout = v_years_fine)$y,
gdp_pc = approx(v_years_key, gdp_pc, xout = v_years_fine)$y
)
v_bg <- "#0A0E1A"
v_colors <- c(
"Africa" = "#FF80AB",
"Americas" = "#69F0AE",
"Europe" = "#4FC3F7",
"Asia" = "#FFD740"
)
p_anim <- ggplot(df_world,
aes(x = gdp_pc, y = life_exp,
color = continent, icon = icon)) +
geom_icon_point(size = 2.5, dpi = 100) +
geom_text_repel(
aes(label = country, color = continent),
size = 2.8,
fontface = "bold",
box.padding = unit(0.4, "lines"),
point.padding = unit(0.3, "lines"),
max.overlaps = Inf,
segment.color = "#546E7A",
segment.size = 0.3,
segment.alpha = 0.6,
seed = 42,
show.legend = FALSE
) +
scale_x_log10(
breaks = c(500, 1000, 5000, 10000, 50000),
labels = c("$500", "$1K", "$5K", "$10K", "$50K")
) +
scale_color_manual(values = v_colors) +
scale_legend_icon(size = 6) +
labs(
title = "A World Transformed",
subtitle = "Life expectancy vs. GDP per capita \u00b7 Year: {round(frame_time)}",
caption = "Source: World Bank / Gapminder \u00b7 Visualization: ggpop",
x = "GDP per Capita (USD, log scale)",
y = "Life Expectancy (years)",
color = "Region"
) +
theme_minimal(base_size = 13) +
theme(
plot.background = element_rect(fill = v_bg, color = NA),
panel.background = element_rect(fill = v_bg, color = NA),
panel.grid.major = element_line(color = "#1C2A3A", linewidth = 0.4),
panel.grid.minor = element_blank(),
legend.background = element_blank(),
legend.key = element_blank(),
axis.text = element_text(color = "#78909C", size = 10),
axis.title = element_text(color = "#90A4AE", size = 11),
plot.title = element_markdown(size = 22, face = "bold", hjust = 0.5,
color = "white", margin = margin(b = 6)),
plot.subtitle = element_text(size = 11, hjust = 0.5, color = "#78909C",
margin = margin(b = 16)),
plot.caption = element_text(size = 8.5, hjust = 0.5, color = "#546E7A",
lineheight = 1.4, margin = margin(t = 14)),
legend.position = "bottom",
legend.title = element_text(color = "#90A4AE", size = 10, face = "bold"),
legend.text = element_text(color = "#B0BEC5", size = 10),
plot.margin = margin(20, 24, 16, 20)
) +
transition_time(year) +
ease_aes("cubic-in-out")
animate(
p_anim,
nframes = 150, # ~3 frames per year across 51 years
fps = 12,
width = 1000,
height = 650,
res = 120,
renderer = gifski_renderer("world_transformed.gif")
)