Search code examples
rshinyshinydashboard

How can we save and restore shiny app having multiple tabs?


I have built a shiny app having multiple tabs and tried to save the state of the app and restore it but I am not getting result as I expected. Following is the example code I have used to save and restore. file will stored in .rds format.

library(shinydashboard)
library(shinyWidgets)
library(plotly)
library(DT)
library(corrr)
library(dplyr)
library(Robyn)
library(qgraph)
library(shinyjs)
library(utils)
library(tools)
library(stringi)

ui <- function(request){fluidPage(
  useShinyjs(),
  titlePanel("APP"),
  useShinydashboard(),
  fileInput(
    "file",
    "Choose CSV File",
    accept = c("text/csv",
               "text/comma-separated-values,text/plain",
               ".csv")
  ),
  checkboxInput("header",
                "Header",
                value = TRUE),
  radioButtons(
    "disp",
    "Display",
    choices = c(Head = "head",
                All = "all"),
    selected = "head"
  ),
  fileInput("restore_bookmark", 
            "Restore Session", 
            multiple = FALSE 
            #accept = ".rds"),
  ),
  #  SIDEBAR --------------------------------------------------------
  navlistPanel(
    widths = c(2,10),
    #  Input data ---------------------------------------------------
    tabPanel('Input data',
             fluidRow(
               box(width = 12,
                   dataTableOutput('table'),
                   title = 'Raw data'),
               box(width = 6,
                   dataTableOutput('miss'),
                   title = 'Missing percentage table'),
               box(width = 6,
                   dataTableOutput('dtype'),
                   title = 'Datatype')
             )
    ),
    #  Basic EDA ----------------------------------------------------
    tabPanel('Basic EDA',
             fluidRow(
               column(width = 7,
                      box(
                        width = NULL,
                        plotlyOutput('correlation',
                                     height = 450),
                        title = 'Correlation plot',
                        style = 'overflow-y:scroll; max-height: 600px'
                      ),
                      box(
                        width = NULL,
                        selectInput(
                          inputId = 'x_axis',
                          label = 'X-axis',
                          'Names',
                          multiple = FALSE
                        ),
                        selectInput(
                          inputId = 'y_axis',
                          label = 'Y-axis',
                          'Names',
                          multiple = FALSE
                        )
                      )
               ),
               column(width = 5,
                      box(
                        width = NULL,
                        plotOutput('network',
                                   height = 250),
                        title = 'Correlation network',
                        sliderInput('netslider',
                                    'Min corr',
                                    min = 0,
                                    max = 1,
                                    value = 0.3)
                      ),
                      box(
                        width = NULL,
                        plotlyOutput('scatter',
                                     height = 300),
                        title = 'Scatter plot'
                      )
               )
             ),
             actionButton("save_inputs", 
                          'Save Session', 
                          icon = icon("download"))
    )
  )
)}

