I have a leaflet map and I want the option of switching from the values of A being mapped to the values of B. Every example I can find says to use shiny and leaflet and all of these examples include something along the lines of:
ui <- fluidPage(
selectInput(inputId = "Data",
label = "Data",
choices = c("A","B"),
leafletProxy(outputId = "map") #or leafletOutput
))
but I keep getting the error that
leafletProxy (or leafletOutput)does not exist
. How do I solve this? My leaflet is created with :
mypal <- colorNumeric(palette = "viridis", domain = d$A)
leaflet() %>%
addProviderTiles("OpenStreetMap.Mapnik") %>%
setView(lat = 39.8283, lng = -98.5795, zoom = 4) %>%
addPolygons(data =
USA, stroke = TRUE, color='black', opacity=1, weight=.5, smoothFactor = 0.2, fillOpacity = 1,
fillColor = ~mypal(d$A),
popup = paste('<b>',d$state, "</b><br>A:", d$A) %>%
addLegend(position = "bottomleft", pal = mypal, values = d$A,
title = "A",
opacity = 1)
It seems from your example that your shiny has no server function, so it is not going to work.
Please, find attached a mock shiny you can start building on:
library(shiny)
library(leaflet)
ui <- fluidPage(
selectInput(inputId = "Data",
label = "Data",
choices = c("A", "B")),
leafletOutput("map")
)
server <- server <- function(input, output, session) {
output$map <- renderLeaflet({
if((input$Data) == "A"){
point = c(42.6525, -73.757222)
}
if((input$Data) == "B"){
point = c(39.283333, -76.616667)
}
leaflet() %>%
addProviderTiles("OpenStreetMap.Mapnik") %>%
addMarkers(lat = point[1], lng = point[2])
})
It will show "Albany" when you select "A" and Baltimore when you select "B"
Basically:
ui is kind of the "interface", what it is going to be shown:
selectInput: you can choose A or B here
leafletOutput: will show the leaflet map
server will do the "hard job" of creating the map and computing actions when you use selecInput:
output$map means that we want to render the leafletOuput (that is why it is called map, as in leafletOutput("map")
Then, according to the input selected (A or B)
if((input$Data) == "A"){
point = c(42.6525, -73.757222)
}
if((input$Data) == "B"){
point = c(39.283333, -76.616667)
}
We assign coordinates of Albany or Baltimore to point.
Finally, we build the map:
leaflet() %>%
addProviderTiles("OpenStreetMap.Mapnik") %>%
addMarkers(lat = point[1], lng = point[2])
PLEASE, take into account that this is a mock shiny, it is far from perfect, it is only illustrative.
Best!
Related
I have successfully created an interactive choropleth map using Leaflet in R that projects a single variable across a set of polygons.
library(RSocrata)
library(rgdal)
library(leaflet)
library(sp)
library(dplyr)
#library(mapview)
area_bound <- rgdal::readOGR("https://data.cityofchicago.org/resource/igwz-8jzy.geojson")
area_bound$area_num_1 <- as.numeric(area_bound$area_numbe)
health <- read.socrata("https://data.cityofchicago.org/resource/iqnk-2tcu.json")
data_num <- as.data.frame(apply(health[3:29], 2, as.numeric))
data_num <- bind_cols(health[1:2], data_num)
health_area <- sp::merge(area_bound, data_num, by.x = "area_numbe", by.y = "community_area")
pal <- colorNumeric("viridis", NULL)
leaflet(health_area) %>%
addTiles() %>%
addPolygons(stroke = FALSE, smoothFactor = 0.3, fillOpacity = 1,
fillColor = ~pal(as.numeric(firearm_related)),
label = ~paste0(community, ": ", formatC(firearm_related, big.mark = ",")))
The health data set has multiple variables and I would like to create a shiny app that allows users to choose a different variable to produce a choropleth map. Using the code provided by Kyle Walker as a model for my server, I came up with the code below that allows users to choose from a list of two variables. Unfortunately I am having problems running it, getting a Warning: Error in min: invalid 'type' (list) of argument error. Any help in resolving this would be appreciated. I have also looked at the RStudio, Using Leaflet With Shiny tutorial, but the examples provided are not choropleth maps.
Here is my non-working code:
## app.R ##
library(shiny) # for shiny apps
library(leaflet) # renderLeaflet function
library(RSocrata)
library(rgdal)
library(sp)
library(dplyr)
area_bound <- rgdal::readOGR("https://data.cityofchicago.org/resource/igwz-8jzy.geojson")
area_bound$area_num_1 <- as.numeric(area_bound$area_numbe)
health <- read.socrata("https://data.cityofchicago.org/resource/iqnk-2tcu.json")
data_num <- as.data.frame(apply(health[3:29], 2, as.numeric))
data_num <- bind_cols(health[1:2], data_num)
health_area <- sp::merge(area_bound, data_num, by.x = "area_numbe", by.y = "community_area")
groups <- c("Breast Cancer" = "breast_cancer_in_females", "Firearm" = "firearm_related")
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
radioButtons(
inputId = "group",
label = "Select a group to map",
choices = groups
)
),
mainPanel(
leafletOutput("map", height = "600")
)
)
)
server = function(input, output) {
group_to_map <- reactive({
input$group
})
output$map <- renderLeaflet({
leaflet(options = leafletOptions(zoomControl = FALSE)) %>%
addProviderTiles(providers$Stamen.TonerLite) %>%
setView(lng = -87.623177,
lat = 41.881832,
zoom = 8.5)
})
observeEvent(input$group, {
pal <- colorNumeric("viridis", group_to_map)
leafletProxy("map") %>%
clearShapes() %>%
clearControls() %>%
addPolygons(data = group_to_map,
color = ~pal(),
weight = 0.5,
fillOpacity = 0.5,
smoothFactor = 0.2) %>%
addLegend(
position = "bottomright",
pal = pal,
values = group_to_map,
title = "% of population"
)
})
}
shinyApp(ui, server)
There are several issues with your shiny code. First, to refer to values from a reactive you have to call it like a function, i.e. you have to do group_to_map(). Next, group_to_map() is just a character. To use the data column whose name is stored in group_to_map() you have to do health_area[[group_to_map()]]. I also fixed the issue with your palette functions. Finally, note that I switched to sf for reading the geo data as I'm more familiar with sf objects:
## app.R ##
library(shiny) # for shiny apps
library(leaflet) # renderLeaflet function
library(RSocrata)
library(dplyr)
area_bound <- sf::st_read("https://data.cityofchicago.org/resource/igwz-8jzy.geojson")
health <- read.socrata("https://data.cityofchicago.org/resource/iqnk-2tcu.json")
health[3:29] <- lapply(health[3:29], as.numeric)
#> Warning in lapply(health[3:29], as.numeric): NAs introduced by coercion
health_area <- left_join(area_bound, health, by = c("area_num_1" = "community_area"))
groups <- c("Breast Cancer" = "breast_cancer_in_females", "Firearm" = "firearm_related")
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
radioButtons(
inputId = "group",
label = "Select a group to map",
choices = groups
)
),
mainPanel(
leafletOutput("map", height = "600")
)
)
)
server = function(input, output) {
group_to_map <- reactive({
input$group
})
output$map <- renderLeaflet({
leaflet(options = leafletOptions(zoomControl = FALSE)) %>%
addProviderTiles(providers$Stamen.TonerLite) %>%
setView(lng = -87.623177,
lat = 41.881832,
zoom = 8.5)
})
observeEvent(input$group, {
pal <- colorNumeric("viridis", range(health_area[[group_to_map()]]))
leafletProxy("map") %>%
clearShapes() %>%
clearControls() %>%
addPolygons(data = health_area,
color = ~pal(health_area[[group_to_map()]]),
weight = 0.5,
fillOpacity = 0.5,
smoothFactor = 0.2) %>%
addLegend(
position = "bottomright",
pal = pal,
values = health_area[[group_to_map()]],
title = "% of population"
)
})
}
shinyApp(ui, server)
#>
#> Listening on http://127.0.0.1:5938
I'm creating a mapping visualization in RShiny, and I'm trying to detect a map shape click. I know that you can do this by using observeEvent, but my issue is that I'm using leafsync to synchronize my maps, so I have to wrap my Leaflet maps in a renderUI. Here's a simplified version of my code:
ui <- fluidPage(
navbarPage('CS4All and CTE Progress Visualization',
tabPanel('Map',
div(class='outer',
sidebarLayout(
mainPanel(
uiOutput('synced_maps')
)
)
)
)
)
)
server <- function(input, output) {
output$synced_maps <- renderUI({
# --- CS4ALL MAP
cs4all_map <- leaflet() %>%
setView(lat=40.730610, lng=-73.935242, zoom = 10) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(data = ctecscrs_geo,
stroke = TRUE,
color = '#808080',
weight = 1)
# --- CTE MAP
cte_map <- leaflet() %>%
setView(lat=40.730610, lng=-73.935242, zoom = 10) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(data = ctecscrs_geo,
stroke = TRUE,
color = '#808080',
weight = 1)
# --- DEMOGRAPHICS MAP
dem_map <- leaflet() %>%
setView(lat=40.730610, lng=-73.935242, zoom = 10) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(data = csd_dem_geo,
stroke = TRUE,
color = '#808080',
weight = 1)
sync(cs4all_map, cte_map, dem_map)
})
}
I tried adding the following both inside and outside of the renderUI function:
observeEvent(input$cte_map_shape_click, {
click <- input$cte_map_shape_click
print(click$id)
})
and in both instances, nothing printed to the console. Does anyone know if it is possible to observe events on synchronized maps in Leaflet/RShiny?
I would like to create a leaflet map where you can select multiple polygons and this will update the selectizeInput() in a shiny app. This would including removing a selected polygon, when it is removed in the selectizeInput().
I have slightly changed/updated the code from the answer here (use of sf instead of sp and more dplyr where I could work out what the base R was).
The polygons could probably be updated with an observeEvent tied in with input$clicked_locations, but not sure exactly how.
Here is the code:
library(shiny)
library(leaflet)
library(sf)
library(dplyr)
#load shapefile
nc <- st_read(system.file("shape/nc.shp", package="sf")) %>%
st_transform(4326)
shinyApp(
ui = fluidPage(
"Update selectize input by clicking on the map",
leafletOutput("map"),
"I would like the selectize input to update to show all the locations clicked,",
"but also when items are removed here, they are removed on the map too, so linked to the map.",
selectizeInput(inputId = "clicked_locations",
label = "Clicked",
choices = nc$NAME,
selected = NULL,
multiple = TRUE)
),
server <- function(input, output, session){
#create empty vector to hold all click ids
clicked_ids <- reactiveValues(ids = vector())
#initial map output
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addPolygons(data = nc,
fillColor = "white",
fillOpacity = 0.5,
color = "black",
stroke = TRUE,
weight = 1,
layerId = ~NAME,
group = "regions",
label = ~NAME)
}) #END RENDER LEAFLET
observeEvent(input$map_shape_click, {
#create object for clicked polygon
click <- input$map_shape_click
#define leaflet proxy for second regional level map
proxy <- leafletProxy("map")
#append all click ids in empty vector
clicked_ids$ids <- c(clicked_ids$ids, click$id) # name when clicked, id when unclicked
#shapefile with all clicked polygons - original shapefile subsetted by all admin names from the click list
clicked_polys <- nc %>%
filter(NAME %in% clicked_ids$ids)
#if the current click ID [from CNTY_ID] exists in the clicked polygon (if it has been clicked twice)
if(click$id %in% clicked_polys$CNTY_ID){
#define vector that subsets NAME that matches CNTY_ID click ID - needs to be different to above
name_match <- clicked_polys$NAME[clicked_polys$CNTY_ID == click$id]
#remove the current click$id AND its name match from the clicked_polys shapefile
clicked_ids$ids <- clicked_ids$ids[!clicked_ids$ids %in% click$id]
clicked_ids$ids <- clicked_ids$ids[!clicked_ids$ids %in% name_match]
# just to see
print(clicked_ids$ids)
# update
updateSelectizeInput(session,
inputId = "clicked_locations",
label = "",
choices = nc$NAME,
selected = clicked_ids$ids)
#remove that highlighted polygon from the map
proxy %>% removeShape(layerId = click$id)
} else {
#map highlighted polygons
proxy %>% addPolygons(data = clicked_polys,
fillColor = "red",
fillOpacity = 0.5,
weight = 1,
color = "black",
stroke = TRUE,
layerId = clicked_polys$CNTY_ID)
# just to see
print(clicked_ids$ids)
# update
updateSelectizeInput(session,
inputId = "clicked_locations",
label = "",
choices = nc$NAME,
selected = clicked_ids$ids)
} #END CONDITIONAL
}) #END OBSERVE EVENT
}) #END SHINYAPP
This is also posted here where you can also find the edited version of the code from the answer (originally an sp dataset), that works. This code for the nc data set seems to be the same to me, but doesn't seem to work, although updating the polygons based on the selectizeInput() isn't in there.
Any ideas on this?
Please see the following workaround:
I'm adding all polygons on rendering the map and hiding the red overlay. Furthermore each of the red polygons is assigned to it's own group. On click the according group and therefore the polygon is shown/hidden.
library(shiny)
library(leaflet)
library(sf)
library(dplyr)
#load shapefile
nc <- st_read(system.file("shape/nc.shp", package="sf")) %>%
st_transform(4326)
shinyApp(
ui = fluidPage(
"Update selectize input by clicking on the map",
leafletOutput("map"),
"I would like the selectize input to update to show all the locations selected,",
"but also when items are removed here, they are removed on the map too, so linked to the map.",
selectizeInput(inputId = "selected_locations",
label = "Selected:",
choices = nc$NAME,
selected = NULL,
multiple = TRUE)
),
server <- function(input, output, session){
#create empty vector to hold all click ids
selected_ids <- reactiveValues(ids = vector())
#initial map output
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addPolygons(data = nc,
fillColor = "white",
fillOpacity = 0.5,
color = "black",
stroke = TRUE,
weight = 1,
layerId = ~NAME,
group = "regions",
label = ~NAME) %>%
addPolygons(data = nc,
fillColor = "red",
fillOpacity = 0.5,
weight = 1,
color = "black",
stroke = TRUE,
layerId = ~CNTY_ID,
group = ~NAME) %>%
hideGroup(group = nc$NAME) # nc$CNTY_ID
}) #END RENDER LEAFLET
#define leaflet proxy for second regional level map
proxy <- leafletProxy("map")
#create empty vector to hold all click ids
selected <- reactiveValues(groups = vector())
observeEvent(input$map_shape_click, {
if(input$map_shape_click$group == "regions"){
selected$groups <- c(selected$groups, input$map_shape_click$id)
proxy %>% showGroup(group = input$map_shape_click$id)
} else {
selected$groups <- setdiff(selected$groups, input$map_shape_click$group)
proxy %>% hideGroup(group = input$map_shape_click$group)
}
updateSelectizeInput(session,
inputId = "selected_locations",
choices = nc$NAME,
selected = selected$groups)
})
observeEvent(input$selected_locations, {
removed_via_selectInput <- setdiff(selected$groups, input$selected_locations)
added_via_selectInput <- setdiff(input$selected_locations, selected$groups)
if(length(removed_via_selectInput) > 0){
selected$groups <- input$selected_locations
proxy %>% hideGroup(group = removed_via_selectInput)
}
if(length(added_via_selectInput) > 0){
selected$groups <- input$selected_locations
proxy %>% showGroup(group = added_via_selectInput)
}
}, ignoreNULL = FALSE)
})
Edit: regarding your initial approach adapting this answer you would need to pass the layerId as character to make things work again:
proxy %>% removeShape(layerId = as.character(click$id))
proxy %>% addPolygons(data = clicked_polys,
fillColor = "red",
fillOpacity = 0.5,
weight = 1,
color = "black",
stroke = TRUE,
layerId = as.character(clicked_polys$CNTY_ID))
I filed an issue regarding this.
However, I'd still prefer the above show/hide approach as I guess it's more performant than adding and removing polygons.
Is it possible to split the code of a map so that a part of the map only updates if it's own input is changed?
In the reproducible example below, when selecting the "toner" tile and selecting a new station, the whole leaflet map is executed again because addLegend needs to be updated. Which makes the tile jump back to "OSM (default)" tile. I would like to stay at the tile I selected when I select other stations.
library(leaflet)
library(shiny)
library(dplyr)
pal <- colorFactor(
palette = "YlGnBu",
domain = quakes$stations
)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
checkboxGroupInput("stations",
"Choose a station",
choices=sort(unique(quakes$stations)),
selected = c(10, 11))
),
mainPanel(
leafletOutput("map")
)
)
)
server <- function(input, output) {
points <- reactive({
quakes %>%
filter(stations %in% input$stations)
})
output$map <- renderLeaflet({
leaflet(quakes) %>%
addTiles(group = "OSM (default)") %>%
addProviderTiles(providers$Stamen.Toner, group = "Toner") %>%
addLayersControl(
baseGroups = c("OSM (default)", "Toner"),
options = layersControlOptions(collapsed = FALSE)) %>%
addLegend("Legend", position = "topleft", pal = pal, values = input$stations)
})
observe({
if(nrow(points()) == 0) {
leafletProxy("map", data = points()) %>%
clearMarkers()
} else {
leafletProxy("map", data = points()) %>%
clearMarkers() %>%
addCircleMarkers(radius = 2)
}
})
}
shinyApp(ui, server)
I tried several things, including adding addLegend to the else statement, but that does not go well. I'm new to leaflet/shiny, moving addLegend seemed most logic to me. I really appreciate any suggestions!
As far as I get it you were on the right track by trying to move addLegend to the observer. Doing so worked fine for me.
Move addLegend to observe
Before adding the legend use clearControls to remove any existing legend (otherwise you get multiple legends)
I removed the duplicated code in the observe
As far as I get it the condition nrow(points()) > 0 is only needed to decide whether a legend should be drawn or not. For the markers it doesn't matter.
library(leaflet)
library(shiny)
library(dplyr)
pal <- colorFactor(
palette = "YlGnBu",
domain = quakes$stations
)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
checkboxGroupInput("stations",
"Choose a station",
choices=sort(unique(quakes$stations)),
selected = c(10, 11))
),
mainPanel(
leafletOutput("map")
)
)
)
server <- function(input, output) {
points <- reactive({
quakes %>%
filter(stations %in% input$stations)
})
output$map <- renderLeaflet({
leaflet(quakes) %>%
addTiles(group = "OSM (default)") %>%
addProviderTiles(providers$Stamen.Toner, group = "Toner") %>%
addLayersControl(
baseGroups = c("OSM (default)", "Toner"),
options = layersControlOptions(collapsed = FALSE))
})
observe({
proxy <- leafletProxy("map", data = points()) %>%
clearMarkers() %>%
clearControls() %>%
addCircleMarkers(radius = 2)
if (nrow(points()) > 0)
proxy <- proxy %>% addLegend("Legend", position = "topleft", pal = pal, values = input$stations)
proxy
})
}
shinyApp(ui, server)
I am quite new to shiny, and I am facing a difficulty. I want to have a map with interactive circles. When clicked, these circles will allow me to make a query to a SQL database to get the corresponding data and so make plots.
I don't manage to get the circles info into a variable, although I am able to print it to the shiny ui.
Here is the example code:
library(shiny)
library(leaflet)
ui <- fluidPage(
leafletOutput("mymap"),
verbatimTextOutput("marker")
)
server <- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet(data = mapStates, options = leafletOptions(minZoom = 3, maxZoom = 18)) %>%
addTiles() %>%
addProviderTiles(providers$Stamen.TonerLite,
options = providerTileOptions(noWrap = TRUE))%>%
addCircleMarkers(data = data.frame(lat = 51, lng = 13,STANAME = "somewhere",STAID = "1" ), lng = ~lng, lat = ~lat,radius = 1, color = "red", fill = "red", popup = ~STANAME,layerId = ~STAID)
})
# here the circle info
output$marker <- renderPrint(input$mymap_marker_click)
}
shinyApp(ui, server)
but I don't manage to get the id of the marker into a variable in the server function. I tried:
input$mymap_marker_click$id
But it tells me that I need a reactive context. If I do:
renderPrint(input$mymap_marker_click)$id
Error : object of type 'closure' is not subsettable
I can't use the output in the server side, but I need this variable in the server side to do the query and the plots.
I should I proceed ?
Thank you for your help.
In Shiny you need to create an observer to "listen" for the click event (or any event/change to input) and perform a certain response.
Removing the map tiles, bc I don't know where mapStates comes from but the idea is identical.
library(shiny)
library(leaflet)
ui <- fluidPage(
leafletOutput("mymap"),
verbatimTextOutput("marker")
)
server <- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet(options = leafletOptions(minZoom = 3, maxZoom = 18)) %>%
addCircleMarkers(data = data.frame(lat = 51, lng = 13,STANAME = "somewhere",STAID = "1" ), lng = ~lng, lat = ~lat,radius = 1, color = "red", fill = "red", popup = ~STANAME,layerId = ~STAID)
})
# needs to be in a observer to "listen" for events
observeEvent(input$mymap_marker_click, {
output$marker <- renderPrint(input$mymap_marker_click$id)
})
}
shinyApp(ui, server)
Live demo