Courvoisier
Courvoisier

Reputation: 993

Shiny environment to use for renderImage

In shiny I am building a list of boxes and inside each there is a renderImage like this:

images = ["i1.png", "i2.png", "i3.png"]
for(i in 1:3){
  print(i)
  q_list[[i]] = box(
    width = 12,
    status = "primary",
    renderImage({
      print(i)
      img_path = images[i]
      print(img_path)
      list(src = img_path, alt = "This is alternate text")},
      deleteFile = F)
  )
}

unfortunately the boxes do not seem to register the i in the loop. In the above, the first i it prints is correct (1-2-3). But the second i, the one inside renderImage prints as 3 and the img_path is i3.png. Do I need to put another environment to renderImage for this to work?

as requested in the comment here is a minimal example. the dahsboard displays the same graph (i3.png) in the 3 boxes:

rm(list = ls(all.names = TRUE))
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinybusy)
library(shinyjs)
options(shiny.error = browser)
options(shiny.fullstacktrace = TRUE)
options(shiny.trace = TRUE)

images = c("i1.png", "i2.png", "i3.png")

ui = dashboardPage(
  sidebar = dashboardSidebar(disable = T),
  body = dashboardBody(uiOutput("module_body")),
  header = dashboardHeader(disable = T)
)

server = function(input, output, session){
  imgs_fct = function(){
    im_list = list()
    for(i in 1:3){
      print(i) # prints correctly
      img_path = images[i] # prints correctly
      print(img_path)
      # store each box in im_list
      im_list[[i]] = box(
        width = 12,
        status = "primary",
        renderImage({
          print(img_path) # prints "i3.png"
          list(src = img_path, alt = "This is alternate text")}, deleteFile = F)
      )
    }
    return(im_list)
  }

  output$module_body = renderUI({
    theitems = tabItem(tabName = "xxx", fluidRow(column(width = 8, offset = 2, id = "form", imgs_fct())))}
  )
}

# run the app
shinyApp(ui = ui, server = server)

Upvotes: 2

Views: 246

Answers (1)

henryn
henryn

Reputation: 1237

As suggested in the comments, if you use lapply in the imgs_fct function, this will work as expected, with each plot rendered as expected rather than just the final plot for all of them. Note that images is a vector of file paths to the images, so have assumed they're in the same directory as your Shiny app code:

  imgs_fct = function(){
    lapply(images, 
           function(x) {
             box(
               width = 12,
               status = "primary",
               renderImage({
                 list(src = x, alt = "This is alternate text")}, deleteFile = F)
             )
           })
    }

The reason this works and a for loop doesn't is because the counter you use to iterate (here i) is shared across all the renderImage calls as they're evaluated in the same environment (as they're within the same loop). What happens is, the calculation runs, and then when Shiny then tries to resolve any reactive dependencies at the end of this process, all the render functions share the same reference i which at this point is equal to 3, and so all render with the same image.

In contrast, with lapply, each renderImage is in a separate function call and hence are evaluated in a separate environment, where each function has as an argument a different image file path. Hence, these render separately as you expect. You could probably wrap the contents of your for loop in local({}) but I personally like the lapply approach as it seems more 'R'-y.

Upvotes: 2

Related Questions