R Leaflet : conditional panel does not appear in map - r

I would like to display a conditional panel in my map when I click on a circle, and this conditional panel must disappear if I click outside a circle, but it does not appear and I don't know why.
I think it's about reactive values (one more time).
If any idea, please tell me.
Thank you very much, this is a reproducible example (thanks to SymbolixAU) :
ui :
library(shiny)
library(leaflet)
ui <- fluidPage(
leafletOutput("mymap",width="100%",height="750px"),
conditionalPanel(
condition = "output.COND == '2'",
fluidRow(
absolutePanel(id = "cond_panel",
class = "panel panel-default",
fixed = TRUE,
draggable = TRUE,
top = "auto",
left = 200,
right = "auto",
bottom = 0,
width = 400,
height = 400,
fluidRow(
) # e. of fluidRow(
) # # e. of absolutePanel
) # e. of fluidRow
) # e. of conditionalPanel
) # e. of fluidPage
and the server :
server <- function(input, output){
rv <- reactiveValues()
rv$myDf <- NULL
rv$cond <- NULL
cities <- read.csv(textConnection("
City,Lat,Long,Pop
Boston,42.3601,-71.0589,645966
Hartford,41.7627,-72.6743,125017
New York City,40.7127,-74.0059,8406000
Philadelphia,39.9500,-75.1667,1553000
Pittsburgh,40.4397,-79.9764,305841
Providence,41.8236,-71.4222,177994
"))
cities$id <- 1:nrow(cities)
output$mymap <- renderLeaflet({
leaflet(cities) %>% addTiles() %>%
addCircles(lng = ~Long, lat = ~Lat, weight = 1,
radius = ~sqrt(Pop) * 30, popup = ~City, layerId = ~id)
})
observeEvent(input$mymap_click, {
print("map clicked")
rv$cond <- "1"
print(paste("Value rv$cond = ", rv$cond))
output$COND <- reactive({rv$cond})
leafletProxy("mymap")
}) # e. of observeEvent
observeEvent(input$mymap_shape_click, {
print("shape clicked")
rv$cond <- "2"
print(paste("Value rv$cond = ", rv$cond))
output$COND <- reactive({rv$cond})
leafletProxy("mymap")
}) # e. of observeEvent
} # e. of server

I'm going to propose a slightly different approach that uses library(shinyjs) to use javascript to control whether the panel is hidden or not.
In this example I've created a hidden div element (i.e., the panel will start hidden when the app opens). Then the 'div' is shown when the circle is clicked, and hidden again when the map is clicked.
This answer is inspired by #Daattali's answer here (he's the author of library(shinyjs).
library(shiny)
library(leaflet)
library(shinyjs)
ui <- fluidPage(
useShinyjs(), ## Call to use shinyJS
leafletOutput("mymap",width="100%",height="750px"),
#conditionalPanel(
#condition = "output.COND === '2'",
hidden(
div(id = "conditionalPanel",
fluidRow(
absolutePanel(id = "cond_panel",
class = "panel panel-default",
fixed = TRUE,
draggable = TRUE,
top = "auto",
left = 200,
right = "auto",
bottom = 0,
width = 400,
height = 400,
fluidRow(
) # e. of fluidRow(
) # # e. of absolutePanel
) # e. of fluidRow
)
)
# ) # e. of conditionalPanel
) # e. of fluidPage
server <- function(input, output){
rv <- reactiveValues()
rv$myDf <- NULL
rv$cond <- NULL
cities <- read.csv(textConnection("
City,Lat,Long,Pop
Boston,42.3601,-71.0589,645966
Hartford,41.7627,-72.6743,125017
New York City,40.7127,-74.0059,8406000
Philadelphia,39.9500,-75.1667,1553000
Pittsburgh,40.4397,-79.9764,305841
Providence,41.8236,-71.4222,177994
"))
cities$id <- 1:nrow(cities)
output$mymap <- renderLeaflet({
leaflet(cities) %>% addTiles() %>%
addCircles(lng = ~Long, lat = ~Lat, weight = 1,
radius = ~sqrt(Pop) * 30, popup = ~City, layerId = ~id)
})
observeEvent(input$mymap_click, {
shinyjs::hide(id = "conditionalPanel")
print("map clicked")
rv$cond <- "1"
print(paste("Value rv$cond = ", rv$cond))
output$COND <- reactive({rv$cond})
leafletProxy("mymap")
}) # e. of observeEvent
observeEvent(input$mymap_shape_click, {
shinyjs::show(id = "conditionalPanel")
print("shape clicked")
rv$cond <- "2"
print(paste("Value rv$cond = ", rv$cond))
output$COND <- reactive({rv$cond})
leafletProxy("mymap")
}) # e. of observeEvent
} # e. of server
shinyApp(ui, server)

Just use the absolutePanel inside a conditionalPanel whose condition you reset based on user input.

Related

Gauge chart from GoogleVis displaying in browser but on shiny app I get the error $ operator is invalid for atomic vectors

I am trying to make a shiny app where I can select a location on the map and display a gauge chart for each corresponding location.
I have been able to make the app reactive but the googlevis gauge display appears on the browser instead of in the app. In the app I get the error $ operator is invalid for atomic vectors. I tried converting the data into a dataframe but I am still getting this error.
the code is as follows
library(shiny)
library(leaflet)
library(shinydashboard)
library(dplyr)
library(googleVis)
#Making the Dataframe
locations<-c("A","B","C")
x<-c(36.05617,36.05626,36.05634)
y<-c(-2.1007,-2.05553,-2.01035)
yield<-c(5.86,3.06,1.07)
df<-data.frame(locations,x,y,yield)
################## Defining UI for application ############################
ui <- shinyUI(dashboardPage(title = "Yield Lookup",
dashboardHeader(title = "Crop Yield (Tonnes per Hectare)",titleWidth = 350),
dashboardSidebar(
sidebarMenu(
menuItem("Map Dashboard", tabName = "datavis", icon = icon("map", verify_fa = FALSE)),
menuItem("Select by Location Name", icon = icon("leaf"),
selectizeInput("locations", "Click on Location", choices = levels(factor(df$locations)))
)
)
),
dashboardBody(
tabItems(
tabItem(tabName = "datavis",
h4("Map and Plot"),
fluidRow(box(width= 8, leafletOutput("map", height = 800)),
box("Gauge for crop yield by area",width = 4, htmlOutput("myplot")))
)
)
)
)
)
################## Defining Server for application ############################
server<- shinyServer(function(input,output, session){
## Sub data
lo<-reactive({
})
locat_data <- reactive({
df[df$locations %in% input$locations,]
})
output[["myplot"]] <- renderGvis({
newdf<-locat_data()%>%select(locations, yield)
newdf<-as.data.frame(newdf)
Gauge <- gvisGauge(as.data.frame(newdf),
options=list(min=0, max=6, greenFrom=4,
greenTo=6, yellowFrom=2, yellowTo=4,
redFrom=0, redTo=2, width=400, height=300))
plot(Gauge)
})
output$map <- renderLeaflet({
leaflet(df) %>%
addTiles() %>%
addCircleMarkers(lng = ~x, lat = ~y, layerId = ~locations, color = "blue", radius = 3) %>%
addCircles(lng = ~x, lat = ~y, weight = 1,
radius = 1, label = ~locations
)
})
observeEvent(input$locations,{
updateSelectInput(session, "locations", "Click on Locations",
choices = levels(factor(df$locations)),
selected = c(input$locations))
})
observeEvent(input$map_marker_click, {
click <- input$map_marker_click
location <- df[which(df$y == click$lat & df$x == click$lng), ]$locations
updateSelectInput(session, "locations", "Click on Location",
choices = levels(factor(df$locations)),
selected = c(input$locations, location))
})
})
shinyApp(ui=ui, server = server)
I am not sure where I am going wrong. Please help.
You were very close.
Just drop the plot() and leave Gauge in the server section. (Alternatively drop the Gauge <- and Gauge on the next line and just leave gvisGauge())
library(shiny)
library(leaflet)
library(shinydashboard)
library(dplyr)
library(googleVis)
#Making the Dataframe
locations<-c("A","B","C")
x<-c(36.05617,36.05626,36.05634)
y<-c(-2.1007,-2.05553,-2.01035)
yield<-c(5.86,3.06,1.07)
df<-data.frame(locations,x,y,yield)
################## Defining UI for application ############################
ui <- shinyUI(dashboardPage(title = "Yield Lookup",
dashboardHeader(title = "Crop Yield (Tonnes per Hectare)",titleWidth = 350),
dashboardSidebar(
sidebarMenu(
menuItem("Map Dashboard", tabName = "datavis", icon = icon("map", verify_fa = FALSE)),
menuItem("Select by Location Name", icon = icon("leaf"),
selectizeInput("locations", "Click on Location", choices = levels(factor(df$locations)))
)
)
),
dashboardBody(
tabItems(
tabItem(tabName = "datavis",
h4("Map and Plot"),
fluidRow(box(width= 8, leafletOutput("map", height = 800)),
box("Gauge for crop yield by area",width = 4, htmlOutput("myplot")))
)
)
)
)
)
################## Defining Server for application ############################
server<- shinyServer(function(input,output, session){
## Sub data
lo<-reactive({
})
locat_data <- reactive({
df[df$locations %in% input$locations,]
})
output[["myplot"]] <- renderGvis({
newdf<-locat_data()%>%select(locations, yield)
newdf<-as.data.frame(newdf)
Gauge <- gvisGauge(as.data.frame(newdf),
options=list(min=0, max=6, greenFrom=4,
greenTo=6, yellowFrom=2, yellowTo=4,
redFrom=0, redTo=2, width=400, height=300))
Gauge
})
output$map <- renderLeaflet({
leaflet(df) %>%
addTiles() %>%
addCircleMarkers(lng = ~x, lat = ~y, layerId = ~locations, color = "blue", radius = 3) %>%
addCircles(lng = ~x, lat = ~y, weight = 1,
radius = 1, label = ~locations
)
})
observeEvent(input$locations,{
updateSelectInput(session, "locations", "Click on Locations",
choices = levels(factor(df$locations)),
selected = c(input$locations))
})
observeEvent(input$map_marker_click, {
click <- input$map_marker_click
location <- df[which(df$y == click$lat & df$x == click$lng), ]$locations
updateSelectInput(session, "locations", "Click on Location",
choices = levels(factor(df$locations)),
selected = c(input$locations, location))
})
})
shinyApp(ui=ui, server = server)

Load multiple shapefiles Shiny r

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)
})
})
}

