Putting two R maps() side by side - r

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))

Related

Make Raster Fill Extent of Plot with ggplot

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)

ggplot2: Map create with several elements is not saved in the image

I'd like to create a map in ggplot2 with my target coordinates, the north arrow and scale bar for example, but despite the ggsave() function saving the last plot, it doesn't work in mymap.png image.
In my example:
#Packages
library(ggplot2)
library(ggsn)
# Get data set - x any are the points
all.stands.predict<-read.csv("https://raw.githubusercontent.com/Leprechault/trash/main/prediction__bug_2021-03-18.csv")
all.stands.predict<-all.stands.predict[all.stands.predict[,3]=="VILA PALMA",] # Area selection
#Create a map
gg <- ggplot() +
geom_point(data=all.stands.predict,
aes(x=x, y=y), color="red") +
xlab("Latitude") + ylab("Longitude") +
theme_bw()
#Add a scale bar.
gg <- gg + scalebar(location="bottomright",y.min=max(all.stands.predict$y)-0.001, y.max=max(all.stands.predict$y),
x.min=max(all.stands.predict$x)-0.001, x.max=max(all.stands.predict$y), model='WGS84',
transform=TRUE)
#
#Add a north arrow
north2(gg, 0.85, 0.85, symbol = 10)
#Save image in png
ggsave("mymap.png", dpi=300, width = 20, height = 20)
#
When I inspected my "mymap.png" image created, just the north arrow is represented and looks like this:
Please, any ideas for saved all the map elements?
Thanks in advance!
A better solution is possible with the ggspatial package:
#Packages
library(ggplot2)
library(ggspatial)
library(sf)
# Get data set - x any are the points
all.stands.predict<-read.csv("https://raw.githubusercontent.com/Leprechault/trash/main/prediction__bug_2021-03-18.csv")
all.stands.predict<-all.stands.predict[all.stands.predict[,3]=="VILA PALMA",] # Area selection
#Create a map
(sites <- st_as_sf(all.stands.predict, coords = c("x", "y"),
crs = 4326, agr = "constant"))
gg <- ggplot() +
geom_sf(data=sites, color="red") +
annotation_north_arrow(location = "bl", which_north = "true",
pad_x = unit(0.3, "in"), pad_y = unit(0.5, "in"),
style = north_arrow_fancy_orienteering) + #Add a north arrow
annotation_scale(location = "bl", width_hint = 0.55) + #Add a scale bar
xlab("Latitude") + ylab("Longitude") +
theme_bw()
plot(gg)
ggsave("mymap.png", dpi=300, width = 20, height = 20)

Create a concentric circle legend for a ggplot bubble chart

I am trying to recreate this visualization of a bubble chart using ggplot2 (I have found the code for doing this in R, but not with the ggplot2 package). This is what I have so far. There are some other errors with my code at the moment, but I want to have the legend show concentric circles for size, versus circles shown in rows. Thanks for your help!
Original visualization:
My reproduction:
My (simplified) code:
crime <-
read.csv("http://datasets.flowingdata.com/crimeRatesByState2005.tsv",
header=TRUE, sep="\t")
ggplot(crime,
mapping= aes(x=murder, y=burglary))+
geom_point(aes(size=population), color="red")+
geom_text(aes(label=state.name), show.legend=FALSE, size=3)+
theme(legend.position = c(0.9, 0.2))
Here's an approach where we build the legend as imagined from scratch.
1) This part slightly tweaks your base chart.
Thank you for including the source data. I missed that earlier and have edited this answer to use it. I switched to a different point shape so that we can specify both outside border (color) as well as interior fill.
gg <- ggplot(crime,
mapping= aes(x=murder, y=burglary))+
geom_point(aes(size=population), shape = 21, color="white", fill = "red")+
ggrepel::geom_text_repel(aes(label = state.name),
size = 3, segment.color = NA,
point.padding = unit(0.1, "lines")) +
theme_classic() +
# This scales area to size (not radius), specifies max size, and hides legend
scale_size_area(max_size = 20, guide = FALSE)
2) Here I make another table to use for the concentric legend circles
library(dplyr); library(ggplot2)
legend_bubbles <- data.frame(
label = c("3", "20", "40m"),
size = c(3E6, 20E6, 40E6)
) %>%
mutate(radius = sqrt(size / pi))
3) This section adds the legend bubbles, text, and title.
It's not ideal, since different print sizes will require placement tweaks. But it seems like it'd get complicated to get into the underlying grobs with ggplot_build to extract and use those sizing adjustments...
gg + geom_point(data = legend_bubbles,
# The "radius/50" was trial and error. Better way?
aes(x = 8.5, y = 250 + radius/50, size = size),
shape = 21, color = "black", fill = NA) +
geom_text(data = legend_bubbles, size = 3,
aes(x = 8.5, y = 275 + 2 * radius/50, label = label)) +
annotate("text", x = 8.5, y = 450, label = "Population", fontface = "bold")

