I recently faced the same problem so here're my solutions for people with the same issue.
tmaptools has a function get_asp_ratio to compute the aspect ratio of spatial objects. We can use that ratio to remove the outer margin of any map.
library(ggplot2)
world <- rnaturalearth::ne_countries(returnclass = "sf") # to get the world map
ggplot() +
geom_sf(data = world) +
coord_sf(expand = FALSE) +
theme_void()
asp <- tmaptools::get_asp_ratio(world) # returns 2.070007
height <- 5
ggsave("world.svg", width = height * asp, height = height)
Alternatively, if we don't want to depend on tmaptools, we can make our own functions with a wrapper around ggsave. This is essentially based on the tmaptools::get_asp_ratio function.
deg_to_rad <- function(x) {
(mean(x) * pi) / 180
}
get_aspect_ratio <- function(geometry) {
if (!inherits(geometry, c("Spatial", "Raster", "sf", "sfc"))) {
stop('"geometry" must be of class "Spatial", "Raster", "sf" or "sfc".')
}
bbox <- sf::st_bbox(geometry)
xlim <- bbox[c(1, 3)]
ylim <- bbox[c(2, 4)]
xdeg <- diff(xlim)
ydeg <- diff(ylim)
if (xdeg == 0 || ydeg == 0) {
asp <- 1
} else {
is_lon_lat <- sf::st_is_longlat(geometry)
asp <- unname((xdeg / ydeg) * ifelse(is_lon_lat, cos(deg_to_rad(ylim)), 1))
}
asp
}
save_ggmap <- function(filename, plot = last_plot(), width = NA, height = NA, ...) {
geometry <- ggplot_build(plot)$data[[1]]$geometry
asp <- get_aspect_ratio(geometry)
if (is.na(width) && !is.na(height)) {
width <- height * asp
} else if (is.na(height) && !is.na(width)) {
height <- width / asp
}
ggsave(filename, plot, width = width, height = height, ...)
}
We can use the function the same way as ggsave. We only have to specify either the width or height and the function will save the map with the right aspect ratio automatically.
ggplot() +
geom_sf(data = world) +
coord_sf(expand = FALSE) +
theme_void()
save_ggmap("world.svg", width = 8)
To remove the inner margin, we can use theme(plot.margin = margin(0, 0, 0, 0)).