library(ggplot2)
library(dplyr)
library(readr)
library(maps)
library(cowplot)
library(ggrepel)
library(tidyr)
library(scales)
# ── Shared palette ───────────────────────────────────────────
BG <- "#0D1117"
PANEL <- "#161B22"
GRID <- "#21262D"
TEXT1 <- "#E6EDF3"
TEXT2 <- "#8B949E"
RED <- "#E74C3C"
AMBER <- "#F39C12"
AMBER_BRIGHT <- "#FFD88C"
GREEN <- "#2ECC71"
GREEN_LIGHT <- "#B5ddD0"
GREY_DOT <- "#4A5568"
status_colors <- c("Competitor" = RED, "Neutral" = AMBER, "Ally" = GREEN)
status_fill <- c("Competitor" = RED, "Neutral" = AMBER, "Ally" = GREEN)
theme_base <- function(base_size = 12) {
theme_minimal(base_size = base_size) +
theme(
plot.background = element_rect(fill = BG, color = NA),
panel.background = element_rect(fill = PANEL, color = NA),
panel.grid.major = element_line(color = GRID, linewidth = 0.35),
panel.grid.minor = element_blank(),
axis.text = element_text(color = TEXT2, size = 10),
axis.title = element_text(color = TEXT2, size = 11),
plot.title = element_text(color = TEXT1, size = 17, face = "bold",
margin = margin(b = 5)),
plot.subtitle = element_text(color = TEXT2, size = 10,
lineheight = 1.4, margin = margin(b = 12)),
plot.caption = element_text(color = TEXT2, size = 8,
margin = margin(t = 8)),
legend.background = element_rect(fill = PANEL, color = NA),
legend.text = element_text(color = TEXT2, size = 9),
legend.title = element_text(color = TEXT1, size = 10, face = "bold"),
plot.margin = margin(20, 24, 14, 20)
)
}
wide <- read_csv("data/minerals_wide.csv",
show_col_types = FALSE)
src <- read_csv("data/sources_long.csv",
show_col_types = FALSE)
# SLIDE 1 — WORLD MAP + FLOATING DONUT
top10_meta <- data.frame(
region = c("China","South Africa","Canada","Brazil","Japan",
"Democratic Republic of the Congo","Russia",
"Estonia","Mexico","Australia"),
status = c("Competitor","Neutral","Ally","Neutral","Ally",
"Neutral","Competitor","Neutral","Neutral","Ally"),
stringsAsFactors = FALSE
)
world <- map_data("world") %>%
left_join(top10_meta, by = "region", relationship = "many-to-many")
ann <- data.frame(
country = c("China","Russia","South Africa","Canada","Japan",
"Brazil","DRC","Estonia","Mexico","Australia"),
dep = c(2108, 87, 366, 274, 106, 106, 89, 54, 42, 38),
status = c("Competitor","Competitor","Neutral","Ally","Ally",
"Neutral","Neutral","Neutral","Neutral","Ally"),
# Label anchor (lon/lat in data space)
lx = c( 110, 70, 14, -75, 155, -72, 40, 8, -148, 148),
ly = c( 10, 76, -57, 75, 10, -52, 5, 72, 30, -51),
# Leader line endpoint on country
sx = c( 103, 95, 26, -95, 138, -54, 25, 24, -102, 134),
sy = c( 34, 60, -30, 60, 36, -12, 1, 59, 22, -27),
stringsAsFactors = FALSE
)
ann$color <- status_fill[ann$status]
ann$label_sub <- paste0("Score: ", ann$dep)
p_map <- ggplot() +
geom_polygon(data = world %>% filter(is.na(status)),
aes(x = long, y = lat, group = group),
fill = "#1C2333", color = "#0A0F1A", linewidth = 0.12) +
geom_polygon(data = world %>% filter(!is.na(status)),
aes(x = long, y = lat, group = group, fill = status),
color = "#0A0F1A", linewidth = 0.2, alpha = 0.88) +
scale_fill_manual(values = status_fill, na.value = "#1C2333", guide = "none") +
geom_segment(data = ann,
aes(x = lx, y = ly, xend = sx, yend = sy),
color = "#4A5568", linewidth = 0.35, lineend = "round") +
geom_point(data = ann, aes(x = sx, y = sy, color = status),
size = 2.0, show.legend = FALSE) +
geom_text(data = ann,
aes(x = lx, y = ly + 2.5, label = country, color = status),
size = 3.7, fontface = "bold", hjust = 0.5, show.legend = FALSE) +
geom_text(data = ann,
aes(x = lx, y = ly - 1.5, label = label_sub, color = status),
size = 2.7, hjust = 0.5, alpha = 0.75, show.legend = FALSE) +
scale_color_manual(values = status_fill) +
coord_fixed(ratio = 1.3, xlim = c(-165, 175), ylim = c(-60, 90)) +
labs(
title = "Who Controls America's Critical Minerals?",
subtitle = "64 % By Competitors | 20% on Neutrals | Allies only 13 % ",
caption = ""
) +
theme_void() +
theme(
plot.background = element_rect(fill = BG, color = NA),
panel.background = element_rect(fill = BG, color = NA),
plot.title = element_text(color = TEXT1, size = 21, face = "bold",
hjust = 0.5, margin = margin(t = 14, b = 5)),
plot.subtitle = element_text(color = TEXT2, size = 10.5,
hjust = 0.5, margin = margin(b = 4)),
plot.caption = element_text(color = TEXT2, size = 8,
hjust = 0.5, margin = margin(t = 8)),
plot.margin = margin(4, 16, 4, 16)
)
donut_df <- data.frame(
grp = c("Competitor", "Neutral", "Ally", "pad"),
value = c(64.5, 20.1, 12.8, 2.6),
stringsAsFactors = FALSE
) %>%
mutate(
grp = factor(grp, levels = grp),
ymax = cumsum(value),
ymin = lag(ymax, default = 0),
ymid = (ymin + ymax) / 2
)
lbl <- donut_df %>% filter(grp != "pad")
p_donut <- ggplot(donut_df) +
geom_rect(aes(xmin = 2.4, xmax = 4.0, ymin = ymin, ymax = ymax, fill = grp),
color = BG, linewidth = 0.9) +
geom_text(data = lbl,
aes(x = 5.2, y = ymid, label = paste0(value, "%"), color = grp),
size = 3.0, fontface = "bold", hjust = 0.5) +
geom_text(data = lbl,
aes(x = 5.2, y = ymid - 6.5, label = grp, color = grp),
size = 2.2, hjust = 0.5, alpha = 0.85) +
annotate("text", x = 0, y = 50, label = "Dependency\nSplit",
color = TEXT2, size = 2.6, hjust = 0.5, vjust = 0.5, lineheight = 1.3) +
scale_fill_manual(values = c("Competitor" = RED, "Neutral" = AMBER,
"Ally" = GREEN, "pad" = "#00000000")) +
scale_color_manual(values = c("Competitor" = RED, "Neutral" = AMBER,
"Ally" = GREEN, "pad" = PANEL)) +
coord_polar(theta = "y", start = 0, clip = "off") +
xlim(c(-1, 7)) + ylim(c(0, 100)) +
theme_void() +
theme(
legend.position = "none",
plot.background = element_rect(fill = NA, color = NA), # transparent
panel.background = element_rect(fill = NA, color = NA),
plot.margin = margin(8, 8, 8, 8)
)
slide1 <- ggdraw(p_map) +
draw_plot(p_donut, x = 0.38, y = 0.04, width = 0.20, height = 0.30)
ggsave("s1_world_map.png",slide1, width = 16, height = 9, dpi = 200, bg = BG)
slide1

