A series of graphs representing conflict in Sub-Suharan Africa from 2021 - 2022
As we went through the data visualisation journey, one of the classes that grabbed me the most was the one involving maps. I hadn’t thought of using R to build much more than statistical representations, so the idea of a map wormed it’s way into my brain and seemed novel.
I also had it in my head to do the project on an interesting topic - I didn’t want to choose something dry beacuse the data was easier to work with. I settled on conflict in sub-suharan africa as it is a significant series of international conflicts that consistently go under-reported. With that as a target, I came across a map of conflict in sub-suharan Africa from 2022.
The replication process was more complicated than anticipated and I strugged at times. Annotations at scale are not for the faint of heart!
library(giscoR)
library(sf)
library(ggplot2)
library(ggforce)
library(patchwork)
library(grid)
library(png)
library(tidyverse)
This is the code to clean the data. I’ve saved the data used as two separate CSV files as the original file is large.
# data <- read_csv("data/Africa_aggregated_data_up_to-2025-11-29.csv")
#
# data <-
# data |>
# mutate(WEEK = dmy(WEEK))
#
# filtered_data <-
# data |>
# filter(WEEK >= as.Date("2021-03-01") & WEEK <= as.Date("2022-04-30"))
#
# country_list <- c("Mali", "Burkina Faso", "Niger", "Chad",
# "Sudan", "Nigeria", "Cameroon", "Central African Republic",
# "South Sudan", "Ethiopia", "Somalia",
# "Democratic Republic of Congo", "Uganda", "Mozambique")
#
# filtered_data <-
# filtered_data |>
# filter(COUNTRY %in% country_list) |>
# group_by(COUNTRY) |>
# summarise(total_fatalities = sum(FATALITIES))
#
# # lookup table
# values_lookup <-
# tribble(
# ~COUNTRY, ~human_impact, ~incidence, ~geopolitical_impact,
# "Burkina Faso", 10, 15, 17,
# "Cameroon", 6, 7, 7,
# "Central African Republic", 8, 5, 44,
# "Chad", 2, 1, 23,
# "Democratic Republic of Congo", 32, 25, 42,
# "Ethiopia", 24, 13, 8,
# "Mali", 5, 10, 48,
# "Mozambique", 4, 4, 24,
# "Niger", 2, 3, 33,
# "Nigeria", 26, 27, 12,
# "Somalia", 20, 24, 42,
# "South Sudan", 19, 7, 45,
# "Sudan",19, 6, 30,
# "Uganda", 0, 3, 7
# )
#
# filtered_data <-
# filtered_data |>
# left_join(values_lookup, by = "COUNTRY")
#
# write_csv(filtered_data, "data/filtered_data.csv")
# write_csv(values_lookup, "data/values_lookup.csv")
filtered_data <- read_csv("data/filtered_data.csv")
values_lookup <- read_csv("data/values_lookup.csv")
color_points <- tibble(
value = c(0, 5, 10, 15, 17, 20, 23, 30, 32, 42, 44, 48),
color = c("#FAF8B2", "#FAF3B0", "#F6E5A8", "#F0DA9F", "#EFD69C",
"#EDCF97", "#E9C790", "#E4B585", "#E3B080", "#D89670",
"#D9956D", "#CF8A69")
) |>
arrange(value)
value_to_color <- function(val) {
if (val %in% color_points$value) {
return(color_points$color[color_points$value == val])
} else {
lower_idx <- max(which(color_points$value < val))
upper_idx <- min(which(color_points$value > val))
lower_val <- color_points$value[lower_idx]
upper_val <- color_points$value[upper_idx]
lower_col <- color_points$color[lower_idx]
upper_col <- color_points$color[upper_idx]
weight <- (val - lower_val) / (upper_val - lower_val)
return(colorRamp(c(lower_col, upper_col))(weight) |>
rgb(maxColorValue = 255))
}
}
# Get African countries from giscoR
countries <- gisco_get_countries(
year = "2020",
epsg = "4326",
resolution = "20"
)
# Filter to region
bbox <- st_bbox(c(xmin = -27, xmax = 64, ymin = -31, ymax = 41),
crs = st_crs(4326))
bbox_poly <- st_as_sfc(bbox)
countries_in_view <- countries[st_intersects(countries, bbox_poly, sparse = FALSE), ]
# Define country colors
country_colors <- tribble(
~COUNTRY, ~color,
# Conflict-affected (orange-y)
"Mali", "#ED8758",
"Burkina Faso", "#EC8657",
"Niger", "#F29464",
"Chad", "#F7B88D",
"Sudan", "#F18B5C",
"Abyei", "#F08A5B",
"South Sudan", "#F08A5B",
"Ethiopia", "#DD6F4B",
"Somalia", "#ED8556",
"Uganda", "#F6D1A5",
"Democratic Republic of The Congo", "#E67D52",
"Mozambique", "#F19F70",
"Nigeria", "#D46443",
"Cameroon", "#F3A172",
"Central African Republic", "#F28C5D",
# Other Sub-Saharan (white)
"Mauritania", "#FFFFFF",
"Senegal", "#FFFFFF",
"Gambia", "#FFFFFF",
"Guinea-Bissau", "#FFFFFF",
"Cape Verde", "#FFFFFF",
"Guinea", "#FFFFFF",
"Sierra Leone", "#FFFFFF",
"Côte D’Ivoire", "#FFFFFF",
"Ghana", "#FFFFFF",
"Togo", "#FFFFFF",
"Benin", "#FFFFFF",
"Gabon", "#FFFFFF",
"Congo", "#FFFFFF",
"Angola", "#FFFFFF",
"Zambia", "#FFFFFF",
"Namibia", "#FFFFFF",
"Botswana", "#FFFFFF",
"Zimbabwe", "#FFFFFF",
"South Africa", "#FFFFFF",
"Madagascar", "#FFFFFF",
"Malawi", "#FFFFFF",
"United Republic of Tanzania", "#FFFFFF",
"Burundi", "#FFFFFF",
"Rwanda", "#FFFFFF",
"Kenya", "#FFFFFF",
"Eritrea", "#FFFFFF",
"Djibouti", "#FFFFFF",
"Liberia", "#FFFFFF",
"Equatorial Guinea", "#FFFFFF",
"Eswatini", "#FFFFFF",
"Lesotho", "#FFFFFF",
)
# Merge colors with map data
countries_in_view_colored <- countries_in_view |>
left_join(country_colors, by = c("NAME_ENGL" = "COUNTRY")) |>
mutate(fill_color = ifelse(is.na(color), "#F0EDE3", color))
# Create coastline
all_countries_union <- st_union(countries_in_view)
coastline <- st_cast(st_boundary(all_countries_union), "LINESTRING")
# COUNTRY LABELS
# Auto-labeled countries (using centroids)
auto_countries <- c(
"Niger",
"Chad",
"Senegal",
"Liberia",
"Togo",
"Gabon",
"Angola",
"Namibia",
"South Africa",
"Botswana",
"Zimbabwe",
"Malawi",
"Madagascar",
"Burundi",
"Rwanda",
"Kenya",
"Djibouti"
)
countries_auto <- countries_in_view |>
filter(NAME_ENGL %in% auto_countries)
# Precisely positioned countries (many did not behave well. Here the text at
# the base of the plot about the sources of the data was plotted as well
#originally, but I needed to affect it independently later so it was dropped to a different section.
custom_positions <- tribble(
~name, ~lon, ~lat,
"SOUTH\nSUDAN", 30, 8.0,
"CENTRAL AFRICAN\nREPUBLIC", 20.0, 6.5,
"SOMALIA", 45.5, 4.2,
"ETHIOPIA", 40.5, 9.0,
"BURKINA\nFASO", -0.5, 12.5,
"MOZAMBIQUE", 35.0, -18.0,
"UGANDA", 33, 2.5,
"SUDAN", 30.0, 15.0,
"CABO\nVERDE", -23.5, 18.5,
"MALI", -7, 14,
"ERITREA", 38, 17,
"TANZANIA", 35, -6,
"CÔTE\nD'IVOIRE", -5.2, 8,
"DEMOCRATIC\nREPUBLIC\nOF THE CONGO", 23.5, -5,
"GUINEA-\nBISSAU", -18, 11,
"REPUBLIC\nOF CONGO", 16, 1,
"EQUATORIAL\nGUINEA", 5.5, 2,
"CAMEROON", 12, 5,
"BENIN", 2.5, 10.5,
"GHANA", -1.5, 6.5,
"SIERRA\nLEONE", -11.5, 9,
"GUINEA", -11, 11,
"THE GAMBIA", -18, 13,
"MAURITANIA", -13, 19.5,
"NIGERIA", 6.5, 9.5,
"ZAMBIA", 25, -15) |>
st_as_sf(coords = c("lon", "lat"), crs = 4326)
last_line_of_text <- tribble(
~name, ~lon, ~lat,
"Sources: Armed Conflict Location & Event Data Project (ACLED), www.acleddata.com; IISS analysis", -6, -29) |>
st_as_sf(coords = c("lon", "lat"), crs = 4326)
# FINAL MAP
# Incorporates countries with colours, the coastline, the labels and the ocean
# colour
base_map <- ggplot() +
geom_sf(data = countries_in_view_colored,
aes(fill = fill_color),
color = "#B8C0BE",
linewidth = 0.2) +
scale_fill_identity() +
geom_sf(data = coastline, color = "#387298", linewidth = 0.2) +
geom_sf_text(data = countries_auto,
aes(label = toupper(NAME_ENGL)),
fontface = "plain",
size = 3, #2.3
lineheight = 0.9) +
geom_sf_text(data = custom_positions,
aes(label = name),
fontface = "plain",
size = 3, #2.3
lineheight = 0.9) +
geom_sf_text(data = last_line_of_text,
aes(label = name),
fontface = "plain",
size = 2.5, #2.3
lineheight = 0.9) +
coord_sf(xlim = c(-27, 64), ylim = c(-31, 41), expand = FALSE) +
theme_void() +
theme(panel.background = element_rect(fill = "#DCE8F2"),
plot.background = element_rect(fill = "white"))
create_country_data <- function(country_name, filtered_data, value_to_color) {
filtered_data |>
filter(COUNTRY == country_name) |>
pivot_longer(
cols = c(human_impact, incidence, geopolitical_impact),
names_to = "category",
values_to = "value"
) |>
mutate(original_segment = row_number()) |>
arrange(desc(row_number())) |>
mutate(
color = map_chr(value, value_to_color),
segment = row_number(),
original_start = pi - ((original_segment - 1) / 3 * pi),
original_end = pi - (original_segment / 3 * pi),
start_angle = (pi - ((segment - 1) / 3 * pi)) - pi/2,
end_angle = (pi - (segment / 3 * pi)) - pi/2,
mid_angle = (original_start + original_end) / 2,
label_x = cos(mid_angle) * 1.08,
label_y = sin(mid_angle) * 1.08
)
}
# Create all country data frames using the function
cameroon_data <- create_country_data("Cameroon", filtered_data, value_to_color)
central_african_republic_data <- create_country_data("Central African Republic",
filtered_data, value_to_color)
chad_data <- create_country_data("Chad", filtered_data, value_to_color)
democratic_republic_of_congo_data <- create_country_data("Democratic Republic of Congo", filtered_data, value_to_color)
ethiopia_data <- create_country_data("Ethiopia", filtered_data, value_to_color)
mali_data <- create_country_data("Mali", filtered_data, value_to_color)
mozambique_data <- create_country_data("Mozambique", filtered_data, value_to_color)
niger_data <- create_country_data("Niger", filtered_data, value_to_color)
nigeria_data <- create_country_data("Nigeria", filtered_data, value_to_color)
somalia_data <- create_country_data("Somalia", filtered_data, value_to_color)
south_sudan_data <- create_country_data("South Sudan", filtered_data, value_to_color)
sudan_data <- create_country_data("Sudan", filtered_data, value_to_color)
uganda_data <- create_country_data("Uganda", filtered_data, value_to_color)
burkina_faso_data <- create_country_data("Burkina Faso", filtered_data, value_to_color)
create_arc_plot <- function(data, country_name, is_tall = FALSE) {
ymin_val <- if(is_tall) -0.8 else -0.6
ylim_val <- if(is_tall) c(-0.8, 1.0) else c(-0.6, 1.0)
text_y <- if(is_tall) -0.55 else -0.42
# Create base plot
p <- ggplot(data) +
annotate("rect", xmin = -1.2, xmax = 1.2, ymin = ymin_val, ymax = 1,
fill = alpha("white", 0.5), color = "black", linewidth = 0.2) +
geom_arc_bar(aes(x0 = 0, y0 = -0.3, r0 = 0.6, r = 0.9,
start = start_angle, end = end_angle, fill = color),
color = "white", linewidth = 0.2) +
scale_fill_identity()
# Add text layers conditionally based on the height of the plot (the
# placement of the text differs for 2 of the plots as the names of the
# countries are longer, so they needed to be taller.)
if (is_tall) {
p <- p +
geom_text(aes(x = label_x * 1.05, y = (label_y - 0.3) * 1.05,
label = value),
fontface = "plain", size = 2.75, lineheight = 0.9) +
annotate("text", x = 0, y = text_y, label = country_name,
fontface = "bold", size = 2.75, lineheight = 0.9)
} else {
p <- p +
geom_text(aes(x = label_x * 1.05, y = (label_y - 0.3) * 1.05,
label = value),
fontface = "plain", size = 2.75, lineheight = 0.9) +
annotate("text", x = 0, y = text_y, label = country_name,
fontface = "bold", size = 2.75, lineheight = 0.9)
}
p <- p +
coord_fixed(xlim = c(-1.3, 1.3), ylim = ylim_val) +
theme_void() +
theme(plot.background = element_rect(fill = NA, color = NA),
plot.margin = margin(0, 0, 0, 0),
text = element_text(size = rel(1)))
return(p)
}
# Create all arc plots
cameroon_plot <- create_arc_plot(cameroon_data, "Cameroon")
central_african_republic_plot <- create_arc_plot(central_african_republic_data,
"Central African\nRepublic", is_tall = TRUE)
chad_plot <- create_arc_plot(chad_data, "Chad")
democratic_republic_of_congo_plot <-
create_arc_plot(democratic_republic_of_congo_data,
"Democratic Rep.\nof the Congo", is_tall = TRUE)
ethiopia_plot <- create_arc_plot(ethiopia_data, "Ethiopia")
mali_plot <- create_arc_plot(mali_data, "Mali")
mozambique_plot <- create_arc_plot(mozambique_data, "Mozambique")
niger_plot <- create_arc_plot(niger_data, "Niger")
nigeria_plot <- create_arc_plot(nigeria_data, "Nigeria")
somalia_plot <- create_arc_plot(somalia_data, "Somalia")
south_sudan_plot <- create_arc_plot(south_sudan_data, "South Sudan")
sudan_plot <- create_arc_plot(sudan_data, "Sudan")
uganda_plot <- create_arc_plot(uganda_data, "Uganda")
burkina_faso_plot <- create_arc_plot(burkina_faso_data, "Burkina Faso")
pixel_to_lonlat <- function(x_pixels, y_pixels,
map_width = 2232, map_height = 1783,
lon_min = -27, lon_max = 64,
lat_min = -31, lat_max = 41) {
x_prop <- x_pixels / map_width
y_prop <- y_pixels / map_height
lon <- lon_min + x_prop * (lon_max - lon_min)
lat <- lat_max - y_prop * (lat_max - lat_min)
return(list(lon = lon, lat = lat))
}
# ALL CONNECTION LINES (these are the pixel measurements discussed above.
# You read them as pairs; distance from the keft edge of the map is the x and
# from the top is the y. Taken together they define points for the lines
# to be plotted to.)
connections <- list(
#arc graphs
mali = list(
x = c(298, 298, 445, 445),
y = c(212, 250, 250, 670)
),
burkina_faso = list(
x = c(586, 586),
y = c(209, 714)
),
niger = list(
x = c(869, 869),
y = c(209, 531)
),
chad = list(
x = c(1131, 1131),
y = c(209, 600)
),
sudan = list(
x = c(1379, 1379),
y = c(209, 576)
),
south_sudan = list(
x = c(1630, 1630, 1459, 1459),
y = c(330, 519, 519, 886)
),
ethiopia = list(
x = c(1967, 1612, 1612),
y = c(627, 627, 768)
),
somalia = list(
x = c(1967, 1871, 1871, 1800),
y = c(810, 810, 887, 887)
),
uganda = list(
x = c(1967, 1450),
y = c(992, 992)
),
drc = list(
x = c(1967, 1351),
y = c(1185, 1185)
),
mozambique = list(
x = c(1967, 1477),
y = c(1593, 1593)
),
central_african_republic = list(
x = c(1090, 1126, 1126, 1090),
y = c(1197, 1197, 885, 885)
),
cameroon = list(
x = c(557, 874, 874, 955, 955),
y = c(1051, 1051, 940, 940, 921)
),
nigeria = list(
x = c(308, 840, 840),
y = c(924, 924, 807)
),
#country/flag boxes
the_sahel_left = list(
x = c(613, 613),
y = c(553, 600)
),
the_sahel_middle = list(
x = c(644, 644),
y = c(553, 661)
),
the_sahel_right = list(
x = c(804, 804),
y = c(553, 605)
),
lake_chad_basin_top_left = list(
x = c(975, 975, 882),
y = c(664, 609, 609)
),
lake_chad_basin_bottom_left = list(
x = c(902, 902, 862),
y = c(790, 822, 822)
),
lake_chad_basin_bottom = list(
x = c(971, 971),
y = c(790, 841)
),
lake_chad_basin_right = list(
x = c(1129, 1157, 1157),
y = c(721, 721, 662)
),
great_lakes_region_bottom = list(
x = c(1252, 1252),
y = c(1061, 1084)
),
great_lakes_region_right = list(
x = c(1340, 1447),
y = c(1023, 1023)
),
rawanda_eu_sadcmm = list(
x = c(1697, 1697, 1550),
y = c(1370, 1437, 1437)
),
turkey_uk_us_au_eu = list(
x = c(1773, 1773),
y = c(1022, 942)
),
eritrea = list(
x = c(1657, 1681),
y = c(862, 862)
),
un_top = list(
x = c(1332, 1332),
y = c(687, 653)
),
un_bottom = list(
x = c(1333, 1333),
y = c(750, 850)
),
russia_rwanda_un = list(
x = c(1252, 1252, 1191),
y = c(445, 785, 785)
)
)
# white endpoints
# The lines used for the arc graphs and countries involved in the various
# conflicts are differentiated by the colour of the end points they use.
white_endpoints <- c("the_sahel_left",
"the_sahel_middle",
"the_sahel_right",
"lake_chad_basin_top_left",
"lake_chad_basin_bottom_left",
"lake_chad_basin_bottom",
"lake_chad_basin_right",
"great_lakes_region_bottom",
"great_lakes_region_right",
"rawanda_eu_sadcmm_bottom",
"rawanda_eu_sadcmm",
"turkey_uk_us_au_eu",
"eritrea",
"un_top",
"un_bottom",
"russia_rwanda_un")
# Lines
lines_df <- map_dfr(names(connections), function(country) {
coords <- pixel_to_lonlat(
x_pixels = connections[[country]]$x,
y_pixels = connections[[country]]$y
)
data.frame(
x = coords$lon,
y = coords$lat,
country = country
)
})
# Endpoints
# This section makes a dataframe using the final coordinates of the lines as
# the position of the endpoint.
endpoints_df <- map_dfr(names(connections), function(country) {
n <- length(connections[[country]]$x)
coords <- pixel_to_lonlat(
x_pixels = connections[[country]]$x[n],
y_pixels = connections[[country]]$y[n]
)
data.frame(
x = coords$lon,
y = coords$lat,
country = country,
is_white = country %in% white_endpoints
)
})
# Split into black and white endpoints
black_endpoints <- endpoints_df |> filter(!is_white)
white_endpoints_df <- endpoints_df |> filter(is_white)
# Add lines and endpoints to map
base_map <- base_map +
geom_path(data = lines_df,
aes(x = x, y = y, group = country),
color = "black",
linewidth = 0.3) +
# Black endpoints (fill and color both black)
geom_point(data = black_endpoints,
aes(x = x, y = y),
color = "black",
fill = "black",
shape = 21,
size = 1.5,
stroke = 0.3) +
# White endpoints
geom_point(data = white_endpoints_df,
aes(x = x, y = y),
color = "black",
fill = "white",
shape = 21,
size = 1.5,
stroke = 0.3)
# Standard arc box dimensions
# These dimensions applied for the majority of the arc graphs
arc_w <- 215 / 2232
arc_h <- 156 / 1783
# CAR and DRC dimensions
# I realised quite late that these graphs were subtly different in height due
# to the length of the country name needing to fit in the box
car_drc_h <- 183 / 1783
# Map dimensions
map_width <- 2232
map_height <- 1783
# Helper function for positioning
# I reused the same method as for the lines, this time measuring to the left
# edge of the plots and the bottom edge.
pixel_to_prop <- function(left_px, top_px, height_px = 156) {
list(
left = left_px / map_width,
bottom = 1 - (top_px + height_px) / map_height
)
}
# Helper function to add inset to map
add_inset <- function(map, plot, left, bottom, width, height) {
map + inset_element(
plot,
left = left,
right = left + width,
bottom = bottom,
top = bottom + height
)
}
# Define all arc plot configurations
arc_configs <- list(
list(plot = mali_plot, pos = pixel_to_prop(190, 77), w = arc_w, h = arc_h),
list(plot = burkina_faso_plot, pos = pixel_to_prop(487, 77),
w = arc_w, h = arc_h),
list(plot = niger_plot, pos = pixel_to_prop(754, 77), w = arc_w, h = arc_h),
list(plot = chad_plot, pos = pixel_to_prop(1020, 77), w = arc_w, h = arc_h),
list(plot = sudan_plot, pos = pixel_to_prop(1272, 77), w = arc_w, h = arc_h),
list(plot = south_sudan_plot, pos = pixel_to_prop(1528, 196),
w = arc_w, h = arc_h),
list(plot = ethiopia_plot, pos = pixel_to_prop(1947, 547), w = arc_w, h = arc_h),
list(plot = somalia_plot, pos = pixel_to_prop(1947, 733), w = arc_w, h = arc_h),
list(plot = uganda_plot, pos = pixel_to_prop(1947, 914), w = arc_w, h = arc_h),
list(plot = democratic_republic_of_congo_plot,
pos = pixel_to_prop(1947, 1108, height_px = 183),
w = arc_w, h = car_drc_h),
list(plot = mozambique_plot, pos = pixel_to_prop(1947, 1517),
w = arc_w, h = arc_h),
list(plot = nigeria_plot, pos = pixel_to_prop(114, 836),
w = arc_w, h = arc_h),
list(plot = cameroon_plot, pos = pixel_to_prop(364, 943),
w = arc_w, h = arc_h),
list(plot = central_african_republic_plot,
pos = pixel_to_prop(897, 1104, height_px = 183),
w = arc_w, h = car_drc_h)
)
# Add all arc plots to map
for (config in arc_configs) {
base_map <- add_inset(base_map, config$plot, config$pos$left,
config$pos$bottom, config$w, config$h)
}
create_sahel_box <- function() {
france_flag <- readPNG("flags_and_logos/france.png")
italy_flag <- readPNG("flags_and_logos/italy.png")
usa_flag <- readPNG("flags_and_logos/us.png")
eu_logo <- readPNG("flags_and_logos/eu.png")
un_logo <- readPNG("flags_and_logos/un.png")
au_logo <- readPNG("flags_and_logos/au.png")
g5_logo <- readPNG("flags_and_logos/sahel_g5.png")
flag_width <- 65 / 245
flag_height <- 43 / 205
logo_width <- 65 / 245
logo_height <- 43 / 205
logo_circle_width <- 43 / 245
logo_circle_height <- 43 / 205
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1,
fill = "white", color = NA) +
annotate("text", x = 0.5, y = 0.92, label = "The Sahel",
hjust = 0.5, fontface = "bold", size = 2.75, lineheight = 0.9) +
annotation_raster(france_flag,
xmin = 0.08, xmax = 0.08 + flag_width,
ymin = 0.62, ymax = 0.62 + flag_height) +
annotation_raster(italy_flag,
xmin = 0.5 - (flag_width / 2), xmax = 0.5 +
(flag_width / 2),
ymin = 0.62, ymax = 0.62 + flag_height) +
annotation_raster(usa_flag,
xmin = 0.92 - flag_width, xmax = 0.92,
ymin = 0.62, ymax = 0.62 + flag_height) +
annotation_raster(eu_logo,
xmin = 0.08, xmax = 0.08 + logo_width,
ymin = 0.30, ymax = 0.30 + logo_height) +
annotation_raster(un_logo,
xmin = 0.5 - (logo_width / 2), xmax = 0.5 +
(logo_width / 2),
ymin = 0.30, ymax = 0.30 + logo_height) +
annotation_raster(au_logo,
xmin = 0.92 - logo_width, xmax = 0.92,
ymin = 0.30, ymax = 0.30 + logo_height) +
annotation_raster(g5_logo,
xmin = 0.5 - (logo_circle_width / 2),
xmax = 0.5 + (logo_circle_width / 2),
ymin = 0.02,
ymax = 0.02 + logo_circle_height) +
coord_fixed(ratio = 205/245, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = "black",
linewidth = 0.5, radius = unit(0, "pt")),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
create_lake_chad_box <- function() {
france_flag <- readPNG("flags_and_logos/france.png")
uk_flag <- readPNG("flags_and_logos/uk.png")
usa_flag <- readPNG("flags_and_logos/us.png")
multi_jtf_logo <- readPNG("flags_and_logos/multi.png")
left_margin <- 50 / 235
flag_width <- 65 / 235
gap <- 5 / 235
flag_height <- 43 / 145
logo_width <- 65 / 235
logo_height <- 43 / 145
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1,
fill = "white", color = NA) +
annotate("text", x = 0.5, y = 0.88, label = "Lake Chad Basin",
hjust = 0.5, fontface = "bold", size = 2.75, lineheight = 0.9) +
annotation_raster(france_flag,
xmin = left_margin,
xmax = left_margin + flag_width,
ymin = 0.4,
ymax = 0.4 + flag_height) +
annotation_raster(uk_flag,
xmin = left_margin + flag_width + gap,
xmax = left_margin + flag_width + gap + flag_width,
ymin = 0.4,
ymax = 0.4 + flag_height) +
annotation_raster(usa_flag,
xmin = left_margin,
xmax = left_margin + flag_width,
ymin = 0.06,
ymax = 0.06 + flag_height) +
annotation_raster(multi_jtf_logo,
xmin = left_margin + flag_width + gap,
xmax = left_margin + flag_width + gap + logo_width,
ymin = 0.06,
ymax = 0.06 + logo_height) +
coord_fixed(ratio = 145/235, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = "black",
linewidth = 0.5, radius = unit(0, "pt")),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
create_great_lakes_box <- function() {
un_logo <- readPNG("flags_and_logos/un.png")
au_logo <- readPNG("flags_and_logos/au.png")
rwanda_flag <- readPNG("flags_and_logos/rwanda.png")
burundi_flag <- readPNG("flags_and_logos/burundi.png")
box_width <- 177
box_height <- 177
left_margin <- 13 / box_width
flag_width <- 65 / box_width
gap <- 25 / box_width
flag_height <- 43 / box_height
logo_width <- 65 / box_width
logo_height <- 43 / box_height
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1,
fill = "white", color = NA) +
annotate("text", x = 0.5, y = 0.88, label = "Great\nLakes Region",
hjust = 0.5, fontface = "bold", size = 2.75, lineheight = 0.9) +
annotation_raster(au_logo,
xmin = left_margin,
xmax = left_margin + flag_width,
ymin = 0.4,
ymax = 0.4 + flag_height) +
annotation_raster(un_logo,
xmin = left_margin + flag_width + gap,
xmax = left_margin + flag_width + gap + flag_width,
ymin = 0.4,
ymax = 0.4 + flag_height) +
annotation_raster(rwanda_flag,
xmin = left_margin,
xmax = left_margin + flag_width,
ymin = 0.06,
ymax = 0.06 + flag_height) +
annotation_raster(burundi_flag,
xmin = left_margin + flag_width + gap,
xmax = left_margin + flag_width + gap + logo_width,
ymin = 0.06,
ymax = 0.06 + logo_height) +
coord_fixed(ratio = box_height/box_width, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = "black",
linewidth = 0.5, radius = unit(0, "pt")),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
create_two_row_box <- function() {
rwanda_flag <- readPNG("flags_and_logos/rwanda.png")
eu_logo <- readPNG("flags_and_logos/eu.png")
sadcmm_flag <- readPNG("flags_and_logos/sadcmm.png")
box_width <- 195
box_height <- 135
top_margin <- 65 / box_width
top_flag_width <- 65 / box_width
bottom_left_margin <- 22 / box_width
bottom_flag_width <- 65 / box_width
bottom_gap <- 21 / box_width
flag_height <- 43 / box_height
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1,
fill = "white", color = NA) +
annotation_raster(rwanda_flag,
xmin = top_margin,
xmax = top_margin + top_flag_width,
ymin = 0.55,
ymax = 0.55 + flag_height) +
annotation_raster(eu_logo,
xmin = bottom_left_margin,
xmax = bottom_left_margin + bottom_flag_width,
ymin = 0.15,
ymax = 0.15 + flag_height) +
annotation_raster(sadcmm_flag,
xmin = bottom_left_margin + bottom_flag_width +
bottom_gap,
xmax = bottom_left_margin + bottom_flag_width +
bottom_gap + bottom_flag_width,
ymin = 0.15,
ymax = 0.15 + flag_height) +
coord_fixed(ratio = box_height/box_width, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = "black",
linewidth = 0.5, radius = unit(0, "pt")),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
create_three_two_box <- function() {
top_flag1 <- readPNG("flags_and_logos/turkey.png")
top_flag2 <- readPNG("flags_and_logos/uk.png")
top_flag3 <- readPNG("flags_and_logos/us.png")
bottom_flag1 <- readPNG("flags_and_logos/au.png")
bottom_flag2 <- readPNG("flags_and_logos/eu.png")
box_width <- 245
box_height <- 138
top_space <- 12.5 / box_width
top_flag_width <- 65 / box_width
bottom_flag_width <- 65 / box_width
bottom_gap <- 10 / box_width
bottom_margin <- 52.5 / box_width
flag_height <- 43 / box_height
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1,
fill = "white", color = NA) +
annotation_raster(top_flag1,
xmin = top_space,
xmax = top_space + top_flag_width,
ymin = 0.55,
ymax = 0.55 + flag_height) +
annotation_raster(top_flag2,
xmin = top_space * 2 + top_flag_width,
xmax = top_space * 2 + top_flag_width * 2,
ymin = 0.55,
ymax = 0.55 + flag_height) +
annotation_raster(top_flag3,
xmin = top_space * 3 + top_flag_width * 2,
xmax = top_space * 3 + top_flag_width * 3,
ymin = 0.55,
ymax = 0.55 + flag_height) +
annotation_raster(bottom_flag1,
xmin = bottom_margin,
xmax = bottom_margin + bottom_flag_width,
ymin = 0.15,
ymax = 0.15 + flag_height) +
annotation_raster(bottom_flag2,
xmin = bottom_margin + bottom_flag_width + bottom_gap,
xmax = bottom_margin + bottom_flag_width + bottom_gap +
bottom_flag_width,
ymin = 0.15,
ymax = 0.15 + flag_height) +
coord_fixed(ratio = box_height/box_width, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = "black",
linewidth = 0.5, radius = unit(0, "pt")),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
create_single_flag_box <- function() {
eritrea_flag <- readPNG("flags_and_logos/eritrea.png")
box_width <- 95
box_height <- 65
flag_margin <- 15 / box_width
flag_width <- 65 / box_width
flag_height <- 43 / box_height
flag_y_margin <- 11 / box_height
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1,
fill = "white", color = NA) +
annotation_raster(eritrea_flag,
xmin = flag_margin,
xmax = flag_margin + flag_width,
ymin = flag_y_margin,
ymax = flag_y_margin + flag_height) +
coord_fixed(ratio = box_height/box_width, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = "black",
linewidth = 0.5, radius = unit(0, "pt")),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
create_single_flag_box_2 <- function() {
un_logo <- readPNG("flags_and_logos/un.png")
box_width <- 95
box_height <- 65
flag_margin <- 15 / box_width
flag_width <- 65 / box_width
flag_height <- 43 / box_height
flag_y_margin <- 11 / box_height
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1,
fill = "white", color = NA) +
annotation_raster(un_logo,
xmin = flag_margin,
xmax = flag_margin + flag_width,
ymin = flag_y_margin,
ymax = flag_y_margin + flag_height) +
coord_fixed(ratio = box_height/box_width, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = "black",
linewidth = 0.5, radius = unit(0, "pt")),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
create_three_flag_row_box <- function() {
russia_flag <- readPNG("flags_and_logos/russia.png")
rwanda_flag <- readPNG("flags_and_logos/rwanda.png")
un_logo <- readPNG("flags_and_logos/un.png")
box_width <- 205
box_height <- 84
space <- 2.5 / box_width
flag_width <- 65 / box_width
flag_height <- 43 / box_height
flag_y_margin <- (box_height - 43) / 2 / box_height
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1,
fill = "white", color = NA) +
annotation_raster(russia_flag,
xmin = space,
xmax = space + flag_width,
ymin = flag_y_margin,
ymax = flag_y_margin + flag_height) +
annotation_raster(rwanda_flag,
xmin = space * 2 + flag_width,
xmax = space * 2 + flag_width * 2,
ymin = flag_y_margin,
ymax = flag_y_margin + flag_height) +
annotation_raster(un_logo,
xmin = space * 3 + flag_width * 2,
xmax = space * 3 + flag_width * 3,
ymin = flag_y_margin,
ymax = flag_y_margin + flag_height) +
coord_fixed(ratio = box_height/box_width, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = "black",
linewidth = 0.5, radius = unit(0, "pt")),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
sahel_box <- create_sahel_box()
lake_chad_box <- create_lake_chad_box()
great_lakes_box <- create_great_lakes_box()
two_row_box <- create_two_row_box()
three_two_box <- create_three_two_box()
single_flag_box <- create_single_flag_box()
single_flag_box_2 <- create_single_flag_box_2()
three_flag_row_box <- create_three_flag_row_box()
### DEFINE ALL BOX CONFIGURATIONS ###
# Here I position the boxes relative to the overall map
box_configs <- list(
list(plot = sahel_box, left = 0.27, bottom = 0.68, w = 245/2232,
h = 205/1783),
list(plot = lake_chad_box, left = 0.4, bottom = 0.55, w = 235/2232,
h = 145/1783),
list(plot = great_lakes_box, left = 0.52, bottom = 0.4, w = 177/2232,
h = 177/1783),
list(plot = two_row_box, left = 0.71, bottom = 0.22, w = 195/2232,
h = 135/1783),
list(plot = three_two_box, left = 0.76, bottom = 0.35, w = 245/2232,
h = 138/1783),
list(plot = single_flag_box, left = 0.70, bottom = 0.50, w = 95/2232,
h = 65/1783),
list(plot = single_flag_box_2, left = 0.58, bottom = 0.577, w = 95/2232,
h = 65/1783),
list(plot = three_flag_row_box, left = 0.515, bottom = 0.75, w = 205/2232,
h = 84/1783)
)
for (config in box_configs) {
base_map <- add_inset(base_map, config$plot, config$left, config$bottom,
config$w, config$h)
}
grey_legend_data <- tibble(
segment = 1:3,
label = c("A", "B", "C"),
value = c("A", "B", "C"),
color = "#D7D7D5",
original_segment = 1:3
) |>
arrange(desc(row_number())) |>
mutate(
segment = row_number(),
original_start = pi - ((original_segment - 1) / 3 * pi),
original_end = pi - (original_segment / 3 * pi),
start_angle = (pi - ((segment - 1) / 3 * pi)) - pi/2,
end_angle = (pi - (segment / 3 * pi)) - pi/2,
mid_angle = (original_start + original_end) / 2,
label_x = cos(mid_angle) * 0.77,
label_y = sin(mid_angle) * 0.77
)
# Plotting the grey legend arc plot
grey_legend_arc <- ggplot(grey_legend_data) +
annotate("rect", xmin = -1.2, xmax = 1.2, ymin = -0.6, ymax = 1,
fill = "white", color = NA) +
geom_arc_bar(aes(x0 = 0, y0 = -0.3, r0 = 0.6, r = 0.9,
start = start_angle, end = end_angle, fill = color),
color = "white", linewidth = 0.2) +
scale_fill_identity() +
geom_text(aes(x = label_x * 1.05, y = (label_y - 0.3) * 1.05, label = value),
fontface = "bold", size = 2.75, lineheight = 0.9) +
coord_fixed(xlim = c(-1.3, 1.3), ylim = c(-0.6, 1.0)) +
theme_void() +
theme(plot.background = element_rect(fill = "white", color = NA),
plot.margin = margin(0, 0, 0, 0),
text = element_text(size = rel(1)))
white_rectangle <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1,
fill = "white") +
# Text - line 1
annotate("text", x = 0.02, y = 1.00,
label = "ACGRI pillars: IISS calculation based on multiple sources",
hjust = 0, vjust = 1, size = 2.5) +
# Text - line 2
annotate("text", x = 0.02, y = 0.93,
label = "for 2021 and January–April 2022 (scale: 0–100)",
hjust = 0, vjust = 1, size = 2.5) +
# Text to the right of arc - line 1
annotate("text", x = 0.35, y = 0.80,
label = "A: human impact, B: Incidence;",
hjust = 0, vjust = 1, size = 2.5) +
# Text to the right of arc - line 2
annotate("text", x = 0.35, y = 0.73,
label = "C: Geopolitical impact",
hjust = 0, vjust = 1, size = 2.5) +
# tText below arc
annotate("text", x = 0.02, y = 0.59,
label = "Number of fatalities, March 2021–April 2022",
hjust = 0, vjust = 1, size = 2.5) +
# Text left of bar
annotate("text", x = 0.02, y = 0.49, label = "High", hjust = 0, vjust = 0.5,
size = 2.5) +
# Text right of bar
annotate("text", x = 0.62, y = 0.49, label = "Low", hjust = 0, vjust = 0.5,
size = 2.5) +
# Text below bar
annotate("text", x = 0.02, y = 0.41,
label = "Involvement of third-party countries",
hjust = 0, vjust = 1, size = 2.5) +
# Peacekeeping text
annotate("text", x = 0.02, y = 0.23,
label = "Peacekeeping, stabilisation and military-training
operations",
hjust = 0, vjust = 1, size = 2.5) +
coord_fixed(ratio = 582/833, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = "black",
linewidth = 0.5),
plot.margin = margin(0, 0, 0, 0)
)
white_rect_w <- 833 / 2232
white_rect_h <- 582 / 1783
white_rect_left <- 50 / 2232
white_rect_bottom <- 80 / 1783
# Add white rectangle to map
base_map <- base_map +
inset_element(
white_rectangle,
left = white_rect_left,
right = white_rect_left + white_rect_w,
bottom = white_rect_bottom,
top = white_rect_bottom + white_rect_h
)
# Add grey arc
grey_arc_w_map <- 215 / 2232
grey_arc_h_map <- 156 / 1783
# Position it inside the white rectangle
grey_arc_left <- white_rect_left + (20 / 2232)
grey_arc_bottom <- white_rect_bottom + (345 / 1783)
base_map <- base_map +
inset_element(
grey_legend_arc,
left = grey_arc_left,
right = grey_arc_left + grey_arc_w_map,
bottom = grey_arc_bottom,
top = grey_arc_bottom + grey_arc_h_map
)
# Colours
color_scale_data <- tibble(
x = seq(0, 48, length.out = 100),
y = 1,
fill_color = map_chr(x, value_to_color)
)
# Colour bar
color_scale_bar_only <- ggplot(color_scale_data, aes(x = x, y = y,
fill = fill_color)) +
geom_tile(width = 0.5, height = 1) +
scale_fill_identity() +
scale_x_reverse(expand = c(0, 0)) +
scale_y_continuous(expand = c(0, 0)) +
coord_fixed(ratio = 5, xlim = c(0, 48), ylim = c(0.5, 1.5)) +
theme_void() +
theme(
plot.background = element_rect(fill = "white", color = NA),
plot.margin = margin(0, 0, 0, 0)
)
create_involvement_flags <- function() {
# Load flag images
burundi_flag <- readPNG("flags_and_logos/burundi.png")
eritrea_flag <- readPNG("flags_and_logos/eritrea.png")
france_flag <- readPNG("flags_and_logos/france.png")
italy_flag <- readPNG("flags_and_logos/italy.png")
russia_flag <- readPNG("flags_and_logos/russia.png")
rwanda_flag <- readPNG("flags_and_logos/rwanda.png")
south_africa_flag <- readPNG("flags_and_logos/south_africa.png")
turkey_flag <- readPNG("flags_and_logos/turkey.png")
uk_flag <- readPNG("flags_and_logos/uk.png")
us_flag <- readPNG("flags_and_logos/us.png")
# Flag dimensions and spacing
flag_width_px <- 54
flag_height_px <- 37
flag_width <- flag_width_px / 833
flag_height <- flag_height_px / 582
n_flags <- 10
total_flags_width <- n_flags * flag_width
spacing <- (1 - total_flags_width) / (n_flags + 1)
# Helper function to calculate flag position
flag_x <- function(i) spacing + (i * (flag_width + spacing))
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1, fill = NA,
color = NA) +
# Burundi
annotation_raster(burundi_flag,
xmin = flag_x(0), xmax = flag_x(0) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(0) + flag_width/2, y = 0.58,
label = "Burundi", hjust = 0.5, vjust = 1, size = 2) +
# Eritrea
annotation_raster(eritrea_flag,
xmin = flag_x(1), xmax = flag_x(1) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(1) + flag_width/2, y = 0.58,
label = "Eritrea", hjust = 0.5, vjust = 1, size = 2) +
# France
annotation_raster(france_flag,
xmin = flag_x(2), xmax = flag_x(2) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(2) + flag_width/2, y = 0.58,
label = "France", hjust = 0.5, vjust = 1, size = 2) +
# Italy
annotation_raster(italy_flag,
xmin = flag_x(3), xmax = flag_x(3) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(3) + flag_width/2, y = 0.58,
label = "Italy", hjust = 0.5, vjust = 1, size = 2) +
# Russia
annotation_raster(russia_flag,
xmin = flag_x(4), xmax = flag_x(4) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(4) + flag_width/2, y = 0.58,
label = "Russia", hjust = 0.5, vjust = 1, size = 2) +
# Rwanda
annotation_raster(rwanda_flag,
xmin = flag_x(5), xmax = flag_x(5) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(5) + flag_width/2, y = 0.58,
label = "Rwanda", hjust = 0.5, vjust = 1, size = 2) +
# South Africa
annotation_raster(south_africa_flag,
xmin = flag_x(6), xmax = flag_x(6) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(6) + flag_width/2, y = 0.58,
label = "S.Africa", hjust = 0.5, vjust = 1, size = 2) +
# Turkey
annotation_raster(turkey_flag,
xmin = flag_x(7), xmax = flag_x(7) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(7) + flag_width/2, y = 0.58,
label = "Turkey", hjust = 0.5, vjust = 1, size = 2) +
# UK
annotation_raster(uk_flag,
xmin = flag_x(8), xmax = flag_x(8) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(8) + flag_width/2, y = 0.58,
label = "UK", hjust = 0.5, vjust = 1, size = 2) +
# US
annotation_raster(us_flag,
xmin = flag_x(9), xmax = flag_x(9) + flag_width,
ymin = 0.6, ymax = 0.6 + flag_height) +
annotate("text", x = flag_x(9) + flag_width/2, y = 0.58,
label = "US", hjust = 0.5, vjust = 1, size = 2) +
coord_fixed(ratio = 582/833, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = NA, color = NA),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
# Plot flags
flags_plot <- create_involvement_flags()
# Position the bar
bar_w_map <- 600 / 2232
bar_h_map <- 30 / 1783
bar_left <- white_rect_left + (0.02 * (833 / 2232)) + (5 / 2232)
bar_bottom <- white_rect_bottom + (260 / 1783)
# Add the bar
base_map <- base_map +
inset_element(
color_scale_bar_only,
left = bar_left,
right = bar_left + bar_w_map,
bottom = bar_bottom,
top = bar_bottom + bar_h_map
)
# Add flags
flags_w_map <- 833 / 2232
flags_h_map <- 600 / 1783
base_map <- base_map +
inset_element(
flags_plot,
left = white_rect_left,
right = white_rect_left + flags_w_map,
bottom = white_rect_bottom - (400 / 1783),
top = white_rect_bottom + (50 / 1783) + flags_h_map
)
create_peacekeeping_logos <- function() {
# Load logo images
un_logo <- readPNG("flags_and_logos/un.png")
eu_logo <- readPNG("flags_and_logos/eu.png")
au_logo <- readPNG("flags_and_logos/au.png")
g5_logo <- readPNG("flags_and_logos/sahel_g5.png")
multi_logo <- readPNG("flags_and_logos/multi.png")
sadcmm_logo <- readPNG("flags_and_logos/sadcmm.png")
# Logo dimensions
logo_width_px <- 54
logo_height_px <- 37
logo_width <- logo_width_px / 833
logo_height <- logo_height_px / 582
# Manual positioning
spacing_left <- 0.02
logo_gap <- 0.12
logo_x <- function(i) spacing_left + (i * logo_gap)
last_logo_x <- 0.75
p <- ggplot() +
annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 1, fill = NA,
color = NA) +
# UN
annotation_raster(un_logo,
xmin = logo_x(0), xmax = logo_x(0) + logo_width,
ymin = 0.5, ymax = 0.5 + logo_height) +
annotate("text", x = logo_x(0) + logo_width/2, y = 0.48,
label = "UN", hjust = 0.5, vjust = 1, size = 2, lineheight = 0.9) +
# EU
annotation_raster(eu_logo,
xmin = logo_x(1), xmax = logo_x(1) + logo_width,
ymin = 0.5, ymax = 0.5 + logo_height) +
annotate("text", x = logo_x(1) + logo_width/2, y = 0.48,
label = "EU", hjust = 0.5, vjust = 1, size = 2, lineheight = 0.9) +
# AU
annotation_raster(au_logo,
xmin = logo_x(2), xmax = logo_x(2) + logo_width,
ymin = 0.5, ymax = 0.5 + logo_height) +
annotate("text", x = logo_x(2) + logo_width/2, y = 0.48,
label = "African\nUnion", hjust = 0.5, vjust = 1, size = 2,
lineheight = 0.9) +
# G5 Sahel
annotation_raster(g5_logo,
xmin = logo_x(3), xmax = logo_x(3) + logo_width,
ymin = 0.5, ymax = 0.5 + logo_height) +
annotate("text", x = logo_x(3) + logo_width/2, y = 0.48,
label = "G5\nSahel", hjust = 0.5, vjust = 1, size = 2,
lineheight = 0.9) +
# MNJTF
annotation_raster(multi_logo,
xmin = logo_x(4), xmax = logo_x(4) + logo_width,
ymin = 0.5, ymax = 0.5 + logo_height) +
annotate("text", x = logo_x(4) + logo_width/2, y = 0.48,
label = "Multinational\nJoint Task\nForce", hjust = 0.5, vjust = 1,
size = 2, lineheight = 0.9) +
# SADCMM
annotation_raster(sadcmm_logo,
xmin = last_logo_x, xmax = last_logo_x + logo_width,
ymin = 0.5, ymax = 0.5 + logo_height) +
annotate("text", x = last_logo_x + logo_width/2, y = 0.48,
label = "Southern African\nDevelopment Community\nMission in Mozambique", hjust = 0.5, vjust = 1, size = 2, lineheight = 0.9) +
coord_fixed(ratio = 582/833, xlim = c(0, 1), ylim = c(0, 1)) +
theme_void() +
theme(
plot.background = element_rect(fill = NA, color = NA),
plot.margin = margin(0, 0, 0, 0)
)
return(p)
}
# Create logos plot
logos_plot <- create_peacekeeping_logos()
logos_w_map <- 833 / 2232
logos_h_map <- 200 / 1783
base_map <- base_map +
inset_element(
logos_plot,
left = white_rect_left,
right = white_rect_left + logos_w_map,
bottom = white_rect_bottom - (620 / 1783),
top = white_rect_bottom + (590 / 1783) + logos_h_map
)
# after fiddling for a VERY LONG time, I realised I could define these as variables
fig.width = 11
fig.height = 1.75 * fig.width
base_map

