Shiny downloadHandler doesn't save PNG files - r

I've got my download function to do everything right, when the save as screen comes up, the file name I specified appears. When I click on save the window closes, but no file gets saved...
The same plot works fine in the app, the only problem is I cant seem to save it to a PNG file.
I run the shine app on my laptop and use RStudio.
Here is some extracts of my code.
ui.R
downloadButton('downloadSMemPlot', 'Download Graph')
server.R
'#draw membersip plot
s.MemPlotInput <- reactive({
'#some code to get data
s.MemPlot <- ggplot() +
geom_density(aes(x=Age, fill = Years), data=s.ben, alpha = 0.5) +
ggtitle("Density of beneficiary ages") +
theme_igray() +
theme(plot.title = element_text(lineheight=.8, face="bold")) +
xlab("Age in full years") + ylab("Density")+
scale_fill_hue()
})
output$s.memplot <- renderPlot({
print(s.MemPlotInput())
})
'#download membership plot
output$downloadSMemPlot <- downloadHandler(
filename = "MembershipPlot.png",
content = function(file) {
png(file, type='cairo')
print(s.MemPlotInput())
dev.off()
},
contentType = 'application/png'
)

You want
contentType = 'image/png'
not
contentType = 'application/png'
Although I don't think that's the problem. Are you running it within RStudio's preview pane or in an external browser? I had the same problem with downloading when using the preview pane but it worked fine on my browser.

Related

How to display a QR-code svg image in a shiny app

I've this piece of code which is generating me an QR code, from an input field, and than saving it as .svg.
observeEvent(input$safe_qr,{
qr <- qr_code(input$qr_gen)
generate_svg(qr, "QR_Code.svg",
size = 100,
foreground = "black",
background = "white",
show = interactive())
renderPlot(qr)
})
Now I don't really need to save it, I want to see it next to my input field on the page. I have no idea how put an image inside the page.
One thing I was trying was Plot(qr) then a new window opened in edge and showed me the saved QR code. But yeah that's not what I want.
Here's a working shiny app to allow arbitrary QR generate (of text), showing in both SVG and PNG.
library(shiny)
ui <- fluidPage(sidebarLayout(
sidebarPanel(
textInput("text", label = NULL, placeholder = "(some phrase)"),
imageOutput("svgplot")
),
mainPanel(
plotOutput("plot")
)
))
server <- function(input, output, session) {
QR <- eventReactive(input$text, {
qrcode::qr_code(input$text)
})
output$svgplot <- renderImage({
txt <- isolate(input$text)
tf <- tempfile(fileext = ".svg")
qrcode::generate_svg(QR(), tf, size = 100, foreground = "black", background = "white", show = FALSE)
list(
src = normalizePath(tf),
contentType = "image/svg+xml",
width = 100, height = 100,
alt = paste("My QR code:", sQuote(txt, FALSE))
)
}, deleteFile = TRUE)
output$plot <- renderPlot({ plot(QR()) })
}
shinyApp(ui, server)
You don't need to show both, I thought I'd compare/contrast how to show them since they require different steps.
Key takeaways:
renderImage allows us to show an arbitrary image that the browser should support. The expression in this call must be a list with the attributes for the HTML img attribute, so here my list(...) is creating
<img src="path/to/tempfile.svg" contentType="image/svg+xml" width=100 height=100 alt="...">
I'm doing a two-step here: create the QR object as reactive data, and then anything that will use that will depend on QR() (my QR object). This way there will be fewer reactive-chain calls. This may not be strictly necessary if all you're doing is showing a single QR code, over to you.
shiny::renderImage requires the deleteFile= argument; if all you want to is show it, this is fine; if the user wants to right-click on the displayed SVG file and download it locally, it's still fine. In fact, since the "link" text is data:image/svg+xml;base64,PD94bWwgdmVy... and is a fairly long string (39K chars in one example), even if the temp file is deleted, this link remains unchanged and working.

from ggplotly, open all new hyperlinks in the same new window/tab

