Search code examples
rshinyr-leaflet

Dynamic labels on leaflet map


So I've been trying to add a functionality on my leaflet map in Shiny dashboard where the user would be able to choose what the popup label would show through an input checkbox statement (in this case, they would choose whether they would want to see Area of Land or Area of Water or both - default is set to both). In other words, I would like to have a list of column options that I can choose from to show on the popup label when I hover over the map.

The code I have so far is below

library(dplyr)
library(readxl)
library(shinydashboard)
library(rgdal)
library(leaflet)
library(htmltools)

download.file('https://www2.census.gov/geo/tiger/GENZ2018/shp/cb_2018_us_county_5m.zip',
              'cb_2018_us_county_5m.zip')
unzip('cb_2018_us_county_5m.zip',exdir='cb_2018_us_county_5m')

download.file('https://www2.census.gov/programs-surveys/popest/geographies/2019/all-geocodes-v2019.xlsx',
              'all-geocodes-v2019.xlsx')

shapes <- rgdal::readOGR("cb_2018_us_county_5m","cb_2018_us_county_5m")

df_geo <- read_excel('all-geocodes-v2019.xlsx',skip=4)  %>% # the table starts from row 5
  filter(`Summary Level`=='040') %>%
  select(`State Code (FIPS)`, `Area Name (including legal/statistical area description)`)

colnames(df_geo) <- c('STATEFP','STATENAME')

shapes@data <- shapes@data %>% 
  left_join(df_geo) %>%
  mutate(ALAND = as.numeric(as.character(ALAND)),
         AWATER = as.numeric(as.character(AWATER)),
         content = paste0('<b>',NAME,' (',STATENAME,')</b>',
                          '<br>Area of Land: ', ALAND, 
                          '<br>Area of Water: ', AWATER),
         NAME = as.character(NAME))

shapes <- shapes[!is.na(shapes@data$STATENAME),] # remove shapes that are not in a state (e.g., Guam)

names_state <- sort(df_geo$STATENAME)

#### UI ####
header <- dashboardHeader(
  title = "Leaflet - Layer Specific Legend",
  titleWidth = 300
)

body <- dashboardBody(
  fluidRow(
    column(width=2,
           selectInput("select_state", label='Select State:',
                       choices = names_state,
                       selected= 'New York'),
           style='margin-left:20px;z-index:100000'
           )
  ),
  fluidRow(
    column(width = 12,
           box(width = NULL, height = 620,
               leafletOutput("map",height=595),
               status='warning')
    )
  )
)

ui <- dashboardPage(
  title = "Leaflet - Layer Specific Legend",
  skin = 'yellow',
  header,
  dashboardSidebar(disable = TRUE),
  body
)

#### Server ####
server <- function(input, output, session) {
  
  #### initialize reactive values ####
  rvs <- reactiveValues(poly_state=shapes[shapes@data$STATENAME == 'New York',])
  

  #### output ####
  ## output: leaflet map
  output$map <- renderLeaflet({
    
    rvs$map <- rvs$poly_state %>%
      leaflet() %>%
      addTiles('http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png') %>%
      addPolygons(data = rvs$poly_state,
                  group = 'Area of Land',
                  weight=1, opacity = 1.0,color = 'white',
                  fillOpacity = 0.9, smoothFactor = 0.5,
                  fillColor = ~colorBin('OrRd',ALAND)(ALAND),
                  label = lapply(rvs$poly_state$content,HTML)) %>%
      addPolygons(data = rvs$poly_state,
                  group = 'Area of Water',
                  weight=1, opacity = 1.0,color = 'grey',
                  fillOpacity = 0.9, smoothFactor = 0.5,
                  fillColor = ~colorBin('YlGnBu',AWATER)(AWATER),
                  label = lapply(rvs$poly_state$content,HTML)) %>%
      addLayersControl(
        position = "bottomright",
        baseGroups = c('Area of Land','Area of Water'),
        options = layersControlOptions(collapsed = TRUE)) %>%
      addLegend(
        "topright",
        pal = colorBin('OrRd', rvs$poly_state$ALAND),
        values = rvs$poly_state$ALAND
      ) %>%
      hideGroup(c('Area of Land','Area of Water')) %>%
      showGroup('Area of Land')
    
  })
  
 
  #### observe mouse events ####
  ## update rv when the selected state changes
  observeEvent(input$select_state, {
    rvs$poly_state <- shapes[shapes@data$STATENAME == input$select_state,]
  })
  
  ## update legend when the selected layer group changes
  observeEvent(input$map_groups, {
    my_map <- leafletProxy("map") %>% clearControls()

    if (input$map_groups == 'Area of Land'){
      my_map <- my_map %>%
        addLegend(
          "topright",
          pal = colorBin('OrRd', rvs$poly_state$ALAND),
          values = rvs$poly_state$ALAND)
    }else{
      my_map <- my_map %>%
        addLegend(
          "topright",
          pal = colorBin('YlGnBu', rvs$poly_state$AWATER),
          values = rvs$poly_state$AWATER)
    }
  })
}

#### Run App ####
shinyApp(ui = ui, server = server)

