I'm trying to select a place from a list and then go to it a map provided by the leaflet package.
I tried this:
First, create the variables on the ui
vars <- c(
"LAS CRUZADAS" = "lc",
"PUENTE SAN ISIDRO" = "psi",
"FUNDO EL PROGRESO" = "fep",
"CALLE SANTA MARÍA" = "csm",
"ASENTAMIENTO NOGALES" = "an"
)
And then set up the panel
navbarPage("PLATAFORMA NAUTILUS", id="nav",
absolutePanel(id = "controls", class = "panel panel-default", fixed = TRUE,
draggable = TRUE, top = 60, left = "auto", right = 20, bottom = "auto",
width = 330, height = "auto",
h2(""),
selectInput("color", "Seleccionar Estación", vars)
),
)
in the server, add a marker in the respective places
output$mymap <- renderLeaflet({
leaflet() %>% addTiles() %>%
#h v
addCircleMarkers(lng=-71.294563, lat=-32.933843, color="blue" ,popup="LAS CRUZADAS") %>%
addCircleMarkers(lng=-71.240000, lat=-32.900000, color="blue" ,popup="PUENTE SAN ISIDRO") %>%
addCircleMarkers(lng=-71.226667, lat=-32.832778, color="blue" ,popup="CALLE SANTA MARÍA") %>%
addCircleMarkers(lng=-71.183889, lat=-32.733333, color="blue" ,popup="ASENTAMIENTO NOGALES") %>%
addCircleMarkers(lng=-71.221667, lat=-32.866111, color="blue" ,popup="FUNDO EL PROGRESO") %>%
setView(lng=-71.294563, lat=-32.933843, zoom=11)
})
Use a data.frame to store the lat/lon for each location, then use an observeEvent and to update leaflet when the selection changes.
To update leaflet in shiny you should use leafletProxy to update the map
Here's a working example
library(shiny)
library(leaflet)
df_vars <- data.frame(location = c("LAS CRUZADAS","ASENTAMIENTO NOGALES"),
lat = c(-32.9338, -32.8661),
lon = c(-71.2945, -71.2216)
)
ui <- fluidPage(
selectInput(inputId = "myLocations", label = "Locations",
choices = df_vars$location),
leafletOutput(outputId = "mymap")
)
server <- function(input, output){
output$mymap <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addCircleMarkers(lng=-71.294563, lat=-32.933843, color="blue" ,popup="LAS CRUZADAS") %>%
addCircleMarkers(lng=-71.221667, lat=-32.866111, color="blue" ,popup="FUNDO EL PROGRESO")
})
observeEvent({
input$myLocations
},{
selectedLocation <- df_vars[df_vars$location == input$myLocations, c("lat","lon")]
leafletProxy(mapId = "mymap") %>%
setView(lng = selectedLocation$lon, lat = selectedLocation$lat, zoom = 11)
})
}
shinyApp(ui, server)
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 am trying to load shapefiles in shiny r based on a selection users make in selectInput. This is easy to do when the user can only select one shapefile. However when the user can select multiple shapefiles it becomes trickier. I am looking for a way to avoir having to write several times addPolygons(data = input$input_company[1] %>% addPolygons(data = input$input_company[2] %>% addPolygons(data = input$input_company[3] and so on.
Here is my attempt: writing a loop in server :
# Working directory ------------------------------------------------------------
wd <- "~/path/"
# Read multiple shapefiles with standardised name ------------------------------
items <- c("item_1", "item_2", "item_3")
for (sp in items) {
files.sp <- readOGR(dsn = wd, layer = sp,
verbose = FALSE)
assign(sp, files.sp)
}
# UI ---------------------------------------------------------------------------
ui <- navbarPage(
title = "Here my Title",
id="nav",
theme = shinytheme("flatly"),
mainPanel("Interactive map",
div(class="outer",
tags$head(
includeCSS("styles.css")),
leafletOutput("m", width="100%", height="100%"),
absolutePanel(
id = "hist_panel", class = "panel panel-default",
fixed = TRUE, draggable = TRUE,
top = 100, left = "auto", right = 0,
bottom = "auto",
width = "27%", height = "auto"),
absolutePanel(
id = "hist_panel", class = "panel panel-default",
fixed = FALSE, draggable = TRUE,
top = 100, left = "auto", right = 0,
bottom = "auto",
width = "27%", height = "auto",
selectInput(inputId = "input_items", label = "Items",
choices = c("Item 1" = "item_1", "Item 2" = "item_2", "Item 3" = "item_3"),
multiple = TRUE,
selected = "item_1")),
)
)
)
# Server -----------------------------------------------------------------------
server <- function(input, output, session) {
output$m <- renderLeaflet({
for (i in 1:length(input$input_items)) {
sp <- input$input_items[i]
tmp <- get(sp)
m <- leaflet() %>%
# Add Basemap OSM
addTiles(group = "OSM (default)") %>%
addPolygons(data = get(tmp))
}
}
)
}
# Run shiny app on laptop
shinyApp(ui, server)
What I am trying to avoid is this (because I may have 100+ items to display, and also because if the user selects less than 3 items I get an error message...):
# Server -----------------------------------------------------------------------
server <- function(input, output, session) {
output$m <- renderLeaflet({
m <- leaflet() %>%
# Add Basemap OSM
addTiles(group = "OSM (default)") %>%
addPolygons(data = input$input_items[1]) %>%
addPolygons(data = input$input_items[2]) %>%
addPolygons(data = input$input_items[3])
}
)
}
Thank you!
Here is a solution
First merge shapefiles together
shp <- bind(item_1, item_2, item_3)
Then in server side:
# Server -----------------------------------------------------------------------
server <- function(input, output, session) {
observeEvent(input$input_items,{
sel_shp <- shp[shp#data$id %in% input$input_items, ]
output$m <- renderLeaflet({
m <- leaflet() %>%
# Add Basemap OSM
addTiles(group = "OSM (default)") %>%
addPolygons(data = sel_shp)
})
})
}
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)
I needed to add data = cities in the last addPulseMarkers() call.
observeEvent(input$reset, {
leafletProxy("map") %>%
setView(lng = -56, lat = 49.2402, zoom = 4) %>%
addPulseMarkers(data = cities, lng = ~lng, lat = ~lat,
label = ~cities,
icon = makePulseIcon())
})
I'm trying to modify this repo to display a choropleth map and use a sliderInput to update the map. Everything Ok until I try to animate the slider input, nothing happens . I get this console error: input_binding_slider.js:199 Uncaught TypeError: Cannot read property 'options' of undefined.
This is the code i'm using:
library(dplyr) ; library(rgdal) ; library(leaflet)
gdp = read.csv("mexico2.csv", header= T) %>%
as.data.frame()
mexico <- readOGR("mexico.shp", layer = "mexico", encoding = "UTF-8")
ui <- shinyUI(fluidPage(
fluidRow(
column(7, offset = 1,
br(),
div(h4(textOutput("title"), align = "center"), style = "color:black"),
div(h5(textOutput("period"), align = "center"), style = "color:black"),
br())),
fluidRow(
column(7, offset = 1,
leafletOutput("map", height="530"),
br(),
actionButton("reset_button", "Reset view")),
column(3,
uiOutput("category", align = "left")))
))
server <- (function(input, output, session) {
output$category <- renderUI({
sliderInput("category", "Year:",
min=1994, max = 1999,
value = 1994, sep = "", animate=TRUE)
})
selected <- reactive({
subset(gdp,
category==input$category)
})
output$title <- renderText({
req(input$category)
paste0(input$category, " GDP by State")
})
output$period <- renderText({
req(input$category)
paste("...")
})
lat <- 23
lng <- -102
zoom <- 5
output$map <- renderLeaflet({
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
setView(lat = lat, lng = lng, zoom = zoom)
})
observe({
mexico#data <- left_join(mexico#data, selected())
qpal <- colorQuantile("YlGn", mexico$value, n = 5, na.color = "#bdbdbd")
popup <- paste0("<strong>ID: </strong>",
mexico$id,
"<br><strong>Estado: </strong>",
mexico$name,
"<br><strong>Valor: </strong>",
mexico$value)
leafletProxy("map", data = mexico) %>%
addProviderTiles("CartoDB.Positron") %>%
clearShapes() %>%
clearControls() %>%
addPolygons(data = mexico, fillColor = ~qpal(value), fillOpacity = 0.7,
color = "white", weight = 2, popup = popup) %>%
addLegend(pal = qpal, values = ~value, opacity = 0.7,
position = 'bottomright',
title = paste0(input$category, "<br>"))
})
observe({
input$reset_button
leafletProxy("map") %>% setView(lat = lat, lng = lng, zoom = zoom)
})
})
shinyApp(ui, server)
Here is a link to the shinyapp
Any help would be aprecieted.
Thanks!
It's just a naming mistake. You named your htmlOutput and your sliderOutput for "category". Internally, this messes things up.
Just change e.g. the output into
uiOutput("categoryOutput", align = "left")
and
output$categoryOutput <- renderUI({ ... })
and you should be good to go.
Edit: Full Code
library(dplyr) ; library(rgdal) ; library(leaflet)
gdp = read.csv("mexico2.csv", header= T) %>%
as.data.frame()
mexico <- readOGR("mexico.shp", layer = "mexico", encoding = "UTF-8")
ui <- shinyUI(fluidPage(
fluidRow(
column(7, offset = 1,
br(),
div(h4(textOutput("title"), align = "center"), style = "color:black"),
div(h5(textOutput("period"), align = "center"), style = "color:black"),
br())),
fluidRow(
column(7, offset = 1,
leafletOutput("map", height="530"),
br(),
actionButton("reset_button", "Reset view")),
column(3,
uiOutput("categoryOut", align = "left")))
))
server <- (function(input, output, session) {
output$categoryOut <- renderUI({
sliderInput("category", "Year:",
min=1994, max = 1999,
value = 1994, sep = "", animate=TRUE)
})
selected <- reactive({
subset(gdp,
category==input$category)
})
output$title <- renderText({
req(input$category)
paste0(input$category, " GDP by State")
})
output$period <- renderText({
req(input$category)
paste("...")
})
lat <- 23
lng <- -102
zoom <- 5
output$map <- renderLeaflet({
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
setView(lat = lat, lng = lng, zoom = zoom)
})
observe({
mexico#data <- left_join(mexico#data, selected())
qpal <- colorQuantile("YlGn", mexico$value, n = 5, na.color = "#bdbdbd")
popup <- paste0("<strong>ID: </strong>",
mexico$id,
"<br><strong>Estado: </strong>",
mexico$name,
"<br><strong>Valor: </strong>",
mexico$value)
leafletProxy("map", data = mexico) %>%
addProviderTiles("CartoDB.Positron") %>%
clearShapes() %>%
clearControls() %>%
addPolygons(data = mexico, fillColor = ~qpal(value), fillOpacity = 0.7,
color = "white", weight = 2, popup = popup) %>%
addLegend(pal = qpal, values = ~value, opacity = 0.7,
position = 'bottomright',
title = paste0(input$category, "<br>"))
})
observe({
input$reset_button
leafletProxy("map") %>% setView(lat = lat, lng = lng, zoom = zoom)
})
})
shinyApp(ui, server)
I am trying to add two different markers for two different inputs. I got the first one working but not for the second one. Here is my code
ui.R
library(shiny)
library(leaflet)
shinyUI(fluidPage(
# Application title
titlePanel("Aspen GBS Population Structure results on map"),
# Side bar layout
sidebarLayout(
sidebarPanel(
selectInput("structure", label = "Select K for display", choices = c("2", "3", "4", "5", "6"), selected = "2"),
checkboxInput("origin", label = "Flood path")),
mainPanel(
leafletOutput("map")
)
)
)
)
server.R
leafIcons <- icons(
iconUrl = ifelse(data_K2$FP_Icon == "greenleafIcon",
"http://leafletjs.com/docs/images/leaf-green.png",
"http://leafletjs.com/docs/images/leaf-red.png"
),
iconWidth = 38, iconHeight = 95,
iconAnchorX = 22, iconAnchorY = 94,
shadowUrl = "http://leafletjs.com/docs/images/leaf-shadow.png",
shadowWidth = 50, shadowHeight = 64,
shadowAnchorX = 4, shadowAnchorY = 62
)
library(shiny)
shinyServer(function(input, output, session) {
dt <- reactive(
switch(input$structure,
"2" = data_K2$Structure.2,
"3" = data_K2$Structure.3))
output$map <- renderLeaflet(
leaflet(data = data_K2) %>% addTiles() %>% setView(lng = -106.1039361,lat = 50.543981, zoom = 4) %>%
addCircleMarkers(lat = ~Lat, lng = ~Long, popup = ~Location_discription, radius=2, color = ~dt(), fill = TRUE) %>%
addMarkers(lat = ~Lat, lng = ~Long, popup = ~Location_discription, icon = leafIcons)
)
})
I want the addMarkers to get activated when i use the checkboxInput button only. But right now it is selected by default.
I've found the easiest way is to label the markers with groups, then just show/hide them on input. That way you save some computation, and leaflet is designed to do this with leafletProxy (it's well documented on the Rstudio guide). You would need to add an observer as well that would update the map, as in this example,
library(shiny)
library(leaflet)
ui <- shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
checkboxInput("show", "Show/Hide")
),
mainPanel(
leafletOutput("map")
)
)
))
dat <- data.frame(lng = rnorm(3, -106.1039361, 0.5) ,
lat = rnorm(3, 50.543981, 0.5))
server <- shinyServer(function(input, output, session) {
## Your map, give the markers groups
output$map <- renderLeaflet(
leaflet(data = dat) %>%
addTiles() %>% setView(lng = -106.1039361,lat = 50.543981, zoom = 4) %>%
addCircleMarkers(group="circles",
popup = ~paste(lat), radius=2, fill = TRUE) %>%
addMarkers(group="markers")
)
## Observer to update map on input
observeEvent(input$show, {
proxy <- leafletProxy('map')
if (input$show) proxy %>% showGroup('markers')
else proxy %>% hideGroup('markers')
})
})
shinyApp(ui, server)