How to display map dynamically changed as per drilldown selectInput() based on previous selections?

I would like to render a map based on selectInput(). I have two selectInput()s: first one product_type and second one product_name. In the second one selectInput() the drop down options should be display only relevant to first selectInput(). Based on these drill down inputs map should change dynamically.
Here is the code:
ui <- shinyUI(dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
fluidPage(
box("",
leafletOutput("abc", width = '100%', height = 300),
height = 350, width = 12),
box("",
selectInput('vtype', label = 'Prod Type',choices = brand$prod_type),
selectInput('vname', label = 'Prod Name',choices = brand$prod_name),
width = 4),
valueBoxOutput("gr", width = 8)
)
)
))
server <- shinyServer(function(input,output,session){
a <- ship %>% select(prod_name,prod_type,LON,LAT) %>% filter(prod_type == input$vtype)
output$gr <- renderValueBox({
box(table(a))
})
output$abc <- renderLeaflet({
leaflet() %>% addProviderTiles(providers$OpenTopoMap )
%>% setView(lat = a$LAT ,lng = A$LON, zoom = 4)
})
})
shinyApp(ui,server)
The dynamically changed points in the map should be marked up. I tried but could not able to build the code.
Any help on this code would be graceful for me.
I hope my example helps. I invented a data.frame 'ship' and made everything dependent on it. That means it is used for your variable 'brand' as well as 'ship'.
I'm not sure how you envisioned the value box, so I put category and products in it.
library(shiny)
library(shinydashboard)
library(dplyr)
library(leaflet)
ship <- data.frame(
product_type = c("food","food","tool","tool","tool","accessories","accessories","lighting","lighting","lighting"),
product_name=c("eggs", "bread","clamp","hammer","screw driver", "watch" ,"sun glases","LED","bulb","briquette"),
LON=c(-61.783,2.632,47.395,20.068,44.563,17.544,-170.730,-65.167,136.189,50.562),
LAT=c(17.078 ,28.163 ,40.430 ,41.143 ,40.534 ,-12.296 ,-14.318 ,-35.377 ,-24.973 ,26.019),
stringsAsFactors = F)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(collapsed = TRUE, disable = FALSE),
dashboardBody(
# fluidPage(
box(
leafletOutput("abc", width = '100%', height = 300),
height = 350,
width = 12),
box(
selectInput('vtype', label = 'Prod Type', choices = c("< select product type>"="", ship$product_type)),
selectInput('vname', label = 'Prod Name', choices = c("< select item>"="", ship$product_name)),
width = 4),
valueBoxOutput("gr", width = 8)
#)
)
)
server <- shinyServer(function(input,output,session){
a <- reactive({
ship %>%
select(product_name, product_type, LON, LAT) %>%
filter(product_type %in% input$vtype)
})
output$gr <- renderValueBox({
valueBox( input$vtype, paste(a()$product_name, collapse=' - ') )
})
observe({
updateSelectInput(session,
inputId='vname',
choices = c("< select item>"="", a()$product_name ))
})
output$abc <- renderLeaflet({
leaflet() %>%
addProviderTiles(providers$OpenTopoMap ) %>%
setView(lng=0, lat=0, zoom = 1)
})
observe({
selection <- a() %>% filter(product_name %in% input$vname)
leafletProxy("abc") %>%
flyTo(lat = selection$LAT,
lng = selection$LON,
zoom = 4)
})
})
shinyApp(ui,server)
Please provide example data next time.

