R Shiny: Can't run JS code in a leafletProxy instance - r

I have shinyapp where I have a leaflet map. I want users to be able to click on the polygons and highlight them right away - while unhighlighting the previous polygon. I successfully do this using some JS code after the leaflet call with htmlwidgets::onRender:
library(shiny)
library(leaflet)
library(htmlwidgets)
library(sf)
library(dplyr)
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "80%")
)
server <- function(input, output, session) {
data <- st_read(system.file("shape/nc.shp", package="sf")) %>% st_transform(4326)
output$map <- renderLeaflet({
pal <- colorNumeric(
palette = "Blues",
domain = data$AREA)
leaflet() %>%
addTiles() %>% # Add default OpenStreetMap map tiles
addPolygons(data = data,
fillColor = ~pal(data$AREA), fillOpacity = 0.5) %>%
htmlwidgets::onRender("
function(el, x) {
L.control.zoom({
position: 'topright'
}).addTo(this);
var map = this;
var prevLayerClicked = null;
map.eachLayer(function(layer) {
layer.on('click', function(e) {
if (prevLayerClicked == null) {
layer.setStyle({
fillOpacity: 1
});
} else if (prevLayerClicked !== null) {
layer.setStyle({
fillOpacity: 1
});
prevLayerClicked.setStyle({
fillOpacity: 0.5
});
}
prevLayerClicked = layer;
})
.addTo(map);
});
}
")
})
}
shinyApp(ui, server)
But my application has a lot of variables and I wanted to be able to change the map using leafletProxy. But as soon as I implement some observer with leafletProxy (in that case, I created a dummy button to reset the colors), the highlighting function stops working. Same previous example, but now with the observer:
library(shiny)
library(leaflet)
library(htmlwidgets)
library(sf)
library(dplyr)
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "80%"),
actionButton(inputId = "reset", label = "Reset Colors")
)
server <- function(input, output, session) {
data <- st_read(system.file("shape/nc.shp", package="sf")) %>% st_transform(4326)
output$map <- renderLeaflet({
pal <- colorNumeric(
palette = "Blues",
domain = data$AREA)
leaflet() %>%
addTiles() %>% # Add default OpenStreetMap map tiles
addPolygons(data = data,
fillColor = ~pal(data$AREA), fillOpacity = 0.5) %>%
htmlwidgets::onRender("
function(el, x) {
L.control.zoom({
position: 'topright'
}).addTo(this);
var map = this;
var prevLayerClicked = null;
map.eachLayer(function(layer) {
layer.on('click', function(e) {
if (prevLayerClicked == null) {
layer.setStyle({
fillOpacity: 1
});
} else if (prevLayerClicked !== null) {
layer.setStyle({
fillOpacity: 1
});
prevLayerClicked.setStyle({
fillOpacity: 0.5
});
}
prevLayerClicked = layer;
})
.addTo(map);
});
}
")
})
observeEvent(c(input$reset), {
req(input$reset >= 1)
leafletProxy("map") %>%
clearShapes() %>%
addPolygons(data = data)
})
}
shinyApp(ui, server)
I have seen some hints like here and here, but couldn't understand the approaches.
Any idea how to solve this?

Related

AddFlows click event leaflet.minicharts

I'm using addFlows() to add some flow data to a leaflet map in Shiny.
What I need it to do is emit the layerId when the appropriate line is clicked, so that I can display some information to the user in a sidebar. How can I trigger a click event?
I know that with polylines or polygons I can use observeEvent(input$map_shape_click, {}), but I'm not sure of the addFlows variant of this. I can't use addPolylines() instead because I need the arrow heads as representative of direction.
Reproducible code (with non-working click event):
library(shiny)
library(leaflet)
library(leaflet.minicharts)
library(tidyverse)
dat <- data.frame(
Line_no = c("line1", "line2"),
Origin_lat = c(40.15212, 40.65027),
Origin_lng = c(-74.79037, -74.91990),
Dest_lat = c(40.78749, 40.78749),
Dest_lng = c(-73.96188, -73.96188),
flow = c(237, 84)
)
ui <- fluidPage(
leafletOutput("map", height=800)
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>%
addProviderTiles(provider = providers$Esri.WorldImagery) %>%
setView(lat = 40.39650, lng = -74.39541, zoom = 9)
})
observe({
leafletProxy("map") %>%
addFlows(
layerId = dat$Line_no,
lng0 = dat$Origin_lng,
lat0 = dat$Origin_lat,
lng1 = dat$Dest_lng,
lat1 = dat$Dest_lat,
flow = dat$flow
)
})
observeEvent(input$map_shape_click, {
glimpse("Clicked!")
})
}
shinyApp(ui, server)

