Search code examples
rshinydtr-leaflet

How do I clear the markers on a leaflet map linked with a DT?


Based on this example https://travisknocherstats.com/posts/2020-05-18-linked-dt-datatable-with-leaflet-map-in-r-shiny/ it is possible to link DT and leaflet. Selected rows on DT are ploted on leaflet. I've made some modifications to be able to have a map by default ~markers ploted in red, then when the user select a row on DT, the marker on leaflet is highligted in blue.It works fine so far. 1- However, when the row is unselected on the DT the blue highlight still shows. How to solve this problem. 2 - Using the action button ~ Clear table selections ~ remove all the markers blues and reds. However, I just would like to make the blue highlight disapear still keeping my red markers.

my code below with reproducible example.

    require(shiny)
require(leaflet)
require(DT)
require(tidyverse)

shiny::shinyApp(
  ui = fluidPage(
    column(
      width = 3,
      br(),
      actionButton(
        "select_all_rows_button",
        "Select All Table Rows"
      ),
      br(),
      actionButton(
        "clear_rows_button",
        "Clear Table Selections"
      )
    ),
    column(
      width = 9,
      fluidRow(
        column(
          width = 12,
          solidHeader = TRUE,
          leafletOutput(
            "my_leaflet"
          )
        )
      ),
      fluidRow(
        column(
          width = 12,
          solidHeader = TRUE,
          DTOutput(
            "my_datatable"
          )
        )
      )
    )
  ),
  
  server = function(session, input, output) {
    
    quakes_r <- reactive({ as_tibble(quakes) })
    
    output$my_datatable <- renderDT({
      
      quakes_r() %>% 
        datatable()
      
    })
    
    
    # base map that we will add points to with leafletProxy()
    output$my_leaflet <- renderLeaflet({
      
      leaflet() %>% 
        addProviderTiles(
          provider = providers$CartoDB.Positron,
          options = providerTileOptions(
            noWrap = FALSE
          )
        ) %>% 
        addCircleMarkers(
          data = quakes_r(),
          lng = ~long,
          lat = ~lat,
          fillColor = "red",
          stroke = TRUE,
          color = "white",
          radius = 3,
          weight = 1,
          fillOpacity = 0.4
        )%>% 
        setView(
          lat = -25.5,
          lng = 178.58,
          zoom = 4
        )
      
    })
    
    observeEvent(input$my_datatable_rows_selected, {
      
      selected_lats <- eventReactive(input$my_datatable_rows_selected, {
        as.list(quakes_r()$lat[c(unique(input$my_datatable_rows_selected))])
      })
      
      selected_longs <- eventReactive(input$my_datatable_rows_selected, {
        as.list(quakes_r()$long[c(unique(input$my_datatable_rows_selected))])
      })
      
      selected_depths <- eventReactive(input$my_datatable_rows_selected, {
        as.list(quakes_r()$depth[c(unique(input$my_datatable_rows_selected))])
      })
      
      selected_mags <- eventReactive(input$my_datatable_rows_selected, {
        as.list(quakes_r()$mag[c(unique(input$my_datatable_rows_selected))])
      })
      
      selected_stations <- eventReactive(input$my_datatable_rows_selected, {
        as.list(quakes_r()$stations[c(unique(input$my_datatable_rows_selected))])
      })
      
      # this is the data that will be passed to the leaflet in the addCircleMarkers argument,
      # as well as the popups when the points are hovered over
      map_df <- reactive({
        tibble(lat = unlist(selected_lats()),
               lng = unlist(selected_longs()),
               depth = unlist(selected_depths()),
               mag = unlist(selected_mags()),
               stations = unlist(selected_stations()))
      })
      
      leafletProxy("my_leaflet", session) %>% 
        # clearMarkers() %>% 
        addCircleMarkers(
          data = map_df(),
          lng = ~lng,
          lat = ~lat,
          fillColor = "blue",
          stroke = TRUE,
          color = "white",
          radius = 3,
          weight = 1,
          fillOpacity = 0.4,
          popup = paste0("lat: ", map_df()$lat, "<br>",
                         "lng: ", map_df()$lng, "<br>",
                         "depth: ", map_df()$depth, "<br>",
                         "mag: ", map_df()$mag, "<br>",
                         "stations: ", map_df()$stations)
        )
      
    })
    
    # create a proxy to modify datatable without recreating it completely
    DT_proxy <- dataTableProxy("my_datatable")
    
    # clear row selections when clear_rows_button is clicked
    observeEvent(input$clear_rows_button, {
      selectRows(DT_proxy, NULL)
    })
    
    # clear markers from leaflet when clear_rows_button is clicked
    observeEvent(input$clear_rows_button, {
      clearMarkers(leafletProxy("my_leaflet", session))
    })
    
    # select all rows when select_all_rows_button is clicked
    observeEvent(input$select_all_rows_button, {
      selectRows(DT_proxy, input$my_datatable_rows_all)
    })
    
  }
)

Solution

  • This solution here works as intended and gets rid of a lot of unnecessary reactives/observer:

    server = function(session, input, output) {
       
       quakes <- as_tibble(quakes)
       group_name <- "my_additons"
       
       output$my_datatable <- renderDT({
          quakes %>% 
             datatable()
       })
       
       
       # base map that we will add points to with leafletProxy()
       output$my_leaflet <- renderLeaflet({
          
          leaflet() %>% 
             addProviderTiles(
                provider = providers$CartoDB.Positron,
                options = providerTileOptions(
                   noWrap = FALSE
                )
             ) %>% 
             addCircleMarkers(
                data = quakes,
                lng = ~long,
                lat = ~lat,
                group = "original",
                fillColor = "red",
                stroke = TRUE,
                color = "white",
                radius = 3,
                weight = 1,
                fillOpacity = 0.4
             ) %>% 
             setView(
                lat = -25.5,
                lng = 178.58,
                zoom = 4
             )
          
       })
       
       observe({
          sel <- quakes[input$my_datatable_rows_selected, ]
          leafletProxy("my_leaflet") %>% 
             clearGroup(group_name) %>%
             addCircleMarkers(
                data = sel,
                lng = ~long,
                lat = ~lat,
                group = group_name,
                fillColor = "blue",
                stroke = TRUE,
                color = "white",
                radius = 3,
                weight = 1,
                fillOpacity = 0.4,
                popup = ~ paste0("lat: ", lat, "<br>",
                                 "lng: ", long, "<br>",
                                 "depth: ", depth, "<br>",
                                 "mag: ", mag, "<br>",
                                 "stations: ", stations)
             )
       })
       
       
       
       # create a proxy to modify datatable without recreating it completely
       DT_proxy <- dataTableProxy("my_datatable")
       
       # clear row selections when clear_rows_button is clicked
       observeEvent(input$clear_rows_button, {
          selectRows(DT_proxy, NULL)
          leafletProxy("my_leaflet") %>% 
             clearGroup(group_name)
       })
       
       # select all rows when select_all_rows_button is clicked
       observeEvent(input$select_all_rows_button, {
          selectRows(DT_proxy, input$my_datatable_rows_all)
       })
    }
    
    • The idea is that you assign all manual clicks to a group, then you can easily delete the whole group via clearGroup instead of clearMarkers.
    • quakes will not change, so no need to make it reactive.
    • You can massively clean the observer for the row_selection