Wrapping the dropdownButton in an absolutePanel in R Shiny

I'm building an interactive map with Shiny and I'm currently trying to hide my UI elements in a dropdownButton from the shinyWidgets pkg.
My problem is that so far I can either have the dropdownButton working and having to remove the width = "100%", height = "100%" from my leafletOutput
OR
having my map as I want it and the dropdownButton being invisible.
Is there a way of having both? Thanks!
Here's a reprex:
library(shiny)
library(leaflet)
library(RColorBrewer)
library(shinyWidgets)
ui <- bootstrapPage(
tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
leafletOutput("map", width = "100%", height = "100%"),
dropdownButton(sliderInput("range", "Magnitudes", min(quakes$mag), max(quakes$mag),
value = range(quakes$mag), step = 0.1
),
selectInput("colors", "Color Scheme",
rownames(subset(brewer.pal.info, category %in% c("seq", "div")))
),
checkboxInput("legend", "Show legend", TRUE)
)
)
server <- function(input, output, session) {
# Reactive expression for the data subsetted to what the user selected
filteredData <- reactive({
quakes[quakes$mag >= input$range[1] & quakes$mag <= input$range[2],]
})
# This reactive expression represents the palette function,
# which changes as the user makes selections in UI.
colorpal <- reactive({
colorNumeric(input$colors, quakes$mag)
})
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(quakes) %>% addTiles() %>%
fitBounds(~min(long), ~min(lat), ~max(long), ~max(lat))
})
# Incremental changes to the map (in this case, replacing the
# circles when a new color is chosen) should be performed in
# an observer. Each independent set of things that can change
# should be managed in its own observer.
observe({
pal <- colorpal()
leafletProxy("map", data = filteredData()) %>%
clearShapes() %>%
addCircles(radius = ~10^mag/10, weight = 1, color = "#777777",
fillColor = ~pal(mag), fillOpacity = 0.7, popup = ~paste(mag)
)
})
# Use a separate observer to recreate the legend as needed.
observe({
proxy <- leafletProxy("map", data = quakes)
# Remove any existing legend, and only if the legend is
# enabled, create a new one.
proxy %>% clearControls()
if (input$legend) {
pal <- colorpal()
proxy %>% addLegend(position = "bottomright",
pal = pal, values = ~mag
)
}
})
}
shinyApp(ui, server)
Instead of setting html width and height, you can put the map in a full page container like this :
ui <- bootstrapPage(
tags$style(type = "text/css", ".map-container {position:absolute; top:0; bottom:0; right:0; left:0;}"),
tags$style(type = "text/css", "#dropdown {margin-top: 80px; margin-left: 10px;}"),
tags$div(
class = "map-container",
leafletOutput("map", width = "100%", height = "100%")
),
dropdownButton(
inputId = "dropdown",
icon = icon("gears"),
circle = FALSE,
sliderInput("range", "Magnitudes", min(quakes$mag), max(quakes$mag),
value = range(quakes$mag), step = 0.1
),
selectInput("colors", "Color Scheme",
rownames(subset(brewer.pal.info, category %in% c("seq", "div")))
),
checkboxInput("legend", "Show legend", TRUE)
)
)

