I have a raster layer that is larger than my plotting area. I'd like to plot a smaller section of the raster (and other layers), so I'd like to set the plotting area using xlim() and ylim() in ggplot.
However, setting the plotting area in that way clips the raster to the exact xlim and ylim, instead of the actual extent of the plot. That leaves an ugly border around the raster, but none of the other layers. Is there a better way to set the limits of a plot so that the raster isn't cut off?
Here's an example:
library(USAboundaries)
library(elevatr)
library(tidyverse)
states <- us_states(states = c("AL", "FL", "GA", "SC"))
se.elevations <- get_elev_raster(locations = states, z = 7, clip = "locations")
se.elevation.df <- raster::as.data.frame(se.elevations, xy = TRUE) %>%
rename(elevation = 3) %>%
na.omit() %>%
filter(elevation >= 0)
xlimit <- c(-87, -80)
ylimit <- c(29, 34)
ggplot()+
geom_tile(data = se.elevation.df, aes(x = x, y = y, fill = elevation))+
scale_fill_gradientn(colours= c("gray", "black"))+
geom_sf(data = states, fill = NA)+
xlim(xlimit)+
ylim(ylimit)
As you can see, the raster is cut off, but the states continue to the edge of the plot. Ugly!
As a general rule, if you want to zoom on a part of your plot set the limits via the coord, i.e. in your case coord_sf:
library(ggplot2)
ggplot()+
geom_tile(data = se.elevation.df, aes(x = x, y = y, fill = elevation))+
scale_fill_gradientn(colours= c("gray", "black"))+
geom_sf(data = states, fill = NA)+
coord_sf(xlim = xlimit, ylim = ylimit)
Related
I was wondering if it is possible to "stack" the items of the legend on a ggplot2 map (or any package that allows to produce the same result).
For spatial (sf, etc.) objects there is at least one package, mapsf, that produces the desired output, but I would like to produce this type of legend also for non-spatial objects (dataframes/tibble, etc.).
See here a reprex:
# A regular plot with ggplot2
library(ggplot2)
# A plot
ggplot(mtcars, aes(wt, mpg)) +
geom_point(aes(size = drat))
# A map with the circles of the legend "stacked" (overlapping points)
# Need to conver to sf
mtcars_sf <- sf::st_as_sf(mtcars, coords = c("wt", "mpg"))
library(mapsf)
# See the legend on the top-right corner (blue arrow)
mf_map(x = mtcars_sf)
mf_map(x = mtcars_sf, var = "drat", type = "prop",
inches = .2)
As Allan said, there is (to my knowledge) no general way to do this. But you can hack this together for your custom graph. Thomas Pedersen's awesome packages ggforce and patchwork are your friend.
I am essentially faking a legend. The challenge is to get the right dimensions between main plot and legend, and to stitch the legend in a reasonable way to the main plot. This is achieved by using carefully chosen radii on geom_ellipse, setting the coordinate ratio accordingly, and adjusting the coordinate limits from both plots. More comments in the code.
library(ggplot2)
library(ggforce)
library(patchwork)
## we cannot just use geom_point,
## because the size of the circles need to correspond to the legend later
## you will need to play around with this constant
r_constant <- 25
mtcars$r <- mtcars$drat / r_constant
## this is to make the points round - it will have an effect on the panel dimension
ellipse_fac <- .1
p <-
ggplot(mtcars) +
geom_ellipse(aes(x0 = wt, y0 = mpg, a = r, b = r / ellipse_fac, angle = 0), fill = "darkred") +
theme(legend.position = "none") +
coord_equal(ellipse_fac)
## for the legend, chose rounded values from the radius range
unique_r <- unique(plyr::round_any(mtcars$r, .1))
y_circles <- floor(min(mtcars$mpg))
## I'm sorting the radii decreasingly, so that the circles overlap correctly
circles <- data.frame(x = 0, r = sort(unique_r, decreasing = TRUE))
## the segment / label poistion is also arbitrary
x_lab <- max(circles$r) + .1
y_seg <- y_circles + 2 * circles$r / ellipse_fac
p_leg <-
ggplot(circles) +
geom_ellipse(aes(x0 = x, y0 = y_circles + r / ellipse_fac, a = r, b = r / ellipse_fac, angle = 0), fill = "darkred", alpha = .5) +
geom_segment(aes(x = x, xend = x_lab, y = y_seg, yend = y_seg)) +
geom_text(aes(x = x_lab, y = y_seg, label = r), hjust = 0) +
## you need to set the ylimits similar to the main plot for control of legend position
coord_equal(ratio = ellipse_fac, ylim = range(mtcars$mpg), clip = "off") +
theme_void() +
## also need to set a margin
theme(plot.margin = margin(r = .2, unit = "in"))
p + p_leg
I've added an alpha just for aesthetic reasons
I am working in the next code: ggplot()+ geom_sf(data = DPEst_DH, aes(size = di1a), col="orangered") that works.
But I want a colored legend.
In other words, I want apply the same field at two aesthetics, size and color but keeping one legend.
...+ geom_sf(data = DPEst_DH, aes(color=di1a, size=di1a))
With that code line I have the next output but I want to know if is possible to have something like the last image.
One option to achieve your desired result would be to make your di1a column a discrete variable using e.g. cut and to set the colors and sizes via scale_xxx_manual.
Making use of the nc shape file shipped with the sf package as example data:
library(ggplot2)
library(dplyr)
# Example data
nc_center <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) %>%
sf::st_centroid()
# Bin numeric variable
labels <- pretty(range(nc_center$AREA))
breaks <- c(labels, Inf)
nc_center <- nc_center %>%
mutate(area = cut(AREA, breaks = breaks, labels = labels, right = FALSE))
# Color and size palette
colors <- c("#132B43", "#56B1F7") # Default ggplot2 blue colors used for color gradient
pal <- colorRampPalette(colors)(length(labels))
pal_size <- seq(1, 6, length.out = 5) # c(1, 6): Default range for size scale
ggplot() +
geom_sf(data = nc_center, aes(color = area, size = area)) +
scale_color_manual(values = pal) +
scale_size_manual(values = pal_size)
I am using sf and ggplot2 to read shapefiles as simple features and plot various maps. I have been working through the maps chapter in the ggplot2 book but could not really find an answer to the following issue:
Plotting a map using geom_sf and labelling its features with geom_sf_text is a pretty straightforward task.
library(ggplot2)
library(sf)
library(ozmaps)
oz_states <- ozmaps::ozmap_states
ggplot() +
geom_sf(data = oz_states) +
geom_sf_text(data = oz_states, aes(label = NAME))
Once we zoom in on a section of the previous map, not all labels of the features present in the plot are visible.
xlim <- c(120.0, 140.0)
ylim <- c(-40, -24)
ggplot() +
geom_sf(data = oz_states) +
geom_sf_text(data = oz_states, aes(label = NAME)) +
coord_sf(xlim = xlim, ylim = ylim)
I have found a workaround to zoom in on sections of the map and still be able to label the features present in the plot by calculating the centroids of the features, extracting the coordinates as separate columns, selecting the elements I would like to be displayed in the final map, and using ggrepel to label them.
library(dplyr)
library(ggrepel)
oz_states_labels <- oz_states %>% st_centroid()
oz_states_labels <- do.call(rbind, st_geometry(oz_states_labels)) %>%
as_tibble() %>%
rename(x = V1) %>%
rename(y = V2) %>%
cbind(oz_states_labels) %>%
slice(4,5,7,3)
ggplot() +
geom_sf(data = oz_states) +
geom_text_repel(data = oz_states_labels, aes(label = NAME, x = x, y = y)) +
coord_sf(xlim = xlim, ylim = ylim)
Naturally, if possible, I would like to avoid the workaround of first having to calculate the centroids, extract the coordinates from the resulting sf and select the labels to be shown in the final map.
Hence my question: Is there a faster way of labelling all elements visible in the plot for example by specifying this either in geom_sf_text or coord_sf?
Thanks in advance for your tips and answers!
I believe the issue you are facing is caused by your applying the crop at presentation level / the actual data underlying your ggplot object is not cropped.
I suggest applying the crop at data level, for example via sf::st_crop(). In this example I am using the values of your xlim and ylim objects to create a bounding box (called crop_factor for no good reason) to limit the extent of the oz_states at the data level, by creating a new object called oz_cropped & continuing in your original workflow.
All the centroids and labels and what not will be much better behaved now.
library(ggplot2)
library(sf)
library(ozmaps)
oz_states <- ozmaps::ozmap_states
crop_factor <- st_bbox(c(xmin = 120,
xmax = 140,
ymax = -24,
ymin = -40),
crs = st_crs(oz_states))
oz_cropped <- st_crop(oz_states, crop_factor)
ggplot() +
geom_sf(data =oz_cropped) +
geom_sf_text(data = oz_cropped, aes(label = NAME))
Is it possible to get these two maps side by side, with the same height, in one png image? The two images should be separated by minimal but appropriate space, as shown below. I am receptive to other solutions, especially ggmap.
I tried par(mar=... (and also mai) to reduce margin size but that did not seem to affect size or space between the two maps. I also used cex = 1.8 in the second par() function (for the state) which makes the height of the two maps similar but spaces them even farther apart.
When I save the file as PDF, I get each map on a separate page. When I try png, I get only the Missouri map.
MWE:
library(maps)
op <- par(mfrow=c(1,2))
png(file = "maps.png", width = 1000, height = 400)
par(mar=c(0,0,0,0))
map('state')
map('state', 'missouri', add = TRUE, fill = TRUE)
map('state', c('mississippi', 'alabama', 'north carolina', 'florida'), add = TRUE, fill = TRUE, col = "gray")
par(mar=c(0,0,0,0))
map('county', 'missouri')
map('county', 'missouri,scott', add=TRUE, fill=TRUE)
dev.off()
par(op)
Desired result:
The par options are specific to the active "graphic device" at that moment. To demonstrate:
Try this, starting with "normal" (non-file) graphics.
par(mfrow=1:2)
par('mfrow')
# [1] 1 2
png("maps.png")
par('mfrow')
# [1] 1 1
dev.off()
# windows
# 2
par('mfrow')
# [1] 1 2
I didn't close the previous plain-graphics-window, so once I closed the png device, the previously-active window became active again. And it was still thinking mfrow=1:2.
So I think your answer is this, where the only change is the order of png, par(mfrow=1:2).
png(file = "maps.png", width = 1000, height = 400)
op <- par(mfrow=c(1,2))
par(mar=c(0,0,0,0))
map('state')
map('state', 'missouri', add = TRUE, fill = TRUE)
map('state', c('mississippi', 'alabama', 'north carolina', 'florida'), add = TRUE, fill = TRUE, col = "gray")
par(mar=c(0,0,0,0))
map('county', 'missouri')
map('county', 'missouri,scott', add=TRUE, fill=TRUE)
dev.off()
par(op)
Inspired by #Andres comment, I developed a ggplot2 version that places the two maps side by side using patchwork. I found it easier for me to use geom_polygon instead of converting the maps to sf objects.
library(maptools)
library(ggplot2)
library(ggthemes) # for Tufte theme
library(patchwork)
# remove the unneeded ink not removed by
# theme_tufte()
theme_tufte_empty <- function(){
theme(axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks.length = unit(0, "cm"))
}
usa <- map_data("state")
us_missouri <- map_data('state','missouri') #do similar for other states
us_map <- ggplot() +
geom_polygon(data = usa, aes(x=long, y = lat, group = group), fill = NA, color = "black") +
geom_polygon(data = us_missouri, aes(x = long, y = lat, group = group), fill = "black") +
theme_tufte() + # quickly remove most ink
theme_tufte_empty() +
coord_fixed(1.3)
missouri <- map_data("county", "missouri")
mo_scott <- map_data("county", "missouri,scott")
mo_map <- ggplot() +
geom_polygon(data = missouri, aes(x=long, y = lat, group = group), fill = NA, color = "black") +
geom_polygon(data = mo_scott, aes(x = long, y = lat, group = group), fill = "black") +
theme_tufte() +
theme_tufte_empty() +
coord_fixed(1.3)
us_map + mo_map + plot_layout(ncol = 2, widths = c(1.5,1))
I am trying to add a legend to a plot generated by ggmap package in R. The dataset I am working with is
Latitude Longitude amount
61.37072 -152.40442 436774
32.80667 -86.79113 3921030
34.96970 -92.37312 1115087
33.72976 -111.43122 5068957
The code I am using is
library(ggplot2)
library(ggmap)
MyMap <- get_map(location = c(lon = -96.5, lat = 40.68925), zoom = 4,maptype = "terrain", scale = 2)
ggmap(MyMap)+
geom_point(data = data,aes(x = Longitude , y = Latitude ),size=sqrt(data$amount)/800,col='darkred', shape = 19,alpha = .5)
Now I want to add legend to this plot. The legend should show the sizes of the circles on the map correspond to certain amount. How can I do it?
The size argument should be included within the aes() section of the geom_point function, as follows:
plot <- ggmap(MyMap) +
geom_point(data = data,aes(x = Longitude , y = Latitude, size=amount), col='darkred', shape = 19,alpha = .5)
plot
If you want to have further customisation of the scale, you can use the optional argument scale_size_area() to choose the breaks and labels for the legend. For example:
plot + scale_size_area(breaks = c(436774, 1115087, 4000000, 5068957),
labels = c("436774", "1115087", "4000000", "5068957"))
Change Point Size:
If you want to adjust the size of the points, you are better off using the scale_size function, which lets you specify a range:
plot + scale_size(range = c(5,9))