My shiny app generates a number of useful graphs. I would like to allow the user to download the graphs in various formats.
I have done this before for a single graph using How to save plots that are made in a shiny app as a guide. However, I am ending up creating more repeated code for each additional plot. I am not a programmer, but it really seems like I should be able to write one function to do this since I am just passing parameters to downloadHandler and ggsave, but I can't figure it out.
The MRE below represents a page with, say, ten different graphs. Is there a way to write a single function that receives the plot ID from a button (like a tag or something?) and the format from the selectInput to pass those parameters to downloadHandler and ggsave to save each of those graphs in the selected format? The function at the bottom shows my thinking, but I don't know where to go from here or if that is even the right direction.
Thanks!
library(shiny)
library(ggplot2)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show plots and download buttons
mainPanel(
plotOutput("distPlot"),
fluidRow(
column(3,
downloadButton("dl_plot1")
),
column(3,
selectInput("plot1_format",label = "Format",choices = c("SVG","PDF","JPEG","PNG"),width = "75px")
)
),
plotOutput("scat_plot"),
column(3,
downloadButton("dl_plot2")
),
column(3,
selectInput("plot2_format",label = "Format",choices = c("SVG","PDF","JPEG","PNG"),width = "75px")
)
)
)
)
# Define server logic required to draw a histogram and scatterplot
server <- function(input, output) {
output$distPlot <- renderPlot({
x <- faithful$waiting
binwidth<-(max(x)-min(x))/input$bins
p<-ggplot(faithful,aes(waiting))+
geom_histogram(binwidth = binwidth)
p
})
output$scat_plot<-renderPlot({
p<-ggplot(faithful,aes(x=waiting,y=eruptions))+
geom_point()
p
})
downloadPlot <- function(plot_name,file_name,file_format){#concept code
downloadHandler(
filename=function() { paste0(file_name,".",file_format)},
content=function(file){
ggsave(file,plot=plot_name,device=file_format)
}
)
}
}
# Run the application
shinyApp(ui = ui, server = server)
To achieve your desired result without duplicating code you could (or have to) use a Shiny module. Basically a module is a pair of an UI function and a server function. For more on modules I would suggest to have a look at e.g. Mastering shiny, ch. 19.
In the code below I use a module to take care of the download part. The job of downloadButtonUI and downloadSelectUI is to add a download button and a selectInput for the file format. The downloadServer does the hard work and saves the plot in the desired format.
Note: Besides the download module I moved the code for the plots to reactives so that the plots could be passed to the downloadHandler or the download module.
EDIT: Added a fix. We have to pass the reactive (e.g. dist_plot without parentheses) to the download server and use plot() inside the downloadServer instead to export the updated plots.
library(shiny)
library(ggplot2)
# Download Module
downloaButtondUI <- function(id) {
downloadButton(NS(id, "dl_plot"))
}
downloadSelectUI <- function(id) {
selectInput(NS(id, "format"), label = "Format", choices = c("SVG", "PDF", "JPEG", "PNG"), width = "75px")
}
downloadServer <- function(id, plot) {
moduleServer(id, function(input, output, session) {
output$dl_plot <- downloadHandler(
filename = function() {
file_format <- tolower(input$format)
paste0(id, ".", file_format)
},
content = function(file) {
ggsave(file, plot = plot())
}
)
})
}
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30
)
),
# Show plots and download buttons
mainPanel(
plotOutput("distPlot"),
fluidRow(
column(3, downloaButtondUI("distPlot")),
column(3, downloadSelectUI("distPlot"))
),
plotOutput("scat_plot"),
fluidRow(
column(3, downloaButtondUI("scatPlot")),
column(3, downloadSelectUI("scatPlot"))
),
)
)
)
server <- function(input, output) {
dist_plot <- reactive({
p <- ggplot(faithful, aes(waiting)) +
geom_histogram(bins = input$bins)
p
})
scat_plot <- reactive({
p <- ggplot(faithful, aes(x = waiting, y = eruptions)) +
geom_point()
p
})
output$distPlot <- renderPlot({
dist_plot()
})
output$scat_plot <- renderPlot({
scat_plot()
})
downloadServer("distPlot", dist_plot)
downloadServer("scatPlot", scat_plot)
}
shinyApp(ui = ui, server = server)
#>
#> Listening on http://127.0.0.1:4092
Related
Colleagues,
I'm creating a Shiny app that can generate a data set with user-defined properties. The intended data-generation function can take some time, so I've substituted a very simple one.
My problem is that the app seems to just hang, or nothing happens at all, when I hit the GO button.
DEBUG in Rstudio shows nothing, and reactlog also gives no information.
Similar questions on this stackoverflow forum are more than 8 years old, and suggestions don't seem to work either.
I'm sure the solution is head-slapping simple but, right now, I'm lost.
Any suggestions from those more knowledgeable than this Shiny newbie?
## generate data set with user-defined parameters
## load libraries
library(shiny)
library(ggplot2)
library(DT)
##
options(shiny.reactlog = TRUE)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("Synthesise data"),
# Sidebar
sidebarLayout(
sidebarPanel(
## Sample size
numericInput("sample_n", "Sample size", 30,
min = 10, max = 300, step = 1
),
h4("Desired scale moments"),
numericInput("target_mean", "Target mean", 4),
numericInput("target_sd", "Target st dev", 1),
hr(style = "border-top: 1px solid #000000;"),
actionButton("goButton", "Go!"),
actionButton("goChart", "show chart"),
),
mainPanel(
h4("generated data"),
# table of generated data
DT::dataTableOutput("mytable"),
# Show a plot of the generated distribution
plotOutput("resultPlot")
)
)
)
# Define server logic
server <- function(input, output) {
mytable <- reactive(input$goButton, {
## substituting data-gen function that can take some time
mydata <- rnorm(sample_n, target_mean, target_sd) |>
data.frame()
colnames(mydata) <- "scale"
# saveRDS(mydata, file = "generatedData.RDS")
output$mytable <- DT::renderDataTable(DT::datatable({
mydata
}))
})
myplot <- eventReactive(input$goChart, {
output$resultPlot <- renderPlot({
ggplot(mydata, aes(x = scale)) +
geom_density()
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
Few code errors here :
forgot input$ when using sample_n, target_mean and target_sd in server
put some output definition inside eventReactive or reactive is a terrible habit
reactive is not used like you did. EventReactive is what you needed here.
Here is a corrected version of you code
library(shiny)
library(ggplot2)
library(DT)
ui <- fluidPage(
titlePanel("Synthesise data"),
sidebarLayout(
sidebarPanel(
numericInput("sample_n", "Sample size", 30,
min = 10, max = 300, step = 1
),
h4("Desired scale moments"),
numericInput("target_mean", "Target mean", 4),
numericInput("target_sd", "Target st dev", 1),
hr(style = "border-top: 1px solid #000000;"),
actionButton("goButton", "Go!"),
actionButton("goChart", "show chart"),
),
mainPanel(
h4("generated data"),
DT::dataTableOutput("mytable"),
plotOutput("resultPlot")
)
)
)
server <- function(input, output) {
mydata <- eventReactive(input$goButton, {
mydata <- data.frame(scale = rnorm(input$sample_n, input$target_mean, input$target_sd))
return(mydata)
})
output$mytable <- DT::renderDataTable(DT::datatable(
mydata()
))
output$resultPlot <- renderPlot({
input$goChart
isolate(ggplot(mydata(), aes(x = scale)) +
geom_density())
})
}
shinyApp(ui = ui, server = server)
I have created a shiny app should take input from three sliders and:
Plots a distribution in ggplot
Show a summary table of values underneath the plot in #1 above
If I just want to plot the histogram (and I comment out the table data), I can get the code to work correctly. However, when I add the table, the plot disappears even though the plot header is still there. I have tried moving the commas a braces around to see if it's a simple syntax error but haven't had any luck.
library(shiny)
# Define UI for application that draws a histogram
shinyUI(fluidPage(
# Application title
titlePanel("Test Shiny Layout"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
h4("Input Data"),
sliderInput("bins", "Bin Width", min = 4,max = 12, value = 8),
),
# Show a plot of the generated distribution
mainPanel(
h4("Histogram"),
plotOutput("distPlot", width = "600", height = "600"),
h4("Table of Values"),
tableOutput("table")
)
)
))
Server
library(shiny)
library(ggplot2)
# Define server logic required to draw a histogram
shinyServer(function(input, output) {
output$distPlot <- renderPlot({
bins <- input$bins
df1 <- (iris$Sepal.Length)
x <- mean(df1)
y <- sd(df1)
ggplot(data = iris) +
geom_histogram(mapping = aes(x = Sepal.Length), color = "blue", binwidth = "bins")
# Create an empty dataframe and then plug in the mean and standard deviation
results <- data.frame("0", "0")
results[1,1] = x
results[1,2] = y
colnames(results) <- c("Mean", "SD")
rownames(results) <- c("Sepal Length")
output$table <- renderTable(results)
})
})
Your renderTable() is inside your renderPlot() call. So renderPlot isn't returning anything.
You were right: it was a simple syntax error. But you also had several other issues in your code. At least a dozen. Three in binwidth = "bins" alone.
Here's a working version. I suspect you will still want to make tweaks, but at least you have both a histogram and a summary table that both look reasonably sensible.
library(shiny)
library(ggplot2)
data(iris)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$distPlot <- renderPlot({
ggplot(data = iris) +
geom_histogram(aes(x = Sepal.Length), color = "blue", bins = input$bins)
})
output$table <- renderTable({
iris %>%
summarise(Mean=mean(Sepal.Length),
SD=sd(Sepal.Length))
})
}
ui <- fluidPage(
titlePanel("Test Shiny Layout"),
sidebarLayout(
sidebarPanel(
h4("Input Data"),
sliderInput("bins", "Bin Width", min = 4,max = 12, value = 8),
),
mainPanel(
h4("Histogram"),
plotOutput("distPlot", width = "600", height = "600"),
h4("Table of Values"),
tableOutput("table")
)
)
)
shinyApp(ui = ui, server = server)
I'm trying to make a plot with reactive data from the server. Unfortunately I can't get the plot to work. I'm getting an error like: "Error:EXPR must be a length 1 vector". I tried different styles of plots and different libraries: Quantmod, ggplot, so on. Any suggestions?
Server:
library(shiny)
Dat<-read.csv("A:\\home\\Documents\\Franchise_Failureby_Brand2011.csv", sep=';')
names(Dat)[1]<-paste("Brand")
names(Dat)[2]<-paste("Failure")
names(Dat)[3]<-paste("Disbursement")
names(Dat)[4]<-paste("Disb$X$1000")
names(Dat)[5]<-paste("Chgoff")
Dat1<-Dat[is.na(Dat)==FALSE,]
Dat<-Dat1[1:578,]
# Define server logic required to draw a histogram
shinyServer(function(input, output) {
DatSv <- reactive({
Value <- switch(input$Value,
"Failure"= Dat$Failure[1:10],
"Disbursement"=Dat$Disbursement[1:10],
"Disb$X$1000"=Dat$`Disb$X$1000`[1:10],
"Chgoff"=Dat$Chgoff[1:10])
Brand<-Dat$Brand[1:10]
Brand(input$Value)
})
# Generate plot
output$plot1 <- renderPlot({
library("quantmod")
hist(DatSv(),
main=paste('r', Value, '(', Brand, ')', sep=''))
})
# Generate summary of data
output$summary<-renderPrint({
summary(Dat)
})
})
UI:
library(shiny)
shinyUI(fluidPage(
titlePanel("Plot Franchise Failure"),
sidebarLayout(
sidebarPanel(
radioButtons("n", "Chose output Y Axis:",
c("Failure" ,
"Disbursement",
"Disb$X$1000" ,
"Chgoff" )),
checkboxInput("show_xlab", "Show/Hide X Axis Label", value=TRUE),
checkboxInput("show_ylab", "Show/Hide Y Axis Label", value=TRUE),
checkboxInput("show_title", "Show/Hide Title")
),
mainPanel(
tabsetPanel(
type = "tabs",
tabPanel("Plot", plotOutput("plot1")),
tabPanel("Summary", verbatimTextOutput("summary"))
)
)
)
)
)
Hi the problem comes from connecting the inputs in the UI with the server. In the UI you have given the inputid = "n" for the radioButtons. That means we can get the Value of the Radiobuttons with input$n and not input$Value. The later is always NULL since there is no input with inputid = "Value". I had some other small problems with your code but here is a working version of the server code. I didn't modify the UI
library(shiny)
Dat<-read.csv("A:\\home\\Documents\\Franchise_Failureby_Brand2011.csv", sep=';')
names(Dat)[1]<-paste("Brand")
names(Dat)[2]<-paste("Failure")
names(Dat)[3]<-paste("Disbursement")
names(Dat)[4]<-paste("Disb$X$1000")
names(Dat)[5]<-paste("Chgoff")
Dat1<-Dat[is.na(Dat)==FALSE,]
Dat<-Dat1[1:578,]
# Define server logic required to draw a histogram
shinyServer(function(input, output) {
DatSv <- reactive({
switch(input$n,
"Failure"= gsub("%","",as.character( Dat$Failure)),
"Disbursement"=Dat$Disbursement,
"Disb$X$1000"=gsub("\\$","",as.character( Dat$`Disb$X$1000`)),
"Chgoff"=gsub("%","",as.character(Dat$Chgoff)))
})
# Generate plot
output$plot1 <- renderPlot({
library("quantmod")
hist(as.numeric(DatSv()),
main=paste('Histogram of ',input$n, sep=''),
xlab = input$n)
})
# Generate summary of data
output$summary<-renderPrint({
summary(Dat)
})
})
I am trying to plot multiple plots on different pages in my shiny app. Here is a reproducible example:
My module code is :
library(ggplot2)
dfunc <- function(data, page_number) { p <- ggplot(data = data, aes(x = data[, 3], y = data[, 2])) +
geom_point() for (i in 1:page_number) {
print(p) } }
dfUI <- function(id) { ns <- NS(id)
tagList(titlePanel("Multi plots"),
sidebarLayout(
sidebarPanel(
numericInput(
ns("page_number"),
"Number of Plots",
1,
min = 1,
step = 1
),
actionButton(ns("generateplot"), "Generate Plot")
),
mainPanel(plotOutput(ns("view")))
)) }
df <- function(input, output, session) { observeEvent(input$generateplot, {
output$view <- renderPlot({
data = mtcars
dfunc(data = data,
page_number = input$page_number)
}) }) }
There is a global file which calls this file where these functions are stored:
through this:
library(shiny)
library(shinythemes)
source("modules/dfunc.R")
The server file is like this :
source('global.R')
shinyServer(function(input, output, session) {
callModule(df, "dfunc")
})
And ui.R is like this :
source('global.R')
shinyUI(fluidPage(
shinythemes::themeSelector(),
headerPanel("The Shiny App"),
tabsetPanel(
tabPanel("Multi page plot", dfUI('dfunc')),
type = "pills"
)
))
Now the issue is if I use my dfunc function it prints as many plots as I give in page_number but when I call similar function in shiny app it only prints the single one. Is there any way that shiny app can print as many plots required on same page or on multiple page.
Thanks in advance.
I have a task where i need to build an rShiny app that allows the user to choose which kind of R plotting package is used in-order to display a plot.
Currently the only way i have gotten it to work (semi-decently) is using package specific functions for each package on the server side and using a series of conditional panels on the UI side.
However the problem is that when the user enters the page for the first time then all plots are initialized. Second problem is when the user changes some plot input values and after that chooses another package then the old plot will be displayed until a new plot is created.
Questions:
Is this the only available approach?
I feel that there must be a way to use reactive functions for the package selection?
I feel that it should be possible to use a single rShiny's htmlOutput (or something similar) in the ui and therefore not needing the switchPanel?
I have created a small app to demonstrate my current implementation and both problems:
server.R
library(shiny)
#library(devtools)
#install_github("ramnathv/rCharts")
library(rCharts)
shinyServer(function(input, output) {
names(iris) = gsub("\\.", "", names(iris))
#Render the Generic plot
output$GenericPlot <- renderPlot({
data = iris[0:input$variable,]
plot(data$SepalLength ~ data$SepalWidth)
})
#Render the Polychart plot
output$PolychartPlot <- renderChart({
plotData <- rPlot(SepalLength ~ SepalWidth, data = iris[0:input$variable,], color = 'Species', type = 'point')
plotData$addParams(dom = 'PolychartPlot')
return(plotData)
})
#Render the NDV3 plot
output$NDV3Plot <- renderChart({
plotData <- nPlot(SepalLength ~ SepalWidth, data = iris[0:input$variable,], group = 'Species', type = 'scatterChart')
plotData$addParams(dom = 'NDV3Plot')
return(plotData)
})
})
ui.R
library(shiny)
library(rCharts)
shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("lib", label = "Library:",
choices = list("Generic", "rCharts Polychart", "rCharts NDV3"),
selected = "Generic"
),
numericInput("variable", "Observations:",
min = 5,
max = 150,
value = 10
)
),
mainPanel(
conditionalPanel(
condition = "input.lib == 'Generic'",
h3("Generic plot"),
plotOutput("GenericPlot")
),
conditionalPanel(
condition = "input.lib == 'rCharts Polychart'",
h3("rCharts Polychart plot"),
showOutput("PolychartPlot", "polycharts")
),
conditionalPanel(
condition = "input.lib == 'rCharts NDV3'",
h3("rCharts NDV3 plot"),
showOutput("NDV3Plot", "nvd3")
)
)
)
))
The final version will use a different dataset and more charting packages. The provided code is more of a toy example, with most of the stuff stripped out.
Make a single part in the output part of the app that includes some logic based on the input. For example,
library(shiny)
library(ggplot2)
data(cars)
server <- function(input, output) {output$plot<- renderPlot({
if (input$lib == "base") {
p <- plot(cars$speed, cars$dist)
} else if (input$lib == "ggplot") {
p <- ggplot(cars, aes(x = speed, y = dist)) + geom_point()
}
p
})
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("lib", "Library: ", choices = list("base", "ggplot"),
selected = "base")
),
mainPanel(plotOutput("plot"))
)
)
shinyApp(ui = ui, server = server)
This provides one plot and as soon as I change the lib option it regenerates.
Found a solution to my problem. The solution is basically to use uiOutput() in the ui.R and move the plotOutput(), showOutput() methods to the server.R.
The solution based on iacobus code:
ui.R
library(shiny)
library(rCharts)
shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("lib", "Library: ", choices = list("base", "ggplot", "Polychart"),
selected = "base")
),
mainPanel(uiOutput("plot"))
)
))
server.R
library(shiny)
library(ggplot2)
library(rCharts)
data(cars)
server <- function(input, output) {
output$plot<- renderUI({
if (input$lib == "base") {
plotOutput("base")
} else if (input$lib == "ggplot") {
plotOutput("ggplot")
} else if (input$lib == "Polychart") {
showOutput("polychart", "polycharts")
}
})
output$base <- renderPlot({
plot(cars$speed, cars$dist)
})
output$ggplot <- renderPlot({
ggplot(cars, aes(x = speed, y = dist)) + geom_point()
})
output$polychart <- renderChart({
p <- rPlot(speed ~ dist, data = cars, type = "point")
p$addParams(dom = 'plot')
p
})
}
The difficulty arose for me, because i assumed that plotOutput(), showOutput() etc methods can only be used in the ui.R. This however is not the case.
EDIT:
It turned out that this was not enough for pollyCharts to work properly along with other rCharts packages.
instead i am using renderUI and rCharts $show to display the chart inline. The following link was helpful for me: https://github.com/ramnathv/rCharts/issues/373. In the ui i'm using htmlOutput