rggplot2plotlymapsggplotly

ggplotly hover on map area with a text different from the fill option


I am trying to create a simple map using ggplotly:

I want only the western european regions to be coloured in green, and to do this I have created the binary variable weur.

library(rnaturalearth)
library(plotly)
world <- ne_countries(returnclass = "sf")

w <- world[world$iso_a2_eh != -99, c("geometry", "name", "iso_a2_eh", "subregion")]

w$weur <- ifelse(w$subregion == "Western Europe", 1, 0)

Then I have created a text which should be shown when hovering on each of the countries with a very simple message specified in ggplot(aes(text = paste0(...)):

fig <- ggplotly(
  ggplot(data= w, 
         aes(text = paste0("country: ", name,
                           "\n country code: ", iso_a2_eh))) +
    geom_sf(color= "white", aes(fill = as.character(weur)),
            linewidth = 0.2, stat = "identity") +
    theme(legend.position="none",
          panel.background = element_rect(fill = "white"),
          panel.grid.major = element_blank(),
          panel.grid.minor = element_blank()) +
    coord_sf(xlim=c(-10,40), ylim=c(30,70), expand= FALSE, label_axes = "SW") +
    scale_fill_manual(values = c("gray", "lightgreen"), na.value="#dadada", drop=F),
  tooltip = "text"
) 

fig

enter image description here

This works, but the text is shown only when you hover near to the borders of each country, while I would like to also see the text while hovering in the middle.

I thought that specifying the style and hoveron would have solved this issue

fig %>%
  style(hoveron = "fills+points")

enter image description here

However, in this case if I hover in the middle of the map, only the "fill indicator" value (i.e. as.character(w$weur), which is 1 for germany in the image) is shown as text.

Is there any way to make ggplotly show the same text both when hovering near to the border and in the middle of the area?


Solution

  • I found this trick that fixes the hovering behaviour. Or so I thought! Actually it turns out, that ggplotly transforms both weurs to two traces. Each trace can only have one text label. So this "fixer" only enforces this. But it actually does not fix it yet. Because then all green countries have the label "France" and all others have the label "Fiji". So I thought, however silly this might seem, you need to loop through all countries and add them specifically in one geom_sf-call, because only then these are created as seperated traces on the transformation :-/. Now, I know that the runtime is atrocious here, but since we are in buggy territories I thought I might share this insight.

    Code with seperate geom_sf calls

    library(rnaturalearth)
    library(plotly)
    library(ggplot2)
    
    # Get world data and prepare it
    world <- ne_countries(returnclass = "sf")
    w <- world[world$iso_a2_eh != -99, c("geometry", "name", "iso_a2_eh", "subregion")]
    w$weur <- ifelse(w$subregion == "Western Europe", 1, 0) 
    # or w$weur <- +w$subregion=='Western Europe'
    w$color <- c("grey", "lightgreen")[1+w$weur] # pre calculate color
    
    fig <- ggplotly(
      ggplot(data = w) +
        { # Add each country as a seperate layer because plotly only accepts one label per data layer :/
          lapply(seq_len(nrow(w)), \(i) {
            country_data <- w[i, ]
            geom_sf(data = country_data, 
                    color = "white", 
                    fill = country_data$color, 
                    linewidth = 0.2,
                    aes(text = paste0("country: ", name, "\ncountry code: ", iso_a2_eh)))
          })
        } +
        theme(legend.position="none",
              panel.background = element_rect(fill = "white"),
              panel.grid.major = element_blank(),
              panel.grid.minor = element_blank()) +
        coord_sf(xlim=c(-10,40), ylim=c(30,70), expand= FALSE, label_axes = "SW")
    ) |> style(hoveron = "fills")
    fig
    
    

    giving

    out

    Directly in plotly

    One could solve it by plotting directly within plotly sacraficing the layout, e.g.

    plot_ly(
      w,
      split = ~ name,
      color = ~ as.character(weur),
      colors = c("gray", "lightgreen"),
      stroke = I("white"),
      text = ~ paste0("country: ", name, "<br>country code: ", iso_a2_eh),
      hoverinfo = "text",
      hoveron = "fills",
      showlegend = FALSE
    )