After recreating the map I was ready for a simpler graph type. I thought the initial map was very cluttered and difficult to read, privileging the form of the map over the story the data was trying to tell. My approach was to strip away the map and focus on getting the data across as cleanly and clearly as possible. With no map, I judged the positions of the countries and relations with each other and the external parties to the conflicts of lesser importance than the impacts and fatalities. I iterated through different versions of bar/column graphs until I found one I feel communicates the data clearly.
My initial idea was to plot the data for each metric as a separate graph, matching the scale on the x axis. I thought using the same colour scale as the original graph might relate the two, however I found it hard to read. I also realised that the statistic of total fatalities was important to the overall story the original told, so needed to include that as well.
bar_data_1 <- values_lookup |>
pivot_longer(
cols = c(human_impact, incidence, geopolitical_impact),
names_to = "metric",
values_to = "value"
) |>
mutate(
color = map_chr(value, value_to_color),
metric = factor(metric,
levels = c("human_impact", "incidence", "geopolitical_impact"),
labels = c("Human Impact", "Incidence", "Geopolitical Impact"))
)
# Create the 3 stacked bar graphs
improvement_plot_1 <- ggplot(bar_data_1, aes(x = reorder(COUNTRY, value), y = value, fill = color)) +
geom_col() +
scale_fill_identity() +
coord_flip() +
facet_wrap(~metric, ncol = 1) +
labs(x = NULL, y = "Impact") +
theme_minimal() +
theme(
strip.text = element_text(face = "bold", size = 12),
axis.text.y = element_text(size = 9),
panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank()
)
improvement_plot_1

