2

My question is identical to this one: Trigger marker mouse click event in R leaflet for shiny but I don't have enough rep to add a comment, and the edit queue is 'full' so I can't add my thoughts to the original question. Not sure if this goes against community rules/best practice, please remove if so! Apologies for the long winded description below but I think I might be close to a solution that a javascript or shiny guru could fix in no time! Or, I'm barking up the wrong tree completely. Thanks for reading!

I would like to trigger a Leaflet map marker click event when I select a row in a DT datatable in my R shiny web application.

Here's a min example app as a base for adding this functionality:

library(shiny)
library(leaflet)
library(magrittr)
library(shinyjs)

# create js function that triggers a click on a button 'buttona'
jsCode <- 'shinyjs.buttonClick = (function() {
           $("#buttona").click();
           });'

df <- tibble::tibble(id = c(1,2,3,4,5),
                     label = c('One','Two','Three','Four','Five'),
                     lat = c(50,55,60,65,70), lng = c(0,5,-5,10,-10)
                     )

ui <- fluidPage(
    # new lines to enable shinyjs and import custom js function
    shinyjs::useShinyjs(),
    shinyjs::extendShinyjs(text = jsCode, functions = c('buttonClick')),

    leaflet::leafletOutput('map'),
    DT::DTOutput('table'),
    shiny::actionButton('buttona',"Button A") # new button
)

server <- function(input, output, session) {
    
    output$map <- leaflet::renderLeaflet({
        leaflet::leaflet(options = leaflet::leafletOptions(minZoom = 3,maxZoom = 10)) %>%
            leaflet::setView(lng = 10,lat = 60,zoom = 3) %>%
            leaflet::addProviderTiles(provider = leaflet::providers$Esri.OceanBasemap) %>%
            leaflet::addMarkers(data = df,
                                layerId = ~id,
                                group = 'group1',
                                label = ~label,
                                lat = ~lat,
                                lng = ~lng,
                                popup = ~paste("<h3>More Information</h3>",
                                               "<b>Title:</b>",label,sep =" "))
    })
    output$table <- DT::renderDT(df,
                                 selection = 'single',
                                 rownames = FALSE,
                                 editable = FALSE
    )

    # observer looking for datatable row selection and triggering js function
    observeEvent(input$table_rows_selected,{
        shinyjs::js$buttonClick()
    })

    # observer looking for button click to trigger modal
    observeEvent(input$buttona,{
        showModal(
            modalDialog(title = "Test",
                        size = 'm',
                        h1("Test")
                        
            )
        )
    })
    
}

# Run the application 
shinyApp(ui = ui, server = server)

Things I have tried:

shinyjs and javascript

I have been able to successfully use the shinyjs package to create similar funtionality with a button (see example app above), but when I try to do the same thing for the markers I just don't have the js knowledge to find the right element. By browsing in the js console in chrome I am able to find them manually, but they are within an iframe which I don't know how to programatically target, plus there is a random string in the location e.g. jQuery351022343796258432992. Using manual location via chrome js console (I need to use the 'Elements' tab to select the #document within the iframe before this works) I can trigger the click event I want with the following lines:

var mymap = document.getElementsByClassName('leaflet');
var els = mymap.map.jQuery351022343796258432992.leafletMap.layerManager._byGroup.group1;
els[0].fire('click'); //note this is the leaflet.js to trigger a marker click event

shinywidgets

There might be something in using shinywidgets::onRender as per the leaflet documentation at the bottom of this page https://rstudio.github.io/leaflet/morefeatures.html, but I don't know exactly how to implement it in this scenario.

Thanks again for reading!

1 Answer 1

3

Solution using JS

After getting access to the Map object, you need to iterate over all the layers to find the marker with a specific id.

I modified the JS function you call with shinyjs to iterate over all the layers and fire the event click on the marker that matches the id. To avoid looking for the Map object every time, the Map object is retrieved after rendering using htmlwidgets::onRender function. As an alternative to shinyjs, you can use runjs to execute the function (not in code below).

library(shiny)
library(leaflet)
library(magrittr)
library(shinyjs)

# create js function that triggers a click on a marker selected by a row in a DT
jsCode <- 'shinyjs.markerClick = function(id) {
              map.eachLayer(function (layer) {
                if (layer.options.layerId == id) {
                  layer.fire("click");
                }
              })
           };'

df <- tibble::tibble(id = c(1,2,3,4,5),
                     label = c('One','Two','Three','Four','Five'),
                     lat = c(50,55,60,65,70), lng = c(0,5,-5,10,-10)
)

ui <- fluidPage(
  # new lines to enable shinyjs and import custom js function
  shinyjs::useShinyjs(),
  shinyjs::extendShinyjs(text = jsCode, functions = c('markerClick')),
  
  leaflet::leafletOutput('map'),
  DT::DTOutput('table'),
  shiny::actionButton('buttona',"Button A") # new button
)

