mapgl icon indicating copy to clipboard operation
mapgl copied to clipboard

proxy functions should be deferred until the shiny session is flushed by default

Open RWParsons opened this issue 1 year ago • 3 comments

This bug was identified in the discussions of #12.

Default values for shiny inputs that are observed to modify the map via proxy function are not currently applied. This is because they are not deferred until the shiny session is flushed as is the default by leaflet. See the that the proxy function includes a deferUntilFlush argument (https://github.com/rstudio/leaflet/blob/92bc272caa9a268140e75ede1966bcdc7d585636/R/utils.R#L136) and the invocation of this check this value and defers the change until the shiny session is flushed (https://github.com/rstudio/leaflet/blob/92bc272caa9a268140e75ede1966bcdc7d585636/R/utils.R#L194).

reprex:

By default this app should show only one polygon on the map (with the current default inputs for the 'area_range'. If you reduce the lower bound of the input range, it updates the map but this should happen for the default view of the map.

library(shiny)
library(bslib)
library(colourpicker)
library(dplyr)
library(sf)
library(shinyWidgets)
devtools::load_all()

ui <- bootstrapPage(
  sliderInput("slider", "min value:", value = 0, min = -3, max = 3),
  numericRangeInput("area_range", label = "numeric range input for area", value = c(0.03, 0.042), step = 0.01),
  maplibreOutput("map")
)



server <- function(input, output, session) {
  nc <- st_read(system.file("shape/nc.shp", package = "sf"))
  nc$var1 <- rnorm(n = nrow(nc))
  nc$CNTY_ID <- as.character(nc$CNTY_ID)

  output$map <- renderMaplibre({
    maplibre() |>
      fit_bounds(nc, animate = FALSE) |>
      add_fill_layer(
        id = "polygon_layer",
        source = nc,
        fill_color = "blue",
        fill_opacity = 0.5,
        tooltip = "AREA"
      )
  })


  observe({
    ids <- nc |>
      filter(
        AREA >= min(input$area_range),
        AREA <= max(input$area_range)
      ) |>
      pull(CNTY_ID)

    cat(length(ids))

    maplibre_proxy("map") |>
      set_filter(
        "polygon_layer",
        list("in", "CNTY_ID", ids)
      )
  })
}

shinyApp(ui, server)

I think the solution would be to make some wrapper, like leaflet has invokeRemote() which checks the shiny session and defers if necessary, rather than each shiny util, like set_filter(), directly calling proxy$session$sendCustomMessage().

Thanks!

Rex

RWParsons avatar Jul 16 '24 05:07 RWParsons

I've pushed a temporary fix here that allows set_filter() to be used on a regular map object (within or outside of Shiny). So an app developer can hard-code the initial filter to correspond to the initial value of the slider and get the expected behavior. I'll keep looking into this solution as well though.

walkerke avatar Jul 27 '24 13:07 walkerke

Example usage:

library(shiny)
library(bslib)
library(colourpicker)
library(dplyr)
library(sf)
library(shinyWidgets)
devtools::load_all()

ui <- bootstrapPage(
  sliderInput("slider", "min value:", value = 0, min = -3, max = 3),
  numericRangeInput("area_range", label = "numeric range input for area", value = c(0.03, 0.042), step = 0.01),
  maplibreOutput("map")
)



server <- function(input, output, session) {
  nc <- st_read(system.file("shape/nc.shp", package = "sf"))
  nc$var1 <- rnorm(n = nrow(nc))
  nc$CNTY_ID <- as.character(nc$CNTY_ID)
  
  output$map <- renderMaplibre({
    
    initial_ids <- nc |>
      filter(
        AREA >= 0.03,
        AREA <= 0.042
      ) |>
      pull(CNTY_ID)
    
    maplibre() |>
      fit_bounds(nc, animate = FALSE) |>
      add_fill_layer(
        id = "polygon_layer",
        source = nc,
        fill_color = "blue",
        fill_opacity = 0.5,
        tooltip = "AREA"
      ) |>
      set_filter(
        "polygon_layer",
        c("in", "CNTY_ID", initial_ids)
      )
  })
  
  
  observe({
    ids <- nc |>
      filter(
        AREA >= min(input$area_range),
        AREA <= max(input$area_range)
      ) |>
      pull(CNTY_ID)
    
    cat(length(ids))
    
    maplibre_proxy("map") |>
      set_filter(
        "polygon_layer",
        c("in", "CNTY_ID", ids)
      )
  })
}

shinyApp(ui, server)

walkerke avatar Jul 27 '24 13:07 walkerke

I was having the same issue. I have created a reactive called map_rendered() that I set to false initially, and have it get set to true when the bounding box of the initial map gets returned (indicating that the initial map has rendered). I then use that as a trigger for my proxy update.

E.g.

#define map rendered reactive
map_rendered = reactiveVal(FALSE)

#delay first layer drawing----
observeEvent({
    input$map_bbox
},{
    map_rendered(TRUE)
})

#create map layer as viewed by user----
observeEvent({
    map_rendered()...

CIOData avatar Apr 14 '25 16:04 CIOData