I worked on a second graph, correcting the issues I had with the first attempt. As you can see below I didn’t account for the difference in scale of the data between fatalities and the other metrics.
bar_data_2 <- filtered_data |>
pivot_longer(
cols = c(human_impact, incidence, geopolitical_impact, total_fatalities),
names_to = "metric",
values_to = "value"
) |>
mutate(
metric = factor(metric,
levels = c("human_impact", "incidence", "geopolitical_impact", "total_fatalities"),
labels = c("Human Impact", "Incidence", "Geopolitical Impact", "total_fatalities")),
COUNTRY = factor(COUNTRY, levels = sort(unique(COUNTRY), decreasing = TRUE))
)
# Create grouped graph
improvement_plot_2 <- ggplot(bar_data_2, aes(x = COUNTRY, y = value, fill = metric)) +
geom_col(position = position_dodge(width = 0.8), width = 0.7) +
scale_fill_manual(
values = c(
"Human Impact" = "#E63946",
"Incidence" = "#457B9D",
"Geopolitical Impact" = "#2A9D8F",
"total_fatalities" = "black"
),
name = NULL
) +
coord_flip() +
labs(x = NULL, y = "Impact") +
theme_minimal() +
theme(
legend.position = "top",
axis.text.y = element_text(size = 10),
panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank()
)
improvement_plot_2