# SLIDE 2 — RISK MATRIX SCATTER
plot_s2 <- wide %>%
mutate(
geopolitical_risk = (us_import_pct / 100) * (competitor_share / 100),
status = case_when(
top_status == "Competitor" ~ "Competitor",
top_status == "Ally" ~ "Ally",
TRUE ~ "Neutral"
),
label_flag = mineral %in% c(
"Neodymium","Dysprosium","Terbium","Gallium","Graphite",
"Palladium","Cobalt","Rhodium","Manganese","Niobium",
"Magnesium","Bismuth","Scandium","Fluorspar","Germanium",
"Arsenic","Antimony","Lanthanum","Nickel"
)
)
p2 <- ggplot(plot_s2, aes(x = us_import_pct, y = competitor_share)) +
annotate("rect", xmin = 50, xmax = 102, ymin = 50, ymax = 102,
fill = RED, alpha = 0.06) +
annotate("segment", x = 50, xend = 50, y = 0, yend = 100,
color = "#333D4D", linetype = "dashed", linewidth = 0.5) +
annotate("segment", x = 0, xend = 100, y = 50, yend = 50,
color = "#333D4D", linetype = "dashed", linewidth = 0.5) +
annotate("text", x = 99, y = 99, label = "CRITICAL ZONE",
color = RED, size = 3.0, hjust = 1, fontface = "bold", alpha = 0.7) +
geom_point(aes(color = status, size = geopolitical_risk), alpha = 0.88) +
geom_label_repel(
data = filter(plot_s2, label_flag),
aes(label = mineral, color = status),
size = 2.8, fill = BG, label.padding = 0.15,
label.size = 0.15, box.padding = 0.5, point.padding = 0.3,
max.overlaps = 40, segment.color = "#4A5568", segment.size = 0.3,
fontface = "bold", min.segment.length = 0.2
) +
scale_color_manual(values = status_colors,
name = "Primary source\ncountry status") +
scale_size_continuous(range = c(2, 9), guide = "none") +
scale_x_continuous(limits = c(-2, 107),
labels = function(x) paste0(x, "%"),
breaks = seq(0, 100, 25)) +
scale_y_continuous(limits = c(-2, 104),
labels = function(x) paste0(x, "%"),
breaks = seq(0, 100, 25)) +
labs(
title = "The Risk Matrix: Where Are We Most Exposed",
subtitle = "Most Minerals In The Critical Zone | High Import Dependance & High Competitor Import Share",
x = "US Import Dependency (% of needs imported)",
y = "Competitor Supply Share (% sourced from competitors)",
caption = ""
) +
theme_base() +
theme(legend.position = "right") +
guides(color = guide_legend(override.aes = list(size = 4)))
ggsave("s2_risk_matrix.png",
p2, width = 12, height = 7.5, dpi = 180, bg = BG)
p2