server <- function(input, output, session) {
  
  output$map <- leaflet::renderLeaflet({
    m <- leaflet::leaflet(options = leaflet::leafletOptions(minZoom = 3,maxZoom = 10)) %>%
      leaflet::setView(lng = 10,lat = 60,zoom = 3) %>%
      leaflet::addProviderTiles(provider = leaflet::providers$Esri.OceanBasemap) %>%
      leaflet::addMarkers(data = df,
                          layerId = ~id,
                          group = 'group1',
                          label = ~label,
                          lat = ~lat,
                          lng = ~lng,
                          popup = ~paste("<h3>More Information</h3>",
                                         "<b>Title:</b>",label,sep =" "))
    
    # assign the leaflet object to variable 'map'
    m <- m %>% 
      htmlwidgets::onRender("
          function(el, x) {
            map = this;
          }"
      )                                         
    
  })
  output$table <- DT::renderDT(df,
                               selection = 'single',
                               rownames = FALSE,
                               editable = FALSE
  )
  
  # observer looking for datatable row selection and triggering js function
  observeEvent(input$table_rows_selected,{
    rowIndex <- input$table_rows_selected
    df$id[rowIndex]
    shinyjs::js$markerClick(df$id[rowIndex])
  })
  
  # observer looking for button click to trigger modal
  observeEvent(input$buttona,{
    showModal(
      modalDialog(title = "Test",
                  size = 'm',
                  h1("Test")
                  
      )
    ) 
  })
  
}

# Run the application 
shinyApp(ui = ui, server = server)

Solution using Leaflet proxy

Just add a new popup every time a user selects a row in the table. It is important to use the same layerId to automatically update a popup that could be already on the map. Also, since the popup is going to be placed on the marker lat and lng, it is necessary to adjust the relative position on pixels using offset.

library(shiny)
library(leaflet)

df <- tibble::tibble(id = c(1,2,3,4,5),
                     label = c('One','Two','Three','Four','Five'),
                     lat = c(50,55,60,65,70), lng = c(0,5,-5,10,-10)
)

ui <- fluidPage( 
  leaflet::leafletOutput('map'),
  DT::DTOutput('table')
)

server <- function(input, output, session) {
  
  output$map <- leaflet::renderLeaflet({
    m <- leaflet::leaflet(options = leaflet::leafletOptions(minZoom = 3,maxZoom = 10)) %>%
      leaflet::setView(lng = 10,lat = 60,zoom = 3) %>%
      leaflet::addProviderTiles(provider = leaflet::providers$Esri.OceanBasemap) %>%
      leaflet::addMarkers(data = df,
                          layerId = ~id,
                          group = 'group1',
                          label = ~label,
                          lat = ~lat,
                          lng = ~lng,
                          popup = ~paste("<h3>More Information</h3>",
                                         "<b>Title:</b>",label,sep =" "))
    
  })
  
  output$table <- DT::renderDT(df,
                               selection = 'single',
                               rownames = FALSE,
                               editable = FALSE
  )
  
  # observer looking for datatable row selection and use leaflet proxy to add a popup
  observeEvent(input$table_rows_selected,{
    rowIndex <- input$table_rows_selected
    df$id[rowIndex]
    proxy <- leafletProxy("map")
    addPopups(
      proxy,
      lng = df$lng[rowIndex],
      lat =df$lat[rowIndex],
      popup = paste("<h3>More Information</h3>",
                    "<b>Title:</b>",df$label[rowIndex],sep =" "),
      layerId = "popup",
      options  = popupOptions(offset = list (x = 0, y = -26))
    )
  })
}

shinyApp(ui = ui, server = server)
5
  • I'm not familiar with leaflet, but can't we more simply use the proxy? Commented Dec 10, 2021 at 12:28
  • Yes, you are right, I just tried to answer the question as asked using the "click event..." I added the solution using a proxy that is simpler.
    – Geovany
    Commented Dec 10, 2021 at 19:40
  • Thanks Geovany that is the solution I needed. The JS version works better for me for two reasons: I only have to define the popup content once, which is more desirable in my use case, and the proxy version creates a situation where two popups can co-exist on the map. Other than that both solutions are great, so thanks also @Stéphane for the alternate suggestion. Just one note, with the JS solution the 'mousehover' labels stick over the markers. I'll figure that out, and it's not a big problem compared to the added functionality though! Commented Dec 11, 2021 at 9:03
  • I think the Proxy solution might be appropriate for this question Geovany stackoverflow.com/questions/56962857/… if you want to paste it in there. Commented Dec 11, 2021 at 9:12
  • I’m glad it worked out. I added a link to the question you pointed me.
    – Geovany
    Commented Dec 12, 2021 at 0:57

Not the answer you're looking for? Browse other questions tagged or ask your own question.