Identifying application glitter problems

I am developing a Shiny app that demonstrates a plot function, takes inline data or a custom CSV file, creates a custom plot, and can output it to the user as a PDF file. All modules worked great independently in development, but overall the application becomes unstable and regularly refuses to respond to input. Sometimes it needs to update several times to get started. All functionality works intermittently, so I think any errors should relate to the complexities of the Shiny / browser interface. But since there is no feedback from Shiny (up to R) or in the browser console, this is almost impossible to diagnose, and it starts to feel like a major obstacle to using this otherwise very promising platform.

I made the situation reproducible with a minified script that can also be executed with runGist('db479811c6237a0741fe', launch.browser=F)

. I would be very grateful for help from anyone who has experience with this type of question or who understands Shiny under the hood. The council also appreciated ways to streamline or redesign the structure of the code. Any comments / discussions are not appropriate for SO, please post to reddit .

server.R

require(shiny)

# inbuilt dataset
diamonds = ggplot2::diamonds[,c(1,5,7)]

# csv datasets to input via front-end
for(i in 1:3){
    dat <- diamonds[sample(1:nrow(diamonds), 200),]
    write.table(dat, paste0('dat',i,'.csv'), sep=',',row.names=F, col.names=T)
}
diamonds = diamonds[sample(1:nrow(diamonds),200),]

# global variables
inbuilt = FALSE   # whether currently using inbuilt data or not
datapath = ''     # to chech current against previous to see if new dataset input
pagereset = FALSE # to reset when inbuilt de-selected

# function to 'plot' welcome instructions
welcome <- function(){
    plot.new(); plot.window(xlim=c(0,100), ylim=c(0,100))
    text(10,80,"Please input CSV file data with 3 numerical columns", cex=2, pos=4)
    text(10,65,"Use the inbuilt dataset and the csv files in the app folder..", cex=1.5, pos=4)
    text(10,50,"check app reliability and how often commands fail", cex=1.5, pos=4)
    text(10,35,"output to PDF", cex=1.5, pos=4)
    text(10,20,"how stable is the app for you?", cex=1.5, pos=4)
}

shinyServer(function(input, output, session) {

  # REACTIVE FUNCTION
  plotInput = reactive({

    # import data from inbuilt (internal) or a user-input CSV
    # first must check if reactive is triggered by new data or not:
    newdata = FALSE               # initialised
    if(input$inbuilt != inbuilt){ # inbuilt data option toggled
        if(input$inbuilt) {       # inbuilt selected
            inbuilt <<- TRUE      # update global
            d <<- diamonds
            newdata = TRUE
        } else{                   # inbuilt de-selected. 
            inbuilt <<- FALSE     # update global
            d <<- NULL            # return splashscreen
            pagereset <<- TRUE    # would now crash so refresh app instead
        }
    } else {                      # input doesn't relate to inbuilt dataset
        if(!input$inbuilt){       # inbuilt unselected
            if(is.null(input$file1)) { # if null no input received yet
                d = NULL          # so reactive will return splash-screen
            } else {              # data has been input before
                if(input$file1$datapath != datapath){  # new dataset just received
                    datapath <<- input$file1$datapath  # update global
                    d <<- read.csv(datapath, header=TRUE, sep = ',') # update global
                    newdata = TRUE
                    #Sys.sleep(2) # allow file-upload aanimation to finish
                    # reset file handler in page
                    session$sendCustomMessage(type = "resetFileInputHandler", "file1")
                } else NULL    # new input not dataset-related
            }
        }
    }

    # reset/null javascript command - to reset app after inbuilt
    # dataset is de-selected, as the script crashes otherwise..
    reset_js = ifelse(pagereset, "window.location.reload()", '')
    reset_js = paste("<script>", reset_js,";</script>")
    if(pagereset) {
        pagereset <<- FALSE
        return(list(resetpage = reset_js, plot = plot.new())) # reset and null plot
    }

    # no data input so return splash-screen
    if(is.null(d)) return(list(resetpage = reset_js, plot = welcome()))

    # NORMAL PLOT

    # # stroke around polygons
    if(input$border != 'none') border = input$border else border = NA

    # PDF handling (save file locally to be passed forward)
    if(input$returnpdf){
        pdf("plot.pdf", width=as.numeric(input$w), height=as.numeric(input$h))
        symbols(d[[1]], d[[2]], circles=sqrt(d[[3]]), inches=as.numeric(input$inches), 
            bg='#ff000020', fg=border)
        dev.off()
    }

    # return plot and reset instruction in list
    list(
        resetpage = reset_js,
        plot = symbols(d$carat, d$depth, circles=sqrt(d$price), inches=as.numeric(input$inches),
            bg='#ff000020', fg=border)
    )

  }) # end reactive

    # OUTPUT ELEMENTS

    # PDF file
    output$pdflink = downloadHandler(
      filename <- "shiny_plot.pdf", # default browser save filename
      content <- function(file) file.copy("plot.pdf", file) # call pre-saved pdf
    )

    # plot
    output$plot = renderPlot({ plotInput()$plot })

    # reset instruction
    output$reset = renderText({ plotInput()$resetpage })
})

      

ui.R

require(shiny)

fluidPage(
  titlePanel("Stability testing"),
  sidebarLayout(
    sidebarPanel(

      # this css just resets the CSV upload function
      tags$head(
        tags$script('
          Shiny.addCustomMessageHandler("resetFileInputHandler", function(x) {      
          var id = "#" + x + "_progress";
          var idBar = id + " .bar";
          $(id).css("visibility", "hidden");
          $(idBar).css("width", "0%");
          });
        ')
      ),

      # inputs
      h4('Input options'),
      p("Chose inbuilt dataset or upload a CSV:"),
      checkboxInput('inbuilt', 'Inbuilt dataset (app resets when de-selected)', FALSE),
      fileInput('file1', '', accept = 'text/comma-separated-values'),

      # PDF output
      h4('PDF output'),
      p("Buggy: plot disappears, but link still downloads last plot. Sometimes after download app crashes"),
      checkboxInput('returnpdf', 'Save plot to PDF?', FALSE),
      conditionalPanel(
        condition = "input.returnpdf == true",
        strong("PDF size (inches):"),
        sliderInput(inputId="w", label = "width:", min=3, max=20, value=12, width=100, ticks=F),
        sliderInput(inputId="h", label = "height:", min=3, max=20, value=9, width=100, ticks=F),
        downloadLink('pdflink')
      ),

      # plot layout
      h4('Plot options'),
      selectInput(inputId="border", label="Outline colour:", choices=list(black='black', white='white', none='none'), width=150, selected='black'),
      sliderInput(inputId="inches", label = "Circle size (higher values can crash the app)", min=0.05, max=.5, value=.2, width=150)      
    ),

    mainPanel(
      htmlOutput('reset'), # reset command (when inbuild dataset de-selected)
      imageOutput('plot')
    )
  )
)

      

+3


source to share





All Articles