Efficient way to map data to legend text color in ggplot2

I'm wondering if there's an efficient way to map data onto legend text color in ggplot2, just like we can do with axis text. Reproducible example follows.
First, let's make a plot:
library(ggplot2)
library(dplyr)
drv_counts <- mutate(mpg,
drv = case_when(drv == "r" ~ "rear wheel drive",
drv == "4" ~ "4 wheel drive",
drv == "f" ~ "front wheel drive"),
model_drv = interaction(model, drv)) %>%
group_by(model_drv) %>%
summarize(model = model[1], drv = drv[1], count = n()) %>%
arrange(drv, count) %>%
mutate(model = factor(model, levels = model))
p <- ggplot(drv_counts, aes(x=model, y=count, fill=drv)) +
geom_col() + coord_flip() + guides(fill = guide_legend(reverse=T)) +
theme_minimal()
p
Now let's color the axis labels by drive train. This is very easy:
# ggplot2 colors
cols <- c("4 wheel drive" = "#F8766D", "front wheel drive" = "#00BA38", "rear wheel drive" = "#619CFF")
p2 <- p + theme(axis.text.y = element_text(color = cols[drv_counts$drv]))
p2
Now let's try the same trick on the legend. It doesn't work:
p2 + theme(legend.text = element_text(color = cols))
The reason this doesn't work for legend text but does work for axis text is that all the axis labels are drawn in one grob, and hence we can give that grob a vector of colors, but the legend labels are drawn in separate grobs.
We can go in and color all the grobs manually, but that's super ugly and cumbersome:
g <- ggplotGrob(p2)
g$grobs[[15]]$grobs[[1]]$grobs[[9]]$children[[1]]$gp$col <- cols[g$grobs[[15]]$grobs[[1]]$grobs[[9]]$children[[1]]$label]
g$grobs[[15]]$grobs[[1]]$grobs[[10]]$children[[1]]$gp$col <- cols[g$grobs[[15]]$grobs[[1]]$grobs[[10]]$children[[1]]$label]
g$grobs[[15]]$grobs[[1]]$grobs[[11]]$children[[1]]$gp$col <- cols[g$grobs[[15]]$grobs[[1]]$grobs[[11]]$children[[1]]$label]
grid::grid.newpage()
grid::grid.draw(g)
My question is: Can somebody think of a way of getting this effect without having to dig down into the grob tree? I'm Ok with a patch to ggplot2 if it's only a few modified lines. Alternatively, can the digging down into the grob tree be automated so I don't have to access child grobs by manually setting list indices that will change the moment I make a minor change to the figure?
Update: A related question can be found here. To make my question distinct, let's add the requirement that colors aren't copied over from the symbols but rather can be set to any arbitrary values. This added requirement has real-world relevance because I usually use a darker color for text than for symbols.
Here's a pretty mediocre method of hacking grobs together to make a legend. I setup a palette based on the unique values of the drv variable (so it can be scaled to larger datasets or more colors). Then I mapped over the values of the palette to make each legend item: a rectGrob and a textGrob, both with the corresponding color from the palette. These could definitely be tweaked to look better. All of these get arranged into a new grob and stuck alongside the plot with cowplot. It isn't gorgeous but it might be a start.
library(tidyverse)
library(grid)
library(gridExtra)
pal <- colorspace::qualitative_hcl(n = length(unique(drv_counts$drv)), l = 60, c = 70) %>%
setNames(unique(drv_counts$drv))
p2 <- ggplot(drv_counts, aes(x=model, y=count, fill=drv)) +
geom_col() +
coord_flip() +
theme_minimal() +
scale_fill_manual(values = pal, guide = F) +
theme(axis.text.y = element_text(color = pal[drv_counts$drv]))
legend <- pal %>%
imap(function(col, grp) {
rect <- rectGrob(x = 0, width = unit(0.5, "line"), height = unit(0.5, "line"), gp = gpar(col = col, fill = col), hjust = 0)
label <- textGrob(label = grp, gp = gpar(col = colorspace::darken(col, 0.4), fontsize = 10), x = 0, hjust = 0)
cowplot::plot_grid(rect, label, nrow = 1, rel_widths = c(0.12, 1))
}) %>%
arrangeGrob(grobs = rev(.), padding = unit(0.1, "line"), heights = rep(unit(1.1, "line"), 3))
cowplot::plot_grid(p2, legend, rel_widths = c(1, 0.45))
Created on 2018-05-26 by the reprex package (v0.2.0).

plotting shape file in ggplot2

