12

Is there a way to highlight a marker or polyline on a leaflet map when selecting (clicking on) the corresponding record in a datatable?

I looked at these questions/threads:

selecting a marker on leaflet, from a DT row click and vice versa - no answer

https://github.com/r-spatial/mapedit/issues/56 - check timelyportfolio's comment on Jul 23, 2017. As it shows in the gif, I would like to be able to select a row in the datatable so that the corresponding map object (marker/polyline) is highlighted as well (without editing the map).

Here is a working example where the highlighted map object is selected in the datatable below but not vice versa - which is what I am trying to achieve.

##############################################################################
# Libraries
##############################################################################
library(shiny)
library(shinythemes)
library(ggplot2)
library(plotly)
library(leaflet)
library(DT)
##############################################################################
# Data
##############################################################################
qDat <- quakes
qDat$id <- seq.int(nrow(qDat))
str(qDat)
##############################################################################
# UI Side
##############################################################################
ui <- fluidPage(
  titlePanel("Visualization of Fiji Earthquake"),

  # side panel
  sidebarPanel(
    h3('Fiji Earthquake Data'),

    sliderInput(
      inputId = "sld01_Mag",
      label="Show earthquakes of magnitude:", 
      min=min(qDat$mag), max=max(qDat$mag),
      value=c(min(qDat$mag),max(qDat$mag)), step=0.1
      ),

    plotlyOutput('hist01')
    ),

  # main panel
  mainPanel(
    leafletOutput('map01'),
    dataTableOutput('table01')
    )

)
##############################################################################
# Server Side
##############################################################################
server <- function(input,output){
  qSub <-  reactive({

      subset <- subset(qDat, qDat$mag>=input$sld01_Mag[1] &
                         qDat$mag<=input$sld01_Mag[2])
  })

  # histogram
  output$hist01 <- renderPlotly({
    ggplot(data=qSub(), aes(x=stations)) + 
      geom_histogram(binwidth=5) +
      xlab('Number of Reporting Stations') +
      ylab('Count') +
      xlim(min(qDat$stations), max(qDat$stations))+
      ggtitle('Fiji Earthquake')
  })

  # table
  output$table01 <- renderDataTable({

    DT::datatable(qSub(), selection = "single",options=list(stateSave = TRUE))
  })

  # map
  output$map01 <- renderLeaflet({
    pal <- colorNumeric("YlOrRd", domain=c(min(quakes$mag), max(quakes$mag)))
    qMap <- leaflet(data = qSub()) %>% 
      addTiles() %>%
      addMarkers(popup=~as.character(mag), layerId = qSub()$id) %>%
      addLegend("bottomright", pal = pal, values = ~mag,
                title = "Earthquake Magnitude",
                opacity = 1)
    qMap
  })

  observeEvent(input$map01_marker_click, {
    clickId <- input$map01_marker_click$id
    dataTableProxy("table01") %>%
      selectRows(which(qSub()$id == clickId)) %>%
      selectPage(which(input$table01_rows_all == clickId) %/% input$table01_state$length + 1)
  })
}

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

Any suggestions?

MLavoie
  • 9,671
  • 41
  • 36
  • 56
M_M
  • 899
  • 8
  • 21

1 Answers1

17

Yes, that is possible. You can get the selected row form the datatable with input$x_rows_selected where x is the datatable name. We can then use the leafletProxy to remove the old marker and add a new one. I also created a reactiveVal that keeps track of the previously marked row, and reset the marker for that element when a new one is clicked. If you want to keep previously selected markers red as well, simply remove the reactiveVal prev_row() and remove the second part of the observeEvent. Below is a working example.

Note that I added a head(25) in the qSub() reactive to limit the number of rows for illustration purposes.

Hope this helps!


enter image description here


    ##############################################################################