To solve the scale problem, I decided to create two graphs and plot them side-by-side. I didn’t think about the position of the legends and later had to adjust them.
bar_data_3 <- filtered_data |>
pivot_longer(
cols = c(human_impact, incidence, geopolitical_impact, total_fatalities),
names_to = "metric",
values_to = "value"
) |>
mutate(
metric = factor(metric,
levels = c("human_impact", "incidence", "geopolitical_impact", "total_fatalities"),
labels = c("Human Impact", "Incidence", "Geopolitical Impact", "total_fatalities")),
COUNTRY = factor(COUNTRY, levels = sort(unique(COUNTRY), decreasing = TRUE))
)
# Left plot
bar_data_left <- bar_data_3 |> filter(metric != "total_fatalities")
plot_left <- ggplot(bar_data_left, aes(x = COUNTRY, y = value, fill = metric)) +
geom_col(position = position_dodge(width = 0.8), width = 0.7) +
scale_fill_manual(
values = c("Human Impact" = "#E63946", "Incidence" = "#457B9D", "Geopolitical Impact" = "#2A9D8F"),
name = NULL) +
coord_flip() +
labs(x = NULL, y = "Impact") +
theme_minimal() +
theme(legend.position = "top", axis.text.y = element_text(size = 10))
# Right plot
bar_data_right <- bar_data_3 |> filter(metric == "total_fatalities")
plot_right <- ggplot(bar_data_right, aes(x = COUNTRY, y = value, fill = metric)) +
geom_col(width = 0.7) +
scale_fill_manual(
values = c("total_fatalities" = "black"),
name = NULL,
labels = c("Total Fatalities")
) +
coord_flip() +
labs(x = NULL, y = "Fatalities") +
theme_minimal() +
theme(
legend.position = "top",
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
)
# Combine
plot_left +
plot_right +
plot_layout(widths = c(2, 1)) +
plot_annotation(
title = "Regional Armed Conflict in Sub-Suharan Africa",
theme = theme(plot.title = element_text(size = 14, face = "bold"))
)

