识别闪亮的应用稳定性问题

时间:2014-12-10 13:37:01

标签: r shiny

我一直在开发一个Shiny应用程序,它展示了一个绘图功能,接受内置数据或用户输入CSV,生成自定义绘图,并可以PDF格式输出给用户。所有模块在开发过程中都相互独立运行,但总体而言,应用程序变得不稳定,并且经常拒绝对输入作出反应。有时它只需要刷新几次就可以了。所有功能都间歇性地工作,所以我认为任何错误都必须与Shiny /浏览器界面的复杂性有关。但由于没有来自Shiny(对R)或浏览器控制台的反馈,它几乎不可能被诊断出来,并且它开始觉得对使用这个非常有前景的平台感到严重不利

我已经使用简化的脚本重现了这种情况,该脚本也可以runGist('db479811c6237a0741fe', launch.browser=F)执行。我非常感谢任何有此类问题经验或者了解Shiny的人的帮助。建议还对如何简化或重新设计代码结构表示赞赏。任何不适合的评论/讨论请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's 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')
    )
  )
)

0 个答案:

没有答案