Solution

  • First, you can create a data frame from your spatial data and edit your table. Here I delete the column "content".

    shapes_df <- as.data.frame(shapes[,c(1:10)])
    

    Then you create a reactive value that interacts with your input.

      popup <- reactive({
        return(shapes_df %>% select(input$select_column))
        })
    

    Here is a working code for you. I made some changes and commented some lines out.

    library(dplyr)
    library(readxl)
    library(shinydashboard)
    library(leaflet)
    library(htmltools)
    library(sf)
    
    zip_path <- 'cb_2018_us_county_5m.zip'
    
    if (!file.exists(zip_path)) {
      download.file('https://www2.census.gov/geo/tiger/GENZ2018/shp/cb_2018_us_county_5m.zip',
                  )
      unzip(zip_path, exdir='cb_2018_us_county_5m')
    }
    
    xlsx_path <- 'all-geocodes-v2019.xlsx'
    
    if (!file.exists(xlsx_path)) {
      download.file('https://www2.census.gov/programs-surveys/popest/geographies/2019/all-geocodes-v2019.xlsx',
                    xlsx_path, mode = "wb")
    }
    
    shapes <- st_read("cb_2018_us_county_5m","cb_2018_us_county_5m")
    
    df_geo <- read_excel('all-geocodes-v2019.xlsx',skip=4)  %>% # the table starts from row 5
      filter(`Summary Level`=='040') %>%
      select(`State Code (FIPS)`, `Area Name (including legal/statistical area description)`)
    
    colnames(df_geo) <- c('STATEFP','STATENAME')
    
    shapes <- shapes %>%
      left_join(df_geo) %>%
      mutate(ALAND = as.numeric(as.character(ALAND)),
             AWATER = as.numeric(as.character(AWATER)),
             content = paste0('<b>',NAME,' (',STATENAME,')</b>',
                              '<br>Area of Land: ', ALAND,
                              '<br>Area of Water: ', AWATER),
             NAME = as.character(NAME))
    
    # remove shapes that are not in a state (e.g., Guam)
    shapes <- shapes %>%
      dplyr::filter(!is.na(STATENAME)) 
    
    names_state <- sort(df_geo$STATENAME)
    
    # here you can select which columns you want to add to your popup
    shapes_df <- as.data.frame(shapes[,c(1:10)])
    
    
    
    #### UI ####
    header <- dashboardHeader(
      title = "Leaflet - Layer Specific Legend",
      titleWidth = 300
    )
    
    
    
    body <- dashboardBody(
      fluidRow(
        column(width=2,
               selectInput("select_state", label='Select State:',
                           choices = names_state,
                           selected= 'New York'),
               selectInput("select_column", label='Select the column you want to see in pop-up:',
                           choices = c(colnames(shapes))
               ),
               verbatimTextOutput("output"),
               style='margin-left:20px;z-index:100000'
        )
      ),
      fluidRow(
        column(width = 12,
               box(width = NULL, height = 620,
                   leafletOutput("map",height=595),
                   status='warning')
        )
      )
    )
    
    
    ui <- dashboardPage(
      title = "Leaflet - Layer Specific Legend",
      skin = 'yellow',
      header,
      dashboardSidebar(disable = TRUE),
      body
    )
    
    #### Server ####
    server <- function(input, output, session) {
      rvs <- reactive({
        shapes %>%
          dplyr::filter(STATENAME %in% input$select_state)
      })
      
      # we create a reactive value for popup which interacts with the input
      popup <- reactive({
        return(shapes_df %>% select(input$select_column))
      })
      
      
      #### initialize reactive values ####
      
      #### output ####
      ## output: leaflet map
      output$map <- renderLeaflet({
        leaflet() %>%
          addTiles('http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png') %>%
          addPolygons(data = rvs(),
                      group = 'Area of Land',
                      weight=1, opacity = 1.0,color = 'white',
                      fillOpacity = 0.9, smoothFactor = 0.5,
                      fillColor = ~colorBin('OrRd',rvs()$ALAND)(rvs()$ALAND),
                      label = paste(
                        colnames(popup()),": ", popup()[,1]
                      )
                      
          )%>%
          addPolygons(data = rvs(),
                      group = 'Area of Water',
                      weight=1, opacity = 1.0,color = 'grey',
                      fillOpacity = 0.9, smoothFactor = 0.5,
                      fillColor = ~colorBin('YlGnBu',rvs()$AWATER)(rvs()$AWATER),
                      label = paste(
                        colnames(popup()),": ", popup()[,1]
                      )
          ) %>%
          addLayersControl(
            position = "bottomright",
            baseGroups = c('Area of Land','Area of Water'),
            options = layersControlOptions(collapsed = TRUE)) %>%
          addLegend(
            "topright",
            pal = colorBin('OrRd', rvs()$ALAND),
            values =rvs()$ALAND
          ) %>%
          hideGroup(c('Area of Land','Area of Water')) %>%
          showGroup('Area of Land')
        
      })
      
      
      
      
      #### observe mouse events ####
      ## update rv when the selected state changes
      # observeEvent(input$select_state, {
      #   rvs() <- shapes[shapes$STATENAME == input$select_state,]
      # })
      
      
      ## update legend when the selected layer group changes
      observeEvent(input$map_groups, {
        my_map <- leafletProxy("map") %>% clearControls()
        
        if (input$map_groups == 'Area of Land'){
          my_map <- my_map %>%
            addLegend(
              "topright",
              pal = colorBin('OrRd', rvs()$ALAND),
              values = rvs()$ALAND)
        }else{
          my_map <- my_map %>%
            addLegend(
              "topright",
              pal = colorBin('YlGnBu', rvs()$AWATER),
              values = rvs()$AWATER)
        }
      })
      
    }
    
    #### Run App ####
    shinyApp(ui = ui, server = server)