# SLIDE 3 — REAL WORLD IMPACT HEATMAP
app_minerals <- read_csv("data/s3_app_minerals.csv",
show_col_types = FALSE)
app_order <- c("Defense & Military", "Semiconductors", "Clean Energy",
"Electric Vehicles", "Medical & Healthcare",
"Infrastructure & Steel")
app_exposure <- app_minerals %>%
group_by(application) %>%
summarise(
n_total = n(),
pct_comp = round(sum(status == "Competitor") / n() * 100),
exposure = case_when(
pct_comp >= 70 ~ "CRITICAL",
pct_comp >= 50 ~ "HIGH",
pct_comp >= 40 ~ "MODERATE",
TRUE ~ "LOW"
),
.groups = "drop"
)
exp_colors <- c("CRITICAL" = RED, "HIGH" = "#C0392B",
"MODERATE" = AMBER, "LOW" = GREEN)
app_minerals_plot <- app_minerals %>%
mutate(
application = factor(application, levels = rev(app_order)),
status = factor(status, levels = c("Competitor", "Neutral", "Ally"))
) %>%
arrange(application, status, mineral) %>%
group_by(application) %>%
mutate(min_rank = row_number()) %>%
ungroup()
app_exposure_plot <- app_exposure %>%
mutate(
application = factor(application, levels = rev(app_order)),
# Direct color lookup — no fill scale involved
exp_color = exp_colors[exposure]
)
p3 <- ggplot(app_minerals_plot,
aes(x = min_rank, y = application, fill = status)) +
# Mineral tiles
geom_tile(color = BG, linewidth = 0.8,
width = 0.92, height = 0.72, alpha = 0.9) +
geom_text(aes(label = mineral, color = status),
size = 2.6, fontface = "bold") +
geom_rect(data = app_exposure_plot,
aes(xmin = 8.85, xmax = 10.75,
ymin = as.numeric(application) - 0.36,
ymax = as.numeric(application) + 0.36),
fill = app_exposure_plot$exp_color,
color = BG, linewidth = 0.8,
inherit.aes = FALSE, alpha = 0.88) +
geom_text(data = app_exposure_plot,
aes(x = 9.8, y = application, label = exposure),
color = BG, size = 2.7, fontface = "bold",
inherit.aes = FALSE) +
# Column headers
annotate("text", x = 9.8, y = 6.72,
label = "OVERALL\nEXPOSURE",
color = TEXT1, size = 2.8, fontface = "bold",
hjust = 0.5, lineheight = 1.2) +
annotate("text", x = 4.0, y = 6.72,
label = "KEY MINERALS (competitors first within each sector)",
color = TEXT2, size = 2.8, hjust = 0.5) +
# Divider between minerals and exposure column
annotate("segment", x = 8.4, xend = 8.4, y = 0.5, yend = 6.5,
color = GRID, linewidth = 0.5) +
scale_fill_manual(values = status_colors, name = "Source status",
guide = guide_legend(
override.aes = list(color = NA, size = 5))) +
scale_color_manual(values = c("Competitor" = BG,
"Neutral" = BG,
"Ally" = BG),
guide = "none") +
scale_x_continuous(limits = c(0.3, 11.2),
expand = expansion(mult = 0)) +
scale_y_discrete(expand = expansion(add = c(0.6, 1.05))) +
labs(
title = "Real World Impact : What Actually Stops Working",
subtitle = paste0(
"Critical Mineral Dependencies Have Wide Ranging Real World Impact "
),
x = NULL, y = NULL,
caption = ""
) +
theme_minimal(base_size = 11) +
theme(
plot.background = element_rect(fill = BG, color = NA),
panel.background = element_rect(fill = PANEL, color = NA),
panel.grid = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(color = TEXT1, size = 11.5, face = "bold"),
plot.title = element_text(color = TEXT1, size = 17, face = "bold",
margin = margin(b = 5)),
plot.subtitle = element_text(color = TEXT2, size = 10,
lineheight = 1.4, margin = margin(b = 12)),
plot.caption = element_text(color = TEXT2, size = 8,
margin = margin(t = 8)),
legend.position = "top",
legend.direction = "horizontal",
legend.background= element_rect(fill = PANEL, color = NA),
legend.text = element_text(color = TEXT2, size = 9),
legend.title = element_text(color = TEXT1, size = 10, face = "bold"),
plot.margin = margin(20, 24, 14, 20)
)
ggsave("s3_impact_heatmap.png",
p3, width = 14, height = 8, dpi = 180, bg = BG)
cat("Done.\n")
## Done.
p3

