I would like to display multiple output objects inside a tab in my RShiny app. In the tutorial the tabPanel(...) command only takes argument:
tabPanel("Plot", plotOutput("plot"))
However in the reference docs here, it reads "UI elements to include within the tab" leading me to believe that multiple are possible, but I cannot find examples. I have attempted passing objects to it as a vector c(...) and a list list(...).
Here are the server.R and ui.R I have been testing with (from the Shiny Tutorial).
ui.R
library(shiny)
# Define UI for random distribution application
shinyUI(fluidPage(
# Application title
titlePanel("Tabsets"),
# Sidebar with controls to select the random distribution type
# and number of observations to generate. Note the use of the
# br() element to introduce extra vertical spacing
sidebarLayout(
sidebarPanel(
radioButtons("dist", "Distribution type:",
c("Normal" = "norm",
"Uniform" = "unif",
"Log-normal" = "lnorm",
"Exponential" = "exp")),
br(),
sliderInput("n",
"Number of observations:",
value = 500,
min = 1,
max = 1000)
),
# Show a tabset that includes a plot, summary, and table view
# of the generated distribution
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("table"))
)
)
)
))
server.R
library(shiny)
# Define server logic for random distribution application
shinyServer(function(input, output) {
# Reactive expression to generate the requested distribution.
# This is called whenever the inputs change. The output
# functions defined below then all use the value computed from
# this expression
data <- reactive({
dist <- switch(input$dist,
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
dist(input$n)
})
# Generate a plot of the data. Also uses the inputs to build
# the plot label. Note that the dependencies on both the inputs
# and the data reactive expression are both tracked, and
# all expressions are called in the sequence implied by the
# dependency graph
output$plot <- renderPlot({
dist <- input$dist
n <- input$n
hist(data(),
main=paste('r', dist, '(', n, ')', sep=''))
})
# Generate a summary of the data
output$summary <- renderPrint({
summary(data())
})
# Generate an HTML table view of the data
output$table <- renderTable({
data.frame(x=data())
})
})
The following worked for me:
mainPanel(
tabsetPanel(
tabPanel("Some Title",
h5(textOutput("some text output")),
htmlOutput("someHTMLElement")
),
tabPanel("Other Title",
h5(textOutput("some other text output")),
htmlOutput("otherHTMLElement")
),
tabPanel("Yet Another Title",
h5(textOutput("yet another text output")),
htmlOutput("yetAnotherHTMLElement")
)
)
Function tabPanel specification is
tabPanel(title, ..., value = NULL)
which means that it accepts variable number of parameters for "UI elements to include within the tab"
Related
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
I am trying to make my first shinyapp application. But I get this error: Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)
I suspect there is an issue with that if.
This is My code:
#install.packages("shinythemes")
library(shiny)
library(shinythemes)
library(caret)
#library(tree)
library(rattle)
heart=read.csv("Heart3.csv",header = TRUE)
heart=heart[complete.cases(heart),]
#checking missing values
#sum(is.na(heart))
# removing missing values
heart=heart[complete.cases(heart),]
#create training/test split
set.seed(345)
train.index=createDataPartition(heart[,ncol(heart)],p=0.7,list=FALSE)
train=heart[train.index,]
test=heart[-train.index,]
# Define UI for random distribution app ----
ui <- fluidPage(
# theme of the app
theme = shinytheme("cerulean"), #slate
# App title ----
titlePanel("Classification Alghoritms GC3"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
helpText("Classify heart deaseses based on
decision tree or random forest"),
radioButtons(inputId = "algorithm",
label= "Choose an algorithm",
choices = c("Decision Tree",
"Random Forest"),
selected = "Decision Tree"),
# Input: Select whether you want a decision tree or a random forest ----
#radioButtons("option", "Type:",
#c("Decision Tree" = "dt",
#"Random Forest" = "rf")),
# br() element to introduce extra vertical spacing ----
#br(),
# Input: Slider for the values to generate ----
sliderInput("ff", "Fold:",
value = 10,
min = 1,
max = 50,
step = 1),
sliderInput("rr", "Repeats:",
value = 3,
min = 1,
max = 50,
step=1),
sliderInput("cpr", "cp:",
min = 0.01,
max = 0.1,
value=c(0.01,0.03),
step=0.01)
),
# Main panel for displaying outputs ----
mainPanel(
#plotOutput(outputId = "plot"),
# Output: Tabset w/ plot, summary, and table ----
tabsetPanel(type = "tabs",
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("table"))
)# tabsetPanel
)# main panel
)# sidebar panel
)# sidebar layout
# Define server logic for random distribution app ----
server <- function(input, output) {
if(input$algorithm == "Random Forest"){
outuput$plot<- function () renderPlot({
fitcontrol=trainControl(
method ="repeatedcv",
number = input$ff,
repeats = input$rr)
set.seed(324)
cpGrid=expand.grid(cp=input$cpr)
rfFit = train(AHD~. , data=train, method="rf", metric="Accuracy",
trControl=fitControl, tuneGrid=cpGrid)
plot(rFit)
})
}
if(input$algorithm == "Decision Tree"){
output$plot <- function() renderPlot({
# decision tree
fitcontrol=trainControl(method="repeatedcv",
number=input$ff,
repeats = input$rr)
set.seed(1)
cpGrid=expand.grid(cp=input$cpr)
heart.rparts=train(train[,-ncol(heart)],
train[,ncol(heart)],
method = "rpart",
tuneGrid = cpGrid,
trControl = fitcontrol)
#heart.rpart
#plot(heart.rpart$finalModel)
#text(heart.rpart$finalModel, cex=0.8)
fancyRpartPlot(heart.rparts$finalModel) })
}
}
shinyApp(ui = ui, server = server)
Thank you very much for your answer.
Example shiny app:
library(tidyverse)
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("example"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
selectInput(inputId = "cut",
label = "cut",
# choices = unique(diamonds$cut), # works
choices = unique(my_diamonds$cut), # does not work
selected = "Ideal")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
my_diamonds <- diamonds
output$distPlot <- renderPlot({
# generate bins based on input$bins from ui.R
x <- my_diamonds$carat
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}
# Run the application
shinyApp(ui = ui, server = server)
In this case I am using a faux data frame 'my_diamonds'. In my real code I am connecting to a database using dbplyr and then making some transformations to it, so duplicating that in ui section seems wasteful.
What is the 'right' way to use a dataframe defined in server section to get the unique values, in this case my_diamonds$cut to use as a select input's drop down choices?
Instead of using selectInput in the UI, you can instead use UIoutput in the UI and then define the UI element within the server function using renderUI.
So given that you have defined a UIOutput element called otn_race_selection_op, then we can define that as a selectInput object using the below code. Here getData is a reactive element that updates itself to the latest data. So based on that, you can modify the input choices for your selectInput object
output$otn_race_selection_op <- renderUI({
df <- getData()
options <- sort(unique(df$Race))
selectInput(
inputId = "otn_race_selection",
label = "Race",
choices = c("All", options)
,
selected = "All"
)
})
I'm a bit of an RShiny and R novice. I'm trying to program an RShiny application. It would initially graphs a scatterplot matrix using the first three variables of the dataset by default. The user could then choose their own variable selections from a complete list of variables. Once variables are chosen, the user would click and action button and the graph would be recomputed using the newly selected variables.
I'm using selectinput rather than checkboxinput to accommodate datasets with many variables. I'm using the iris dataset. The code below produces the initial graph and allows the user to select the variables. I just can't figure out how to make it recompute the matrix plot. How do I do this? Thanks!
library(shiny)
runApp(list(
ui = fluidPage(
cols = colnames(iris),
headerPanel('Grow Clusters'),
tabsetPanel(
tabPanel("Plot",
sidebarPanel(
# uiOutput("varselect"),
selectInput("choose_vars", "Select variables to plot",
choices=colnames(iris), selected=iris[1:3], multiple=T),
actionButton("submitButton", "Produce Matrix Plot!")
),
mainPanel(
plotOutput('pairsplot')
)
),
tabPanel("Summary")
,
tabPanel("Table")
)
),
server = function(input, output) {
selectedData <- reactive({
cols = colnames(iris)
selectInput("choose_vars", "Select variables to plot",
choices=cols, selected=cols[1:3], multiple=T)
})
output$pairsplot <- renderPlot({
pairs(iris[1:3], pch = 21)
})
output$varselect <- renderUI({
iris[input$choose_vars]
plotOutput("pairsplot")
})
}
)
)
I think what you are looking for is quo function as in the Chris Beely blog: https://chrisbeeley.net/?p=1116
If you want users to pass arguments and then turn that character vector into objects r can read you need to use quo(input$choose_vars) and then in the plot you need to add !! before that passing variable. Notice you need to load dplyr.
library(shiny)
library(dplyr)
runApp(list(
ui = fluidPage(
cols = colnames(iris),
headerPanel('Grow Clusters'),
tabsetPanel(
tabPanel("Plot",
sidebarPanel(
# uiOutput("varselect"),
selectInput("choose_vars", "Select variables to plot",
choices=colnames(iris), selected=iris[1:3], multiple=T),
actionButton("submitButton", "Produce Matrix Plot!")
),
mainPanel(
plotOutput('pairsplot')
)
),
tabPanel("Summary")
,
tabPanel("Table")
)
),
server = function(input, output) {
selectedData <- reactive({
cols <- colnames(iris)
selectInput("choose_vars", "Select variables to plot",
choices=cols, selected=cols[1:3], multiple=T)
})
output$pairsplot <- renderPlot({
if(is.null(input$choose_vars) || length(input$choose_vars)<2){
pairs(iris[1:3], pch = 21)
} else {
var <- quo(input$choose_vars)
pairs(iris %>% select(!!var), pch = 21)
}
})
output$varselect <- renderUI({
iris[input$choose_vars]
plotOutput("pairsplot")
})
}
)
)
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)
})
})