Adding markers using checkbox Leaflet/R

I'm trying to add markers on my interactive Leaflet map if the user ticks off a checkbox. However, I can't get the checkbox-appearance to work within the renderLeaflet function, but I can within a renderPlot function.
My code, that works, looks as below:
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
checkboxInput('check1','Show Counties', value = TRUE)),
mainPanel(
# I'm aware that this should be changed to leaftletOutput, when used
# with the in the second code snippet below
plotOutput("mymap")
),
position = 'right'
)
)
server <- function(input, output) {
output$mymap <- renderPlot({
if (input$check1){
hist(rnorm(10))
} else {
hist(rnorm(100))
}
})
}
shinyApp(ui = ui, server = server)
The same logic doesn't work within Leaflet, and I would highly appreciate input on how to modify the below code. I know you don't have access to the two variables, USstates or coor, but I was hoping one could point the error, potentially in terms a syntax error. The output function looks as follows;
output$mymap <- renderLeaflet({
if (input$check1 == TRUE){
leaflet(USstates) %>%
addTiles() %>%
addPolygons(color = 'navy',
opacity = 1.0,
weight = 1) %>%
addMarkers(lng = coor[,1],lat = coor[,2])
} else {
leaflet(USstates) %>%
addTiles() %>%
addPolygons(color = 'navy',
opacity = 1.0,
weight = 1)
}
})
I came up with a solution, of cause right after I created this post. The solution was to wrap the leaflet code in a reactive function and call that one in the renderLeaflet function. The solution is as follows:
server <- function(input, output) {
display <- reactive({
if (input$check1){
leaflet(USstates) %>%
addTiles() %>%
addPolygons(color = 'navy',
opacity = 1.0,
weight = 1) %>%
addMarkers(lng = coor[,1],lat = coor[,2])
} else {
leaflet(USstates) %>%
addTiles() %>%
addPolygons(color = 'navy',
opacity = 1.0,
weight = 1)
}
})
output$mymap <- renderLeaflet({
display()
})
}

How to update shiny reactive only if threshold is crossed?