# SLIDE 4 — SWING STATES: SPLIT CIRCLE ARC MATRIX
cell_data <- read_csv("data/s4_swing_cells.csv", show_col_types = FALSE)
wants <- c(
"South Africa" = "Investment & trade access",
"Brazil" = "Market access & tech transfer",
"DRC" = "Stability & security",
"Estonia" = "Defense tech cooperation",
"Mexico" = "USMCA renegotiation"
)
country_levels <- c("South Africa","Brazil","DRC","Estonia","Mexico")
sector_levels <- c("Defense & Military","Semiconductors",
"Clean Energy","Electric Vehicles",
"Infrastructure & Steel")
cell_data <- cell_data %>%
mutate(
country_label = ifelse(country == "South Africa", "S. Africa", country),
country_label = factor(country_label,
levels = gsub("South Africa", "S. Africa",
country_levels)),
sector = factor(sector, levels = sector_levels)
)
bars <- cell_data %>%
pivot_longer(
cols = c(current, potential),
names_to = "scenario",
values_to = "score"
) %>%
mutate(
scenario = factor(scenario,
levels = c("current","potential"),
labels = c("Current","Potential if secured"))
)
p_bars <- ggplot(bars, aes(x = scenario, y = score, fill = scenario)) +
geom_col(width = 0.60, alpha = 0.92) +
geom_text(
aes(label = score, color = scenario),
vjust = -0.35, size = 2.5, fontface = "bold"
) +
# Mineral label inside base of potential bar
geom_text(
data = filter(bars, scenario == "Potential if secured"),
aes(x = 2, y = 4,
label = cell_data$minerals[
match(paste(country_label, sector),
paste(cell_data$country_label, cell_data$sector))]),
vjust = 0, hjust = 0.5, size = 1.9,
color = BG, fontface = "bold",
inherit.aes = FALSE
) +
scale_fill_manual(
values = c("Current" = AMBER, "Potential if secured" = AMBER_BRIGHT),
name = NULL
) +
scale_color_manual(
values = c("Current" = AMBER, "Potential if secured" = AMBER_BRIGHT),
guide = "none"
) +
scale_y_continuous(
limits = c(0, 380),
expand = expansion(mult = c(0, 0.12)),
breaks = c(0, 100, 200, 300)
) +
facet_grid(country_label ~ sector, drop = FALSE, switch = "y") +
labs(
title = "Swing States | Masive Untapped Potential",
subtitle = "Each Of These Countries Can Help Us If We Help Them",
x = NULL,
y = "",
caption = ""
) +
theme_minimal(base_size = 10) +
theme(
plot.background = element_rect(fill = BG, color = NA),
panel.background = element_rect(fill = PANEL, color = NA),
panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(color = GRID, linewidth = 0.25),
panel.grid.minor = element_blank(),
panel.spacing.x = unit(0.3, "lines"),
panel.spacing.y = unit(0.4, "lines"),
strip.text.x = element_text(color = TEXT1, size = 9.5, face = "bold",
margin = margin(b = 6, t = 4)),
strip.text.y.left = element_text(color = TEXT1, size = 10.5, face = "bold",
angle = 0, hjust = 1,
margin = margin(r = 8, l = 4)),
strip.placement = "outside",
axis.text.x = element_text(color = TEXT2, size = 8.5),
axis.text.y = element_text(color = TEXT2, size = 8),
axis.title.y = element_text(color = TEXT2, size = 9,
margin = margin(r = 5)),
legend.position = "top",
legend.direction = "horizontal",
legend.text = element_text(color = TEXT2, size = 9.5),
legend.key.size = unit(0.42, "cm"),
plot.title = element_text(color = TEXT1, size = 16, face = "bold",
margin = margin(b = 4)),
plot.subtitle = element_text(color = TEXT2, size = 9.5,
lineheight = 1.4, margin = margin(b = 10)),
plot.caption = element_text(color = TEXT2, size = 8,
margin = margin(t = 8)),
plot.margin = margin(20, 20, 14, 60)
)
p_wants <- ggplot() +
annotate("text", x = 0.05, y = 0.88,
label = paste0("Wants: ", wants["South Africa"]),
color = AMBER, size = 2.7, hjust = 0,
fontface = "italic", alpha = 0.88) +
annotate("text", x = 0.05, y = 0.68,
label = paste0("Wants: ", wants["Brazil"]),
color = AMBER, size = 2.7, hjust = 0,
fontface = "italic", alpha = 0.88) +
annotate("text", x = 0.05, y = 0.50,
label = paste0("Wants: ", wants["DRC"]),
color = AMBER, size = 2.7, hjust = 0,
fontface = "italic", alpha = 0.88) +
annotate("text", x = 0.05, y = 0.30,
label = paste0("Wants: ", wants["Estonia"]),
color = AMBER, size = 2.7, hjust = 0,
fontface = "italic", alpha = 0.88) +
annotate("text", x = 0.05, y = 0.12,
label = paste0("Wants: ", wants["Mexico"]),
color = AMBER, size = 2.7, hjust = 0,
fontface = "italic", alpha = 0.88) +
xlim(0, 1) + ylim(0, 1) +
theme_void() +
theme(
plot.background = element_rect(fill = BG, color = NA),
plot.margin = margin(68, 10, 28, 0)
)
final <- plot_grid(
p_bars, p_wants,
ncol = 2,
rel_widths = c(1, 0.30),
align = "h",
axis = "tb"
)
ggsave("s4_swing_bars.png",
final, width = 16, height = 10, dpi = 180, bg = BG)
final

