Create multiple graphs with button in shiny - r

I have the following shiny script
library(shiny)
ui <- fluidPage(
actionButton("go", "Go"),
#This should trigger a default 100 hist
actionButton("go", "Go"),
numericInput("n", "n", 50),
plotOutput("plot")
)
server <- function(input, output) {
randomVals <- eventReactive(input$go, {
runif(input$n)
})
randomVals2 <- eventReactive(input$go, {
runif(n = 100)
})
output$plot <- renderPlot({
hist(randomVals())
})
output$plot2 <- renderPlot({
hist(randomVals2())
})
}
shinyApp(ui, server)
This provides me with a button I can press to get a histogram. It works fine. However, I would also like to include another button next to it that gives a histogram with default n = 100 (just for learning purposes).
However the second button does not seem to work.
Any thoughts on what should be changed to trigger it?

#PorkChop has correctly pointed out one bug in your code, which is the repeated IDs of both buttons. Also, your don't have plot2 defined in your UI function.
However, I would like to comment that the functions defined is a bit redundant. In principle, both buttons should preform the same functionalities (generate random data and plot their histogram), with different size parameter.
The neatest way would be defining one function that does the full functionality required, and calling it with the specific parameters with each button:
library(shiny)
ui <- fluidPage(
actionButton("go_hist", "Go"),
actionButton("go_hist_100", "Go with 100"), #This should trigger a default 100 hist
numericInput("n", "n", 50),
plotOutput("plot")
)
server <- function(input, output) {
plotHist = function(size){
randomData = runif(size)
output$plot = renderPlot(hist(randomData, main = paste("n =", size)))
}
observeEvent(input$go_hist, plotHist(input$n))
observeEvent(input$go_hist_100, plotHist(100))
}
shinyApp(ui, server)
With such functionalization, your code will be easier to read, debug and maintain.

You cant have multiple buttons with the same id please change button names
actionButton("go", "Go"),
#This should trigger a default 100 hist
actionButton("go1", "Go"),

Related

Is it possible to have one function to download various ggplot plots?

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

Is it possible to use eventReactive for more than 1 checkboxInput in Shiny?

Some days ago I was answered in this post. The solution was perfect in that moment, but I realised that I forgot to ask how I can do that with more than 1 checkboxInput. Since... I have tried a lot of things and that solution doesn't fit me with 2 checkboxInput. Maybe it can be done with the same solution changing some things, but, as I am new using shiny, I cannot find a way to do it.
The difference between the code from the previous post and this one, is that I have added a conditionalPanel and two checkboxInputs instead of 1.
Since the condition here is that if the user selects the condition (play), I thought that the solution was writing eventReactive(input$play,{}). However, none of the checkboxInputs work.
On the other hand, if you write eventReactive(input$change_log2,{}) one of the checkboxInputs (the logaritm) works. But if you select the other (srqt) it won't do nothing.
I have seen that an alternative way could be using observe or observeEvent but I cannot save the results in a variable, so... I need eventReactive...
I am a bit lost.
Someone could help me? Eventually I will add more checkboxInputs... so I need a way which I could use more than 2 checkboxInputs.
Here it is the code:
library(shiny)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("My app"),
sidebarLayout(
sidebarPanel(
uiOutput("selected_sample_one"),
uiOutput("selected_sample_two"),
checkboxInput("play", strong("I want to play my data"), value = FALSE),
conditionalPanel(
condition = "input.play == 1",
checkboxInput("change_log2", "Log2 transformation", value = FALSE),
checkboxInput("run_sqrt", "sqrt option", value = FALSE))
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("plot")
)
)
)
# Define server
server <- function(input, output,session) {
data <- reactive({
numbers <- c(5,345,55,10)
df<-data.frame(t(numbers))
names(df) <- c("S1", "S2", "S3", "S4")
return(df)
})
data1 <- eventReactive(input$play,{
df <- data()
if(input$change_log2 == TRUE){
df <- log2(df)
}
if(input$run_sqrt == TRUE){
df <- sqrt(df)
}
return(df)
})
samples_names <- reactive({
req(data())
samples <- colnames(data())
return(samples)
})
output$selected_sample_one <- renderUI({
selectizeInput(inputId = "sample_one_axis", "Select the 1st sample", choices=samples_names(), options=list(maxOptions = length(samples_names())))
})
# With this function you can select which sample do you want to plot in the y-axis.
output$selected_sample_two <- renderUI({
selectizeInput(inputId = "sample_two_axis", "Select the 2nd sample", choices=samples_names(), selected=samples_names()[2], options=list(maxOptions = length(samples_names())))
})
output$plot <- renderPlot({
req(input$sample_one_axis,input$sample_two_axis,data1())
barplot(c(data1()[,input$sample_one_axis], data1()[,input$sample_two_axis]))
})
}
# Run the application
shinyApp(ui = ui, server = server)
Thanks very much in advance,
Regards
You can simply wrap the relvant inputs in c().
Here's a MWE:
library(shiny)
ui <- fluidPage(
checkboxInput("check1", "Checkbox 1"),
checkboxInput("check2", "Checkbox 2"),
textOutput("text")
)
server <- function(input, output, session) {
v <- reactiveValues(text="Waiting...")
observeEvent(c(input$check1, input$check2), {
s <- "Checked: "
if (input$check1) {
s <- paste(s, "1")
}
if (input$check2) {
s <- paste(s, "2")
}
v$text <- s
},
ignoreInit=TRUE
)
output$text <- renderText({
v$text
})
}
shinyApp(ui, server)
Alternatively, you could perhaps wrap what you need to do in a function and then just call the function in a series of observeEvents, one for each relevant input.
[I started writing before you updated your post.]
Thanks to #Limey, the solution for my problem was to replace data1 <- eventReactive with data1 <- reactive.
Thanks very much.

