R Shiny with leaflet: create a modal window on clicking an icon - r

I want to create a modal window when I click an icon in a leaflet map in shiny. Is this doable? I tried the code below, but the bsModal is not doing anything.
library(shiny)
library(leaflet)
library(shinyBS)
points <- data.frame(cbind(latitude = rnorm(40) * 2 + 13, longitude =
rnorm(40) + 48))
ui <- fluidPage(
leafletOutput("mymap"),
bsModal("modalExample", "This will open a modal", "assign_task", size =
"large",
HTML(""))
)
server <- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet(options = leafletOptions(maxZoom = 18)) %>% addTiles() %>%
addMarkers(lat = ~ latitude, lng = ~ longitude,
data = points,
popup=~ sprintf(
'<button type="button" id="assign_task">Open Modal </button>'
))
})
}
shinyApp(ui, server)

I'll post two possible solutions. The first one is the solution that I think would suit your needs best, the second one more cosely matches your current code. Hope this helps!
Solution 1:
library(shiny)
library(leaflet)
points <- data.frame(cbind(id=seq(1,40),latitude = rnorm(40) * 2 + 13, longitude =
rnorm(40) + 48))
ui <- fluidPage(
leafletOutput("mymap"),
actionButton("action1","Show modal")
)
server <- function(input, output, session) {
observeEvent(input$mymap_marker_click, {
id = input$mymap_marker_click$id
showModal(modalDialog(
title = "You selected a marker!",
paste0("ID: ", id, ", lat: ", round(points$latitude[id==id],2),", lon: ", round(points$longitude[id==id],2))
))
})
output$mymap <- renderLeaflet({
leaflet(options = leafletOptions(maxZoom = 18)) %>% addTiles() %>%
addMarkers(layerId = ~ id,lat = ~ latitude, lng = ~ longitude,
data = points
)
})
}
shinyApp(ui, server)
Solution 2:
library(shiny)
library(leaflet)
library(shinyBS)
points <- data.frame(cbind(latitude = rnorm(40) * 2 + 13, longitude =
rnorm(40) + 48))
ui <- fluidPage(
leafletOutput("mymap"),
actionButton("action1","Show modal")
)
server <- function(input, output, session) {
observeEvent(input$button_click, {
showModal(modalDialog(
title = "Important message",
"This is an important message!"
))
})
output$mymap <- renderLeaflet({
leaflet(options = leafletOptions(maxZoom = 18)) %>% addTiles() %>%
addMarkers(lat = ~ latitude, lng = ~ longitude,
data = points,
popup= ~paste("<b>", latitude, longitude, "</b></br>", actionButton("showmodal", "Show modal", onclick = 'Shiny.onInputChange(\"button_click\", Math.random())')))
})
}
shinyApp(ui, server)

Related

Distinguish between input$map_click and input$map_shape_click in Leaflet R Shiny