# ALLY STATES
cell_data <- read_csv("data/s5_ally_cells.csv", show_col_types = FALSE)
notes <- c(
"Canada" = "12 mineral chains · largest ally contributor · underutilized",
"Japan" = "World-class REE processing · the tech bridge the US isn't using",
"Australia" = "Largest untapped reserves · Compacts signed . investment not there yet"
)
country_levels <- c("Canada","Japan","Australia")
sector_levels <- c("Defense & Military","Semiconductors",
"Clean Energy","Electric Vehicles",
"Infrastructure & Steel")
cell_data <- cell_data %>%
mutate(
country_label = case_when(
country == "Australia" ~ "Aus.",
TRUE ~ country
),
country_label = factor(country_label,
levels = c("Canada","Japan","Aus.")),
sector = factor(sector, levels = sector_levels)
)
bars <- cell_data %>%
pivot_longer(
cols = c(current, potential),
names_to = "scenario",
values_to = "score"
) %>%
mutate(
scenario = factor(scenario,
levels = c("current","potential"),
labels = c("Current","Potential if prioritized"))
)
p_bars <- ggplot(bars, aes(x = scenario, y = score, fill = scenario)) +
geom_col(width = 0.60, alpha = 0.92) +
# Value labels
geom_text(
aes(label = score, color = scenario),
vjust = -0.35, size = 2.5, fontface = "bold"
) +
# Mineral label inside base of potential bar
geom_text(
data = filter(bars, scenario == "Potential if prioritized"),
aes(x = 2, y = 4,
label = cell_data$minerals[
match(paste(country_label, sector),
paste(cell_data$country_label, cell_data$sector))]),
vjust = 0, hjust = 0.5, size = 1.9,
color = "#0D1117", fontface = "bold",
inherit.aes = FALSE
) +
scale_fill_manual(
values = c("Current" = GREEN, "Potential if prioritized" = GREEN_LIGHT),
name = NULL
) +
scale_color_manual(
values = c("Current" = GREEN, "Potential if prioritized" = GREEN_LIGHT),
guide = "none"
) +
scale_y_continuous(
limits = c(0, 290),
expand = expansion(mult = c(0, 0.12)),
breaks = c(0, 100, 200)
) +
scale_x_discrete(labels = c("Now", "Potential")) +
# Countries on LEFT, sectors across TOP
facet_grid(country_label ~ sector, drop = FALSE, switch = "y") +
labs(
title = "Allied Nations | Improving Existing Support Infrastructure ",
subtitle = "Our Main Allies Can Have An Even Bigger Impact if Maximised",
x = NULL,
y = "Contribution Score",
caption = ""
) +
theme_minimal(base_size = 10) +
theme(
plot.background = element_rect(fill = BG, color = NA),
panel.background = element_rect(fill = PANEL, color = NA),
panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(color = GRID, linewidth = 0.25),
panel.grid.minor = element_blank(),
panel.spacing.x = unit(0.3, "lines"),
panel.spacing.y = unit(0.4, "lines"),
strip.text.x = element_text(color = TEXT1, size = 9.5, face = "bold",
margin = margin(b = 6, t = 4)),
strip.text.y.left = element_text(color = TEXT1, size = 11, face = "bold",
angle = 0, hjust = 1,
margin = margin(r = 8, l = 4)),
strip.placement = "outside",
axis.text.x = element_text(color = TEXT2, size = 8.5),
axis.text.y = element_text(color = TEXT2, size = 8),
axis.title.y = element_text(color = TEXT2, size = 9,
margin = margin(r = 5)),
legend.position = "top",
legend.direction = "horizontal",
legend.text = element_text(color = TEXT2, size = 9.5),
legend.key.size = unit(0.42, "cm"),
plot.title = element_text(color = TEXT1, size = 16, face = "bold",
margin = margin(b = 4)),
plot.subtitle = element_text(color = TEXT2, size = 9.5,
lineheight = 1.4, margin = margin(b = 10)),
plot.caption = element_text(color = TEXT2, size = 8,
margin = margin(t = 8)),
plot.margin = margin(20, 20, 14, 80)
)
p_notes <- ggplot() +
annotate("text", x = 0.05, y = 0.83,
label = notes["Canada"],
color = GREEN, size = 2.7, hjust = 0,
fontface = "italic", alpha = 0.88) +
annotate("text", x = 0.05, y = 0.50,
label = notes["Japan"],
color = GREEN, size = 2.7, hjust = 0,
fontface = "italic", alpha = 0.88) +
annotate("text", x = 0.05, y = 0.17,
label = notes["Australia"],
color = GREEN, size = 2.7, hjust = 0,
fontface = "italic", alpha = 0.88) +
xlim(0, 1) + ylim(0, 1) +
theme_void() +
theme(
plot.background = element_rect(fill = BG, color = NA),
plot.margin = margin(55, 10, 28, 0)
)
finalally <- plot_grid(
p_bars, p_notes,
ncol = 2,
rel_widths = c(1, 0.38),
align = "h",
axis = "tb"
)
ggsave("s5_ally_bars.png",
finalally, width = 16, height = 8, dpi = 180, bg = BG)
finalally