server <- function(input, output, session) {
  #  Session saving --------------------------------------------------
  latestBookmarkURL <- reactiveVal()
  
  onBookmarked(
    fun = function(url) { #url
      latestBookmarkURL(parseQueryString(url))
    }
  )
  
  onRestored(function(state) {
    showNotification(paste("Restored session:",
                           basename(state$dir)),
                     duration = 10,
                     type = "message")
  })
  observeEvent(input$save_inputs, {
    showModal(modalDialog(
      title = "Session Name",
      textInput("session_name", 
                "Please enter a session name (optional):"),
      footer = tagList(
        modalButton("Cancel"),
        downloadButton("download_inputs", "OK")
      )
    ))
  }, ignoreInit = TRUE)
  # SAVE SESSION ---------------------------------------------------------------
  output$download_inputs <- downloadHandler(
    filename = function() {
      removeModal()
      session$doBookmark()
      
      if (input$session_name != "") {
        
        tmp_session_name <- sub("\\.rds$", "", input$session_name)
        tmp_session_name <- stri_replace_all(tmp_session_name, "", regex = "[^[:alnum:]]")
        tmp_session_name <- paste0(tmp_session_name, ".rds")
        print(tmp_session_name)
      } else {
        paste(req(latestBookmarkURL()), "rds", sep = ".")
        
      }
    },
    print(latestBookmarkURL()),
    
    content = function(file) {
      file.copy(from = file.path(
        ".",
        "shiny_bookmarks",
        req(latestBookmarkURL()),
        "input.rds"
        #paste0(ses_name(),'.rds')
      ),
      to = file)
      
    }
  )
  # LOAD SESSION ---------------------------------------------------------------
  observeEvent(input$restore_bookmark, {
      sessionName <- file_path_sans_ext(input$restore_bookmark$name)
      print(sessionName)
      targetPath <- file.path(".", "shiny_bookmarks", sessionName, "input.rds")
      print(targetPath)
      restoreURL <- paste0(session$clientData$url_protocol, "//", 
                           session$clientData$url_hostname, ":", 
                           session$clientData$url_port, 
                           session$clientData$url_pathname, 
                           "?_state_id_=", 
                           sessionName)
      
      print(restoreURL)
      # redirect user to restoreURL
      runjs(sprintf("window.location = '%s';", restoreURL))
      
      print(sprintf("window.location = '%s';", restoreURL))
      })
    
  
    
    dataset <- reactive({
      read.csv("./Dataset/data.csv")
    })
    observe(
      output$table <- DT::renderDataTable({
        if (input$disp == 'head') {
          head(dataset())
        }
        else{
          dataset()
        }
      })
    )
    # Missing percentage table ---------------------------------------
    output$miss <- renderDataTable({
      miss_dataframe = data.frame(names(dataset()),
                                  (colMeans(is.na(dataset())))*100)
      setNames(miss_dataframe,c("Variable","Missing percentage"))
    })
    
    # Datatype table -------------------------------------------------
    output$dtype <- renderDataTable({
      dtype_dataframe = data.frame(names(dataset()),
                                   sapply(dataset(),class))
      setNames(dtype_dataframe,c('Variables','Data type'))
    })
    # Correlation plot -----------------------------------------------------------
    sub_dataset <- reactive({
      subset(dataset(),
             select = sapply(dataset(),
                             class) != 'character',
             drop = TRUE)
    })
    output$correlation <- renderPlotly({
      cor_sub <- cor(sub_dataset())
      plot_ly(x = names(sub_dataset()),
              y = names(sub_dataset()),
              z = cor_sub,
              type = 'heatmap',
              colors = colorRamp(c("red", "green")),
              zmin = -1,
              zmax = 1,
              width = 600,
              height = 500) %>%
        layout(title = paste('Correlation plot'))
    })
    # Correlation network --------------------------------------------
    output$network <- renderPlot({
      qgraph(cor(sub_dataset()),
             shape = 'ellipse',
             overlay = TRUE,
             layout = 'spring',
             minimum = input$netslider,
             vsize = 8,
             labels = TRUE,
             nodeNames = colnames(sub_dataset()),
             details = T,
             legend = T,
             legend.cex = 0.4, 
             GLratio = 1.3,
             label.prop = 1.5
      )
    })
    # scatter plot ---------------------------------------------------------------
    observe({
      updateSelectInput(inputId = "x_axis",choices = names(dataset()))
      updateSelectInput(inputId = "y_axis",choices = names(dataset()))
    })
    
    x_axis <- reactive({
      dataset()[,input$x_axis]
    })
    y_axis <- reactive({
      dataset()[,input$y_axis]
    })
    
    output$scatter <- renderPlotly({
      plot_ly(dataset(), x = x_axis(),
              y = y_axis(),
              type = 'scatter',
              mode = 'markers') %>% 
        layout(title = paste("Scatter plot"))
    })

}
enableBookmarking(store = 'server')
shinyApp(ui = ui, server = server)