What I would like to do is that if a user clicks on a line, it displays the line name in the box to the right of the map, and if a user clicks somewhere else on the map, it 'deselects' that line:
The problem is that when a user clicks the polyline, leaflet fires both a map_shape_click (the polyline) and map_click (the map) event. Even more annoyingly, it fires the map_shape_click event before the map_click event.
How can I distinguish whether the user has clicked a line, or just the base map, so that my select/deselect works? Reproducible example:
library(shiny)
library(tidyverse)
library(leaflet)
ui <- fluidPage(
fluidRow(
column(
width = 8,
leafletOutput("map")
),
column(
width = 4,
uiOutput("info")
)
)
)
server <- function(input, output) {
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
setView(lng = -71.03165, lat = 42.37595, zoom = 13) %>%
addPolylines(lng = c(-71.05884, -71.02), lat = c(42.360081, 42.359),
layerId = "line1") %>%
addPolylines(lng = c(-71.05884, -71.05), lat = c(42.360081, 42.4),
layerId = "line2")
})
observeEvent(input$map_shape_click, {
x <- input$map_shape_click
output$info <- renderUI({
div(
"Line: ", x$id
)
})
})
observeEvent(input$map_click, {
output$info <- renderUI({
div(
"Nothing selected"
)
})
})
}
shinyApp(ui = ui, server = server)
library(shiny)
library(tidyverse)
library(leaflet)
ui <- fluidPage(
fluidRow(
column(
width = 8,
leafletOutput("map")
),
column(
width = 4,
uiOutput("info")
)
)
)
server <- function(input, output) {
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
setView(lng = -71.03165, lat = 42.37595, zoom = 13) %>%
addPolylines(lng = c(-71.05884, -71.02), lat = c(42.360081, 42.359),
layerId = "line1") %>%
addPolylines(lng = c(-71.05884, -71.05), lat = c(42.360081, 42.4),
layerId = "line2")
})
clicked <- reactiveVal()
observeEvent(input$map_shape_click, {
freezeReactiveValue(input, 'map_click')
clicked(input$map_shape_click)
})
observeEvent(input$map_click, {
clicked(input$map_click)
})
output$info <- renderUI({
req(clicked())
if(is.null(clicked()[['id']])) return(div("Nothing selected"))
div("Line: ", clicked()$id)
})
}
shinyApp(ui = ui, server = server)
Things are a little tricky here. we use freezeReactiveValue to freeze the map click, meaning if there is any shape click event, we do not update the value of map_click. This is a little advanced shiny. I recommend you read the help file and read this chapter: https://mastering-shiny.org/action-dynamic.html#freezing-reactive-inputs

How to add a downloadButton in a popup?

