rshinyreactiver-leaflet

Variables are not being passed to leaflet loop to generate maps


I am working on a R Shiny app. The user uploads his data file and it creates leaflet maps of the median values. The difficulty is that I need to generate maps for each variable, but that number can be different between each file.

The data is a reactive sf tibble, so providing a reprex is going to be difficult.

The problem is that only the last variable is being generated multiple times.

server.R

# server.R

function(input, output, session){

  # Genrate fluidRow boxes for the maps
  output$all_cartes_nutri_dept <- renderUI({
    req(bilans_nutriments$dept())
    
    # Init a "table" for the maps
    tableau_cartes <- list()
    
    # nutriments to check for validity
    nutriments_mesures_to_check <- paste0(colonnes_disponibles$nutriments, "_med")
    
    # Init of valid nutriments and map names
    nutriments_valides <- c()
    noms_cartes_nutri_dept <- c()
    
    # Loop over each valid nutriment and prepare the output box
    for (i in seq_along(nutriments_mesures_to_check)) {
      
      # Verifier si la colonne est valide dans bilans_nutriments
      if (check_colonne(nutriments_mesures_to_check[i], bilan_nutri_dept_shape())) {
        
        # Add valid nutriment and map names to their respectives vectors
        nutriments_valides <- c(nutriments_valides, colonnes_disponibles$nutriments[i])
        noms_cartes_nutri_dept <- c(noms_cartes_nutri_dept, paste0("carte_nutri_dept_", colonnes_disponibles$nutriments[i]))
        
        # Add valid map box to the table of maps
        tableau_cartes[[length(tableau_cartes) + 1]] <- box(
          withSpinner(leafletOutput(noms_cartes_nutri_dept[i]), color = spinner_color)
          )
      } else {
        message_nodata <- paste("No data for nutriment:", nutriments_mesures_to_check[i])
        print(message_nodata)
      }
    }
    
    print("Nutriments valides :")
    print(nutriments_valides)
    print("Noms des cartes :")
    print(noms_cartes_nutri_dept)
    
    output$message_nodata <- renderText({
      message_nodata
      })
    
    # Créer les cases du tableau avec les boîtes pour chaque carte
    fluid_row_output <- list()
    
    # Pour chaque element du tableau
    for (i in seq(1, length(noms_cartes_nutri_dept), by = 2)) {
      
      fluid_row_output[[length(fluid_row_output) + 1]] <- fluidRow(
        tableau_cartes[[i]],
        if (i + 1 <= length(tableau_cartes)) tableau_cartes[[i+1]] else NULL
      )
    }
    
    # Generer les cartes pour chaque nutriment
    
    print("Génération des cartes pour chaque nutriment")
    for (i in seq_along(noms_cartes_nutri_dept)) {
      
      print(paste("Génération de la carte pour le nutriment :", nutriments_valides[i], "avec le nom :", noms_cartes_nutri_dept[i]))

      # TO FIX 1 ----
      # this is where the error appears. The above print gives me a list of length 7 with the correct values.
      # Then, I pass the same arguments to the output and drawing map function below
      # Bug 1 : it only gives the last element of the vector to generer_carte_nutri_dept() instead of all 7 elements (length(noms_cartes_nutri_dept) = 7)
      # Bug 2 : it only gives the last element of the vector 2 times, instead of looping 7 times

      output[[noms_cartes_nutri_dept[i]]] <- renderLeaflet({
      # TO FIX 2 ----
      # And here it breaks. Instead of getting each element of nutriment_valides, it only receives the last element of the vector.
        print(i) # only print 7 twice instead of 1 2 3 4 5 6 7
        carte_bilan_departemental(
          df_bilan_shape = bilan_nutri_dept_shape(), 
          produit = input$selecteur_MP,
          nutriment = nutriments_valides[i],
          mesure = "med"
        )
      })
    }
    
    return(do.call(tagList, fluid_row_output))
  })


ui.R

body <- dashboardBody(
  
  tabItems(
    tabItem(
        # Génération dynamiques des fluidRows
        uiOutput("all_cartes_nutri_dept")
        )
    )
)

dummy data

# Dummy Data for Nutrient Information and Departments
set.seed(123)

# Departments (for simulation purposes, can be any identifier)
departments <- c("Department A", "Department B", "Department C", "Department D")

# Simulate some nutritional data for 4 departments and 3 nutrients
df_bilan <- data.frame(
  nom_produit = rep(c("Product 1", "Product 2"), each = 4),
  nom = rep(departments, times = 2), # Department names
  humidite_med = runif(8, 10, 30),  # Random values for the nutrient
  proteines_med = runif(8, 2, 15),  # Random values for the nutrient
  lipides_med = runif(8, 5, 20)     # Random values for the nutrient
)

# Simulate available nutrients
colonnes_disponibles <- list(
  nutriments = c("humidite", "proteines", "lipides")
)

# Simulated product selection (input)
input <- list(selecteur_MP = "Product 1")  # Can be any valid product

Any ideas how to solve TO FIX 1 and TO FIX 2. I think that the only issue is that the correct variables are not being fed to generer_carte_nutri_dept().

I learned too late about modules to include them in this app so please don't suggest it at this stage. But it's in the works for later.

EDIT : I moved the code generating the leaflet map inside the main output logic. Still bugged.


Solution

  • I fixed it using the answer to that question : R Shiny renderPlot not respecting parent environment in for loop. The problem was the lazy evaluation of one the variables.

    By replacing the for loop with an apply-like function, it fixed the issue.

    old

    for (i in seq_along(noms_cartes_nutri_dept)) {
        output[[noms_cartes_nutri_dept[i]]] <- generer_carte_nutri_dept(nutriments_valides[i])
        }
    

    new

    map(seq_along(noms_cartes_nutri_dept), function(i) {
            output[[noms_cartes_nutri_dept[i]]] <- generer_carte_nutri_dept(nutriments_valides[i])
        })