I am trying to produce an inset map of London alongside a larger map of the UK. I'm using the package "tmap" which I have found to be an excellent package and particularly easy to move to having used ggplot2 for a while. However, the documentation on how to produce an inset map using tmap is a bit unclear. The reference manual describes how it should be possible to produce an inset map using:
save_tm(...insets_tm = NULL, insets_vp = NULL)
but it is not clear how the second command, insets_vp, should be used. I have only found one example which actually provides suggested syntax for producing an insetmap using tmap:
alaska <- tm_shape(shp_alaska) + … print(alaska, vp=viewport(x=.1,
y=.15, width=.2, height=.3))
See here for the source of the above code. This doesn't actually show how the map of the USA and Alaska/Hawaii are combined. As for my own attempts at coding, I have tried the following (dplyr, magrittr, rgdal, GISTools, RColorBrewer, tmap are all loaded, R vn 3.3.2, RStudio 1.0.136):
I first create two tmap objects polygon and points for all of the UK (UK_Im_Sec) and London (London_Im_Sec):
UK_Im_Sec<-tm_shape(UKNI_LA_ll, is.master = TRUE)+
tm_borders(lwd=0.25)+
tm_shape(Immobile_residuals)+
tm_dots(col="Sec_Name", style="cat", palette="Set1", title="Socio-economic background (NS-SEC)")+
tm_layout(title="Mapping outlier residuals - non-predicted 'immobility' (Social class)", title.size = 3.0,
title.position=c("center","TOP"),legend.outside = TRUE,
legend.outside.position = "right",frame = FALSE)
LDN_Im_Sec<-tm_shape(Immobile_resids_LDN)+
tm_dots(col="Sec_Name", style="cat", palette="Set1", size = 0.25,title="Socio-economic background (NS-SEC)")+
tm_shape(LDN_Poly, is.master = TRUE)+
tm_borders(lwd=0.25)+
tm_text(text="NAME", size = 0.6, auto.placement=TRUE)+
tm_layout("London",title.position = c("center", "BOTTOM"),legend.outside = TRUE, legend.outside.position = "right", frame = FALSE)
I then try to save out a pdf which combines both objects:
save_tmap(UK_Im_Sec,insets_tm = LDN_Im_Sec,filename="ZRMdlNoRg_SEC_-3to-5SDs_ImmobResids_FINAL.pdf", dpi=600)
This prints the pdf but only with the map of the UK. So,
I try and add insets_vp into the code:
save_tmap(UK_Im_Sec,insets_tm = LDN_Im_Sec,insets_vp=UK_Im_Sec, filename="ZRMdlNoRg_SEC_-3to-5SDs_ImmobResids_FINAL.pdf", dpi=600)
But this gives the following error code:
Error in save_tmap(UK_Im_Sec, insets_tm = LDN_Im_Sec, insets_vp = UK_Im_Sec, :
Insets and/or its viewports not in the correct format
I then try to combine the suggested syntax for print(x, viewport=(x=,y=,h=,w=) with insets_vp, as follows:
save_tmap(UK_Im_Sec,insets_tm = LDN_Im_Sec,insets_vp=viewport(x=2, y=.15, width=.2, height=.3), filename="ZRMdlNoRg_SEC_-3to-5SDs_ImmobResids_FINAL.pdf", dpi=600)
Error in inherits(insets_vp, "viewport") :
could not find function "viewport"
I know that other people have had difficulty producing inset maps in other packages and that there are questions that have already been asked and resolved for other packages, notably in ggplot (I can't link to the questions because of limits on links), but as far as I know there is nothing on this particular tmap issue.
This is my first question here so apologies for any errors in laying out the question.
You'll need to load the grid package. So, this should work
library(grid)
save_tmap(UK_Im_Sec,insets_tm = LDN_Im_Sec,insets_vp=viewport(x=2, y=.15, width=.2, height=.3), filename="ZRMdlNoRg_SEC_-3to-5SDs_ImmobResids_FINAL.pdf", dpi=600)
I'll update the US choropleth demo soon, with a save_tmap example.
The last chart in the tmap demo documentation has insets of Alaska and Hawaii. Below is the code, I've just removed the fill variable:
library("readxl")
library("maptools")
library("grid")
library("tmap")
library("tmaptools")
# function to obtain US county shape
get_US_county_2010_shape <- function() {
dir <- tempdir()
download.file("http://www2.census.gov/geo/tiger/GENZ2010/gz_2010_us_050_00_20m.zip", destfile = file.path(dir, "gz_2010_us_050_00_20m.zip"))
unzip(file.path(dir, "gz_2010_us_050_00_20m.zip"), exdir = dir)
read_shape(file.path(dir, "gz_2010_us_050_00_20m.shp"))
}
# obtain US county shape
US <- get_US_county_2010_shape()
# split shape
US_cont <- US[!(US$STATE %in% c("02","15","72")),]
US_AK <- US[US$STATE == "02", ]
US_HI <- US[US$STATE == "15",]
# create state boundaries
US_states <- unionSpatialPolygons(US_cont, IDs=US_cont$STATE)
After setting up the shapefiles, here is where the viewport windows are added to the plot:
# change back to the plotting mode
tmap_mode("plot")
# plot contiguous US
tm_shape(US_cont, projection=2163) +
tm_polygons(border.col = "grey50", border.alpha = .5, title = "", showNA = TRUE) +
tm_shape(US_states) +
tm_borders(lwd=1, col = "black", alpha = .5) +
tm_credits("Data # Unites States Department of Agriculture\nShape # Unites States Census Bureau", position = c("right", "bottom")) +
tm_layout(title.position = c("center", "top"),
legend.position = c("right", "bottom"),
frame = FALSE,
inner.margins = c(0.1, 0.1, 0.05, 0.05))
# Alaska inset
m_AK <- tm_shape(US_AK, projection = 3338) +
tm_polygons(border.col = "grey50", border.alpha = .5, breaks = seq(10, 50, by = 5)) +
tm_layout("Alaska", legend.show = FALSE, bg.color = NA, title.size = 0.8, frame = FALSE)
# Hawaii inset
m_HI <- tm_shape(US_HI, projection = 3759) +
tm_polygons(border.col = "grey50", border.alpha = .5, breaks=seq(10, 50, by = 5)) +
tm_layout(legend.show = FALSE, bg.color=NA, title.position = c("LEFT", "BOTTOM"), title.size = 0.8, frame=FALSE)
# print insets
print(m_AK, vp=viewport(x= 0.15, y= 0.15, width= 0.3, height= 0.3))
print(m_HI, vp=viewport(x= 0.4, y= 0.1, width= 0.2, height= 0.1))
Source: I've adapted this from https://github.com/mtennekes/tmap/tree/master/demo/USChoropleth
Related
I am trying to make my beautiful ggplot map interactive with a tooltip using ggplotly. But the map rendered with ggploty is not beautiful.
Here is a picture of my map with only ggplot:
Here is a picture of my map when using ggplotly. It removes the legend and make the map ugly:
Is there another way of making my ggplot map interactive with a tooltip? And also ggplotly takes some time to render the interactive map:
Here is my sample code for my ggplot:
ggplot(data = sdpf_f, aes( fill = n,x = long, y = lat, group = group, text = tooltip)) +
geom_polygon(color = "white") +
theme_void() +
scale_fill_continuous(low="#c3ffff", high="#0291da",
guide = guide_legend(title.position = "top", label.position = "bottom", keywidth = 2,
keyheight = 0.5,
title = "Number of agreements"),na.value="lightgrey"
) +
theme(legend.position="bottom") +
coord_map()
Thanks & kind regards,
Akshay
I don't have your data and this isn't exactly the same, but it's fairly close to what I think you're expecting.
The libraries:
I called tidyverse for the plotting and piping. I called maps for the data I used and plotly for the Plotly graph.
I used a function that is derived from one of the ways ggplot sets the aspect ratio. I know I found this function on SO, but I don't remember who wrote it.
library(tidyverse)
library(maps)
library(plotly)
map_aspect = function(x, y) {
x.center <- sum(range(x)) / 2
y.center <- sum(range(y)) / 2
x.dist <- ggplot2:::dist_central_angle(x.center + c(-0.5, 0.5), rep(y.center, 2))
y.dist <- ggplot2:::dist_central_angle(rep(x.center, 2), y.center + c(-0.5, 0.5))
y.dist / x.dist
}
I had to create data as your question is not reproducible. I figured I would include it, so that my answer was reproducible.
ms <- map_data("state") %>%
mutate(n = ifelse(str_detect(region, "^a"), 1.0,
ifelse(str_detect(region, "^o"), 1.5,
ifelse(str_detect(region, "^t"), 2.0,
ifelse(str_detect(region, "^s"), 2.5,
ifelse(str_detect(region, "^w"),
3.0,
NA))))))
I modified your ggplot call. The only change is coord_fixed instead of coord_map. I did this so that Plotly could interpret the aspect ratio correctly. Within coord_fixed, I used the function map_aspect.
gp <- ggplot(data = ms, aes(fill = n, x = long, y = lat,
group = group, text = tooltip)) +
geom_polygon(color = "white") +
theme_void() +
scale_fill_continuous(low="#c3ffff", high="#0291da",
guide = guide_legend(title.position = "top",
label.position = "bottom",
keywidth = 2,
keyheight = 0.5,
title = "Number of agreements"),
na.value="lightgrey"
) +
theme(legend.position="bottom") +
coord_fixed(with(ms, map_aspect(long, lat)))
Then I created a Plotly object. I set some requirements for the layout, as well (horizontal legend at the bottom, with the legend title above the elements—similar to the legend in your ggplot call).
pp <- ggplotly(gp) %>%
layout(legend = list(orientation = "h", valign = "bottom",
title = list(side = "top"),
x = .02),
xaxis = list(showgrid = F), yaxis = list(showgrid = F),
showlegend = T)
Next, I needed to add the information for the legend. I chose to name the traces (which are split by color). I started by creating a vector of the names of the traces (which is what you see in the legend). I added a "" at the end, so that the NA-colored states wouldn't have a named trace (so they won't show in the legend).
This is likely something you'll need to change for your data.**
# the color values and the last one, which is for NAs, no legend
nm <- seq(1, 3, by = .5) %>% sprintf(fmt = "%.1f") %>% append(., "")
Then I wanted to ensure that showlegend was true for all but the NA (light gray) states. I created a vector of T and F.
legs <- c(rep(TRUE, 5), FALSE)
Then I added these to the plot.
invisible(lapply(1:length(pp$x$data),
function(i){
pp$x$data[[i]]$name <<- nm[i]
pp$x$data[[i]]$showlegend <<- legs[i]
}))
Is it possible to include more than one tm_compass() on a map created by tmap?
I know it's probably unlikely that you'd need to, but say you wanted to show off the different compass types. Using nz from the spData package I tried adding each new compass as an additional layer, but it seems only the first one is included on the map.
library(spData)
library(tmap)
tm_shape(nz)+
tm_fill()+
tm_compass(type = 'arrow', position = c(0.1, 0.9))+
tm_compass(type = '4star', position = c(0.1, 0.8))+
tm_compass(type = '8star', position = c(0.1, 0.7))+
tm_compass(type = 'radar', position = c(0.1, 0.6))+
tm_compass(type = 'rose', position = c(0.1, 0.5))
If arrow isn't included, then 4star takes it's place:
tm_shape(nz)+
tm_fill()+
# tm_compass(type = 'arrow', position = c(0.1, 0.9))+
tm_compass(type = '4star', position = c(0.1, 0.8))+
tm_compass(type = '8star', position = c(0.1, 0.7))+
tm_compass(type = 'radar', position = c(0.1, 0.6))+
tm_compass(type = 'rose', position = c(0.1, 0.5))
Interesting question. As you point out, in normal use it is unlikely that one would need to display multiple compasses for the same map and that is probably why the default behavior of the tmap library does not handle this case.
That said, it is still possible to add all five tmap compasses to the same map using some workarounds! So, please find below the general "strategy":
Building 5 maps, each with one of the tmap compasses. Then convert these maps into 'grob' objects using the tmap::tmap_grob() function to extract the compasses with the help of the getGrob() function from the base R library grid.
Visualizing the compasses (without and with labels) using the cowplot library
Building the final map with the five compasses using the cowplot library
NB: when running the reprex just below, don't worry about the rendering of the different plots that will be displayed in your plotting device (as the rendering depends on the aspect ratio of the device); what matters is the rendering of the maps saved in the .png files.
Reprex
STEP 1 - EXTRACT EACH COMPASS TYPE FROM FIVE 'DUMMY' MAPS AS 'GROB' OBJECTS
library(tmap)
library(spData)
library(grid)
# Get a list named 'maps' containing 5 maps, each one with a different compass
compass_type <- c("arrow", "4star", "8star", "radar", "rose")
maps <- lapply(compass_type,
function(x)
tm_shape(nz) +
tm_fill() +
tm_compass(type = x, position = c(0.1, 0.7)))
# Get a list named 'compasses' containing the 5 compasses as 'grob' objects
compasses <- lapply(maps, function(x) getGrob(tmap_grob(x), gPath("compass")))
STEP 2 - VISUALIZATION OF THE FIVE tmap COMPASSES
Without labels
library(cowplot)
compasses_only <- plot_grid(plotlist = compasses,
nrow = 3,
ncol = 2,
scale = 0.8,
byrow = TRUE)
compasses_only
# Save the plot (need to install the 'rstudioapi' library)
# NB: adjust 'width' and 'height' params to get the best possible rendering
rstudioapi::savePlotAsImage(
"tmap_compasses.png", # add the path if different of the working directory
format = "png", # other possible formats: "jpeg", "bmp", "tiff", "emf", "svg", "eps"
width = 780,
height = 965
)
With labels
compasses_only_labeled <- plot_grid(plotlist = compasses,
nrow = 3,
ncol = 2,
scale = 0.8,
labels = compass_type,
label_size = 10,
label_x = c(0.42, 0.43, 0.43, 0.42, 0.44),
label_y = 0.1,
byrow = TRUE)
compasses_only_labeled
# Save the plot (need to install the 'rstudioapi' library)
# NB: adjust 'width' and 'height' params to get the best possible rendering
rstudioapi::savePlotAsImage(
"tmap_compasses_labeled.png",
format = "png",
width = 780,
height = 965
)
STEP 3 - BUILDING THE MAP WITH THE FIVE COMPASSES
# Get a 'nz' map (without any compass) as 'grob' object
nz_map <- tmap_grob(tm_shape(nz) + tm_fill())
# Convert 'compasses_only" into 'grob' object
compasses_only <- cowplot::as_grob(compasses_only)
# Build the map with the 5 compasses
map_with_compasses_2 <- ggdraw() +
draw_grob(nz_map) +
draw_grob(compasses_only,
width = 0.23, height = 0.65,
x = 0.28, y = 0.35,
hjust = 0,
vjust = 0,
halign = 0.5,
valign = 0.5,
scale = 0.8)
map_with_compasses_2
# Save the plot (need to install the 'rstudioapi' library)
# NB: adjust 'width' and 'height' params to get the best possible rendering
rstudioapi::savePlotAsImage(
"map_with_compasses_2.png",
format = "png",
width = 1500,
height = 900
)
Created on 2022-01-31 by the reprex package (v2.0.1)
I am trying to do something similar to what is described in the blog here but using R with ggtree, ggmap, and ggplot2.
I want to be able to combine the plots of the phylogenetic tree and the map showing the sampling locations of the tips on a geographical map, and link the tips to the sampling locations by segments. That would allow to see ie. if some clusters appears to specific geographical locations (ie north, south of an area) and would allow also to display different data with tips colors/symbols. This would be at first used as exploratory graphs, but this can also be used later on for publication ...
I would like to use the gg* libraries (ggplot2, ggtree, ggmap ...) to do that, because then it is easy to modify plots to display different variables. Here is a dummy script to describe how I do that so far. I do the tree and map plot separately and combine them. I want also to be able to have a common legend for the two plots. I am stuck after combining, I do not find out how to link the points from the tree plot to the points on the map plot with segments.
Anyone with ideas / possible solutions on how to do that or an alternative approach ?
Here is the dummy dataset to illustrate for creating the plots
library(patchwork)
library(ggpubr)
library(ggtree)
library(tidyverse)
library(ggmap)
library(ggplot2)
mytree <- ggtree::rtree(100)
mymap <- ggmap::get_map(c(left = 0.903, bottom = 44.56, right = 6.72, top = 49.38),
scale = 4, maptype = "terrain",
source = "stamen",
color = "bw")
save(mymap, file = "dummy_map.Rdata")
ggmap require API key to create the map (sorry I cannot share the API key, but you can make one for free on google cloud). I saved the map object and its downloadable from here.
# Loading the map
load("dummy_map.Rdata")
# creating dummy metadata
mytree_data <- tidytree::as_tibble(mytree)
mymetadata <- mytree_data %>%
dplyr::filter(!is.na(label)) %>%
tibble::add_column(year = sample(seq(1990, 2020, by = 1), 100, replace = T),
lon = sample(seq(0.91, 6.7, by = 0.01), 100, replace = T),
lat = sample(seq(44.56, 49.38, by = 0.01), 100, replace = T)) %>%
dplyr::rename(id = label) %>%
dplyr::select(id, year, lat, lon)
# plotting the phylogenetic tree
# phylogenetic tree example
mytree_plot <-
ggtree::ggtree(mytree, layout = "rectangular", ladderize = T, lwd = .2) %<+%
mymetadata +
geom_tippoint(aes(color = year), size = 1, show.legend = T) +
scale_color_gradient(low='red', high="blue", space = "Lab",
limits = c(NA, NA), na.value = "black",
n.breaks = 8,
guide = "colorbar") +
geom_tiplab(aes(label = label), size = 1, offset = -1E-10) +
geom_treescale(fontsize = 2, linesize = 0.5, offset = 1) +
theme(legend.position = c(0.9,0.15),
legend.title = element_text(size = 8),
legend.text = element_text(size = 6),
plot.title = element_text(hjust = 1))
mytree_plot
For some reason, I have to add the theme to be able to see the legend for the points, it is not created automatically. This should not occur. If anyone see what I am doing wrong here please let me know.
Then I add the sampling locations on the map, and deactivate the legend that is common with the tree legend
mymap_plot <- ggmap(mymap, n_pix = 340, darken = c(0.6, "white"))+
geom_point(data = mymetadata,
aes(x = lon, y = lat, color = year),
size = 2, alpha = .8, na.rm = T) +
scale_color_gradient(low='red', high="blue", space = "Lab",
limits = c(NA, NA),
n.breaks = 8,
guide = "colorbar") +
guides(color = F)
mymap_plot
Then I combine the tree plot and the map plot together. I tried with "patchwork" and "ggpubr" packages.
So far it appear easier to combine plots and draw a single legend with ggpubr, so this is currently my first choice at combining plot
# combining plots with patchwork
combined_plot <- mytree_plot + mymap_plot
# combining plots with ggpubr
# which I like better because it allows to combine the legends which is usefull
# when more variables are used ie shape for uncertainty location
other_combined <- ggarrange(mytree_plot, mymap_plot,
ncol = 2,
labels = c("A", "B"),
align = "hv",
legend = "bottom",
common.legend = T)
Here is the combined plot of the phylogenetic tree (ggtree) and the map (ggmap) obtained with ggpubr.
I am stuck at this point.
I need a way to add segments between corresponding points at the tips of the tree to the corresponding sampling locations of each tip on the map
Any solutions/ideas on how I could do that?
I made the following graph using the geomnet package and ggplot2. Then exported it to a pdf. But the graph by itself seems to be larger than the graphing area. it seems to be framed in a small square, as you can see in this picture:
graph
I don't know how to change the size of the square that's framing my graph so that the net nodes will be shown fully in my pdf. Thanks in advance.
Here's the code i'm using, and a data example:
red_list<-data_frame(From=c("A","B","C","D","D"),To=c("C","C","D","Z","A"))
red_list%>%ggplot(aes(from_id=From,to_id=To))+
geom_net(layout.alg = "circle", labelon = TRUE,
size = 12, directed = TRUE, fontsize=2, vjust = 0.5, labelcolour = "grey80",
arrowsize = 1.5, linewidth = 0.5, arrowgap = 0.05, col="darkred",
selfloops = F, ecolour = "grey40") +
theme_net() +
theme(plot.title=element_text(hjust=.5),
plot.subtitle=element_text(hjust=.5))+
ggtitle(label=paste("Figura",i,sep=" "),subtitle = paste("Interacciones entre los sectores de",names(red_list)[i],by=" ")))
I'm not sure how well it will work with graphs, but I uselly play with coord_cartesian(xlim = c(...,...), ylim = c(...,...) to adjust the plotting area.
My map-making code generates a map based on census data and plots important points as a tm_dots() layer. What I'd like to be able to do is differentiate between the types of dots (e.g. if the location is "Informal" or "Commercial").
tm_shape(bristol) + tm_fill("population", palette = "YlOrRd",
auto.palette.mapping = TRUE,
title = "Bristol Population",
breaks = c(0,5,10,15,20,25), colorNA = "darkgrey") + tm_borders("grey25",alpha = 0.7, lwd = 0.1) +
tm_dots("n", size=0.1,col="green", shapeNA = NA, title = "Spaces") +
tm_legend(text.size=1,title.size=1.2,position=c("left","top")) +
tm_layout(legend.outside = TRUE, legend.outside.position = "bottom", title.snap.to.legend = TRUE)
What I'm looking for is essentially:
tm_dots("n", size=0.1,col=Classification, shapeNA = NA, title = "Spaces")
Adding several tm_dots() layers isn't an option. I also can't rename the dot legend, any advice on that too is appreciated.
Thanks for your help!
Solution
For future reference, I added offices to bristol via left_join, thus adding the Classification variable to the SpatialPolygonsDataFrame. I was having issues with it displaying NA values despite the showNA = NA parameter, but colorNA = NULL worked. Final line:
tm_dots(size=0.1,col="Classification", palette = "Set1", colorNA = NULL)
So bristol is a polygon shape (SpatialPolygonDataFrame or sf), and you want to plot dots in some polygons?
Normally, you would have a variable Offices, with two levels "Informal" and "Commercial". Then it's just tm_dots(size = 0.1, col = "Offices"). If you want to place two dots in one polygons because there are Informal and Commercial offices, then you can use your own approach (and use xmod and/or ymod for one group to prevent overlap), or create a SpatialPointsDataFrame or sf object with all offices, and a variable Offices with two levels as described above.
I figured it out, you need to have another tm_shape() for it to work. Still haven't got the title() to appear properly but one step at a time.
tm_shape(bristol) + tm_fill("population", palette = "YlOrRd", auto.palette.mapping = TRUE,
title = "Bristol Population",
breaks = c(0,5,10,15,20,25), colorNA = "darkgrey") + tm_borders("grey25",alpha = 0.7, lwd = 0.1) +
tm_dots("Informal_Offices", size=0.1,col="green", shapeNA = NA, title = "Informal Offices") +
tm_shape(bristol) + tm_dots("Commercial_Offices", size=0.1,col="white",shapeNA=NA, title="Commercial Offices") +
tm_legend(text.size=1,title.size=1.2,position=c("left","top")) +
tm_layout(legend.outside = TRUE, legend.outside.position = "bottom", title.snap.to.legend = TRUE)
Result