Creating hover info box and reactive dropdown menu in Shiny

This is my first Shiny app, and I just got the basics working to where it allows the user to select from a dropdown menu of clients, then a dropdown menu of test codes to receive a plot of the results for the selected test.
I'd like the second dropdown menu to be updated with the available test codes for that client (all are not present for each client). Also, I would like to be able to hover over the point in the plot and receive more information from the row in the original dataframe.
I've looked into tooltips and the nearPoints() function, but I'm not sure if these can be used on this data since it is manipulated. I'm not sure if at this point it would be easier to import the data in a different way (it will ultimately need to accept either excel files or .csv). Thanks for any help that you would be able to provide, please let me know if there is any other supporting info I can give.
Here is my code:
library(shiny)
library(scales)
library(ggplot2)
labData <-
read.table("MockNLData.csv",
header=TRUE, sep=",")
#convert '<10' and '<20' results
labData$ModResult <- labData$Result
levels(labData$ModResult)[levels(labData$ModResult)=="<10"]
<- "0"
levels(labData$ModResult)[levels(labData$ModResult)=="<20"]
<- "0"
#convert results to scientific notation
SciNotResult <-
formatC(as.numeric(as.character(labData$ModResult)),
format="e", digits=2)
ui <- fluidPage(
headerPanel("Dilution History"),
sidebarLayout(
sidebarPanel(
selectInput(inputId="client", label="Select Client
Name", choices=levels(labData$Client.Name)
),
selectInput(inputId="test", label="Select Test Code",
choices=levels(labData$Analysis))
),
mainPanel(
plotOutput("line", hover="plot_hov"),
verbatimTextOutput("info"))
)
)
server <- function(input, output) {
#selected client into data frame
selDF <- reactive({labData[labData[,1]==input$client,]
})
#selected test code into data frame
subsetDF <- reactive({selDF()[selDF()[,5]==input$test,]
})
#points to be plotted
points <-
reactive({as.numeric(levels(subsetDF()$ModResult))
[subsetDF()$ModResult]
})
#plot
output$line <- renderPlot({
qplot(seq_along(points()), points(), xlab ="Index",
ylab ="Result")
})
#hover information
output$info <- renderText({
paste0("x=", input$plot_hov$x, "\ny=",
input$plot_hov$y)
})
}
shinyApp(ui = ui, server = server)
Here is what the data looks like:
MockNLData.csv
EDIT: I figured out updating the menu with updateSelectInput()
In the future, make sure you share a reproducible example :)
Since your code is not reproducible please find below something you can understand and adapt to your case.
On your first question, if I understand correctly, you want to programatically generate a dropdown (selectInput) which is perfectly do-able. *Inputs are, in essence, just HTML content which you can dynamically generate, just like your plots. You do so with uiOutput (in your ui) and renderUI in your server.
library(shiny)
ui <- fluidPage(
selectInput("dataset", "Select a dataset", choices = c("cars", "mtcars")),
uiOutput("column"), # dynamic column selector
verbatimTextOutput("selected_column")
)
server <- function(input, output, session){
data <- reactive({
if(input$dataset == "cars")
return(cars)
else
return(mtcars)
})
output$column <- renderUI({
# build your selectInput as you normally would
selectInput("column_selector", "Select a column", choices = colnames(data()))
})
output$selected_column <- renderPrint({
# use input$column_selector!
print(input$column_selector)
})
}
shinyApp(ui, server)
On your second question, what you want is an interactive plot. There are numerous packages that will let you do that in R and Shiny. Below are some examples, by no means a comprehensive list:
plotly which will also let you make your ggplot2 charts interactive
highcharter another great, well tested library
echarts4r ECharts for R.
billboarder billboard.js for R and Shiny
Below is an example using highcharter. They all follow the same principle within Shiny, an *Output function coupled with a render* function.
library(shiny)
library(highcharter)
ui <- fluidPage(
highchartOutput("chart")
)
server <- function(input, output, session){
output$chart <- renderHighchart({
hchart(mpg, "scatter", hcaes(x = displ, y = hwy, group = class))
})
}
shinyApp(ui, server)
EDIT
Following your question on the flashing error. You need to require (req) the required input. When launching the app below the error will flash, uncomment the req(input$y) line and it'll go away.
library(shiny)
ui <- fluidPage(
uiOutput("sel"),
plotOutput("plot")
)
server <- function(input, output){
output$sel <- renderUI({
numericInput("y", "N:", value = 200, min = 5, max = 1000, step = 100)
})
output$plot <- renderPlot({
# req(input$y)
hist(runif(input$y, 1, 10))
})
}
shinyApp(ui, server)
In essence, since your plot relies on a dynamically generating input for a fraction of second that input is not available as it is being rendered, using req prevents that.
What I understand from your problem above are:
You want to make next dropdown menu based on what the user have chosen from previous dropdown menu.
When the mouse over the point on the plot, it will show row value.
So, here i will give you reproducible example and i hope it is useful for you.
In this example I use Rabbit dataset from library MASS.
To filter data for next dropdown menu, I use filter from library
dplyr (See line 30).
I use reactive expression to manage next dropdown menu (See line
29).
I use nearPoints() to manage hover point (See line 55).
library(shiny)
library(MASS)
library(dplyr)
library(ggplot2)
ui <- fluidPage(
titlePanel("Rabbit dataset from MASS library"),
fluidRow(
column(4, selectInput("var",
"Animal:",
unique(sort(Rabbit$Animal)))),
column(4, uiOutput("selected_var")),
column(4, uiOutput("selected_var1")),
column(12, plotOutput("selected_var2", hover = "plot_hover")),
column(12, verbatimTextOutput("info"))
)
)
server <- function(input, output) {
###FILTER NEXT DROPDOWN MENU BASED ON PREVIOUS SELECTED BY USER
dataset3 <- reactive({
unique(Rabbit %>% filter(Animal == input$var) %>% select(Treatment))
})
output$selected_var <- renderUI({
selectInput("var1", "Treatment:", c(dataset3()))
})
dataset4 <- reactive({
Rabbit %>% filter(Animal == input$var) %>% filter(Treatment == input$var1) %>% select(Run)
})
output$selected_var1 <- renderUI({
selectInput("var2", "Run:", c(dataset4()))
})
####
output$selected_var2 <- renderPlot({
ggplot(Rabbit %>% filter(Animal == input$var) %>% filter(Treatment == input$var1) %>% filter(Run == input$var2), aes(x = BPchange, y = Dose)) + geom_point()
})
###HOVER POINT USING nearPoints()
output$info <- renderPrint({
nearPoints(Rabbit %>% filter(Animal == input$var) %>% filter(Treatment == input$var1) %>% filter(Run == input$var2), input$plot_hover)
})
}
shinyApp(ui = ui, server = server)