This is my final graph for the recreation. I realised that the colours I had chosen were not decided on with colour blindness in mind, so I updated the palette to accomodate that idea.I also corrected the spacing of the legends so they didnt overlap.
bar_data_3 <- filtered_data |>
pivot_longer(
cols = c(human_impact, incidence, geopolitical_impact, total_fatalities),
names_to = "metric",
values_to = "value"
) |>
mutate(
metric = factor(metric,
levels = c("human_impact", "incidence", "geopolitical_impact", "total_fatalities"),
labels = c("Human Impact", "Incidence", "Geopolitical Impact", "total_fatalities")),
COUNTRY = factor(COUNTRY, levels = sort(unique(COUNTRY), decreasing = TRUE))
)
# Left plot
bar_data_left <- bar_data_3 |> filter(metric != "total_fatalities")
plot_left <- ggplot(bar_data_left, aes(x = COUNTRY, y = value, fill = metric)) +
geom_col(position = position_dodge(width = 0.8), width = 0.7) +
scale_fill_viridis_d(name = NULL, option = "viridis") +
coord_flip() +
labs(x = NULL, y = "Impact") +
theme_minimal() +
theme(legend.position = "top", axis.text.y = element_text(size = 10))
# Right plot
bar_data_right <- bar_data_3 |> filter(metric == "total_fatalities")
plot_right <- ggplot(bar_data_right, aes(x = COUNTRY, y = value, fill = metric)) +
geom_col(width = 0.7) +
scale_fill_manual(
values = c("total_fatalities" = "black"),
name = NULL,
labels = c("Total Fatalities")
) +
coord_flip() +
labs(x = NULL, y = "Fatalities") +
theme_minimal() +
theme(
legend.position = "top",
legend.box.margin = margin(l = 5), # I had to plot the legends separately and combine them. This is to position Total fatalities so it doesn't overlap.
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
)
# Combine
plot_left +
plot_right +
plot_layout(widths = c(2, 2)) +
plot_annotation(
title = "Regional Armed Conflict in Sub-Suharan Africa",
subtitle = "Impacts across 3 measures and Total fatalities",
theme = theme(plot.title = element_text(size = 14, face = "bold"))
)

Text and figures are licensed under Creative Commons Attribution CC BY 4.0. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".
For attribution, please cite this work as
Sam (2026, Jan. 19). Data Visualization | MSc CSS: Conflict in Sub-Suharan Africa. Retrieved from https://csslab.uc3m.es/dataviz/projects/2025/100577218/
BibTeX citation
@misc{sam2026conflict,
author = {Sam, Benjamin},
title = {Data Visualization | MSc CSS: Conflict in Sub-Suharan Africa},
url = {https://csslab.uc3m.es/dataviz/projects/2025/100577218/},
year = {2026}
}