I am using the following example code where click on the point opens a link:
data(mtcars)
mtcars$urlD <- paste0("http://google.com/search?q=", gsub(" ", "+", rownames(mtcars)))
p <- ggplot(data=mtcars, aes(x=wt, y=mpg, color=factor(carb), customdata=urlD)) + geom_point()
pp <- ggplotly(p)
ppp <- htmlwidgets::onRender(pp, "
function(el, x) {
el.on('plotly_click', function(d) {
var url = d.points[0].customdata;
window.open(url);
});
}
")
It works fine but every new click opens new window/tab. Is there any way to make it use the same window? (I mean not the window with the plot but the window where the first link was opened) In usual javascript, I would use the name parameter of window.open(), like this: window.open(url, 'MyTargetWindow'); - but it doesn't help here. Any workarounds?
You must be using Rstudio. window.open(url, 'MyTargetWindow') works in browser but not in Rstudio. If you click the "show in new window" button and open the plot in your browser, it works. The reason is window name is recognized in the same browser, but when you open a new browser (from Rstudio Viewer to the actual browser in this case), the name info is not passed. I am not aware of a solution to solve this cross-browser issue.

All but one downloadHandler with shiny.fluent not working, just opens new tab running new instance of application instead of download

We have a very complex app which contains 20 downloadHandler instances that serve either .csvs, .rmds, or .zip files. Until this past week they all worked, we've been doing a lot of work on other pieces, haven't tested the downloadHandlers specifically this week and at some point all of them stopped working except for one single .csv exporting one in the middle of the bunch. That one still correctly starts a download, every other one just opens a new tab in the browser with a new instance of the app, no download.
We're running this on the local machine in browser, not in the RStudio pane.
The full app is very complex so I don't have a non-working reprex for the moment, though here: https://github.com/samdupre/OSDSReprex is a reprex for an earlier very slimmed down version which has this broken functionality working throughout (bottom of each page). I've also cut out working and non-working examples of the functionality from the full app and included it below.
Because shiny.fluent does not work with downloadHandler well, we're using the recommended workaround (https://github.com/Appsilon/shiny.fluent/issues/39) where the server section of our app.R file contains downloadHandler which is triggered programmatically using shinyjs::click() based on a button present in each topic .R file. Here is the single working one and one of the non-working ones.
IN APP.R
### THIS ONE DOES NOT WORK
observeEvent(input$page1_dd,
click("page1_ddb"))
page1_data <- imported_data %>% filter(Metric == "Population")
output$page1_ddb <- downloadHandler(
filename = function() {
'demographic-social-data.csv'
},
content = function(file) {
write.csv(page1_data, file)
}
)
### THIS ONE WORKS
observeEvent(input$page6_dd,
click("page6_ddb"))
page6_data <- imported_data %>% filter(Metric %in% c("CrudeDeathRate","Deaths"))
output$page6_ddb <- downloadHandler(
filename = function() {
'mortality-data.csv'
},
content = function(file) {
write.csv(page6_data, file)
}
)
and the individual page files contain:
IN TOPIC_PAGE_1.R (DOES NOT WORK)
makeCard(" ",
div(
Text(i18n$t("Export demographic and social data (in .csv format)")),
useShinyjs(),
Stack(tokens = list(childrenGap = 10), horizontal = TRUE,
DefaultButton.shinyInput("page1_dd", text = i18n$t("Download Demographic and Social Data"), iconProps = list(iconName = "Download Demographic and Social Data")),
div(style = "visibility: hidden;", downloadButton("page1_ddb", label = "")))
),
size = 11
)
IN TOPIC_PAGE_6.R (WORKS)
makeCard(" ",
div(
Text(i18n$t("Export mortality data (in .csv format)")),
useShinyjs(),
Stack(tokens = list(childrenGap = 10), horizontal = TRUE,
DefaultButton.shinyInput("page6_dd", text = i18n$t("Download Mortality Data"), iconProps = list(iconName = "Download Mortality Data")),
div(style = "visibility: hidden;", downloadButton("page6_ddb", label = "")))
),
size = 11
)
Any advice on why all except one random one out of the bunch has now stopped generating a download and instead just opens a new tab instance of the app would be greatly appreciated.
Thanks in advance!

Save a ggplot generated scatterplot in Shiny as a PDF file without saving extra files

I had trouble to generate a PDF file from a scatterplot created trough ggplot in a ShinyApp. I was succesful with a similar approach as the one from the answer from user juba to this stackoverflow question, but then the nearPoints() function I was using didn't work and gave an error message saying something about not being able to find the coordinfo. Then I used the ggsave option that Yihui Xie recommended, but with this strategy I'm getting files saved in the folder where my ShinyApp resides. I'm worried that if I try to use this in my ShinyApp hosted in the shinyapps.io site, there would be problems when trying to save these transient and temporal files. I also tried removing the file after the download is done but, anytime the plot is shown the file is created, so the file is created again after the copied file is downloaded. Here is only a sample (some important lines) of the code I used to allow for the download of the plot as a PDF file:
#### User Interface ----
# Show scatterplot with clicking capability
plotOutput(outputId = "scatterplot", click = "plot_click")
# Show data table where data points clicked will be shown
dataTableOutput(outputId = "datatable")
# Download button
downloadButton('dlScatPlot', 'Download plot as PDF')
# Server ----
# Wrap the creation of the scatterplot in a function so the plot can be
# downloaded as PDF
makeScatPlot <- function() {
## some code to generate a ggplot plot
}
# Create the scatterplot object the plotOutput function is expecting
output$scatterplot <- renderPlot({
# The file saved as ggsave originally will be first saved in the server, and
# then in the client side if the Download Button is used
filename <- paste('scatterPlot_', Sys.Date(), '.pdf', sep='')
ggsave(filename, makeScatPlot(), width = 11, height = 4, dpi = 300, units = "in")
makeScatPlot()
})
# Create data table showing points that have been clicked
output$datatable <- DT::renderDataTable({
rows <- nearPoints(df1, input$plot_click) %>%
select(sample_ID, compound, DOI)
DT::datatable(rows, rownames = FALSE)
})
output$dlScatPlot <- downloadHandler(
filename = function() {
paste('scatPlot_', Sys.Date(), '.pdf', sep='')
},
content = function(file) {
file.copy(paste('scatPlot_', Sys.Date(), '.pdf', sep=''), file, overwrite = TRUE)
# To avoid the accumulation of PDFs in the server
file.remove(paste('scatPlot_', Sys.Date(), '.pdf', sep=''))
}
)
I guess it can cause trouble if I upload a ShinyApp script to shinyapps.io that creates one PDF file each time the plot is rendered, right?
Instead of saving files to a specific path, you can save them as temporary file using tempfile(fileext = ".pdf"). Those files will be automatically removed once the session is over. So no need to remove them manually.
I finally came out with an obvious answer. I wasn't doing the straightforward thing that was call the ggsave in the downloadHandler call because I was using the Yihui answer directly. So, finally I just don't create the file inside the renderPlot() function, but in the downloadHandler where it rightfully should be.
# Create the scatterplot object the plotOutput function is expecting
output$scatterplot <- renderPlot({
makeScatPlot()
})
# Create the button to download the scatterplot as PDF
output$dlScatPlot <- downloadHandler(
filename = function() {
paste('scatterPlot_', Sys.Date(), '.pdf', sep='')
},
content = function(file) {
ggsave(file, makeScatPlot(), width = 11, height = 4, dpi = 300, units = "in")
}
)
Using the above code, everything (including the nearPoints call) works now :)