# Libraries
##############################################################################
library(shiny)
library(shinythemes)
library(ggplot2)
library(plotly)
library(leaflet)
library(DT)
##############################################################################
# Data
##############################################################################
qDat <- quakes
qDat$id <- seq.int(nrow(qDat))
str(qDat)
##############################################################################
# UI Side
##############################################################################
ui <- fluidPage(
  titlePanel("Visualization of Fiji Earthquake"),

  # side panel
  sidebarPanel(
    h3('Fiji Earthquake Data'),

    sliderInput(
      inputId = "sld01_Mag",
      label="Show earthquakes of magnitude:", 
      min=min(qDat$mag), max=max(qDat$mag),
      value=c(min(qDat$mag),max(qDat$mag)), step=0.1
    ),

    plotlyOutput('hist01')
  ),

  # main panel
  mainPanel(
    leafletOutput('map01'),
    dataTableOutput('table01')
  )

)
##############################################################################
# Server Side
##############################################################################
server <- function(input,output){
  qSub <-  reactive({

    subset <- subset(qDat, qDat$mag>=input$sld01_Mag[1] &
                       qDat$mag<=input$sld01_Mag[2]) %>% head(25)
  })

  # histogram
  output$hist01 <- renderPlotly({
    ggplot(data=qSub(), aes(x=stations)) + 
      geom_histogram(binwidth=5) +
      xlab('Number of Reporting Stations') +
      ylab('Count') +
      xlim(min(qDat$stations), max(qDat$stations))+
      ggtitle('Fiji Earthquake')
  })

  # table
  output$table01 <- renderDataTable({

    DT::datatable(qSub(), selection = "single",options=list(stateSave = TRUE))
  })

  # to keep track of previously selected row
  prev_row <- reactiveVal()

  # new icon style
  my_icon = makeAwesomeIcon(icon = 'flag', markerColor = 'red', iconColor = 'white')

  observeEvent(input$table01_rows_selected, {
    row_selected = qSub()[input$table01_rows_selected,]
    proxy <- leafletProxy('map01')
    print(row_selected)
    proxy %>%
      addAwesomeMarkers(popup=as.character(row_selected$mag),
                        layerId = as.character(row_selected$id),
                        lng=row_selected$long, 
                        lat=row_selected$lat,
                        icon = my_icon)

    # Reset previously selected marker
    if(!is.null(prev_row()))
    {
      proxy %>%
        addMarkers(popup=as.character(prev_row()$mag), 
                   layerId = as.character(prev_row()$id),
                   lng=prev_row()$long, 
                   lat=prev_row()$lat)
    }
    # set new value to reactiveVal 
    prev_row(row_selected)
  })

  # map
  output$map01 <- renderLeaflet({
    pal <- colorNumeric("YlOrRd", domain=c(min(quakes$mag), max(quakes$mag)))
    qMap <- leaflet(data = qSub()) %>% 
      addTiles() %>%
      addMarkers(popup=~as.character(mag), layerId = as.character(qSub()$id)) %>%
      addLegend("bottomright", pal = pal, values = ~mag,
                title = "Earthquake Magnitude",
                opacity = 1)
    qMap
  })

  observeEvent(input$map01_marker_click, {
    clickId <- input$map01_marker_click$id
    dataTableProxy("table01") %>%
      selectRows(which(qSub()$id == clickId)) %>%
      selectPage(which(input$table01_rows_all == clickId) %/% input$table01_state$length + 1)
  })
}

##############################################################################
shinyApp(ui = ui, server = server)
##############################################################################
Florian
  • 24,425
  • 4
  • 49
  • 80
  • This is very helpful! Thank you. Is there a way to highlight the marker instead of drawing another one on top? I assume when I highlight a marker, a row is highlighted in the DT rather than adding a new one. – M_M Feb 14 '18 at 08:13
  • Another question, is it possible to do the same for polyline? In my actual app, I have markers which are connected by polylines. Ideally, I would like to select a DT row and both endpoints and the connecting polyline get highlighted on the map. – M_M Feb 14 '18 at 08:17
  • @M_M To my knowledge, there is no (easy) way of changing the color of a already placed marker. When you select a row in the DT, you are not adding new rows to the DT, you are only adding a marker to the map. For polylines, maybe my answer [here](https://stackoverflow.com/questions/48474440/select-only-one-state-in-a-map-in-a-shiny-application/48478184#48478184) can help you in the right direction. – Florian Feb 14 '18 at 08:31
  • 1
    I removed the `removeMarker()` statements. These are redundant since adding a new marker with the same layerId will remove the old layer. – Florian Feb 14 '18 at 13:27
  • Thanks again - I will look at the link you sent for the polylines. Is it possible to enable double click to deselect the marker/row? And can the selection be only through the table not by clicking on the marker on the map? – M_M Feb 15 '18 at 05:04
  • @M_M I think you could post a separate question on Stack Overflow regarding if it is possible to select rows with a double click, I am not sure how to do that. The other question; I think you could add a reactiveVal that is set to 1 on 2 depending on if a row is selected with a map click or a table, or something along those lines. – Florian Feb 15 '18 at 06:27
  • Florian, apologies for the late reply. I figured out a way to do achieve the selection/de-selection mechanism. Check out this app: https://github.com/moh-salah/Trip-Data-Visualization – M_M Mar 26 '18 at 07:06
  • @Florian, is it also possible to add a popup (that is active) when the row in a datatable is selected? When you do %>% addPopups, the popup is not aligned properly. With javascript, you have the marker.openPopup(); functionality (where the popup string would then be contained in the proxy %>% addAwesomeMarkers() bit), but I'm not sure how to implement this in Shiny. – fifthace Jun 18 '19 at 16:16
  • @Florian: this is an awesome answer. Do you happen to know an example of using `selectInput` drop-down menu to highlight the point on `leaflet` map? Thanks! – Tung Mar 24 '23 at 15:47