I'm currently developing an R Shiny application where I'm mapping services providers on a map and when I click on a specific marker I have a popup with additional information and I would like to include a downloadButton in that popup. Unfortunately when I'm calling the downloadHandler it doesn't work and I'm downloading a html file called qwe_download.html. But if I put the downloadButton outside the popup (i.e. in the ui) then it works. Is it possible to use a downloadButton inside a leaflet popup?
I can't share the original code as it is quite sensitive but you can find below what I'm trying to achieve.
library('leaflet')
library('shinydashboard')
id <- c(1, 2, 3)
lat <- c(10.01, 10.6, 10.3)
long <- c(0.2, 0.3, 0.4)
name <- c('test1', ' test2', 'test3')
test <- data_frame(id, lat, long, name)
#User interface
header <- dashboardHeader(title = 'Title', titleWidth = 900)
sidebar <- dashboardSidebar(
width = 300)
body <- dashboardBody(
tags$style(type = "text/css", "#map {height: calc(100vh - 80px) !important;}"),
leafletOutput("map")
)
ui <- dashboardPage(header, sidebar, body)
server <- function(input, output, session) {
data <- reactiveValues(clickedMarker=NULL)
output$map <- renderLeaflet({
mymap <- leaflet() %>%
addTiles() %>%
addMarkers(data = test, lng = long, lat = lat, layerId = id,
popup = paste0(
"<div>",
"<h3>",
"Name: ",
test$name,
downloadButton(outputId = "dlData",label = "Download Details"),
"</div>"))
observeEvent(input$map_marker_click,{
print("observed map_marker_click")
data$clickedMarker <- input$map_marker_click
print(data$clickedMarker)
x <- filter(test, id == data$clickedMarker$id)
view(x)})
data_react <- reactive({
data_table <- filter(test, test$id == data$clickedMarker$id)
})
output$dlData <- downloadHandler(
filename = "dataset.csv",
content = function(file) {
write.csv(data_react(), file)
}
)
mymap
})
}
# Run app ----
shinyApp(ui, server)
Note that the observeEvent block was just there for me to check if my code was filtering the right selection.
Hope this makes sense.
Thanks!
You need to bind the downloadButtons yourself after placing them in the popup.
Please see this related answer from Joe Cheng.
Here you can find some great answers on how to bindAll custom inputs in a leaflet popup.
And this is how to apply those answers regarding your particular requirements:
library('leaflet')
library('shinydashboard')
id <- c(1, 2, 3)
lat <- c(10.01, 10.6, 10.3)
long <- c(0.2, 0.3, 0.4)
name <- c('test1', ' test2', 'test3')
test <- data.frame(id, lat, long, name)
header <- dashboardHeader(title = 'Title', titleWidth = 900)
sidebar <- dashboardSidebar(width = 300)
body <- dashboardBody(
tags$div(id = "garbage"),
tags$style(type = "text/css", "#map {height: calc(100vh - 80px) !important;}"),
leafletOutput("map")
)
ui <- dashboardPage(header, sidebar, body)
server <- function(input, output, session) {
data <- reactiveValues(clickedMarker = NULL)
output$map <- renderLeaflet({
mymap <- leaflet() %>%
addTiles() %>%
addMarkers(
data = test,
lng = long,
lat = lat,
layerId = id,
popup = sprintf(
paste0(
"<div>",
"<h3>",
"Name: ",
test$name,
br(),
downloadButton(outputId = "dlData%s", label = "Download Details"),
"</div>"
),
id
)
) %>% htmlwidgets::onRender(
'function(el, x) {
var target = document.querySelector(".leaflet-popup-pane");
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if(mutation.addedNodes.length > 0){
Shiny.bindAll(".leaflet-popup-content");
}
if(mutation.removedNodes.length > 0){
var popupNode = mutation.removedNodes[0];
var garbageCan = document.getElementById("garbage");
garbageCan.appendChild(popupNode);
Shiny.unbindAll("#garbage");
garbageCan.innerHTML = "";
}
});
});
var config = {childList: true};
observer.observe(target, config);
}'
)
})
observeEvent(input$map_marker_click,{
print("observed map_marker_click")
data$clickedMarker <- input$map_marker_click
print(data$clickedMarker)
x <- filter(test, id == data$clickedMarker$id)
})
data_react <- reactive({
data_table <- filter(test, test$id == data$clickedMarker$id)
})
lapply(id, function(i) {
output[[paste0("dlData", i)]] <- downloadHandler(
filename = "dataset.csv",
content = function(file) {
write.csv(data_react(), file)
}
)
})
}
shinyApp(ui, server)
The download button is not binded to Shiny. You can use the pointerenter event to run Shiny.bindAll() and the pointerleave event to run Shiny.unbindAll():
library('leaflet')
library('shinydashboard')
library(shiny)
library(dplyr)
id <- c(1, 2, 3)
lat <- c(10.01, 10.6, 10.3)
long <- c(0.2, 0.3, 0.4)
name <- c('test1', ' test2', 'test3')
test <- tibble(id, lat, long, name)
js <- "$('body').on('pointerenter', '#dlData', function(){Shiny.bindAll('#dwnld');}).on('pointerleave', '#dlData', function(){Shiny.unbindAll('#dwnld');})"
header <- dashboardHeader(title = 'Title', titleWidth = 900)
sidebar <- dashboardSidebar(
width = 300)
body <- dashboardBody(
useShinyjs(),
tags$script(HTML(js)),
tags$style(type = "text/css", "#map {height: calc(100vh - 80px) !important;}"),
leafletOutput("map")
)
ui <- dashboardPage(header, sidebar, body)
server <- function(input, output, session) {
data <- reactiveValues(clickedMarker=NULL)
output$map <- renderLeaflet({
mymap <- leaflet() %>%
addTiles() %>%
addMarkers(
data = test, lng = long, lat = lat, layerId = id,
popup = paste0(
"<div id='dwnld'>",
"<h3>",
"Name: ",
test$name,
"</h3>",
downloadButton(
outputId = "dlData", label = "Download Details"
),
"</div>"))
mymap
})
observeEvent(input$map_marker_click,{
data$clickedMarker <- input$map_marker_click
})
data_react <- reactive({
filter(test, id == data$clickedMarker$id)
})
output$dlData <- downloadHandler(
"dataset.csv",
content = function(file) {
write.csv(data_react(), file)
})
}
# Run app ----
shinyApp(ui, server)
To add a summarizing answer, what we need to do:
Call Shiny.[un]bindAll in the "right" moment.
The "right" moment is apparently once the popup is added / removed from the DOM.
Non working downloads can happen as a result of re-using the same id (unfortunately I could not identify a pattern and I thought that unbinding helps, but it does not). Thus, to play it safe creating unique download handlers should avoid this behaviour.
Having said that, the IMHO cleanest option to call Shiny.bindAll() is in response to the popupopen event:
output$map <- renderLeaflet({
mymap <- leaflet() %>%
addTiles() %>%
addMarkers(
data = test, lng = long, lat = lat, layerId = id,
popup = paste0(
"<div id='dwnld'>",
"<h3>",
"Name: ",
test$name,
downloadButton(outputId = "dlData",label = "Download Details"),
"</div>"))
mymap %>% htmlwidgets::onRender(HTML("
function(el, x) {
this.on('popupopen', function() {
Shiny.bindAll('#dwnld');
});
this.on('popupclose', function() {
Shiny.unbindAll('#dwnld');
});
}"))
})

shiny leaflet display labels based on zoom level

I want to display my marker labels based on zoom level.
Based on (https://rstudio.github.io/leaflet/shiny.html) I tried to use "input$MAPID_zoom". In my example, labels stored in location_name should be displayed when zoom level (mapscale) is lower to 6.
What I tried :
library(shiny)
library(leaflet)
# my data
df <- data.frame(
location_name = c('S1', 'S2'),
lng = c(-1.554136, -2.10401),
lat = c(47.218637, 47.218637),
stringsAsFactors = FALSE)
# UI
ui <- shinyUI(fluidPage(
leafletOutput('map')
))
# server
server <- shinyServer(function(input, output, session) {
mapscale <- observe({
input$map_zoom # get zoom level
})
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addMarkers(data=df, lng = ~lng, lat = ~lat,
label =~if(mapscale<6, location_name))
})
})
shinyApp(ui = ui, server = server)
A few remarks on your code if you like.
If you wrap the zoom in a reactive function, reference it like mapscale(). Use the normal if statement in R and the ~ in front of the variable. Then you should be fine.
Reproducible example:
library(shiny)
library(leaflet)
df <- data.frame(
location_name = c('S1', 'S2'),
lng = c(-1.554136, -2.10401),
lat = c(47.218637, 47.218637),
stringsAsFactors = FALSE
)
ui <- shinyUI(
fluidPage(
leafletOutput(outputId = 'map')
)
)
server <- shinyServer(function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>%
addTiles()
})
observeEvent(
eventExpr = input$map_zoom, {
print(input$map_zoom) # Display zoom level in the console
leafletProxy(
mapId = "map",
session = session
) %>%
clearMarkers() %>%
addMarkers(
data = df,
lng = ~lng,
lat = ~lat,
label = if(input$map_zoom < 6) ~location_name
)
}
)
})
shinyApp(
ui = ui,
server = server
)

