I am pretty new to Shiny (and R) and struggling with exporting the plot I make in Shiny to a png-file.
I looked at these two threads but could not figure it out:
Save plots made in a shiny app
Shiny downloadHandler doesn't save PNG files
I manage to create the download button in the ui and the server seems to be doing everything I want it to do, too. When I hit the download button in the preview window, a pop up window asks me to specify the file location and name but no file is saved. When I do the same in a browser window, a png file is created but it is empty.
Any insight is much appreciated!
ui.R
library(shiny)
shinyUI(fluidPage(
titlePanel("This is a scatterplot"),
sidebarLayout(
sidebarPanel(
fileInput('datafile', 'Choose CSV file',
accept=c('text/csv', 'text/comma-separated-values,text/plain')),
uiOutput("varselect1"),
uiOutput("varselect2"),
downloadButton('downloadPlot', 'Download Plot')
),
mainPanel(
h4("Here is your scatterplot"),
plotOutput("plot1")
)
))
)
server.R
library(foreign)
shinyServer(function(session,input, output) {
DataInput <- reactive({
infile <- input$datafile
if (is.null(infile)) {
return(NULL)
}
read.csv(infile$datapath)
})
output$varselect1 <- renderUI({
if (identical(DataInput(), '') || identical(DataInput(),data.frame())) return(NULL)
cols <- names(DataInput())
selectInput("var1", "Select a variable:",choices=c("---",cols[3:length(cols)]), selected=("---"))
})
output$varselect2 <- renderUI({
if (identical(DataInput(), '') || identical(DataInput(),data.frame())) return(NULL)
cols <- names(DataInput())
selectInput("var2", "Select a variable:",choices=c("---",cols[3:length(cols)]), selected=("---"))
})
plotInput <- reactive({
a <- which(names(DataInput())==input$var1)
x_lab <- as.numeric(DataInput()[,a])
b <- which(names(DataInput())==input$var2)
y_lab <- as.numeric(DataInput()[,b])
main.text <- paste("Scatterplot of the variables",colnames(DataInput())[a],"and", colnames(DataInput())[b],sep = " ", collapse = NULL)
plot(x_lab, y_lab, main=main.text, xlab=colnames(DataInput())[a], ylab=colnames(DataInput())[b], xlim=c(min(x_lab),max(x_lab)*1.05), ylim=c(min(y_lab), max(y_lab)*1.05))
observations <- DataInput()[,1]
text(x_lab, y_lab, labels=observations, pos=3)
})
output$plot1 <- renderPlot({
print(plotInput())
})
output$downloadPlot <- downloadHandler(
filename = "Shinyplot.png",
content = function(file) {
png(file)
print(plotInput())
dev.off()
})
})
A workaround for this strange scenario was discussed on the shiny-discuss google group. What you can do is simply change your reactive plotInput statement into a normal function. Not sure why downloadHandler doesn't play nice with reactive objects.
# change
plotInput <- reactive({...})
# into this
plotInput <- function(){...}
You can also remove the print statement in the downloadHandler call:
output$downloadPlot <- downloadHandler(
filename = "Shinyplot.png",
content = function(file) {
png(file)
plotInput()
dev.off()
})
Related
My app allows the user to generate some plots and save them as png.
I want the app as well to update a csv file based on some inputs made into the app by the user.
For example purposes, I created a toy app that generates a csv file in a given folder.
In both cases, the app doesn't display any error message or info, but it doesn't store any file anywhere.
library(shiny)
library(ggplot2)
library(AlphaPart)
ui <- fluidPage(
selectInput("var1","Select var1", choices = names(iris)),
selectInput("var2","Select var2", choices = names(iris)),
plotOutput("myplot"),
downloadLink("downloadPlot", label = icon("download")))
server <- function(input, output, session) {
data <- reactive(iris)
var1 <- reactive({input$var1})
var2 <- reactive({input$var2})
# Genearte plot
draw_boxplot <- function(data, var1, var2){
ggplot(data=data(), aes(x=.data[[input$var1]], y = .data[[input$var2]]))+
geom_boxplot()
}
plot1 <- reactive({
req(data(), input$var1, input$var2)
draw_boxplot(data(), var1(), var2())
})
output$myplot <- renderPlot({
plot1()
})
#Download
output$downloadPlot <- downloadHandler(
filename = function() {
return("Plot.png")
},
content = function(file) {
png(file)
print(plot1())
dev.off()
})
#Write csv
eventReactive(input$downloadPlot, {
dat <- as.data.frame(c(input$num_var_1, input$num_var_2))
write.csv(dat, "C:/dat.csv", row.names = FALSE)
})
}
shinyApp(ui, server)
library(shiny)
library(ggplot2)
library(AlphaPart)
library(spsComps)
ui <- fluidPage(
selectInput("var1","Select var1", choices = names(iris)),
selectInput("var2","Select var2", choices = names(iris)),
plotOutput("myplot"),
downloadLink("downloadPlot", label = icon("download")))
server <- function(input, output, session) {
data <- reactive(iris)
var1 <- reactive({input$var1})
var2 <- reactive({input$var2})
# Genearte plot
draw_boxplot <- function(data, var1, var2){
ggplot(data=data(), aes(x=.data[[input$var1]], y = .data[[input$var2]]))+
geom_boxplot()
}
plot1 <- reactive({
req(data(), input$var1, input$var2)
draw_boxplot(data(), var1(), var2())
})
output$myplot <- renderPlot({
plot1()
})
#Download
downloaded <- reactiveVal(0)
output$downloadPlot <- downloadHandler(
filename = function() {
return("Plot.png")
},
content = function(file) {
png(file)
print(plot1())
dev.off()
on.exit({spsComps::incRv(downloaded)})
})
#Write csv
observeEvent(downloaded(), {
dat <- as.data.frame(c(input$num_var_1, input$num_var_2))
utils::write.csv(dat, "dat.csv", row.names = FALSE)
print("File saved")
}, ignoreInit = TRUE)
}
shinyApp(ui, server)
Unfortunately, you can't observe download event. So here we do, introduce another reactiveVal which we can change inside the download event so we will know if the download button has been clicked.
spsComps::incRv is a short hand function for downloaded(isolate(downloaded()) + 1), so it increase the reactiveVal every time by one.
use on.exit on the end to make sure this happens only when the plot is successful.
Instead of using eventReactive, observeEvent should be used since you are not returning any value but just write a file.
I am making an app which can generate plots from input and it has no problem showing it on the UI but when I try to zip it by putting them into a temporary directory using ggsave() and use zip(), it doesn't work.
The example I have here generated the plot file in the temporary directory, but no zip file was generated. There is an extra directory in the temp dir which makes me think it has tried the process but somehow stopped.
Here is my code:
library(tidyverse)
library(shiny)
data(iris)
write.csv(iris,"C:/Users/User/Downloads/iris.csv") # I generated this file as input for the app to work
#UI
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File", accept = ".csv"), # input button
downloadButton("dl", label = "Download zip!") #download button
),
mainPanel(plotOutput("plot")) # showing the plot
)
)
server <- function(input, output, session) {
# read input file
up_res <- reactive({
inFile <- input$file1
if (is.null(inFile)) {
return(NULL)
}
read.csv(inFile$datapath)
})
# generate plot
output$plot <- renderPlot({
g <<- ggplot(up_res(), aes(x = Sepal.Length, y = Petal.Length)) +
geom_dotplot(binaxis='y', stackdir='center')
return(g)
})
# supposed to create zip file containing png file of plot
output$dl <- downloadHandler(
filename = function() {
paste('iris-', Sys.Date(), '.zip', sep='')
},
content = function(comp) {
owd <- setwd(tempdir())
on.exit(setwd(owd))
ggsave("iris.png",plot = g, device = "png")
zip(zipfile = comp, files = "iris.png")
if(file.exists(paste0(comp,".zip"))) {file.rename(paste0(comp, ".zip"), comp)}
#this is added as advised online zip may have read the pathway name wrongly from downloadHandler's content argument. but omitting it or not the results are the same
}
)
}
shinyApp(ui = ui, server = server)
When running this on Windows make sure zip works. See this related article and follow the procedure in section "Putting Rtools on the PATH".
The following works as intended:
library(ggplot2)
library(shiny)
data(iris)
write.csv(iris, "iris.csv")
print(getwd())
#UI
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File", accept = ".csv"), # input button
downloadButton("dl", label = "Download zip!") #download button
),
mainPanel(plotOutput("plot")) # showing the plot
)
)
server <- function(input, output, session) {
# read input file
up_res <- reactive({
inFile <- input$file1
if (is.null(inFile)) {
return(NULL)
}
read.csv(inFile$datapath)
})
# generate plot
myPlot <- reactiveVal(ggplot())
output$plot <- renderPlot({
g <- ggplot(req(up_res()), aes(x = Sepal.Length, y = Petal.Length)) +
geom_dotplot(binaxis='y', stackdir='center')
myPlot(g)
return(g)
})
# supposed to create zip file containing png file of plot
output$dl <- downloadHandler(
filename = function() {
paste('iris-', Sys.Date(), '.zip', sep='')
},
content = function(comp) {
pngPath <- normalizePath(file.path(tempdir(), "iris.png"))
ggsave(pngPath, plot = myPlot(), device = "png")
zip(zipfile = comp, files = pngPath, extras = '-j')
}
)
}
shinyApp(ui = ui, server = server)
I am relatively new to shiny and trying to add a download button to download the table in the app. I am using the following code:
ui <- fluidPage(
theme = shinytheme("cerulean"),
downloadButton("downloadData", "Download"),
titlePanel("Title"),
selectInput("state", "Select State", unique(ma_pdp$state)),
selectInput('year', "Select Year", unique(ma_pdp$year)),
tabPanel("Table", DT::DTOutput('table')))
#Server
server <- function(input, output) {
output$table <- DT::renderDT({
ma_pdp %>%
filter(year == input$year) %>%
filter(state == input$state)
output$downloadData <- downloadHandler(
filename = function() {
paste(input$ma_pdp, ".csv", sep = "")
},
content = function(file){
write.csv(datasetInput, file, row.names=FALSE)
}
)
})
}
Am I not referencing the function correctly? Or is something else wrong?
Assuming your intention is to create a CSV file containing the data currently displayed in output$table, this will - I think - give you what you want.
library(shiny)
library(tidyverse)
ui <- fluidPage(
downloadButton("downloadData", "Download"),
titlePanel("Title"),
selectInput("state", "Select State", unique(mtcars$cyl)),
selectInput('year', "Select Year", unique(mtcars$am)),
tabPanel("Table", DT::DTOutput('table'))
)
server <- function(input, output) {
filteredData <- reactive({
mtcars %>% filter(am == input$year, cyl == input$state)
})
output$table <- DT::renderDT({
filteredData()
})
output$downloadData <- downloadHandler(
filename = "test.csv",
content = function(file){
write.csv(filteredData(), file, row.names=FALSE)
}
)
}
shinyApp(ui = ui, server = server)
In addition to the observation I made in my comment, I have moved the filtering you do in the renderTable function to a separate reactive. This ensures that the data saved by the download handler is consistent with the data displayed in the table without the need for any code duplication.
Please read the readily available advice on how to ask a good question and provide a minimal reproducible example. This post may help. In the absence of test data, I've used the mtcars dataset and made appropriate changes in the code.
I am trying to calculate the standard deviation of an Excel file loaded onto my Shiny App. I don't appear to be able to do so. When I put sd(output$contents) or sd(file1) it tends to crash the application. Here is what I have so far
For reference, the data I am uploading is univariate time series
I would be very grateful if someone would be able to help me out- I think it is a beginner problem! perhaps I am just viewing the file and not using it?
EDIT: the excel file always contains a single column of numbers, but the title and header may change. Therefore I would like to reference column A in the changing excel files.
ui <- fluidPage(
setBackgroundColor("ghostwhite"),
titlePanel(h1("title", style = "color:navy")),
p("subtitle"),
headerPanel(h3("Input data here", style = "color:navy")),
# Sidebar panel
sidebarLayout(
sidebarPanel( position =c("left"),
sliderInput("SL",
"ServiceScore",
min = 1.28, max = 3.09, value = 2.28),
numericInput("LT", "LT: weeks",0, min=0, max = 30),
fileInput('file1', 'MS, choose xlsx file',
accept = c(".xlsx")
),
br(),
actionButton("action_Calc", label = "Refresh & Calculate"), ),
# Main panel
mainPanel(h3( textOutput("SL"),
textOutput("LT"),
textOutput("SS"),
tableOutput('contents')
))))
server <- function(input, output) {
output$SL <- renderText({
paste("Your score", input$SL)})
output$LT <- renderText ({
paste( "Your LT is", input$LT)})
output$contents <- renderTable({
req(input$file1)
inFile <- input$file1
read_excel(inFile$datapath, 1)
})
values<- reactiveValues()
observe({
input$action_Calc
values$int<- isolate({
input$SL*sqrt(input$LT/4)*sd(**HERE IS WHERE I NEED THE SD of the EXCEL FILE**)
})})
output$SS <- renderText({paste("calculation is", values$int)})
}
shinyApp(ui, server)
Read the Excel file in a reactive conductor:
Data <- reactive({
req(input$file1)
inFile <- input$file1
read_excel(inFile$datapath, 1)
})
output$contents <- renderTable({
Data()
})
Now if you want the standard deviation of the first column:
values<- reactiveValues()
observe({
input$action_Calc
values$int <- isolate({
input$SL*sqrt(input$LT/4)*sd(Data()[[1]])
})
})
output$SS <- renderText({paste("calculation is", values$int)})
I am trying to create a Shiny app that uploads a dynamic number of CSV files and display them through renderTable. I used the answer from this link for the dynamic fileInput. I added an eventReactive to read the CSV files and a renderUI for the dynamic number of tableOutputs. I don't get any errors when I run the code, so I don't know where the reactivity breaks.
Server:
shinyServer(function(input, output, session) {
output$fileInputs=renderUI({
n<-input$nfiles
html_ui = " "
for (i in 1:n){
html_ui <- paste0(html_ui, fileInput(inputId= paste0("fileupload",i), label=paste0("fileupload",i),accept = c("text/csv", "text/comma-separated-values,text/plain",".csv")) )
}
HTML(html_ui)
})
Reading <-eventReactive(input$Read, {
lst<-list()
n<-input$nfiles
for (i in 1:n){
file<-paste0("fileupload",i)
inFile<-input$file
if (is.null(inFile))
return(NULL)
lst[[i]] <- read.csv(inFile$datapath)
}
lst
})
output$fileoutputs <- renderUI({
n<-input$nfiles
for (i in 1:n){
output[[paste0("table",i)]] <- renderTable( Reading()$lst[[i]] )
tableOutput(paste0("table",i))
}
})
})
UI:
shinyUI(pageWithSidebar(
headerPanel('Variable files'),
sidebarPanel(
numericInput("nfiles", "number of files", value = 2, min = 1, step = 1),
uiOutput("fileInputs"),
actionButton("Read", "read")
),
mainPanel(
uiOutput("fileoutputs")
)
))