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')
)
)
)
source to share
No one has answered this question yet
Check out similar questions: