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.
Related
I would like to have a shiny app that, when run for the first time, displays a dataframe defined as a template, and then the user can upload a new one (in csv only) that replaces the current one. Therefore, in case the user imports a file of the wrong type, it produces a message instead. Here is my code, which results in an error, and I don't know why it doesn't work
library(shiny)
library(DT)
library(dplyr)
library(shiny)
ui <- fluidPage(
fileInput("upload", NULL, accept = c(".csv")),
tableOutput("head")
)
server <- function(input, output, session) {
rv <- reactiveValues(
dataframe = NULL
)
observe({
if(is.null(rv$dataframe)){
dataFrameFile <- reactive({
df <- data.frame(
x = seq(1:12),
y = rnorm(12))
rv$dataframe <- datatable(df)
return(rv$dataframe)
})
} else {
dataFrameFile <- reactive({
req(input$upload)
ext <- tools::file_ext(input$upload$name)
rv$dataframe <- switch(ext,
csv = read.csv(input$upload$datapath),
validate(" Please upload a .csv file")
)
})
}
})
output$head <- renderDT({
datatable(dataFrameFile())
})
}
shinyApp(ui, server)
A few corrections/simplifications:
Used DTOutput instead of tableOutput to correspond to renderDT
directly initialized rv
put the validate in the renderDT
library(shiny)
library(DT)
library(dplyr)
library(shiny)
ui <- fluidPage(
fileInput("upload", NULL, accept = c(".csv")),
DTOutput("head")
)
server <- function(input, output, session) {
rv <- reactiveValues(
dataframe = data.frame(
x = seq(1:12),
y = rnorm(12))
)
observe({
req(input$upload)
ext <- tools::file_ext(input$upload$name)
rv$dataframe <- switch(ext,
csv = read.csv(input$upload$datapath),
NULL)
})
output$head <- renderDT({
validate(need(!is.null(rv$dataframe)," Please upload a .csv file"))
rv$dataframe
})
}
shinyApp(ui, server)
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 want to create a modularized Shiny app where one module, dataUpload, is used to import a CSV and another module, chart, is used to
Create dynamic x and y dropdowns based on the column names within the CSV THIS WORKS
Create a plot based on the selected input$xaxis, input$yaxis This produces the error invalid type/length (symbol/0) in vector allocation
I think the issue is with my reactive ggplot in chart.R and I'd love any help - I added all the info here but I also have a github repo if that's easier I think this could be a really great demo into the world of interacting modules so I'd really appreciate any help!!
App.R
library(shiny)
library(shinyjs)
library(tidyverse)
source("global.R")
ui <-
tagList(
navbarPage(
"TWO MODULES",
tabPanel(
title = "Data",
dataUploadUI("datafile", "Import CSV")
),
tabPanel(
title = "Charts",
chartUI("my_chart")
)
)
)
server <- function(input, output, session) {
datafile <- callModule(dataUpload, "datafile", stringsAsFactors = FALSE)
output$table <- renderTable({ datafile() })
# PASS datafile WITHOUT () INTO THE MODULE
my_chart <- callModule(chart, "my_chart", datafile = datafile)
output$plot <- renderPlot({ my_chart() })
}
shinyApp(ui, server)
dataUpload.R
dataUpload <- function(input, output, session, stringsAsFactors) {
# The selected file, if any
userFile <- reactive({
# If no file is selected, don't do anything
# input$file == ns("file")
validate(need(input$file, message = FALSE))
input$file
})
# The user's data, parsed into a data frame
dataframe <- reactive({
read.csv(userFile()$datapath,
stringsAsFactors = stringsAsFactors)
})
# We can run observers in here if we want to
observe({
msg <- sprintf("File %s was uploaded", userFile()$name)
cat(msg, "\n")
})
# Return the reactive that yields the data frame
return(dataframe)
}
dataUploadUI.R
# The first argument is the id -- the namespace for the module
dataUploadUI <- function(id, label = "CSV file") {
# Create a namespace function using the provided id
#ALL UI FUNCTION BODIES MUST BEGIN WITH THIS
ns <- NS(id)
# Rather than fluidPage use a taglist
# If you're just returning a div you can skip the taglist
tagList(
sidebarPanel(
fileInput(ns("file"), label)),
mainPanel(tableOutput("table"))
)
}
chart.R
I believe this is the file that needs some minor changing in order to have the plot properly render?
chart <- function(input, output, session, datafile = reactive(NULL)) {
# SINCE DATAFILE IS A REACTIVE WE ADD THE PRERENTHESIS HERE
# WHERE/HOW CAN I ACCESS input$xaxis?
# Do I need to use ns? Can you do that in the server side of a module?
output$XAXIS <- renderUI(selectInput("xaxis", "X Axis", choices = colnames(datafile())))
output$YAXIS <- renderUI(selectInput("yaxis", "Y Axis", choices = colnames(datafile())))
# NOT WORKING
# Use the selectInput x and y to plot
p <- reactive({
req(datafile)
# WORKS: ggplot(datafile(), aes(x = Sepal_Length, y = Sepal_Width))
# DOES NOT WORK:
ggplot(datafile(), aes_(x = as.name(input$xaxis), y = as.name(input$yaxis))) +
geom_point()
})
return(p)
}
chartUI.R
chartUI <- function(id, label = "Create Chart") {
ns <- NS(id)
tagList(
sidebarPanel(
uiOutput(ns("XAXIS")),
uiOutput(ns("YAXIS"))
),
mainPanel(plotOutput("plot"))
)
}
We need to manually specify the name space within a renderUI function using session$ns
chart <- function(input, output, session, datafile = reactive(NULL)) {
# SINCE DATAFILE IS A REACTIVE WE ADD THE PRERENTHESIS HERE
# WHERE/HOW CAN I ACCESS input$xaxis?
# Do I need to use ns? Can you do that in the server side of a module?
output$XAXIS <- renderUI(selectInput(session$ns("xaxis"), "X Axis", choices = colnames(datafile())))
output$YAXIS <- renderUI(selectInput(session$ns("yaxis"), "Y Axis", choices = colnames(datafile())))
# NOT WORKING
# Use the selectInput x and y to plot
p <- reactive({
req(datafile)
# WORKS: ggplot(datafile(), aes(x = Sepal_Length, y = Sepal_Width))
# DOES NOT WORK:
ggplot(datafile(), aes_(x = as.name(input$xaxis), y = as.name(input$yaxis))) +
geom_point()
})
return(p)
}
I'm trying to write a little app that will allow the user to make a scatterplot, select a subset of points on the plot, then output a table in .csv format with just those selected points. I figured out how to get the page up and running and how to select points using brushedPoints. The table with selected points appears but when I press the Download button, the error "Reading objects from shinyoutput object not allowed." appears. Am I unable to download the table that I can visually see on the screen as a .csv? If so, is there a workaround?
I've recreated the problem using the iris dataset below. Any help figuring out why I cannot download the table of displayed rows would be greatly appreciated.
data(iris)
ui <- basicPage(
plotOutput("plot1", brush = "plot_brush"),
verbatimTextOutput("info"),mainPanel(downloadButton('downloadData', 'Download'))
)
server <- function(input, output) {
output$plot1 <- renderPlot({
ggplot(iris,aes(x=Sepal.Width,y=Sepal.Length)) +
geom_point(aes(color=factor(Species))) +
theme_bw()
})
output$info <- renderPrint({
brushedPoints(iris, input$plot_brush, xvar = "Sepal.Width", yvar = "Sepal.Length")
})
output$downloadData <- downloadHandler(
filename = function() {
paste('SelectedRows', '.csv', sep='') },
content = function(file) {
write.csv(output$info, file)
}
)
}
shinyApp(ui, server)
The issue is that the output object is generating all of the web display stuff as well. Instead, you need to pull the data separately for the download. You could do it with a second call to brushedPoints in the download code. Better, however, is to use a reactive() function to do it just once, then call that everywhere that you need it. Here is how I would modify your code to make that work:
data(iris)
ui <- basicPage(
plotOutput("plot1", brush = "plot_brush"),
verbatimTextOutput("info"),mainPanel(downloadButton('downloadData', 'Download'))
)
server <- function(input, output) {
output$plot1 <- renderPlot({
ggplot(iris,aes(x=Sepal.Width,y=Sepal.Length)) + geom_point(aes(color=factor(Species))) + theme_bw()
})
selectedData <- reactive({
brushedPoints(iris, input$plot_brush)
})
output$info <- renderPrint({
selectedData()
})
output$downloadData <- downloadHandler(
filename = function() {
paste('SelectedRows', '.csv', sep='') },
content = function(file) {
write.csv(selectedData(), file)
}
)
}
shinyApp(ui, server)
(Note, with ggplot2, you do not need to explicitly set xvar and yvar in brushedPoints. So, I removed it here to increase the flexibility of the code.)
I am not aware of any "lasso" style free drawing ability in shiny (though, give it a week -- they are constantly adding fun tools). However, you can mimic the behavior by allowing user to select multiple regions and/or to click on individual points. The server logic gets a lot messier, as you need to store the results in a reactiveValues object to be able to use it repeatedly. I have done something similar to allow me to select points on one plot and highlight/remove them on other plots. That is more complicated than what you need here, but the below should work. You may want to add other buttons/logic (e.g., to "reset" the selections), but I believe that this should work. I did add a display of the selection to the plot to allow you to keep track of what has been selected.
data(iris)
ui <- basicPage(
plotOutput("plot1", brush = "plot_brush", click = "plot_click")
, actionButton("toggle", "Toggle Seletion")
, verbatimTextOutput("info")
, mainPanel(downloadButton('downloadData', 'Download'))
)
server <- function(input, output) {
output$plot1 <- renderPlot({
ggplot(withSelected()
, aes(x=Sepal.Width
, y=Sepal.Length
, color=factor(Species)
, shape = Selected)) +
geom_point() +
scale_shape_manual(
values = c("FALSE" = 19
, "TRUE" = 4)
, labels = c("No", "Yes")
, name = "Is Selected?"
) +
theme_bw()
})
# Make a reactive value -- you can set these within other functions
vals <- reactiveValues(
isClicked = rep(FALSE, nrow(iris))
)
# Add a column to the data to ease plotting
# This is really only necessary if you want to show the selected points on the plot
withSelected <- reactive({
data.frame(iris
, Selected = vals$isClicked)
})
# Watch for clicks
observeEvent(input$plot_click, {
res <- nearPoints(withSelected()
, input$plot_click
, allRows = TRUE)
vals$isClicked <-
xor(vals$isClicked
, res$selected_)
})
# Watch for toggle button clicks
observeEvent(input$toggle, {
res <- brushedPoints(withSelected()
, input$plot_brush
, allRows = TRUE)
vals$isClicked <-
xor(vals$isClicked
, res$selected_)
})
# pull the data selection here
selectedData <- reactive({
iris[vals$isClicked, ]
})
output$info <- renderPrint({
selectedData()
})
output$downloadData <- downloadHandler(
filename = function() {
paste('SelectedRows', '.csv', sep='') },
content = function(file) {
write.csv(selectedData(), file)
}
)
}
shinyApp(ui, server)
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()
})