Creating interactive Leaflet map in R with shiny

I created a shiny app with leaflet and it works pretty well.
library(shiny)
library(shinythemes)
library(leaflet)
ui2 <- fluidPage(theme = shinytheme("united"), tabsetPanel(
tabPanel(
titlePanel("titel"),
mainPanel(
leafletOutput(outputId = "mymap")),
sidebarPanel(
fluidRow(
dateRangeInput("a", h4("date"),language = "en",separator = " to "),
selectInput("select", h4("location"),
c(data8$city)),
submitButton("search"))
))
)
)
server <- function(input, output) {
popupa <- paste(titel)
output$mymap <- renderLeaflet({
leaflet(data8) %>%
addTiles() %>%
addMarkers(lng = ~lng, lat = ~lat, popup = popupa)
})
}
shinyApp(ui2, server)
But at the moment I am trying to add a dateRangeInput to filter(date_start) on my shown locations. But I don't know how to connect my dateRangeInput and the selectInput to my leaflet-function in the server-part. Furthermore, below the map there should be a table with the filtered locations from the map - is this possible at all?
My used dataframe looks like following:
title=c("Event1","Event2")
lng=c(23.3, 23.3)
lat=c(30, 40)
city=c("Berlin", "Hamburg" )
zip=c(39282, 27373)
date_start=c("2018-05-28","2018-05-28")
date_end=c("2018-06-27","2018-08-03")
data8 <- data.frame(title, lng, lat, city, zip, date_start, date_end)
Does anyone know how to get this done? Thanks for every help!
regards
You could try this:
ui2 <- fluidPage(theme = shinytheme("united"), tabsetPanel(
tabPanel(
titlePanel("titel"),
mainPanel(
leafletOutput(outputId = "mymap"),
dataTableOutput("mytable")),
sidebarPanel(
fluidRow(
dateRangeInput("a", h4("date"),language = "en",separator = " to "),
selectInput("selectLoc", h4("location"),
as.character(data8$city)),
submitButton("search"))
))
)
)
server <- function(input, output) {
popupa <- paste("titel")
datatoPlot <- reactive({
date_start <- as.character(input$a[1])
date_end <- as.character(input$a[2])
data8$date_start <- as.Date(data8$date_start, format = "%Y-%m-%d")
data8 <- data8[as.Date(data8$date_start) >= date_start & as.Date(data8$date_start) <= date_end, ]
data8 <- data8 %>% dplyr::filter(city == input$selectLoc)
})
output$mymap <- renderLeaflet({
leaflet(datatoPlot()) %>%
addTiles() %>%
addMarkers(lng = ~lng, lat = ~lat, popup = popupa)
})
output$mytable <- renderDataTable(datatoPlot())
}
shinyApp(ui2, server)

