Search code examples
rshinyreactive-programmingflexdashboardcrosstalk

render new image from disk in R markdown / Dashboard on user selection dropdown


I have a folder full of charts, generated from a previous step. All of them are PNG files. I want to be able to choose anyone using Flexdashboard and load it. As no shiny or server service is needed I tried Crosstalk package

library(crosstalk)
library(magrittr)
library(png)
    
df <- list.files("plots/", full.names = TRUE) %>%
      as_tibble() %>%
      magrittr::set_names("path") 
    
    shared_data <- SharedData$new(df,  key = ~path)
    
    p <- shared_data %>% readPNG(source = path)
    
    bscols( filter_select(id = "file_id", 
                         label = "CHOOSE", 
                         sharedData = shared_data, 
                         group = ~path), 
           p)

I am stuck on a very simple error i cannot solve as all paths are properly read from file:

Error in path.expand(source) : invalid 'path' argument

Tried to use knitr too:

   bscols(filter_select("path", "CHOOSE", shared_data),
      knitr::include_graphics(shared_data, ~path))

Error in makeGroupOptions(sharedData, group, allLevels) : argument "group" is missing, with no default

Maybe there is a simpler approach but crosstalk seemed a very simple one as it does not need shiny or any other component but a data frame.


Solution

  • Updated Answer (Oct 2023) -

    Reactively adding new files to dropdown

    AFAIK, If we want the UI to update itself every time new files are added, we will have to use Shiny and its reactive capabilities. The server observes the directory for any new files and asynchronously updates the client's Selectinput every time a new file is added

    library(shiny)
    
    ########################### Client ################################### 
    
    ui <- fluidPage(
      titlePanel("Image Viewer"),
      
      sidebarLayout(
        sidebarPanel(
          selectInput("imgSelect", "Select an image", choices = NULL ),
          br(),
        ),
        
        mainPanel(
          imageOutput("imgDisplay")
        ) #mp
      )
    )
    
    
    ########################### Server ################################### 
    
    server <- function(input, output, session) {
      
      image_dir <-  "plots"
      
      oldFiles<- function(){
        return(  list.files(path=image_dir, pattern = "\\.png$", full.names = TRUE))
      }
      nowFiles <- function() {
        return(list.files(path=image_dir, pattern = "\\.png$", full.names = TRUE))
      }
      
      # Constantly poll and check(compare) nowFiles against oldFiles
      imgData <- reactivePoll(3000, session, checkFunc = oldFiles , 
                              valueFunc = nowFiles)
      
      # Update dropdown  if files change
      observe({
        # Retain previous selection when new items are added 
        curr<- input$imgSelect #gives current selection
        if(curr ==''){ curr= imgData()[1] }
        # update ui
        updateSelectInput(session, "imgSelect", choices = imgData() , selected = curr )
      })
      
      #  Render new image on selection change
      observeEvent(input$imgSelect, {
        currImg <- input$imgSelect
       #image rendering
        output$imgDisplay <- renderImage({
          # return list to render image
          list(
            src = currImg,contentType = 'image/png',
            alt = "currImg"
          )
        }, deleteFile = FALSE) 
        #end image rendering
        
      })# End observeEvent
      
    }#server
    
    shinyApp(ui, server)
    

    Tip:

    If you place the following lines outside the server() function, you can share the same file list across multiple sessions

    image_dir <-  "plots"
    imgData <- reactivePoll(3000, session = NULL, 
                            checkFunc = function() {list.files(path=image_dir,
                                                               pattern = "\\.png$",
                                                               full.names = TRUE)}  , 
                            valueFunc = function() {list.files(path=image_dir, 
                                                               pattern = "\\.png$", 
                                                               full.names = TRUE) })
     
    

    Old/Accepted Answer (Oct 2021)

    If all you need is a static setup , this still works, and accomplishes it with minimum fuss

    ```{r}
    # 
    library(stringr)
    library(bsselectR)
    
    state_plots <- paste0(list.files("plots", full.names = TRUE, recursive = TRUE))
    names(state_plots) <- str_replace_all(state_plots, 
                                          c("\\.png" = "", 
                                            "plots/" = ""))
    
    
    bsselect(state_plots, type = "img", selected = "sns_heatmap", 
             live_search = TRUE, show_tick = TRUE)
    
    
    ```
    

    Output :

    enter image description here