Shiny interactive document download button overwrites original R markdown

So I'm trying to write an html R markdown document with interactive shiny bits that allow the user to edit a graph and then download the results to a pdf. However, there is something catastrophically wrong with the way that I'm trying to do this because as soon as the html starts, it overwrites the original markdown file with the contents of the pdf - turning it into complete gibberish right in the editor.
I doubt that I've found a completely new way to fail at R but I haven't been able to find where anybody else has had this issue. Additionally, I've looked over the shiny reference material and I'm just going in circles at this point, so any help would be greatly appreciated.
I'm using Rstudio 1.0.44, rmarkdown 1.2 and shiny 0.14.2. A small (not)working example:
---
title: "Minimum Failing Example"
author: "wittyalias"
date: "December 5, 2016"
output: html_document
runtime: shiny
---
```{r echo = FALSE}
library(ggplot2)
today <- Sys.Date()
inputPanel(downloadButton("dnld", label = "Download pdf"))
renderPlot({
# Example code from http://www.cookbook-r.com/Graphs/Multiple_graphs_on_one_page_(ggplot2)/
p1 <<- ggplot(ChickWeight, aes(x=Time, y=weight, colour=Diet, group=Chick)) +
geom_line() +
ggtitle("Growth curve for individual chicks")
p1
})
reactive({
fname <- paste0("Chick Weight - ", today, ".pdf")
output$dnld <- downloadHandler(filename = fname,
content = makethepdf(file))
makethepdf <- function(fname) {
pdf(fname,
width = 14,
height = 8.5)
p1
dev.off()
}
})
```
EDIT: To be clear: I want the user to be able to download multiple pages of graphs, some of which will have different formatting. The user won't be downloading just a pdf version of the markdown document.
This happens because reasons I weren't able to identify makethepdf runs with the file = [name of the file]. Insert a print(fname) to see. The download handler isn't supposed to be inside an observer though. You need to have it outside on its own. I also failed to make pdf() dev.off() combination work for some reason so here's a working version below.
output$dnld = downloadHandler(filename = paste0("Chick Weight - ", today, ".pdf"),
content = function(file){
ggsave(file, plot = p1, width = 14, height = 8.5)
})
Use tempfile() and tempdir() to create a temporary file:
output$downloadReport = downloadHandler(
filename = function() {
normalizePath(tempfile("report_", fileext = ".docx"), winslash = "/")
},
content = function(file) {
out = rmarkdown::render("./report.Rmd",
output_file = file,
output_dir = tempdir(),
output_format = "pdf_document",
intermediates_dir = tempdir(),
envir = new.env(),
params = list( fontSize = 10)
)
})
I usually use a separate .Rmd template for my downloaded reports as the layout and text are usually similar but not identical to what works in an app.
I also find using parameters is a convenient way to pass input settings from my app to my report. See this RStudio post for details
Alright, so there are a number of problems with my code, but using some of the suggestions in the other answers I've been able to work it out.
The primary problem with this little document is that content in the downloadHandler is a function, but in my code I set content equal to the result of a function call. It looks like when the shiny app is first run it compiles content, thinking that it is a function, but actually ends up calling the function. It sends file as an arguement, which doesn't seem to exist except as a base function. Calling makethepdf with just file throws an error when I use it in the console, but for whatever reason in this app it just goes with the call, apparently with file = [name of the .Rmd] (just as OganM said).
To fix, change this:
output$dnld <- downloadHandler(filename = fname,
content = makethepdf(file))
to
output$dnld <- downloadHandler(filename = fname,
content = makethepdf)
To be clear: this code does not overwrite the .Rmd file if content calls makethepdf with any argument other than file. For instance, content = makethepdf(fnm)) causes the download button to display an object not found error and content = makethepdf(fname)) causes the download button to throw an attempt to apply non-function error when pressed.

Resources