renderImage issue on shiny r - r

I have an issue about this code.
observeEvent(input$next, {
output$mark1 <- renderImage({
return(NULL)
}, deleteFile = FALSE)
})
This code is from the server file on shiny. And the UI file, the code is
imageOutput(
outputId = 'mark1',
inline = TRUE
)
My expectation is, if I click 'next', then the image 'mark1' should disappear.
But I got an error message saying:
Warning: Error in basename: a character vector argument expected

Welcome to SO! Please provide a reprex the next time - this will help to get help.
renderImage needs to return a list with at least a src slot. From the help of renderImage
The expression expr must return a list containing the attributes for the img object on the client web page. For the image to display, properly, the list must have at least one entry, src, which is the path to the image file.
So you have to find a way how to tell shiny not to render the image at all when your button is pressed. A neat way to do so is to use req like in the example below. The button render shows the pic and whenever you press delete it goes away. This is due to the fact that req silently raises an error, which tells shiny not to proceed in the observer/render* function and thus not to show the picture in the first place.
library(shiny)
ui <- fluidPage(imageOutput("pic"),
actionButton("go", "Render Print"),
actionButton("delete", "Remove Pic"))
server <- function(input, output, session) {
toggle_pic <- reactiveVal(FALSE)
observeEvent(input$go, toggle_pic(TRUE), ignoreInit = TRUE)
observeEvent(input$delete, toggle_pic(FALSE), ignoreInit = TRUE)
output$pic <- renderImage({
req(toggle_pic())
outfile <- tempfile(fileext = ".png")
# Generate a png
png(outfile, width=400, height=400)
hist(rnorm(100))
dev.off()
# Return a list
list(src = outfile,
alt = "This is alternate text")
}, deleteFile = TRUE)
}
shinyApp(ui, server)

Related

How to use callr::r_bg within a downloadHandler in a Shiny App

