Brilliant (R) issue when updating tilemap inside app
I am using Shiny and I have a Leaflet problem.
What I want to do : The ultimate goal of my project is to select specific French districts. When these counties are selected, I create an on-the-go (by merging) form file (which is needed to build the map on a sheet) and a database containing all kinds of public data (e.g. population, etc ...). which only correspond to specific counties.
My question . I know the "merge" works well inside the Shiny application. BUT, my Outputmap doesn't want to work.
More details . Actually I ran my "merge and build" on a different .R script and it works fine (I only defined "manually" before running the script in which countries I wanted to build). On my ShinyApp, this selection is taken by a variable input$choix_depa
.
Now some code. I have three scripts that react at the same time: global.R ui.R and server.R (this is how Shiny works, as you know). For global.R (I'm only showing the "non-professional" part here), I load a database file containing data for all counties (not form files! Only data)
setwd('path')
data_BP = read_delim(
"database-allFrance.csv",
",",
na = "empty",
quote = "\"",
locale = locale(encoding = 'windows-1252')
)
On ui.R (UI) I have my "pick" of counties:
shinydashboard::tabItem(tabName= "Departements", class = 'active',
shiny::fluidPage(
shiny::absolutePanel(
draggable = FALSE,
fixed = TRUE,
top = 60, left = "auto", right = 20, bottom = "auto",
width = 330, height = "auto",
wellPanel(
shiny::h4("Départements"),
selectizeInput(inputId = "choix_depa", label = "",multiple=TRUE,
choices = seq(1,95))
)
), textOutput("answ")
),
selectizeInput
- a button that allows the user to select one or more counties in seq(1,95)
.
And on the .R server (most importantly) I have:
ObserveEvent(input$choix_depa, {
output$answ<- renderText({
paste("You choose", input$choix_depa)
})
choice=input$choix_depa
print(choice)
for (i in input$choix_depa){
setwd(sprintf("path/county%s",i))
assign(paste("contouriris",i,sep=""), readOGR(
dsn = "contours_IRIS_BP.shp",
layer = "contours_IRIS_BP",
verbose = FALSE,
encoding = 'UTF-8'
))
print("modification en cours...")
assign(paste("data_BP",i,sep=""),subset(data_BP,as.numeric(as.character(data_BP$IRIS))>=as.numeric(i)*10000000&as.numeric(as.character(data_BP$IRIS))<(as.numeric(i)+1)*10000000))
}
if (length(input$choix_depa)>=1){
contours_IRIS <- get(paste("contouriris",input$choix_depa[1],sep=""))
data_BPC <- get(paste("data_BP",input$choix_depa[1],sep=""))
}
if (length(input$choix_depa)>1){
for (i in input$choix_depa[-1]){
contours_IRIS <- rbind(contours_IRIS,get(paste("contouriris",i,sep="")))
data_BPC <- rbind(data_BPC,get(paste("data_BP",i,sep="")))
}
}
map_WGS84 = spTransform(
merge(contours_IRIS, data_BPC, by.x = 'CODE_IRIS', by.y = 'IRIS'),
CRS("+init=epsg:4326")
)
# Correction of names :
names(map_WGS84)[names(map_WGS84) == "TYP_IRIS.x"] <- "TYP_IRIS"
})
You don't need to understand all this code. You have a selection of counties in a variable input$choix_depa
. This variable is similar to ["4", "87"] if environments 4 and 87 are selected by the user inside the application (for example). I have 95 folders on my computer (one for each county). I use setwd to navigate to the correct path, and load the "shape file" in contouririsK
, where K is the county number. In the previous example, we would have contouriris4
and countouriris87
. These form files are bundled into contours_IRIS
. I do the same with a data file, I took the data associated with those counties and merge all the files intodata_BPC
. (if I take the previous example, we would have data_BPC4
and data_BPC87
that were merged into data_BPC
).
After that, I use the two variables ( contours_IRIS
and data_BPC
) to create a variable map_WGS84
(I need this variable for LeafletOutput).
So, after this selection of the counties that I want to plot on the Sheets map, I need to select the variables in which I am interested. This is a different menu, I am not writing all the code here (optional)
observeEvent(input$choix_var_pop, {XXXXXXXXX})
The selection of the variable that the user wants to plot on the map is in input$choix_var_pop
. After that, I create the specific variables that I will need in my LeafletMap page:
label_reac_pop = reactive({as.character(input$choix_var_pop)})
var_reac_pop = reactive({dico$Variable[dico$Label == label_reac_pop()]})
col_reac_pop = reactive({as.character(dico$Couleur[dico$Label == label_reac_pop()])})
type_reac_pop = reactive({as.character(dico$Type[dico$Label == label_reac_pop()])})
unite_reac_pop = reactive({ifelse(as.character(type_reac_pop()) == "Pct", " %", "")})
Finally, I draw the LeafletMap: (I have greatly reduced the code below for clarity)
output$Carte_Pop <- renderLeaflet({
label = label_reac_pop()
var = var_reac_pop()
col = col_reac_pop()
type = type_reac_pop()
unite = unite_reac_pop()
values_var = map_WGS84@data[,var]
leaflet(map_WGS84) %>%
addProviderTiles("CartoDB.PositronNoLabels") %>%
addProviderTiles("CartoDB.PositronOnlyLabels") %>%
setView(lng = 2.468738900000062, lat = 49.19316, zoom = 7) %>%
clearShapes() %>%
clearPopups() %>%
})
Of course, I call this $ Carte_Pop output in the ui.R file to build it. OKAY so what is the result of all this? As I mentioned earlier, this script works when it is "alone" and when it is not input$choix_depa
(I manually enter the districts I want to combine into an array and they are well combined and the map is well built). BUT when I am on ShinyApp with my 3 scripts (global.R, ui.R and server.R) the "new" value of the "map" is not "saved" .
For example: if I chose (my one is script) to merge and draw counties # 4 and 87, it works great (the merge part and the build part work well)!
BUT , when I run my ShinyApp, when I select the county that I need (eg, 13 and 91), EVEN IF contours_IRIS
and data_BPC
well integrated with the data corresponding to 13 and 91, therefore, assume that map_WGS84
created INSIDE observeEvent(input$choix_depa....)
, CORRESPONDING the WELL to 13 and 91 when I ask to build a specific variable (after the observeEvent(input$choix_var_pop)
displayed map is NOT the previously created map, but the "older MAP" with 4 and 87 (the map that was created in "alone-script" ... before running ShinyApp!). But I'm sure which is 100% that the MAP created internally observeEvent(input$choix_depa .... )
is good. But the "value" of this MAP is not "saved" by ShinyApp (they are instead the old value for MAP ...).
So my question is, what should I do to sketch a GOOD NEW MAP (created inside APP) instead of OLD BAD ONE (created BEFORE and EXTERNAL app ...)?
This problem is a bit “tricky”, if you have any questions feel free to ask!
Thank!:)
source to share
Summary. If you want some output to depend on other expressions, you must use eventReactive, or store the object in a reactive value and update it with the registerEvent function.
Ok I read your question and here are my thoughts:
ObserveEvent(input$choix_depa, {
output$answ<- renderText({
paste("You choose", input$choix_depa)
})
This is bad practice. observers should only be used for side effects, not for generating output. This should become:
output$answ<- renderText({
paste("You choose", input$choix_depa)
})
ObserveEvent(input$choix_depa, {
....
renderText will also fire whenever $ choix_depa changes, as it reacts to that. So there is no need to place it in the observer.
map_WGS84 = spTransform(
merge(contours_IRIS, data_BPC, by.x = 'CODE_IRIS', by.y = 'IRIS'),
CRS("+init=epsg:4326")
)
What object is map_WGS84? This will only work if it is a reactive value. ... Otherwise, you are not overwriting the global variable, only the local one in the observEvent function. When the observEvent function finishes executing, the global map_WGS84 has not changed. I think this is the problem here.
The best option is probably to make it eventReactive rather than a watchdog function as you want it to generate output that can be used elsewhere. Another option is to store map_WGS84 in a reactiveValues expression and overwrite it from your watcher.
source to share
Thanks to Florian for your help, I'll tell you exactly how I succeeded:
output$answ<- renderText({
paste("Choix départements:", input$choix_depa)
})
observeEvent(input$choix_depa, {
choice=input$choix_depa
})
map_reactive <- eventReactive(input$choix_depa,{
... merging and creating contours_IRIS (shape file)
and data_BPC given input$choix_depa ...
map_WGS84 = spTransform(
merge(contours_IRIS, data_BPC, by.x = 'CODE_IRIS', by.y = 'IRIS'),
CRS("+init=epsg:4326")
)
list(map = map_WGS84)
})
observeEvent(input$choix_var_pop, {XXXXXXXXX})
... defining variables...
output$Carte_Pop <- renderLeaflet({
compulsive = map_reactive()$map
label = label_reac_pop()
var = var_reac_pop()
col = col_reac_pop()
type = type_reac_pop()
unite = unite_reac_pop()
values_var = compulsive@data[,var]
leaflet(compulsive) %>%
})
What was also "important" was to add a "list" at the end of the Reactive event to call map_reactive $ map later. The problem is solved!
source to share