6

Am I doing something wrong, or why does the below example not work? I am trying to make leaflet markercluster plugin work with leafletProxy() in an R Shiny app, using the option iconCreateFunction. Is the plugin not capable of adding customized icon markers to the map using leafletProxy()?

When I press the first button and zoom out in below example, I get an error saying:

TypeError: this._group.options.iconCreateFunction is not a function

enter image description here

I tried to copy the original example from the markercluster documentation:

library(shiny)
library(dplyr)
library(leaflet)

ui <- fluidPage(
  titlePanel("Hello Shiny!"),
  sidebarLayout(
    sidebarPanel(
      actionButton(inputId = "my_button1",
                   label = "Use leafletProxy()"),
      actionButton(inputId = "my_button2",
                   label = "Use renderLeaflet()")
    ),
    mainPanel(
      leafletOutput(
        outputId = "map",
        width = "100%",
        height = "300px"
      )
    )
  )
)

server <- function(input, output, session) {

  some_data <- data.frame(
    "lon"=c(4.905167,4.906357,4.905831),
    "lat"=c(52.37712,52.37783,52.37755),
    "number_var"=c(5,9,7),
    "name"=c("Jane","Harold","Mike"),
    stringsAsFactors = F
  )

  output$map <- renderLeaflet({
    return(
      leaflet(data = some_data[0,]) %>%
         addProviderTiles(providers$CartoDB.Positron) %>%
        fitBounds(
          min(some_data$lon),
          min(some_data$lat),
          max(some_data$lon),
          max(some_data$lat)
        ) %>%
        addMarkers(
          lng = ~lon,
          lat = ~lat,
          clusterOptions = markerClusterOptions(
            iconCreateFunction = JS(paste0("function(cluster) {",
                                           "return new L.DivIcon({",
                                           "html: '<div style=\"background-color:rgba(77,77,77,0.5)\"><span>' + cluster.getChildCount() + '</div><span>',",
                                           "className: 'marker-cluster'",
                                           "});",
                                           "}"))



          )
        )
    )
  })

  observeEvent(input$my_button1,{
      leafletProxy(mapId = "map",
                   session = session,
                   data = some_data) %>%
        addProviderTiles(providers$CartoDB.Positron) %>%
        clearMarkerClusters() %>%
        clearMarkers() %>%
        fitBounds(
          min(some_data$lon),
          min(some_data$lat),
          max(some_data$lon),
          max(some_data$lat)
        ) %>%
        addMarkers(
          lng = ~lon,
          lat = ~lat,
          clusterOptions = markerClusterOptions(
            iconCreateFunction = JS(paste0("function(cluster) {",
                                           "console.log('Here comes cluster',cluster); ",
                                           "return new L.DivIcon({",
                                           "html: '<div style=\"background-color:rgba(77,77,77,0.5)\"><span>' + cluster.getChildCount() + '</div><span>',",
                                           "className: 'marker-cluster'",
                                           "});",
                                           "}"))
          )
        )
  })

  observeEvent(input$my_button2,{
    output$map <- renderLeaflet({

      leaflet(data = some_data) %>%
        addProviderTiles(providers$CartoDB.Positron) %>%
        fitBounds(
          min(some_data$lon),
          min(some_data$lat),
          max(some_data$lon),
          max(some_data$lat)
        ) %>%
        addMarkers(
          lng = ~lon,
          lat = ~lat,
          clusterOptions = markerClusterOptions(
            iconCreateFunction = JS(paste0("function(cluster) {",
                                           "console.log('Here comes cluster',cluster); ",
                                           "return new L.DivIcon({",
                                           "html: '<div style=\"background-color:rgba(77,77,77,0.5)\"><span>' + cluster.getChildCount() + '</div><span>',",
                                           "className: 'marker-cluster'",
                                           "});",
                                           "}"))
          )
        )
    })
  })
}

shinyApp(ui = ui, server = server)

Package versions:

dplyr_0.7.4
leaflet_1.1.0
shiny_1.0.5
R version 3.4.3 (2017-11-30)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 16.04.3 LTS

Browser version: Firefox Quantum 57.0.1 (64-bit)

enter image description here

