UseR10085
UseR10085

Reputation: 8198

How to annotate dataframe to tmap panels in R?

I am trying to add mean and SD to panel plot created using tmap R package. But each panel contains all the annotations overlapped. Now how can I have single annotations per panel? Here is a minimal reproducible code

library(tmap)
library(stars)
library(terra)

#Read some data
tif = system.file("tif/L7_ETMs.tif", package = "stars")
x = terra::rast(tif)

# Summarize data (one summary per raster layer)
summ <- x |> 
  as.data.frame() |> 
  pivot_longer(everything()) |> 
  group_by(name) |> 
  summarise(Mean = mean(value, na.rm = TRUE), 
            SD = sd(value, na.rm = TRUE)) %>% 
  mutate(across(c(Mean, SD), ~round(., 3))) %>% 
  mutate(lab = paste0("Mean = ", Mean, "\nSD = ", SD)) %>% 
  dplyr::select(name, lab)

# Add coordinates for bottom-left placement
bbox <- st_bbox(x)
annotations <- summ %>%
  mutate(
    x = bbox["xmin"] + 0.05 * (bbox["xmax"] - bbox["xmin"]), # Slightly offset from xmin
    y = bbox["ymin"] + 0.1 * (bbox["ymax"] - bbox["ymin"])  # Slightly offset from ymin
  )

# Convert annotations to sf object
annotations_sf <- st_as_sf(annotations, coords = c("x", "y"), crs = st_crs(x))

#Plot it using tmap r package
map <- tm_shape(x) + 
  tm_raster(style="quantile") + 
  tm_facets(nrow = 3) +
  tm_layout(panel.labels = names(x), attr.outside = T, 
            attr.outside.position = "bottom", attr.just = "right",
            legend.outside = T, legend.outside.position = "right",
            legend.position = c("left", "top"),
            legend.text.size = 0.8, legend.title.size = 0.9,
            legend.format = list(digits = 2, text.separator = "-"))+
  tm_compass(position = c("left", "top"))+
  tm_scale_bar(text.size = 1, position = c("RIGHT", "top"))

# Add annotations
map +
  tm_shape(annotations_sf) +
  tm_text("lab", xmod = 0.05, ymod = -0.05, col = "black", just = "left", size = 1)

enter image description here

Upvotes: 1

Views: 59

Answers (1)

Sean McKenzie
Sean McKenzie

Reputation: 909

You can solve this issue by using tm_credits() with a character vector instead of adding another tm_shape() and annotating it with a tm_text() aesthetic. Simply take the text from annotations_sf$lab and pass them as the first argument in tm_credits(). For the position within the facet, you can specify this with the argument position=c("bottom", "left"). Finally, you will need to slightly alter your call to tm_layout(). Instead of setting attr.outside=T, set it to attr.outside=F this will ensure the credits are plotted within the facets. You can still plot the legend outside of the facets by setting legend.outside=T, which will override the previous argument. Here is the full, reproducible solution:

library(tmap)
library(stars)
library(terra)
library(dplyr)
library(tidyr)

#Read some data
tif = system.file("tif/L7_ETMs.tif", package = "stars")
x = terra::rast(tif)

# Summarize data (one summary per raster layer)
summ <- x |> 
  as.data.frame() |> 
  pivot_longer(everything()) |> 
  group_by(name) |> 
  summarise(Mean = mean(value, na.rm = TRUE), 
            SD = sd(value, na.rm = TRUE)) %>% 
  mutate(across(c(Mean, SD), ~round(., 3))) %>% 
  mutate(lab = paste0("Mean = ", Mean, "\nSD = ", SD)) %>% 
  dplyr::select(name, lab)

# Add coordinates for bottom-left placement
bbox <- st_bbox(x)
annotations <- summ %>%
  mutate(
    x = bbox["xmin"] + 0.05 * (bbox["xmax"] - bbox["xmin"]), # Slightly offset from xmin
    y = bbox["ymin"] + 0.1 * (bbox["ymax"] - bbox["ymin"])  # Slightly offset from ymin
  )

# Convert annotations to sf object
annotations_sf <- st_as_sf(annotations, coords = c("x", "y"), crs = st_crs(x))

#Plot it using tmap r package
tm_shape(x) + 
  tm_raster(style="quantile") +
  tm_credits(text=st_drop_geometry(annotations_sf$lab), position=c("left", "bottom"), col = "black", just = "left", size = 2)+
  tm_facets(nrow=3)+
  tm_layout(panel.labels = names(x), attr.outside = F, 
            attr.outside.position = "bottom", attr.just = "right",
            legend.outside = T, legend.outside.position = "right",
            legend.position = c("left", "top"),
            legend.text.size = 0.8, legend.title.size = 0.9,
            legend.format = list(digits = 2, text.separator = "-"))+
  tm_compass(position = c("left", "top"))+
  tm_scale_bar(text.size = 1, position = c("RIGHT", "top"))

Resulting Map

Upvotes: 1

Related Questions