BG <- "#0D1117"; PANEL <- "#161B22"; GRID <- "#21262D"
TEXT1 <- "#E6EDF3"; TEXT2 <- "#8B949E"
RED <- "#E74C3C"; AMBER <- "#F39C12"; GREEN <- "#2ECC71"
exp_colors <- c("CRITICAL"=RED, "HIGH"="#C0392B", "MODERATE"=AMBER, "LOW"=GREEN)
app_order <- c("Defense & Military","Semiconductors","Clean Energy",
"Electric Vehicles","Medical & Healthcare","Infrastructure & Steel")
sector_data <- data.frame(
application = factor(app_order, levels = rev(app_order)),
today = c("CRITICAL","CRITICAL","CRITICAL","HIGH","HIGH","MODERATE"),
driver = c(
"Secure Estonia REE processing\n+ ally defense supply chains",
"Invest in Japan & Estonia\nREE refining capacity",
"Activate Australia compact\n+ South Africa cobalt deal",
"DRC cobalt deal\n+ Australia lithium investment",
"Domestic REE processing\ninvestment required",
"Brazil niobium partnership\n+ Mexico USMCA terms"
),
result = c("HIGH","HIGH","MODERATE","MODERATE","MODERATE","LOW"),
stringsAsFactors = FALSE
)
# Donut
make_donut <- function(comp, neut, ally, label1, label2) {
pad <- max(0, 100 - comp - neut - ally)
df <- data.frame(
grp = c("Competitor","Neutral","Ally","pad"),
value = c(comp, neut, ally, pad),
stringsAsFactors = FALSE
) %>%
mutate(grp = factor(grp, levels = grp),
ymax = cumsum(value),
ymin = lag(ymax, default = 0),
ymid = (ymin + ymax) / 2)
lbl <- filter(df, grp != "pad")
ggplot(df) +
geom_rect(aes(xmin = 2.3, xmax = 4.0,
ymin = ymin, ymax = ymax, fill = grp),
color = BG, linewidth = 0.7) +
geom_text(data = lbl,
aes(x = 5.1, y = ymid,
label = paste0(value, "%"), color = grp),
size = 2.9, fontface = "bold", hjust = 0.5) +
geom_text(data = lbl,
aes(x = 5.1, y = ymid - 6.5, label = grp, color = grp),
size = 2.1, hjust = 0.5, alpha = 0.8) +
annotate("text", x = 0, y = 50,
label = paste0(label1, "\n", label2),
color = TEXT2, size = 2.5, hjust = 0.5, vjust = 0.5,
lineheight = 1.3) +
scale_fill_manual(values = c("Competitor"=RED,"Neutral"=AMBER,
"Ally"=GREEN,"pad"=PANEL)) +
scale_color_manual(values = c("Competitor"=RED,"Neutral"=AMBER,
"Ally"=GREEN,"pad"=PANEL)) +
coord_polar(theta = "y", start = 0, clip = "off") +
xlim(c(-1, 7)) + ylim(c(0, 100)) +
theme_void() +
theme(
legend.position = "none",
plot.background = element_rect(fill = BG, color = NA),
panel.background = element_rect(fill = BG, color = NA),
plot.margin = margin(4, 4, 4, 4)
)
}
d_before <- make_donut(64.5, 20.1, 12.8, "TODAY", "Dependency split")
d_after <- make_donut(38.0, 35.0, 27.0, "AFTER", "If we act")
# Column 1: Category labels
p_cat <- ggplot(sector_data, aes(y = application)) +
geom_text(aes(x = 1, label = as.character(application)),
color = TEXT1, size = 3.4, fontface = "bold", hjust = 0.5) +
scale_x_continuous(limits = c(0.5, 1.5), expand = expansion(mult = 0)) +
scale_y_discrete(expand = expansion(add = c(0.5, 0.7))) +
labs(title = "SECTOR", x = NULL, y = NULL) +
theme_void() +
theme(plot.background = element_rect(fill = BG, color = NA),
plot.title = element_text(color = TEXT1, size = 11, face = "bold",
hjust = 0.5, margin = margin(b = 8)),
plot.margin = margin(14, 6, 12, 10))
# ── Column 2: Today exposure tiles
p_today <- ggplot(sector_data, aes(y = application)) +
geom_tile(aes(x = 1, fill = today),
width = 0.85, height = 0.72, color = BG, linewidth = 0.8) +
geom_text(aes(x = 1, label = today),
color = BG, size = 2.8, fontface = "bold") +
scale_fill_manual(values = exp_colors, guide = "none") +
scale_x_continuous(limits = c(0.5, 1.5), expand = expansion(mult = 0)) +
scale_y_discrete(expand = expansion(add = c(0.5, 0.7))) +
labs(title = "TODAY", x = NULL, y = NULL) +
theme_void() +
theme(plot.background = element_rect(fill = BG, color = NA),
plot.title = element_text(color = TEXT1, size = 11, face = "bold",
hjust = 0.5, margin = margin(b = 8)),
plot.margin = margin(14, 4, 12, 4))
# ── Column 4: What drives the change
p_driver <- ggplot(sector_data, aes(y = application)) +
geom_text(aes(x = 1, label = driver),
color = TEXT2, size = 2.65, hjust = 0.5, lineheight = 1.35) +
scale_x_continuous(limits = c(0.5, 1.5), expand = expansion(mult = 0)) +
scale_y_discrete(expand = expansion(add = c(0.5, 0.7))) +
labs(title = "WHAT DRIVES THE CHANGE", x = NULL, y = NULL) +
theme_void() +
theme(plot.background = element_rect(fill = BG, color = NA),
plot.title = element_text(color = TEXT1, size = 11, face = "bold",
hjust = 0.5, margin = margin(b = 8)),
plot.margin = margin(14, 8, 12, 8))
# Column 6: Result tiles
p_result <- ggplot(sector_data, aes(y = application)) +
geom_tile(aes(x = 1, fill = result),
width = 0.85, height = 0.72, color = BG, linewidth = 0.8) +
geom_text(aes(x = 1, label = result),
color = BG, size = 2.8, fontface = "bold") +
scale_fill_manual(values = exp_colors, guide = "none") +
scale_x_continuous(limits = c(0.5, 1.5), expand = expansion(mult = 0)) +
scale_y_discrete(expand = expansion(add = c(0.5, 0.7))) +
labs(title = "RESULT", x = NULL, y = NULL) +
theme_void() +
theme(plot.background = element_rect(fill = BG, color = NA),
plot.title = element_text(color = GREEN, size = 11, face = "bold",
hjust = 0.5, margin = margin(b = 8)),
plot.margin = margin(14, 10, 12, 4))
# Arrow between donuts
p_arrow <- ggplot() +
geom_segment(
data = data.frame(x=0.1, xend=0.9, y=0.5, yend=0.5),
aes(x=x, xend=xend, y=y, yend=yend),
color = GREEN, linewidth = 1.4,
arrow = arrow(length = unit(0.12, "inches"), type = "closed")
) +
xlim(0,1) + ylim(0,1) +
theme_void() +
theme(plot.background = element_rect(fill = BG, color = NA))
# Title & caption
p_title <- ggplot() +
annotate("text", x=0.5, y=0.70,
label = "A Conscious Decision For A Secure Future ",
color = TEXT1, size = 8, fontface = "bold", hjust = 0.5) +
annotate("text", x=0.5, y=0.22,
label = "",
color = TEXT2, size = 3.4, hjust = 0.5) +
xlim(0,1) + ylim(0,1) + theme_void() +
theme(plot.background = element_rect(fill = BG, color = NA))
blank <- ggplot() + theme_void() +
theme(plot.background = element_rect(fill = BG, color = NA))
# Centre each donut vertically: pad above and below
donut_before_col <- plot_grid(
blank, d_before, blank,
ncol = 1, rel_heights = c(0.15, 0.70, 0.15)
)
donut_after_col <- plot_grid(
blank, d_after, blank,
ncol = 1, rel_heights = c(0.15, 0.70, 0.15)
)
# Row: category | today | donut_before | arrow | driver | donut_after | result
content <- plot_grid(
p_cat, p_today, donut_before_col, p_arrow, p_driver, donut_after_col, p_result,
ncol = 7,
rel_widths = c(1.1, 0.85, 0.90, 0.22, 1.5, 0.90, 0.85),
align = "h",
axis = "tb"
)
full <- plot_grid(p_title, content,
ncol = 1, rel_heights = c(0.12, 0.76, 0.12))
slide6 <- ggdraw(full) +
theme(plot.background = element_rect(fill = BG, color = NA))
ggsave("s6_synthesis_final.png",
slide6, width = 18, height = 9, dpi = 180, bg = BG)
slide6