I have taken this save and restore technique from this link. please give any suggestions.

Here is the output of dput(head(read.csv("./Dataset/data.csv")))

structure(list(Date = c("2020-01-01", "2020-01-02", "2020-01-03", 
"2020-01-04", "2020-01-05", "2020-01-06", "2020-01-07", "2020-01-08", 
"2020-01-09", "2020-01-10"), CRM_web_visits = c(72531L, 74512L, 
102819L, 79954L, 36726L, 35314L, 32973L, 67710L, 56590L, 236847L
), DIRECT.NOSOURCE._web_visits = c(170419L, 201539L, 182053L, 
174788L, 169971L, 191405L, 205873L, 198961L, 199704L, 235057L
), DISPLAY_ad_spend = c(5974.94, 6791.05, 6475.65, 6977.87, 7184.88, 
7282.68, 6990.11, 7184.7, 7310.45, 7381.47), DISPLAY_impression = c(5195802L, 
6419806L, 6851564L, 7465473L, 8542588L, 8856138L, 9563437L, 9741881L, 
10102445L, 10764759L), EARNEDSOCIAL_web_visits = c(8468L, 13646L, 
17214L, 15885L, 16675L, 12983L, 12985L, 18746L, 19377L, 42041L
), ORGANICSEARCH_web_visits = c(161203L, 228753L, 228830L, 223210L, 
219383L, 228044L, 228522L, 262009L, 239033L, 250576L), OTHERS_web_visits = c(709L, 
1561L, 1698L, 1541L, 1448L, 1685L, 1838L, 2060L, 2213L, 2400L
), PAIDSEARCH_ad_spend = c(83432.41, 103529.01, 102688.27, 109478.01, 
109835.46, 102679.45, 106726.33, 145900.64, 149793.69, 135749.34
), PAIDSEARCH_impression = c(9614558L, 10974797L, 11177990L, 
12129001L, 11936305L, 11635109L, 11320728L, 12709154L, 13554402L, 
13776665L), PAIDSOCIAL_ad_spend = c(11538.3, 8512.8, 8805.4, 
11433.27, 11323.38, 11344.67, 11273.9, 11785.63, 11559.53, 18486.82
), PAIDSOCIAL_impression = c(12212695L, 8692666L, 8456129L, 9878943L, 
10315930L, 11530289L, 10552150L, 10546136L, 8784657L, 12968591L
), PARTNERSHIPMARKETING_ad_spend = c(63636.11, 6130.62, 8362.65, 
6208.49, 6114.99, 5079.42, 9484.97, 22930.46, 10150.6, 22321.9
), PARTNERSHIPMARKETING_click = c(72785L, 119086L, 113134L, 92235L, 
92232L, 81516L, 96305L, 126095L, 130431L, 249288L), REFERRINGSITES_web_visits = c(7955L, 
12286L, 13948L, 12509L, 10906L, 11595L, 11818L, 13143L, 13179L, 
17014L), Overall_Revenue = c(941026.4, 1293915.56, 1485440.42, 
1395251.29, 1358603.2, 1342233.84, 1385053.29, 1883013.32, 1438745.75, 
3017775.46)), row.names = c(NA, 10L), class = "data.frame")

thanks in advance


