I'm building a Leaflet app in Shiny, and the idea is that the user logs in to see an overview of specific cities that can be clicked on. Clicking on those cities will zoom in to that particular city. The code as shown below works in making this happen.
What I can't figure out, is that I also want to provide a button (that appears after a city is selected) called "Return to see other cities" that, when clicked, will "reset" the map back to its original state.
However, when I click on the button, I get the following error:
Warning: Error in UseMethod: no applicable method for 'metaData' applied to an object of class "NULL"
Here I'm using input$map_marker_click
to determine which city the app should zoom in on, and I think a part of the problem is that I can't set that back to its initial state of NULL after it's been clicked. Is there another way I'm missing?
library(shiny)
library(leaflet)
library(leaflet.extras)
library(dplyr)
cities <- data.frame(cities = c("London", "Chicago", "New York", "Philadelphia", "Los Angeles"),
lng = c(-0.118092, -87.6298, -74.0060, -75.1642, -118.2477),
lat = c(51.509865, 41.848, 40.7128, 39.9586, 34.0522),
zoom = c(11, 11, 12, 12, 10))
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "100%"),
conditionalPanel("isNaN(input.map_marker_click)", uiOutput("controls"))
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
leaflet(cities, width = "100%", height = "100%") %>%
addProviderTiles("CartoDB.DarkMatter") %>%
setView(lng = -56, lat = 49.2402, zoom = 4) %>%
addPulseMarkers(lng = ~lng, lat = ~lat,
label = ~cities,
icon = makePulseIcon())
})
output$controls <- renderUI({
req(input$map_marker_click)
absolutePanel(id = "controls", top = 100, left = 50,
right = "auto", bottom = "auto", width = "auto", height = "auto",
actionButton(inputId = "reset", label = "Return to see other cities", class = "btn-primary")
)
})
observeEvent(input$map_marker_click, {
city_selected <- filter(cities, lat == input$map_marker_click$lat[1])
leafletProxy("map") %>%
clearMarkers() %>%
clearControls() %>%
setView(lng = city_selected$lng[1], lat = city_selected$lat[1], zoom = city_selected$zoom[1])
})
#I know the below is wrong, but I don't know what I'm supposed to do to "reset" the map.
observeEvent(input$reset, {
leafletProxy("map") %>%
setView(lng = -56, lat = 49.2402, zoom = 4) %>%
addPulseMarkers(lng = ~lng, lat = ~lat,
label = ~cities,
icon = makePulseIcon())
})
}
shinyApp(ui, server)
An alternative reactive based output
Enclosing map creation inside a function so that resetting becomes easier by just a function call without code repetition:
library(shiny)
library(leaflet)
library(leaflet.extras)
library(dplyr)
library(shinyjs) #for hide function
cities <- data.frame(cities = c("London", "Chicago", "New York", "Philadelphia", "Los Angeles"),
lng = c(-0.118092, -87.6298, -74.0060, -75.1642, -118.2477),
lat = c(51.509865, 41.848, 40.7128, 39.9586, 34.0522),
zoom = c(11, 11, 12, 12, 10))
ui <- bootstrapPage(
useShinyjs(),
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "100%"),
conditionalPanel("isNaN(input.map_marker_click)", uiOutput("controls"))
)
server <- function(input, output, session) {
#creating the first map within a function so that reset becomes easy
base_map <- function(){
leaflet(cities, width = "100%", height = "100%") %>%
addProviderTiles("CartoDB.DarkMatter") %>%
setView(lng = -56, lat = 49.2402, zoom = 4) %>%
addPulseMarkers(lng = ~lng, lat = ~lat,
label = ~cities,
icon = makePulseIcon())
}
# reactiveVal for the map object, and corresponding output object.
react_map <- reactiveVal(base_map())
output$map <- renderLeaflet({
react_map()
})
output$controls <- renderUI({
req(input$map_marker_click)
absolutePanel(id = "controls", top = 100, left = 50,
right = "auto", bottom = "auto", width = "auto", height = "auto",
actionButton(inputId = "reset", label = "Return to see other cities", class = "btn-primary")
)
})
observeEvent(input$map_marker_click, {
city_selected <- filter(cities, lat == input$map_marker_click$lat[1])
show('controls')
leafletProxy("map") %>%
clearMarkers() %>%
clearControls() %>%
setView(lng = city_selected$lng[1], lat = city_selected$lat[1], zoom = city_selected$zoom[1])
})
# Making the entire map creation inside reactive function makes it easier to reset
observeEvent(input$reset, {
#hiding the control button
hide('controls')
# resetting the map
react_map(base_map())
})
}
shinyApp(ui, server)