The scenario I'm emulating with the below minimal example is allowing a user to engage with a Shiny App (click the numericInput control and see server-side events occur) while a long-running download is occurring (simulated with Sys.sleep(10) within downloadHandler).
In a synchronous setting, when the "Download" button is clicked, the user can still interact with UI elements, but other Shiny calculations (in this case, renderText), get put in a queue. I'd like the asynchronous setting, where the download occurs in the background, and users can still interact with the UI elements and get desired output (e.g. renderText).
I'm using callr::r_bg() to achieve asynchronicity within Shiny, but the issue is that my current code of the downloadHandler is incorrect (mtcars should be getting downloaded, but the code is unable to complete the download, 404 error message), I believe it's due to the specific way in which downloadHandler expects the content() function to be written, and the way I've written callr::r_bg() is not playing nicely with that. Any insights would be appreciated!
Reference:
https://www.r-bloggers.com/2020/04/asynchronous-background-execution-in-shiny-using-callr/
Minimal Example:
library(shiny)
ui <- fluidPage(
downloadButton("download", "Download"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text")
)
server <- function(input, output, session) {
long_download <- function(file) {
Sys.sleep(10)
write.csv(mtcars, file)
}
output$download <- downloadHandler(
filename = "data.csv",
content = function(file) {
x <- callr::r_bg(
func = long_download,
args = list(file)
)
return(x)
}
)
observeEvent(input$count, {
output$text <- renderText({
paste(input$count)
})
})
}
shinyApp(ui, server)
I figured out a solution, and learned the following things:
Because downloadHandler doesn't have a traditional input$X, it can be difficult to include reactivity in the traditional way. The workaround was to present the UI as a hidden downlodButton masked by an actionButton which the user would see. Reactivity was facilitated in the following process: user clicks actionButton -> reactive updates -> when the reactive finishes (reactive()$is_alive() == FALSE), use shinyjs::click to initiate the downloadHandler
Instead of placing the callr function within the downloadHandler, I kept the file within the content arg. There seems to be some difficulties with scoping because the file needs to be available within the content function environment
I'm using a reactive function to track when the background job (the long-running computation) is finished to initiate the download using the syntax: reactive()$is_alive()
The invalidateLater() and toggling of a global variable (download_once) is important to prevent the reactive from constantly activating. Without it, what will happen is your browser will continually download files ad infinitum -- this behavior is scary and will appear virus-like to your Shiny app users!
Note that setting global variables is not a best practice for Shiny apps (will think of a better implementation)
Code Solution:
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
download_once <- TRUE
#output that tracks progress of background task
check <- reactive({
invalidateLater(millis = 1000, session = session)
if (long_run()$is_alive()) {
x <- "Job running in background"
} else {
x <- "Async job in background completed"
if(isTRUE(download_once)) {
shinyjs::click("download")
download_once <<- FALSE
}
invalidateLater(millis = 1, session = session)
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)
Thanks #latlio for your great answer. I think it cloud be easily improved.
invalidateLater should be used very carefully and only WHEN needed. I use invalidateLater only once and moved it to the logical part where we are waiting for the result. Thus we are NOT invalidating the reactivity infinitely.
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
#output that tracks progress of background task
check <- reactive({
if (long_run()$is_alive()) {
x <- "Job running in background"
invalidateLater(millis = 1000, session = session)
} else {
x <- "Async job in background completed"
shinyjs::click("download")
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)

Use custom visual in Shiny with adaptive constraints

Say I wanted to use a custom image or shapefile in an interactive environment (like R Shiny) such as this image of a paper airplane:
I would also be willing to draw the image myself in the program to allow for full control.
But the overall goal would be to allow a user to drag the edges of the image and the program could keep track of the size of the changes for each dimension (say wingspan of the paper airplane)
Would Shiny be a possibility here, or do I need to use another program? I would also want some statistics of the changes available to the user.
Does anyone have any similar examples of such a thing, or can point me in the right direction?
Like i wrote in the comment you could use the shinyjqui package and read the session info of the user.
A reproducible example can be found below:
library(shiny)
library(shinyjqui)
library(ggplot2)
server <- function(input, output, session){
global <- reactiveValues(width = 400, height = 400)
observe({
print(session)
if(!is.null(session$clientData[["output_plot1_height"]])){
global$height <- session$clientData[["output_plot1_height"]]
global$width <- session$clientData[["output_plot1_width"]]
}
})
output$plot1 <- renderImage({
outfile <- tempfile(fileext='.png')
png(outfile, width = global$width, height = global$height)
hist(rnorm(200))
dev.off()
list(src = outfile)
}, deleteFile = TRUE)
output$clientdataText <- renderText({
paste("height is ", global$height, "width is", global$width)
})
}
ui <- fluidPage(
verbatimTextOutput("clientdataText"),
jqui_resizabled(plotOutput("plot1"))
)
shinyApp(ui, server)

Shiny SelectInput to generate filepath for renderImage

I have a series of ".jpg" files within my www folder of my script. I want to have renderImage to render the image with the filename that is named "dateplatform.jpg" based on the inputs (date, platform) of my UI. When I try the script below within my server.r file the app isn't displaying any image. Any thoughts?
ui (partial)
fluidRow(
column(width=12,
imageOutput("platformimage")
)
)
server (partial)
filename <- reactive ({
paste(input$date, input$platform, sep="")
})
output$platformimage <- reactive({
renderImage({
list(src = filename(),
width = 600,
height = 600)
},deleteFile = FALSE)
})
filename has to have at least the extension attached to it, and it's probably safer to normalizePath it:
filename <- reactive({
normalizePath(file.path(paste0(input$date, input$platform, '.jpg')))
})
If this fail, it's probably because of the server can't find the file. Check the path created by filename(), and fix inside file.path()
Hope this helps.

"Download" button opens a new app window without downloading - Shiny

My "download" button does not work as expectation. It opens a new app window every time I click on it. I am wondering why it functions in this way?
download function in server.R:
output$down_load <- downloadHandler(
# specify the file name
filename = function() {
paste('cls_result_export', Sys.Data(),'.csv', sep='')
},
# Write the plot back
content = function(file){
write.csv(cls_output()$raw_data, file)
}
)
download function in ui.R:
downloadButton(outputId = "down_load", label = "Download the CLS Raw Data")
Try using an actionButton wired to an observe clause like this:
library(shiny)
ui <- fluidPage( actionButton("dodo", "Download" ) )
server <- function(input, output)
{
observe({
if (input$dodo>0){
fname <- paste0('cls_result_export', Sys.Date(),'.csv')
write.csv(mtcars,fname)
}
})
}
shinyApp(ui = ui, server = server)
Another possible way to try to fix this issue, is to include this line in your server.R script:
outputOptions(output, 'down_load', suspendWhenHidden=FALSE)

Shiny downloadHandler not working

I have a Shiny downloadHandler
in server.R:
output$DownloadButton <- downloadHandler(
filename = function() {
paste("test", Sys.Date(), ".csv",sep="")
},
content = function(con) {
print("in download")
print(con) # this prints C:\\Users\\me\\Local\\Temp\\RtmpI1EjY7\\file668338e4c33
Data<-ReactiveGetData()$Data #Here I get the data I want to download
print(head(Data)) #This prints out the data with no errors
write.csv(Data, con)
}
)
here is ui.r:
sidebarPanel(
downloadButton("DownloadButton", label = "Download",class = NULL), ....
So far it printed the temp file:
C:\\Users\\me\\Local\\Temp\\RtmpI1EjY7\\file668338e4c33
BUT When I go to this path manually I get an error saying "File not found"
and then when I click on the download button I do not get an error and nothing happens.
Any idea why the temp file doesn't seem to be created?
Should the temp file end in csv?
HERE IS AN EVER SIMPLER EXAMPLE which you can run if you run the server.r and ui.r files belwo. I cannot download the file below:
The "file" object does not exist below any idea why?
ui.r
library(shiny)
shinyUI(fluidPage(
sidebarPanel(
downloadButton("Download", label = "Download",class = NULL)
),
mainPanel(
tabsetPanel(
tabPanel("test",
h3("test")
)
)
)
))
server.r
library(rJava)
shinyServer(function(input, output, session) {
output$Download <- downloadHandler(
filename = function() {
paste("test.csv",sep="")
},
content = function(file) {
print("in download")
print(file) #this file does not exist ???
Data<- data.frame(name= c(1,2,3,4))
print(head(Data))
write.csv(Data, file)
}
)
})#end of server function
you can run this by:
library(rJava)
library(shiny)
runApp("C://Users//me//pathToShinyProjectFolder")
SOULTION: click "open in browser" in upper left and user CHROME OR FIREFOX as default browser.
Try opening the application in another browser. Not all browsers are created equally. This can be done by simply typing the following in another browser of your choosing.
localhost:5586
Note, that the port number may be different for you.

Resources