Shiny leaflet map, on marker click decrease map width and insert tableoutput

I'm working on a shiny app, which shows some markers on a leaflet map. When one of the markers is clicked, the corresponding row of the data.frame should be shown in a rhandsontable on the right of the map. In detail the map width should be decreased (e.g. from 100% to 50%) and in the free space the rhandsontable should be inserted.
There a some problems with my code, which I couldn't solve so far:
Markers are not plotted on map when they are inside a leafletProxy (which is necessary in a more complex app).
Only the first marker click is observed, then the table does not change anymore (probably something wrong with observeEvent)
the rhandsontable is added below the map, not in the space to the right, which gets free, when the map width is reduced.
The data should be stored in a reactive value (to make changes possible).
Here is a minimal reproducible example:
library(shiny)
library(leaflet)
library(rhandsontable)
ui <- fluidPage(
fluidRow(
uiOutput("map2"),
uiOutput("table2")
)
)
server <- function(input, output, session){
values <- reactiveValues(
data = data.frame(X = c(1, 2), lat = c(48, 49), lng = c(11, 11.5)),
which_marker = NULL,
leaflet_map_width = "100%"
)
output$map2 <- renderUI({
leafletOutput("map", width = values$leaflet_map_width, height = "500px")
})
output$map <- renderLeaflet({
leaflet() %>% addTiles() %>% setView(11, 48.5, 8) # %>% addMarkers(data = values$data, layerId = values$data$X)
})
observe({
leafletProxy("map") %>% addMarkers(data = values$data, layerId = values$data$X)
})
observeEvent(input$map_marker_click, {
print("observed map_marker_click")
values$which_marker <- input$map_marker_click$id
values$leaflet_map_width = "50%"
output$table2 <- renderUI({
rHandsontableOutput("table")
})
})
output$table <- renderRHandsontable({
data <- values$data[values$which_marker, ]
rhandsontable(t(data), rowHeaderWidth = 120)
})
}
shinyApp(ui, server)
Note: This answers only 1/3 of the questions.
But as one can see in the comments it does not make sense to give hints there:
To answer your third questions see a solution below. (set the width of the columns according to your needs)
library(shiny)
library(leaflet)
library(rhandsontable)
ui <- fluidPage(
uiOutput("map2")
)
server <- function(input, output, session){
values <- reactiveValues(
data = data.frame(X = c(1, 2), lat = c(48, 49), lng = c(11, 11.5)),
which_marker = NULL,
leaflet_map_width = "100%"
)
observe({
values$which_marker <- input$map_marker_click$id
})
output$map2 <- renderUI({
if(!is.null(input$map_marker_click)){
fluidRow(
column(width = 10, offset = 0, style='padding:0px;',
leafletOutput("map", width = "100%", height = "500px")),
column(width = 2, offset = 0, style='padding:0px;',
rHandsontableOutput("table")
)
)
}else{
leafletOutput("map", width = values$leaflet_map_width, height = "500px")
}
})
output$map <- renderLeaflet({
leaflet() %>% addTiles() %>% addMarkers(data = values$data, layerId = values$data$X)
})
output$table <- renderRHandsontable({
data <- values$data[values$which_marker, ]
rhandsontable(t(data), rowHeaderWidth = 120)
})
}
shinyApp(ui, server)

Resources