RStudio and Shiny: define a reactiveValues with a Reactive

I am having a dataframe that is build depending on user input, choosing different filters. I then want to create a bar plot from this custom dataframe where the user can click on to exclude bars from the plot. I basically followed this example:
https://gallery.shinyapps.io/106-plot-interaction-exclude/
However, when I try to define my reactiveValues value with my reactive, I can not define it and get an error. I am suspecting I can not define a reactiveValues with a reactive, is this right? How should I handle this then? Should I use reactives instead of reactiveValues?
Example code:
Server
server <- function(input, output) {
df <- reactive({
input$input1
})
vals2 <- reactive({
(df())
})
output$Id1 <- renderText({
vals2()
})
vals <- reactiveValues()
vals$bla <- df()
}
UI
library(shiny)
ui <- fluidPage(
fluidRow(
column(width= 4,
textInput(inputId = "input1", label = "Select number of rows", value = "10")
),
column(width = 12,
verbatimTextOutput(outputId = "Id1"),
verbatimTextOutput(outputId = "Id2")
)
)
)
Create your reactiveValues towards the beginning of your function and initialise is with a NULL
vals <- reactiveValues(bla = NULL)
You can then write to vals$bla from inside observeEvent, for instance when a button is pressed.
You can read from the reactiveValue, for instance to draw a plot in the form:
output$myPlot <- renderPlot( some function of values$bla )
EDIT updating to add my comment, create an observeEvent which watches your input1, when this changes it will execture the code within {} which will write to your reactiveValue.
observeEvent(input$input1, {
vals$bla <- input$input1
})