In the code below, the leaflet addCircles get drawn twice after a change in zoom. I think this double plotting occurs because the reactive to create a dataframe always updates with a change in zoom. However, I only want the reactive dataframe (race.dots.all.r) to update when a zoom threshold is crossed. Any ideas?
EDIT: I removed even more code to simplify and made it reproducible by adding a dropbox link to the data.
library(shiny)
library(leaflet)
library(dplyr)
load(url("https://www.dropbox.com/s/umhqvoqvbhlkrc6/shiny_app_seg_gap_stackoverflow.RData?dl=1"))
ui <- shinyUI(fluidPage(
leafletOutput("map"),
checkboxInput("togglewhite", "White", value = TRUE)
))
server <- shinyServer(function(input, output, session) {
per.person <- eventReactive(input$map_zoom,{
new_zoom <- 12
if (!is.null(input$map_zoom)) {
new_zoom <- input$map_zoom}
if ( new_zoom < 13 ) {
per.person <- "1000"
} else {
per.person <- "250"
}
return(per.person)
})
race.dots.all.r <- eventReactive(per.person(),{
race.dots.all <- race.dots.all[[per.person()]]
return(race.dots.all)
})
values <- reactiveValues(school = NULL)
output$map <- renderLeaflet({
leaflet(options = leafletOptions(preferCanvas = TRUE)) %>%
addProviderTiles("CartoDB") %>%
setView(lat=40.73771, lng=-74.18958, zoom = 8)
})
observeEvent(c(input$togglewhite, race.dots.all.r()), {
proxy <- leafletProxy('map')
proxy %>% clearGroup(group = "White")
if (input$togglewhite){
race.dots.all.selected.race <- dplyr::filter( race.dots.all.r(), group == "White")
proxy %>% addCircles(group = race.dots.all.selected.race$group,
race.dots.all.selected.race$lng,
race.dots.all.selected.race$lat)
}
},ignoreInit = TRUE)
}) # close server
shinyApp(ui, server)
If I understand your requirement correctly then here's one way to do it. I have set the zoom threshold at 0 for this example just so that it is easy to demo. You can change it to 4 for your app.
library(shiny)
library(leaflet)
shinyApp(
ui = fluidPage(
leafletOutput("map")
),
server = function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>%
addTiles()
})
map_proxy <- leafletProxy("map")
observeEvent(input$map_zoom, {
if(input$map_zoom > 0) {
map_proxy %>%
addCircleMarkers(lng = 74.0060, lat = 40.7128,
group = "high_zoom_in", radius = 50, color = "red")
} else {
map_proxy %>%
clearGroup("high_zoom_in")
}
})
}
)

Overlay an image in a shiny leaflet observe event

I have a shiny app where I need to add a png image in the instance of an observe event.
I can achieve this outside of Shiny, however, not within an observe function. I assume it has something to do with the map already being rendered?
I've simplified the example (hence just one png), but ideally I want to be able to quickly insert additional png's (i.e radar images)
library(shiny)
library(leaflet)
library(htmlwidgets)
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "80%"),
p(),
actionButton("recalc", "Action")
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
# Use leaflet() here, and only include aspects of the map that
# won't need to change dynamically (at least, not unless the
# entire map is being torn down and recreated).
leaflet() %>%
setView(lng = 153.240001, lat = -27.717732, zoom = 7) %>%
addProviderTiles(providers$OpenStreetMap) %>%
addTiles() %>% # Add default OpenStreetMap map tiles
addMarkers(lng=153.240001, lat=-27.717732, popup="Mt Stapylton")
})
points2 <- eventReactive(input$recalc, {
TRUE
}, ignoreNULL = FALSE)
# Use the onRender function to add a png
observe({
points <- points2()
leafletProxy("map") %>%
htmlwidgets::onRender("
function(el, x) {
console.log(this);
var myMap = this;
var imageUrl = 'https://www.google.com.au/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png';
var imageBounds = [[-25.58,150.71], [-30,155.88]];
L.imageOverlay(imageUrl, imageBounds).addTo(myMap);
}
")
print("pass")
})
}
shinyApp(ui, server)
### Working outside of leaflet
leaflet() %>%
setView(lng = 153.240001, lat = -27.717732, zoom = 7) %>%
addProviderTiles(providers$OpenStreetMap) %>%
addTiles() %>% # Add default OpenStreetMap map tiles
addMarkers(lng=153.240001, lat=-27.717732, popup="Mt Stapylton") %>%
htmlwidgets::onRender("
function(el, x) {
console.log(this);
var myMap = this;
var imageUrl = 'https://www.google.com.au/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png';
var imageBounds = [[-25.58,150.71], [-30,155.88]];
L.imageOverlay(imageUrl, imageBounds).addTo(myMap);
}
")
As is seems, the leafletProxy does not provide a means of accessing the Leaflet Api from the R side.
onRender definitely won't work since the whole point of leafletProxy is to not rerender the map.
The solution I found was to add a custom event handler on creation of the leaflet, using the onRender such that we have access to the Leaflet Api later on.
Using messages is of course kind of restricting, but if the way you want to render images (giving src and bounds) is always the same, it should suffice.
library(shiny)
library(leaflet)
library(htmlwidgets)
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "80%"),
actionButton("recalc", "Action")
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>%
setView(lng = 153.240001, lat = -27.717732, zoom = 7) %>%
addProviderTiles(providers$OpenStreetMap) %>%
addTiles() %>% # Add default OpenStreetMap map tiles
addMarkers(lng=153.240001, lat=-27.717732, popup="Mt Stapylton") %>%
htmlwidgets::onRender("
function(el, x) {
var myMap = this;
// Saving a copy of the overlay to remove it when the next one comes.
var overlay;
Shiny.addCustomMessageHandler('setOverlay', function(message) {
if (myMap.hasLayer(overlay)) myMap.removeLayer(overlay);
overlay = L.imageOverlay(message.src, message.bounds);
overlay.addTo(myMap);
});
}
")
})
observeEvent(input$recalc, {
session$sendCustomMessage("setOverlay", list(
src = 'https://www.google.com.au/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png',
bounds = list(list(-25.58,150.71), list(-30,155.88))
))
})
}
shinyApp(ui, server)

