rplotlyhovergeospatial

Show data point names in grouped geodata plotly in R


I am trying to set up a plotly in R that shows the division of the German states into three groups, called ‘Red’, ‘Green’ and ‘Blue’ for the sake of simplicity. They should also have the same colors in the plot, the three groups should be shown in the legend, but the state name should be shown when hovering (e. g. "Berlin" instead of the name of the group it's assigned to).

library(geodata)
library(sf)
library(plotly)
library(dplyr)

# Load and preprocess the data
ger1 <- geodata::gadm("Germany", level = 1, path = "Downloads", resolution = 2)

# Cast all POLYGON to MULTIPOLYGON and add groups/colors
ger1_sf <- st_as_sf(ger1) %>%
  st_cast("MULTIPOLYGON") %>%
  mutate(Group = c(rep("Red", 7), rep("Blue", 7), rep("Green", 2))) %>%
  mutate(GroupColor = c(rep("red", 7), rep("blue", 7), rep("green", 2)))

# Plot
plot_ly(
  data = ger1_sf, 
  split = ~Group,
  color = ~I(GroupColor),
  text = ~NAME_1,           # Text is state names
  hoverinfo = "text",       # Display state names on hover
  stroke = I("white"),
  span = I(1.5),
  opacity = 1
)

However the hovertext still displays the group names instead:

Screenshot Plotly

Also: the plot uses half-transparent shapes making the colors all look very light and not looking like the colors I defined in the code.

I tried to play around with different settings for hoverinfo, hovertemplate, customdata and so on, but it just seems to ignore the template completely and persists on only displaying the group name. Also tried to define the groups in separate add_trace calls, use plot_geo instead of plotly, add_sf instead of add_trace - either everything stays the same or breaks the plot completely.

Same for the color issue. As you can see I already set opacity to 1, but that does not have any effect at all (also nothing changes when opacity set to 0).


Solution

  • You need to add hoveron = "fills". However, since you've used split for your grouping, the text will come out as the same name repeated for the number of polygons used to create the map space....it can be...frustrating.

    You have 2 simple-ish possible fixes (both covered in this answer):


    I don't care that Plotly stole my legend

    I've commented out the code you won't need from your original plot - that's been moved to the call add_sf()

    plot_ly(
      data = ger1_sf, 
      split = ~Group,
      color = ~I(GroupColor),
      # text = ~NAME_1,           # Text is state names
      # hoverinfo = "text",       # Display state names on hover
      stroke = I("white"),
      span = I(1.5),
      opacity = 1
    ) %>% 
      add_sf(text = ~NAME_1, split = ~NAME_1, showlegend = F,
             hoverinfo = "text", hoveron = "fills")
    

    missing the legend


    Plotly needs to give me a legend

    This method uses a fixer function that takes the plot, and goes through each trace (one for each federal state) and turns on the legend for 1 of each color.

    I'm going to split this up into 3 variations:

    Version 1 - ideal: The legend interactivity is preserved and group gapping is removed . The legend doesn't have added gaps between entries. The argument tracegroupgap, in layout requires a newer version of Plotly than what the R library uses (it's WAY behind). (You'll see the gapping difference in the images below.)

    Version 2 - Who cares about gaps? Keep the gap between legend groups (you won't need to update Plotly)

    Version 3 - Drop the grouped legend --there is a loss of proper legend interactivity (check out the images below)

    The 3 variations:

    in the 3rd image, 'Red' has been selected, but only 1 state is hidden

    v1 v2 V3

    In the plot call, you can leave legendgroup out of the call, if you okay with losing the ability to interact with the legend (e.g., click on Blue to hide the Blue group).

    Here's all the code to create Version 1.

    For version 2, you won't need fixer2() or the call to layout().

    For version 3, you won't need items identified in version 2, nor will you need the argument legendgroup in add_sf()

    library(plotly)
    library(tidyverse)  ## now using purrr
    
    fixer <- function(plt) {
      plt <- plotly_build(plt)                      # prepare the plot
                              # id the row index values for the first of each Group
      gimme = 1:nrow(ger1_sf) %in% map(unique(ger1_sf$Group),
                                       \(m) which(ger1_sf$Group == m)[1]) %>% unlist()
      imap(gimme, \(j, k) {                          
        if(j == T) plt$x$data[[k]]$showlegend <<- T # if the first in group, show legend
        else plt$x$data[[k]]$showlegend <<- F       # if not, hide legend
      })
      plt                                           # return modified plot
    }
    
    fixer2 <- function(plt) {
      # changes to dependency so that all code works
      plt$dependencies[[5]]$src$file = NULL
      plt$dependencies[[5]]$src$href = "https://cdn.plot.ly"
      plt$dependencies[[5]]$script = "plotly-2.33.0.min.js"  # R is currently using 2.11 (sigh)
      plt$dependencies[[5]]$local = FALSE
      plt$dependencies[[5]]$package = NULL
      plt                                          # return modified plot
    }
    
    
    plot_ly(
      data = ger1_sf, 
      split = ~Group,
      color = ~I(GroupColor),
      stroke = I("white"),
      span = I(1.5),
      opacity = 1) %>% 
      add_sf(text = ~NAME_1, split = ~NAME_1, name = ~Group,           # add hover content
             hoverinfo = "text", hoveron = "fills", legendgroup = ~Group) %>% 
      layout(legend = list(tracegroupgap = 0)) %>% fixer() %>% fixer2() # fix grouping