Adding labels to plotly map created using plot_geo

Viewed 942

I have a choropleth map created using plotly::plot_geo. I would like to add labels on top of the map so that, for instance, over the location of Alabama on the map, it would say 'AL (68)', but for all states, as in the example below:

enter image description here

Can anyone tell me if there is a way to do this?

library(tidyverse)
library(plotly)

set.seed(1)

density <- sample(1:100, 50, replace = T)

g <- list(
  scope = 'usa',
  projection = list(type = 'albers usa'),
  lakecolor = toRGB('white')
)

plot_geo() %>%
  add_trace(
    z = ~density, text = state.name, span = I(0),
    locations = state.abb, locationmode = 'USA-states'
  ) %>%
  layout(geo = g) 
3 Answers

I don't think this is reasonably possible in R, at least for the time being. However, this is supported in python (see @r-beginners comment and https://plotly.com/python/text-and-annotations/).

I am showing a couple of examples of alternative approaches using ggplot and leaflet, but each presents considerable drawbacks if you are wedded to plotly. Mapbox appears to be another option, but I have never used it. Examples using all of these packages in the article that was already linked in the comments (https://plotly-r.com/maps.html).

Example dataset

library(sf)

set.seed(1)

nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
nc$density <- sample(1:100, nrow(nc), replace = T)

nc$lat <- st_coordinates(st_centroid(nc))[,"Y"]
nc$lon <- st_coordinates(st_centroid(nc))[,"X"]

Plot with ggplot (static)

library(ggplot2)

ggplot(nc) +
  geom_sf(aes(fill = density)) +
  geom_text(
    aes(x = lon, y = lat),
    label = paste0(nc$NAME, "\n", "(", nc$density, ")"),
    check_overlap = TRUE) +
  scale_fill_viridis_c() +
  theme_void()

Plot with leaflet (interactive)

library(leaflet)
library(viridis)

pal <- colorNumeric(viridis_pal(option = "C")(2), domain = nc$density)

leaflet() %>%
  addProviderTiles("CartoDB.Positron") %>%
  setView(-80, 34.5, zoom = 6.2) %>% 
  addPolygons(
    data = nc,
    fillColor = ~pal(nc$density),
    fillOpacity = 0.8,
    weight = 0.2,
    smoothFactor = 0.2,
    popup = ~density) %>%
  addLabelOnlyMarkers(
    lng = nc$lon,
    lat = nc$lat,
    label = paste0(nc$NAME, "\n", "(", nc$density, ")"),
    #label = "LABEL",
    labelOptions = labelOptions(noHide = T, textOnly = TRUE)) %>%
  addLegend(
    pal = pal,
    values = nc$density,
    position = "bottomright",
    title = "Density")

Using tmap in view-mode allows to reach nearly what you want (shapes are used via the urbnmapr package):

library(tidyverse)
library(tmap)
library(sf)
library(urbnmapr)

states <- get_urbn_map("states", sf = T) %>%  
  as.tibble() %>% 
  mutate(density = sample(1:100, 51, replace = T)) %>% 
  mutate(abbvAndDens = str_c(state_abbv, " (", density, ")")) %>% 
  st_as_sf() 

tmap_mode("view")
tm_shape(states) +
tm_fill("density",
        palette = "viridis",
        style = "cont",
        breaks = seq(0, 100, 20)) +
tm_borders(lwd = .5, col = "black") +
tm_text("abbvAndDens", size= .75, col = "black")

enter image description here

Using your example, this is possible with plotly::plot_ly()

set.seed(1)

density <- sample(1:100, 50, replace = T)

g <- list(
  scope = 'usa',
  projection = list(type = 'albers usa'),
  lakecolor = toRGB('white')
  )

plot_ly() %>%
  layout(geo = g) %>%
  add_trace(type = "choropleth", locationmode = 'USA-states',
            locations = state.abb,
            z = ~density, text = state.name,
            color = ~density, autocolorscale = TRUE) %>%
  add_trace(type = "scattergeo", locationmode = 'USA-states',
            locations = state.abb, text = paste0(state.abb, "\n", density),
            mode = "text",
            textfont = list(color = rgb(0,0,0), size = 12))

Output is:

enter image description here

Still not sure how to do this with plotly::plot_geo(), but this solution does allow you to stay within the plotly family.

Related