R Auto Zoom Updated leafletProxy Map object in Shiny

I am experimenting with the leaflet package for some maps in Shiny. I would like to be able to have a base map that data will change and subsequently be remapped. However, I am trying to use the leafletProxy function whereby there is a base map and I just change the data points that are added. All of this works however the map doesn't zoom to the datapoints location. It remains at the farthest zoom.
The code to reproduce the problem:
library(shiny)
library(leaflet)
r_colors <- rgb(t(col2rgb(colors()) / 255))
names(r_colors) <- colors()
ui <- fluidPage(
leafletOutput("mymap"),
p(),
actionButton("goButton", "New Points")
)
server <- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet() %>%
addProviderTiles("Stamen.TonerLite",
options = providerTileOptions(noWrap = TRUE)
)
})
observeEvent(input$goButton, {
points <- cbind(rnorm(40) * 2 + 13, rnorm(40) + 48)
leafletProxy('mymap', session) %>%
clearMarkers() %>%
addMarkers(data = points)
})
}
shinyApp(ui, server)
I would like the map to automatically zoom in once new points have been added.
try to add argument "fitBounds" in leafletProxy()
leafletProxy('mymap', session) %>%
clearMarkers() %>%
addMarkers(data = points) %>%
fitBounds(lng1 = max(points$long),lat1 = max(points$lat),
lng2 = min(points$long),lat2 = min(points$lat))
I revised G. Cocca's answer (mostly by adding a dataframe of "points") to get this:
library(shiny)
library(leaflet)
r_colors <- rgb(t(col2rgb(colors()) / 255))
names(r_colors) <- colors()
ui <- fluidPage(
leafletOutput("mymap"),
p(),
actionButton("goButton", "New Points")
)
server <- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet() %>%
addProviderTiles("Stamen.TonerLite",
options = providerTileOptions(noWrap = TRUE)
)
})
observeEvent(input$goButton, {
points <- data.frame("long" = rnorm(40) * 2 + 13,
"lat" = rnorm(40) + 48)
leafletProxy('mymap', session) %>%
clearMarkers() %>%
addMarkers(data = points) %>%
fitBounds(lng1 = max(points$long),lat1 = max(points$lat),
lng2 = min(points$long),lat2 = min(points$lat))
})
}
shinyApp(ui, server)

Resources