I'm trying to figure out how to display my complete map in gglot2 including the island Both r_base and tmap were able to display the islands but ggplot2 couldn't differentiate the island from the rest of the waterbody...
.
My question is how to make the Islands appear in ggplot2?
See the code i used below.
library(ggplot2)
library (rgdal)
library (rgeos)
library(maptools)
library(tmap)
Loading the Persian Gulf shape fill referred to as iho
PG <- readShapePoly("iho.shp")
the shape file is available here
http://geo.vliz.be:80/geoserver/wfs?request=getfeature&service=wfs&version=1.0.0&typename=MarineRegions:iho&outputformat=SHAPE-ZIP&filter=%3CPropertyIsEqualTo%3E%3CPropertyName%3Eid%3C%2FPropertyName%3E%3CLiteral%3E41%3C%2FLiteral%3E%3C%2FPropertyIsEqualTo%3E
plot with r_base
Q<-plot(PG)
Corresponds to figure A
Ploting with tmap
qtm(PG)
Corresponds to figure B
convert to dataframe
AG <- fortify(PG)
Plot with ggplot2
ggplot()+ geom_polygon(data=AG, aes(long, lat, group = group),
colour = alpha("darkred", 1/2), size = 0.7, fill = 'skyblue', alpha = .3)
Corresponds to figure C
You need to tell ggplot you want the holes filled in with a different color..for example:
ggplot()+ geom_polygon(data=AG, aes(long, lat, group = group, fill = hole), colour = alpha("darkred", 1/2), size = 0.7) + scale_fill_manual(values = c("skyblue", "white")) + theme(legend.position="none")
Also try readOGR() function from the rgdal package instead of readShapePoly() it keeps all the projection and datum information when you read the shape file.
Further to #AdamMccurdy's answer:, there are some possibilities to get the same colour for islands and adjacent background.
The first sets the fill colour of the islands and the colour of the background to be the same. But the grid lines are under the polygon, and thus disappear.
The second is an attempt to get the grid lines back. It plots the background (which includes the grid lines) on top of the panel (using panel.ontop = TRUE). But it's a bit of a fiddle adjusting alpha values to get the same background and island colour.
The third sets the background and island colours to be the same (as in the first), then plots the grid lines on top of the panel. There's a couple of ways to do this; here, I grab the grid lines grob from the original plot, then draw them on top of the panel. Thus the colours remain the same, and no need for alpha transparencies.
library(ggplot2)
library (rgdal)
library (rgeos)
library(maptools)
PG <- readOGR("iho.shp", layer = "iho")
AG <- fortify(PG)
Method 1
bg = "grey92"
ggplot() +
geom_polygon(data = AG, aes(long, lat, group = group, fill = hole),
colour = alpha("darkred", 1/2), size = 0.7) +
scale_fill_manual(values = c("skyblue", bg)) +
theme(panel.background = element_rect(fill = bg),
legend.position = "none")
Method 2
ggplot() +
geom_polygon(data = AG, aes(long, lat, group = group, fill = hole),
colour = alpha("darkred", 1/2), size = 0.7) +
scale_fill_manual(values = c("skyblue", "grey97")) +
theme(panel.background = element_rect(fill = alpha("grey85", .5)),
panel.ontop = TRUE,
legend.position = "none")
Method 3
Minor edit updating to ggplot version 3.0.0
library(grid)
bg <- "grey92"
p <- ggplot() +
geom_polygon(data = AG, aes(long, lat, group = group, fill = hole),
colour = alpha("darkred", 1/2), size = 0.7) +
scale_fill_manual(values = c("skyblue", bg)) +
theme(panel.background = element_rect(fill = bg),
legend.position = "none")
# Get the ggplot grob
g <- ggplotGrob(p)
# Get the Grid lines
grill <- g[7,5]$grobs[[1]]$children[[1]]
# grill includes the grey background. Remove it.
grill$children[[1]] <- nullGrob()
# Draw the plot, and move to the panel viewport
p
downViewport("panel.7-5-7-5")
# Draw the edited grill on top of the panel
grid.draw(grill)
upViewport(0)
But this version might be a little more robust to changes to ggplot
library(grid)
bg <- "grey92"
p <- ggplot() +
geom_polygon(data = AG, aes(long, lat, group = group, fill = hole),
colour = alpha("darkred", 1/2), size = 0.7) +
scale_fill_manual(values = c("skyblue", bg)) +
theme(panel.background = element_rect(fill = bg),
legend.position = "none")
# Get the ggplot grob
g <- ggplotGrob(p)
# Get the Grid lines
grill <- getGrob(grid.force(g), gPath("grill"), grep = TRUE)
# grill includes the grey background. Remove it.
grill = removeGrob(grill, gPath("background"), grep = TRUE)
# Get the name of the viewport containing the panel grob.
# The names of the viewports are the same as the names of the grobs.
# It is easier to select panel's name from the grobs' names
names = grid.ls(grid.force(g))$name
match = grep("panel.\\d", names, value = TRUE)
# Draw the plot, and move to the panel viewport
grid.newpage(); grid.draw(g)
downViewport(match)
# Draw the edited grill on top of the panel
grid.draw(grill)
upViewport(0)

Resources