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:
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).
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'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")
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
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