Solution

  • Well, you deleted (or didn't copy) the dir.create and file.copy calls in the observeEvent(input$restore_bookmark, [...] from my original answer. They are mandatory for this to work.

    Furthermore I added an id to your navlistPanel so its state can be bookmarked and your updateSelectInput(inputId = "x_axis" ... is overwriting the restored bookmark state for your selectInputs - you might want to change the logic, so that is is used only if the session wasn't restored - check ?onRestore.

    library(shiny)
    library(shinydashboard)
    library(shinyWidgets)
    library(plotly)
    library(DT)
    library(corrr)
    library(dplyr)
    library(Robyn)
    library(qgraph)
    library(shinyjs)
    library(utils)
    library(tools)
    library(stringi)
    
    ui <- function(request){fluidPage(
      useShinyjs(),
      titlePanel("APP"),
      useShinydashboard(),
      fileInput(
        "file",
        "Choose CSV File",
        accept = c("text/csv",
                   "text/comma-separated-values,text/plain",
                   ".csv")
      ),
      checkboxInput("header",
                    "Header",
                    value = TRUE),
      radioButtons(
        "disp",
        "Display",
        choices = c(Head = "head",
                    All = "all"),
        selected = "head"
      ),
      fileInput("restore_bookmark", 
                "Restore Session", 
                multiple = FALSE 
                #accept = ".rds"),
      ),
      #  SIDEBAR --------------------------------------------------------
      navlistPanel(
        id = "navlistPanelID",
        widths = c(2,10),
        #  Input data ---------------------------------------------------
        tabPanel('Input data',
                 fluidRow(
                   box(width = 12,
                       dataTableOutput('table'),
                       title = 'Raw data'),
                   box(width = 6,
                       dataTableOutput('miss'),
                       title = 'Missing percentage table'),
                   box(width = 6,
                       dataTableOutput('dtype'),
                       title = 'Datatype')
                 )
        ),
        #  Basic EDA ----------------------------------------------------
        tabPanel('Basic EDA',
                 fluidRow(
                   column(width = 7,
                          box(
                            width = NULL,
                            plotlyOutput('correlation',
                                         height = 450),
                            title = 'Correlation plot',
                            style = 'overflow-y:scroll; max-height: 600px'
                          ),
                          box(
                            width = NULL,
                            selectInput(
                              inputId = 'x_axis',
                              label = 'X-axis',
                              choices = NULL,
                              multiple = FALSE
                            ),
                            selectInput(
                              inputId = 'y_axis',
                              label = 'Y-axis',
                              choices = NULL,
                              multiple = FALSE
                            )
                          )
                   ),
                   column(width = 5,
                          box(
                            width = NULL,
                            plotOutput('network',
                                       height = 250),
                            title = 'Correlation network',
                            sliderInput('netslider',
                                        'Min corr',
                                        min = 0,
                                        max = 1,
                                        value = 0.3)
                          ),
                          box(
                            width = NULL,
                            plotlyOutput('scatter',
                                         height = 300),
                            title = 'Scatter plot'
                          )
                   )
                 ),
                 actionButton("save_inputs", 
                              'Save Session', 
                              icon = icon("download"))
        )
      )
    )}
    
    server <- function(input, output, session) {
      #  Session saving --------------------------------------------------
      latestBookmarkURL <- reactiveVal()
      
      onBookmarked(
        fun = function(url) { #url
          latestBookmarkURL(parseQueryString(url))
        }
      )
      
      onRestored(function(state) {
        showNotification(paste("Restored session:",
                               basename(state$dir)),
                         duration = 10,
                         type = "message")
      })
      observeEvent(input$save_inputs, {
        showModal(modalDialog(
          title = "Session Name",
          textInput("session_name", 
                    "Please enter a session name (optional):"),
          footer = tagList(
            modalButton("Cancel"),
            downloadButton("download_inputs", "OK")
          )
        ))
      }, ignoreInit = TRUE)
      # SAVE SESSION ---------------------------------------------------------------
      output$download_inputs <- downloadHandler(
        filename = function() {
          removeModal()
          session$doBookmark()
          
          if (input$session_name != "") {
            
            tmp_session_name <- sub("\\.rds$", "", input$session_name)
            tmp_session_name <- stri_replace_all(tmp_session_name, "", regex = "[^[:alnum:]]")
            tmp_session_name <- paste0(tmp_session_name, ".rds")
            print(tmp_session_name)
          } else {
            paste(req(latestBookmarkURL()), "rds", sep = ".")
            
          }
        },
        print(latestBookmarkURL()),
        
        content = function(file) {
          file.copy(from = file.path(
            ".",
            "shiny_bookmarks",
            req(latestBookmarkURL()),
            "input.rds"
            #paste0(ses_name(),'.rds')
          ),
          to = file)
          
        }
      )
      # LOAD SESSION ---------------------------------------------------------------
      observeEvent(input$restore_bookmark, {
        sessionName <- file_path_sans_ext(input$restore_bookmark$name)
        print(sessionName)
        targetPath <- file.path(".", "shiny_bookmarks", sessionName, "input.rds")
        
        print(targetPath)
        restoreURL <- paste0(session$clientData$url_protocol, "//", 
                             session$clientData$url_hostname, ":", 
                             session$clientData$url_port, 
                             session$clientData$url_pathname, 
                             "?_state_id_=", 
                             sessionName)
        
        print(restoreURL)
        if (!dir.exists(dirname(targetPath))) {
          dir.create(dirname(targetPath), recursive = TRUE)
        }
        
        file.copy(
          from = input$restore_bookmark$datapath,
          to = targetPath,
          overwrite = TRUE
        )
        
        restoreURL <- paste0(session$clientData$url_protocol, "//", session$clientData$url_hostname, ":", session$clientData$url_port, session$clientData$url_pathname, "?_state_id_=", sessionName)
        
        # redirect user to restoreURL
        runjs(sprintf("window.location = '%s';", restoreURL))
        
        print(sprintf("window.location = '%s';", restoreURL))
      })
      
      
      
      dataset <- reactive({
        # read.csv("./Dataset/data.csv")
        structure(list(Date = c("2020-01-01", "2020-01-02", "2020-01-03",
                                "2020-01-04", "2020-01-05", "2020-01-06", "2020-01-07", "2020-01-08",
                                "2020-01-09", "2020-01-10"), CRM_web_visits = c(72531L, 74512L,  102819L,
                                                                                79954L, 36726L, 35314L, 32973L, 67710L, 56590L, 236847L ),
                       DIRECT.NOSOURCE._web_visits = c(170419L, 201539L, 182053L,  174788L,
                                                       169971L, 191405L, 205873L, 198961L, 199704L, 235057L ), DISPLAY_ad_spend =
                         c(5974.94, 6791.05, 6475.65, 6977.87, 7184.88,  7282.68, 6990.11, 7184.7,
                           7310.45, 7381.47), DISPLAY_impression = c(5195802L,  6419806L, 6851564L,
                                                                     7465473L, 8542588L, 8856138L, 9563437L, 9741881L,  10102445L, 10764759L),
                       EARNEDSOCIAL_web_visits = c(8468L, 13646L,  17214L, 15885L, 16675L,
                                                   12983L, 12985L, 18746L, 19377L, 42041L ), ORGANICSEARCH_web_visits =
                         c(161203L, 228753L, 228830L, 223210L,  219383L, 228044L, 228522L, 262009L,
                           239033L, 250576L), OTHERS_web_visits = c(709L,  1561L, 1698L, 1541L,
                                                                    1448L, 1685L, 1838L, 2060L, 2213L, 2400L ), PAIDSEARCH_ad_spend =
                         c(83432.41, 103529.01, 102688.27, 109478.01,  109835.46, 102679.45,
                           106726.33, 145900.64, 149793.69, 135749.34 ), PAIDSEARCH_impression =
                         c(9614558L, 10974797L, 11177990L,  12129001L, 11936305L, 11635109L,
                           11320728L, 12709154L, 13554402L,  13776665L), PAIDSOCIAL_ad_spend =
                         c(11538.3, 8512.8, 8805.4,  11433.27, 11323.38, 11344.67, 11273.9,
                           11785.63, 11559.53, 18486.82 ), PAIDSOCIAL_impression = c(12212695L,
                                                                                     8692666L, 8456129L, 9878943L,  10315930L, 11530289L, 10552150L, 10546136L,
                                                                                     8784657L, 12968591L ), PARTNERSHIPMARKETING_ad_spend = c(63636.11,
                                                                                                                                              6130.62, 8362.65,  6208.49, 6114.99, 5079.42, 9484.97, 22930.46, 10150.6,
                                                                                                                                              22321.9 ), PARTNERSHIPMARKETING_click = c(72785L, 119086L, 113134L,
                                                                                                                                                                                        92235L,  92232L, 81516L, 96305L, 126095L, 130431L, 249288L),
                       REFERRINGSITES_web_visits = c(7955L,  12286L, 13948L, 12509L, 10906L,
                                                     11595L, 11818L, 13143L, 13179L,  17014L), Overall_Revenue = c(941026.4,
                                                                                                                   1293915.56, 1485440.42,  1395251.29, 1358603.2, 1342233.84, 1385053.29,
                                                                                                                   1883013.32, 1438745.75,  3017775.46)), row.names = c(NA, 10L), class =
                    "data.frame")
      })
      observe(
        output$table <- DT::renderDataTable({
          if (input$disp == 'head') {
            head(dataset())
          }
          else{
            dataset()
          }
        })
      )
      # Missing percentage table ---------------------------------------
      output$miss <- renderDataTable({
        miss_dataframe = data.frame(names(dataset()),
                                    (colMeans(is.na(dataset())))*100)
        setNames(miss_dataframe,c("Variable","Missing percentage"))
      })
      
      # Datatype table -------------------------------------------------
      output$dtype <- renderDataTable({
        dtype_dataframe = data.frame(names(dataset()),
                                     sapply(dataset(),class))
        setNames(dtype_dataframe,c('Variables','Data type'))
      })
      # Correlation plot -----------------------------------------------------------
      sub_dataset <- reactive({
        subset(dataset(),
               select = sapply(dataset(),
                               class) != 'character',
               drop = TRUE)
      })
      output$correlation <- renderPlotly({
        cor_sub <- cor(sub_dataset())
        plot_ly(x = names(sub_dataset()),
                y = names(sub_dataset()),
                z = cor_sub,
                type = 'heatmap',
                colors = colorRamp(c("red", "green")),
                zmin = -1,
                zmax = 1,
                width = 600,
                height = 500) %>%
          layout(title = paste('Correlation plot'))
      })
      # Correlation network --------------------------------------------
      output$network <- renderPlot({
        qgraph(cor(sub_dataset()),
               shape = 'ellipse',
               # overlay = TRUE,
               layout = 'spring',
               minimum = input$netslider,
               vsize = 8,
               labels = TRUE,
               nodeNames = colnames(sub_dataset()),
               details = T,
               legend = T,
               legend.cex = 0.4, 
               GLratio = 1.3,
               label.prop = 1.5
        )
      })
      # scatter plot ---------------------------------------------------------------
      
      isBookmarkedSession <- reactiveVal(FALSE)
      onRestore(function(state) {
        isBookmarkedSession(TRUE)
        updateSelectInput(inputId = "x_axis", choices = names(dataset()), selected = state$input$x_axis)
        updateSelectInput(inputId = "y_axis", choices = names(dataset()), selected = state$input$y_axis) 
      })
      
      observe({
        if(!isBookmarkedSession()){
          updateSelectInput(inputId = "x_axis", choices = names(dataset()))
          updateSelectInput(inputId = "y_axis", choices = names(dataset())) 
        }
      })
      
      output$scatter <- renderPlotly({
        req(dataset(), input$x_axis, input$y_axis)
        plot_ly(dataset(), x = ~ get(input$x_axis),
                y = ~ get(input$y_axis),
                type = 'scatter',
                mode = 'markers') %>% 
          layout(title = paste("Scatter plot"))
      })
      
    }
    enableBookmarking(store = 'server')
    shinyApp(ui = ui, server = server)