radioButtons in shiny app?

I have a radioButtons input with Yes and No :
radioButtons("rd", "3 rotor diameter circles:",list("Yes", "No"))
How should I modify my output in server.R, in a way that if the choice is YES, it consider the addCircles command and if is No ignore that line ?
output$mymap <- renderLeaflet({
infile=input$File
if (is.null(infile))
return(NULL)
df.20 <- Coor1
getColor <- function(Layout) {
sapply(Layout$j1, function(j1) {
if(j1 < 1) {
"red"
} else {
"green"
} })
}
icons <- awesomeIcons(
spin=TRUE,
icon = 'star',
iconColor = 'lightgray',
library = 'fa',
markerColor = getColor(df.20)
)
leaflet() %>%
addProviderTiles("OpenTopoMap", group = "MapQuestOpen.Aerial") %>%
addAwesomeMarkers(data = df.20,~long, ~lat, icon=icons, popup = ~as.character(mag), label = ~as.character(Name))%>%
addCircles(data = df.20,lng=~long, lat=~lat,radius=~rad,weight = 1,fill = FALSE)%>%
addMeasure(primaryLengthUnit='meters',primaryAreaUnit='sqmeters')
})
You should use leaflet's proxy method to do that, and add/remove circles in an observer, consider this example :
library("shiny")
library("leaflet")
df.20 <- quakes[1:20,]
ui <- fluidPage(
radioButtons(inputId = "add_circles", label = "Add circles", choices = c("Yes", "No")),
leafletOutput(outputId = "mymap")
)
server <- function(input, output, session) {
# your initial map
output$mymap <- renderLeaflet({
leaflet(df.20) %>% addTiles() %>% addCircles()
})
# add or remove circles when clicking the radio button
observeEvent(input$add_circles, {
if (input$add_circles == "Yes") {
leafletProxy(mapId = "mymap", data = df.20) %>% addCircles()
} else if (input$add_circles == "No") {
leafletProxy(mapId = "mymap") %>% clearShapes()
}
}, ignoreInit = TRUE)
}
shinyApp(ui = ui, server = server)
Look at https://rstudio.github.io/leaflet/shiny.html
Note : here I used clearShapes, it remove also polygons, rectangles and polylines, you can remove only circles by defining layerId in addCircles and use removeShape.
You could try this:
leaflet() %>%
addProviderTiles("OpenTopoMap", group = "MapQuestOpen.Aerial") %>%
addAwesomeMarkers(data = df.20,~long, ~lat, icon=icons, popup = ~as.character(mag), label = ~as.character(Name))%>%
{if(input$rd == "Yes")addCircles(data = df.20,lng=~long, lat=~lat,radius=~rad,weight = 1,fill = FALSE)}%>%
addMeasure(primaryLengthUnit='meters',primaryAreaUnit='sqmeters')
i have used if statement inside of the leaflet code, saying that if input$rd == "Yes", then addCircles(...) should be considered.

Resources