Using length of checkboxGroupInput as an input for a loop to create multiple elements

I'm creating Shiny app and I want to use checkboxGroupInput in order to print out multiple plots. However, I want to print out plots only for the elements of checkboxGroupInput that were checked. There is a similar example in Shiny gallery to create UI elements in a loop that uses lapply. Here is a simplified version of that example to show what I want to do:
#server.R
library(shiny)
library(ggplot2)
shinyServer(function(input, output, session) {
numberInput <- reactive({
input$checkbox
})
lapply(1:10, function(i) {
output[[paste0('b', i)]] <- renderPlot({
qplot(x = rnorm(100, mean = as.numeric(numberInput()[i]))) +
ggtitle(paste("This plot was plotted with", numberInput()[i], "option"))
})
})
})
#ui.R
library(shiny)
shinyUI(fluidPage(
title = 'lapply example',
sidebarLayout(
sidebarPanel(
checkboxGroupInput("checkbox", "Checkbox",
choices = sample(1:10, 5))
),
mainPanel(
lapply(1:10, function(i) {
plotOutput(paste0('b', i))
})
)
)
))
This works, but obviously when Shiny tries to extract numberInput()[i] where i is bigger than number of currently checked elements, there is nothing to extract and instead of a plot there is an error. Therefore I need to somehow tell lapply to iterate only n number of times where n is length(input$checkbox).
I tried to use length(input$checkbox) directly, tried putting that element in the numberInput() reactive statement and returning it as the list, I tried to use reactiveValues() in a following way:
v <- reactiveValues(n = length(input$checkbox))
lapply(1:isolate(v$n), function(i) {
However, in all of those instances Shiny complains about lack of active reactive context.
So, what am I missing? How can I use length of input in lapply outside of reactive context?
I've generally had more luck using this approach (only because it's easier for me to wrap my head around it), but the idea is to render your plots into a UI on the server and then render the UI in ui.R
#server.R
library(shiny)
library(ggplot2)
server <- shinyServer(function(input, output, session) {
output$checks <- renderText(input$checkbox)
output$plots <- renderUI({
plot_output_list <-
lapply(input$checkbox,
function(i){
plotOutput(paste0("plot", i))
})
do.call(tagList, plot_output_list)
})
observe({
for (i in input$checkbox) {
local({
local_i <- i
output[[paste0("plot", local_i)]] <-
renderPlot({
qplot(x = rnorm(100, mean = as.numeric(local_i))) +
ggtitle(paste("This plot was plotted with", local_i, "option"))
})
})
}
})
})
#ui.R
library(shiny)
ui <- shinyUI(fluidPage(
title = 'lapply example',
sidebarLayout(
sidebarPanel(
checkboxGroupInput("checkbox", "Checkbox",
choices = sample(1:10, 5))
),
mainPanel(
verbatimTextOutput("checks"),
uiOutput('plots')
)
)
))
shinyApp(ui = ui, server = server)

Resources