nilsole
  • 1,663
  • 2
  • 12
  • 28
  • I don't get any errors while running your code. I am on a mac though. – MLavoie Dec 21 '17 at 19:00
  • You see markers when pressing both buttons? Thanks for trying. – nilsole Dec 21 '17 at 19:34
  • after pressing leafletProxy() the first time yes, but not after with the zoom out – MLavoie Dec 21 '17 at 19:39
  • Thanks. That is exactly the problem. The markercluser plugin is supposed to organize the zooming, but in case of `leafletProxy` this does not seem to work (although dynamically adding markers to the map would be very useful of course). – nilsole Dec 21 '17 at 19:42
  • 1
    This unanswered [question](https://stackoverflow.com/questions/43483696/iconcreatefunction-does-not-work-when-adding-markers-with-leafletproxy) suggests this problem has been around awhile and is referenced in two related github issues ([420](https://github.com/rstudio/leaflet/issues/420) and [440](https://github.com/rstudio/leaflet/issues/440)) without comment from the team there. – Kevin Arseneau Dec 29 '17 at 13:07

3 Answers3

5

To follow Kevin's answer, modifying the clusterId to a vector gets the leafletProxy version to work for me. Not sure if this causes unintended consequences though...

app.R

library(shiny)
library(dplyr)
library(leaflet)

ui <- fluidPage(
  titlePanel("Hello Shiny!"),
  sidebarLayout(
    sidebarPanel(
      actionButton(inputId = "my_button1",
                   label = "Use leafletProxy()"),
      actionButton(inputId = "my_button2",
                   label = "Use renderLeaflet()")
    ),
    mainPanel(
      leafletOutput(
        outputId = "mymap",
        width = "100%",
        height = "300px"
      ))
  ))

server <- function(input, output, session) {

  some_data <- data.frame(
    lon = c(4.905167, 4.906357, 4.905831),
    lat = c(52.37712, 52.37783, 52.37755),
    number_var = c(5, 9, 7),
    name = c("Jane", "Harold", "Mike"),
    stringsAsFactors = FALSE
  )

  marker_js <- JS("function(cluster) {
                  var html = '<div style=\"background-color:rgba(77,77,77,0.5)\"><span>' + cluster.getChildCount() + '</div><span>'
                  return new L.DivIcon({html: html, className: 'marker-cluster'});
}")

  output$mymap <- renderLeaflet({

    leaflet(some_data) %>%
      addProviderTiles(providers$CartoDB.Positron) %>%
      fitBounds(
        ~min(lon),
        ~min(lat),
        ~max(lon),
        ~max(lat)
      ) %>%
      addMarkers(
        layerId = "mylayer",
        clusterId = "mycluster",
        lng = ~lon,
        lat = ~lat,
        clusterOptions = markerClusterOptions(
          iconCreateFunction = marker_js
        )
      )

  })

  observeEvent(input$my_button1, {

    leafletProxy("mymap", data = some_data) %>%
      removeMarker(layerId = "mylayer") %>%
      clearTiles %>%
      addProviderTiles(providers$CartoDB.Positron) %>%
      fitBounds(
        ~min(lon),
        ~min(lat),
        ~max(lon),
        ~max(lat)
      ) %>%
      addMarkers(
        layerId = "mylayer",
        clusterId = ~name,
        lng = ~lon,
        lat = ~lat,
        clusterOptions = markerClusterOptions(
          iconCreateFunction = marker_js
        )
      )

  })

  observeEvent(input$my_button2,{

    output$mymap <- renderLeaflet({

      leaflet(some_data) %>%
        addProviderTiles(providers$CartoDB.Positron) %>%
        fitBounds(
          ~min(lon),
          ~min(lat),
          ~max(lon),
          ~max(lat)
        ) %>%
        addMarkers(
          layerId = "mylayer",
          clusterId = "mycluster",
          lng = ~lon,
          lat = ~lat,
          clusterOptions = markerClusterOptions(
            iconCreateFunction = marker_js
          )
        )
    })

  })

  }

shinyApp(ui = ui, server = server)
whatamidoing
  • 51
  • 1
  • 2
1

Revised solution

The behaviour of iconCreateFunction is definitely flakey when used within leafletProxy. Although I think there is caching in some browsers making it difficult to track visually.

In order to eliminate the javascript error you were experiencing, it is important to apply layerId and clusterId values as well as using removeMarker in lieu of clearMarkers.

N.B. A strange side-effect of my solution is that a marker is dropped when re-drawn, I'm getting a bit tired and will have another look later. That problem may or may not be trivial.

app.R

library(shiny)
library(dplyr)
library(leaflet)

ui <- fluidPage(
  titlePanel("Hello Shiny!"),
  sidebarLayout(
    sidebarPanel(
      actionButton(inputId = "my_button1",
                   label = "Use leafletProxy()"),
      actionButton(inputId = "my_button2",
                   label = "Use renderLeaflet()")
    ),
    mainPanel(
      leafletOutput(
        outputId = "mymap",
        width = "100%",
        height = "300px"
    ))
))

server <- function(input, output, session) {

  some_data <- data.frame(
    lon = c(4.905167, 4.906357, 4.905831),
    lat = c(52.37712, 52.37783, 52.37755),
    number_var = c(5, 9, 7),
    name = c("Jane", "Harold", "Mike"),
    stringsAsFactors = FALSE
  )

  marker_js <- JS("function(cluster) {
                  var html = '<div style=\"background-color:rgba(77,77,77,0.5)\"><span>' + cluster.getChildCount() + '</div><span>'
                  return new L.DivIcon({html: html, className: 'marker-cluster'});
                  }")

  output$mymap <- renderLeaflet({

    leaflet(some_data) %>%
      addProviderTiles(providers$CartoDB.Positron) %>%
      fitBounds(
        ~min(lon),
        ~min(lat),
        ~max(lon),
        ~max(lat)
      ) %>%
      addMarkers(
        layerId = "mylayer",
        clusterId = "mycluster",
        lng = ~lon,
        lat = ~lat,
        clusterOptions = markerClusterOptions(
          iconCreateFunction = marker_js
        )
      )

  })

  observeEvent(input$my_button1, {

    leafletProxy("mymap", data = some_data) %>%
      removeMarker(layerId = "mylayer") %>%
      clearTiles %>%
      addProviderTiles(providers$CartoDB.Positron) %>%
      fitBounds(
        ~min(lon),
        ~min(lat),
        ~max(lon),
        ~max(lat)
      ) %>%
      addMarkers(
        layerId = "mylayer",
        clusterId = "mycluster",
        lng = ~lon,
        lat = ~lat,
        clusterOptions = markerClusterOptions(
          iconCreateFunction = marker_js
        )
      )

  })

  observeEvent(input$my_button2,{

    output$mymap <- renderLeaflet({

      leaflet(some_data) %>%
        addProviderTiles(providers$CartoDB.Positron) %>%
        fitBounds(
          ~min(lon),
          ~min(lat),
          ~max(lon),
          ~max(lat)
        ) %>%
        addMarkers(
          layerId = "mylayer",
          clusterId = "mycluster",
          lng = ~lon,
          lat = ~lat,
          clusterOptions = markerClusterOptions(
            iconCreateFunction = marker_js
          )
        )
    })

  })

}

shinyApp(ui = ui, server = server)

in-browser

enter image description here

No other javascript errors were noted.

Kevin Arseneau
  • 6,186
  • 1
  • 21
  • 40
  • Thanks for your input; I agree that leaflet maps should be initialized and pre-rendered before manipulation with `leafletProxy()`. Yet my major problem remains: A customized function for option `iconCreateFunction` is not accepted by the plugin when using `leafletProxy()`. I consider that a bug unless I get to know how to use customized create functions for markers that get injected using `leafletProxy()`. – nilsole Dec 28 '17 at 13:51
  • 1
    @nilsole, updated solution following your clarification. – Kevin Arseneau Dec 28 '17 at 22:51
  • 1
    Thanks. The code above causes errors. 1) Run app. 2) Open Developer Tools / Firebug. 3) Click "Use leafletProxy()". 4) JS error as described above: `Uncaught TypeError: this._group.options.iconCreateFunction is not a function`. Can you reproduce, please? – nilsole Dec 29 '17 at 09:38
  • Almost perfect, good work. The only question that remains is: Why are only 2 points added to the refreshed proxy map instead of 3? One marker seems to be missing. I tried to alter the `layerId` and `clusterId` on refresh, but without any positive effect. – nilsole Dec 29 '17 at 15:38
  • I think this turns into a speculative discussion from here, so be rewarded for your discovery and research (I am feeling bad that I did not come up with the links, sorry). – nilsole Dec 29 '17 at 15:45
1

For anyone else who is struggling with this, the bug has been recently solved: https://github.com/rstudio/leaflet/pull/696

You need to reinstall leaflet from github using:

remotes::install_github("rstudio/leaflet")

and then restart R.