I would like to plot a map using geom_sf() and add labels like the following hand-manipulated map (but we don't need the colors or the legend, that's just the map we had on hand).
New map:
The map data can be downloaded here. The map consist of three columns: "Kommunenummer" (municipality number), "Kommunenavn" (municipality name), "geometry". The two sites pointed to in the map above are "Volda" (southern) and "Tromsø" (northern) municipalities.
I guess we would need to determine the centroid position of those municipalities and then use something like geom_text_repel() to point to them, but I haven't gotten it to work well. Does anyone have any tips?
Minimal working example:
mapdata <- st_read("~/Downloads/Basisdata_0000_Norge_25833_NorskeFylkerKommunerIllustrasjonsdata2021_GeoJSON/kommuner2021.json")
ggplot(mapdata) +
geom_sf() +
theme_minimal() +
theme(
panel.grid.major = element_blank(), # Removes major grid lines
panel.grid.minor = element_blank(), # Removes minor grid lines
axis.text.x = element_blank(), # Removes x-axis labels (Longitude)
axis.text.y = element_blank(), # Removes y-axis labels (Latitude)
axis.title.x = element_blank(), # Removes x-axis title
axis.title.y = element_blank() # Removes y-axis title
)
Unless you are working with hundreds of labels, ggrepel
is often not the most efficient approach. You can spend an inordinate amount of time getting labels to plot correctly, and correcting issues can create issues elsewhere.
I generally find it easier to stick with the sf
package and create label geometries manually. It can take some trial and error to get the label locations right, but with only a few labels, it's almost always easier to get the desired result compared to using ggrepel
. Plus, I am not sure that ggrepel
accepts functions from e.g. ggtext
to allow plot labels with different sized fonts.
This repex uses a uniform offset of -/+0.5 decimal degrees for both labels. There's nothing stopping you setting different offsets for each label. As my Norwegian is a little 'rusty', I could not download your linked data, so I have applied this workflow to the built-in nc dataset. Your offsets may differ relative to the CRS of your actual data.
I have used st_point_on_surface()
instead of st_centroid()
to ensure the point does not get plotted outside the polygon (which can occur with some irregular-shaped polygons). Note st_point_on_surface()
is unlikely to plot the true centroid location in cases where st_centroid()
would otherwise have plotted it inside the polygon. If this is undesriable, replace st_point_on_surface()
with st_centroid()
.
Load packages and example map data:
library(sf)
library(dplyr)
library(ggplot2)
# Example data
mapdata <- st_read(system.file("shape/nc.shp", package = "sf"))
Generate label geometries:
# Create 'centroids' of municipalities (you can safely ignore the warning)
sf_centroids <- mapdata %>%
filter(NAME %in% c("Watauga", "Swain")) %>% # proxies for your municipalities
st_point_on_surface()
# Create points for label locations
sf_labels <- sf_centroids %>%
mutate(lon = st_coordinates(.)[,1] - 0.5,
lat = st_coordinates(.)[,2] + 0.5) %>%
st_drop_geometry() %>%
st_as_sf(coords = c("lon", "lat")) %>%
st_set_crs(st_crs(sf_centroids))
# Create lines between label locations and centroids
sf_lines <- rbind(sf_labels[,"NAME"],
sf_centroids[,"NAME"]) %>%
group_by(NAME) %>%
summarise(geometry = st_union(geometry)) %>%
st_cast("LINESTRING")
Create labels lists and plot:
# Create labels
labels1 <- c("Exp's 2-3 - Northern Norway:", "Exp 1 - Sunnmøre:")
labels2 <- c("Bokmål vs. Northern Norwegian Dialect", "Bokmål")
# Plot labels using geo_sf_text
ggplot() +
geom_sf(data = mapdata) +
geom_sf(data = sf_lines, linewidth = 0.15) +
geom_sf(data = sf_labels, shape = 21, fill = "white", size = 0.75) +
geom_sf(data = sf_centroids, size = 0.75) +
geom_sf_text(data = sf_labels,
aes(label = labels1),
size = 2,
vjust = -2,
fun.geometry = st_centroid,
colour = "black") +
geom_sf_text(data = sf_labels,
aes(label = labels2),
size = 1.5,
vjust = -1,
fun.geometry = st_centroid,
colour = "black") +
coord_sf(clip = "off") +
theme_void()