我一直在开发一个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')
)
)
)