Best way to update plots within a reactive environment with Shiny - r

I want to display an animation of a simulation using two plots.
One is a "population" view of the simulation where points represent individuals. At every step in the simulation, I want to draw random points within a circle keeping those sampled in the next generation. The simulation stops when the frequency of any of the two types of individuals reaches 1 or 0.
The other one is a frequency chart with frequency on the y-axis and generations on the x-axis. Ideally, I want this graph to expand at every generation and stop when the frequency reaches 1/0.
My first problem is that I can't get reactiveTimer() to work as want it to. It does not self-update or if it does it goes back to the starting point without "remembering" previous states.
My second problem is that if I use an if statement for the condition to keep the simulation going it only iterates a single generation after pressing run. Alternatively, if I use a while loop it will just go to directly to the last generation, skipping all the middle parts of the simulation.
My third problem is that I cannot grow a data.frame within a reactive environment so that I can plot the frequencies after each generation.
Code:
library(shiny)
library(ggplot2)
# function to make a circle data.frame
# https://stackoverflow.com/questions/6862742/draw-a-circle-with-ggplot2
circleFun <- function(center=c(0,0), diameter=10, npoints=100){
r = diameter / 2
tt = seq(0,2*pi,length.out = npoints)
xx = center[1] + r * cos(tt)
yy = center[2] + r * sin(tt)
return(data.frame(x = xx, y = yy))
}
# ui
ui <- fluidPage(
titlePanel("Genetic Drift Simulator"),
sidebarLayout(
sidebarPanel(
# Input: select from menu
numericInput(inputId = "population_size",
label = "Population size:",
value = 10,
step = 10),
sliderInput(inputId = "initial_frequency",
label = "Initial frequency of allele 1:",
min = 0,
max = 1,
step = 0.1,
value = 0.5),
actionButton(inputId = "run",
label = "Run simulation"),
actionButton(inputId = "reset",
label = "Reset values")
),
mainPanel(
fluidRow(
column(4,
verbatimTextOutput("text")
)
),
fluidRow(
column(8,
plotOutput("pop_plot")
)
),
fluidRow(
column(8,
plotOutput("freq_plot")
)
)
)
)
)
server <- function(input, output, session) {
waits <- reactiveValues(timer = reactiveTimer(Inf))
returns <- reactiveValues(
z=NULL,
x=NULL,
y=NULL,
freq=NULL,
circle=NULL,
i=NULL
)
frequencies <- reactiveValues(df=NULL)
observe({
returns$z=rbinom(input$population_size, 1, input$initial_frequency)
returns$x=rnorm(input$population_size)
returns$y=rnorm(input$population_size)
returns$freq=input$initial_frequency
returns$circle=circleFun()
returns$i=0
frequencies$df = data.frame(x=returns$i, y=returns$i)
})
population <- reactive({
data.frame(x=returns$x, y=returns$y, z=returns$z)
})
grow_freq <- function(df, x, y){
rbind(df, c(x,y))
}
grow <- reactive({
frequencies$df = grow_freq(frequencies$df, returns$i, returns$freq)
})
drift <- reactive({
returns$z = sample(returns$z, replace=T)
# random locations
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
# calculate frequency
returns$freq = sum(returns$z == 1)/input$population_size
# increase to next generation
returns$i = returns$i+1
})
observeEvent(input$run, {
if (returns$freq < 1 & returns$freq > 0){
# observeEvent(reactiveTimer(200), {
drift()
grow()
# })
}
# else {
# waits$timer <- reactiveTimer(Inf)
# }
})
observeEvent(input$reset, {
timer = reactiveTimer(Inf)
returns$z = rbinom(input$population_size, 1, input$initial_frequency)
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
returns$freq = input$initial_frequency
returns$circle=circleFun(center=c(0,0), diameter=10, npoints=100)
returns$i = 0
frequencies$df = data.frame(x=returns$i, y=returns$i)
})
output$text <- renderText({
text = paste("Population size: ",input$population_size,"\n",
"Frequency allele 1: ",returns$freq,"\n",
"Generation: ",returns$i, sep="")
print(text)
})
output$pop_plot <- renderPlot({
ggplot(data=population(), aes(x, y)) +
geom_point(aes(color=factor(z)), size=5, alpha=0.7) +
geom_path(data=returns$circle, color="black", size=2) +
scale_color_brewer(type="qual", palette=1, name="allele") +
theme(axis.title=element_blank(), axis.text=element_blank()) +
theme(legend.title=element_text(size=16), legend.text=element_text(size=14))
},
height = 400, width = 450)
output$freq_plot <- renderPlot({
ggplot(data=frequencies$df, aes(x, y)) +
geom_point() +
geom_line() +
ylim(0,1)
},
height = 300, width = 500)
}
shinyApp(ui = ui, server = server)

The three problems you noted are resolved in the code below. I changed your first observe to observeEvent. Please note that you need to click on reset when the simulation ends.
# ui
ui <- fluidPage(
titlePanel("Genetic Drift Simulator"),
sidebarLayout(
sidebarPanel(
# Input: select from menu
numericInput(inputId = "population_size",
label = "Population size:",
value = 10,
step = 10),
sliderInput(inputId = "initial_frequency",
label = "Initial frequency of allele 1:",
min = 0,
max = 1,
step = 0.1,
value = 0.5),
actionButton(inputId = "run",
label = "Run simulation"),
actionButton(inputId = "reset",
label = "Reset values")
),
mainPanel(
fluidRow(
column(4,
verbatimTextOutput("text")
)
),
fluidRow(
column(8,
plotOutput("pop_plot")
)
),
fluidRow(
column(8,
plotOutput("freq_plot")
)
)
)
)
)
server <- function(input, output, session) {
# Anything that calls autoInvalidate will automatically invalidate every 2 seconds.
autoInvalidate <- reactiveTimer(2000)
#waits <- reactiveValues(timer = reactiveTimer(Inf))
returns <- reactiveValues(
z=NULL,
x=NULL,
y=NULL,
freq=NULL,
circle=NULL,
i=NULL
)
frequencies <- reactiveValues(df=NULL)
#observe({
observeEvent(list(input$population_size,input$initial_frequency), {
returns$z=rbinom(input$population_size, 1, input$initial_frequency)
returns$x=rnorm(input$population_size)
returns$y=rnorm(input$population_size)
returns$freq=input$initial_frequency
returns$circle=circleFun()
returns$i=0
frequencies$df = data.frame(x=returns$i, y=returns$i)
})
population <- reactive({
data.frame(x=returns$x, y=returns$y, z=returns$z)
})
grow_freq <- function(df, x, y){
rbind(df, c(x,y))
}
grow <- reactive({
frequencies$df <- grow_freq(frequencies$df, returns$i, returns$freq)
})
drift <- reactive({
returns$z = sample(returns$z, replace=T)
# random locations
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
# calculate frequency
returns$freq = sum(returns$z == 1)/input$population_size
# increase to next generation
returns$i = returns$i+1
})
observeEvent(list(input$run,autoInvalidate()), {
if (returns$freq < 1 & returns$freq > 0){
# observeEvent(reactiveTimer(200), {
drift()
grow()
# })
}
# else {
# waits$timer <- reactiveTimer(200)
# }
})
observeEvent(input$reset, {
#timer = reactiveTimer(Inf)
returns$z = rbinom(input$population_size, 1, input$initial_frequency)
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
returns$freq = input$initial_frequency
returns$circle=circleFun(center=c(0,0), diameter=10, npoints=100)
returns$i = 0
frequencies$df = data.frame(x=returns$i, y=returns$i)
})
output$text <- renderText({
text = paste("Population size: ",input$population_size,"\n",
"Frequency allele 1: ",returns$freq,"\n",
"Generation: ",returns$i, sep="")
print(text)
})
output$pop_plot <- renderPlot({
autoInvalidate()
ggplot(data=population(), aes(x, y)) +
geom_point(aes(color=factor(z)), size=5, alpha=0.7) +
geom_path(data=returns$circle, color="black", size=2) +
scale_color_brewer(type="qual", palette=1, name="allele") +
theme(axis.title=element_blank(), axis.text=element_blank()) +
theme(legend.title=element_text(size=16), legend.text=element_text(size=14))
},
height = 400, width = 450)
output$freq_plot <- renderPlot({
autoInvalidate()
ggplot(data=frequencies$df, aes(x, y)) +
geom_point() +
geom_line() +
ylim(0,1)
},
height = 300, width = 500)
}
shinyApp(ui = ui, server = server)

My edits to the accepted answers were rejected, so I'm posting the revised code here as an answer. The reason was that I was not addressing the OP's questions. Which is strange because the OP is me.
In short:
I removed reactiveTimer functions from plots.
The counter now starts after pressing the run button and not automatically.
Fixed a bug with initial frequency on 2nd plot which was set to 0 and not 0.5
Removed else statement on the simulation block to allow the reset button to work.
An example of the ShinyApp can be found in here.
# ui
ui <- fluidPage(
titlePanel("Genetic Drift Simulator"),
sidebarLayout(
sidebarPanel(
# Input: select from menu
numericInput(inputId = "population_size",
label = "Population size:",
value = 10,
step = 10),
sliderInput(inputId = "initial_frequency",
label = "Initial frequency of allele 1:",
min = 0,
max = 1,
step = 0.1,
value = 0.5),
actionButton(inputId = "run",
label = "Run simulation"),
actionButton(inputId = "reset",
label = "Reset values")
),
mainPanel(
fluidRow(
column(4,
verbatimTextOutput("text")
)
),
fluidRow(
column(8,
plotOutput("pop_plot")
)
),
fluidRow(
column(8,
plotOutput("freq_plot")
)
)
)
)
)
server <- function(input, output, session) {
# Anything that calls autoInvalidate will automatically invalidate.
autoInvalidate <- reactiveValues(timer=NULL)
returns <- reactiveValues(
z=NULL,
x=NULL,
y=NULL,
freq=NULL,
circle=NULL,
i=NULL
)
frequencies <- reactiveValues(df=NULL)
observeEvent(list(input$population_size,input$initial_frequency), {
returns$z=rbinom(input$population_size, 1, input$initial_frequency)
returns$x=rnorm(input$population_size)
returns$y=rnorm(input$population_size)
returns$freq=input$initial_frequency
returns$circle=circleFun()
returns$i=0
frequencies$df = data.frame(x=returns$i, y=returns$freq)
autoInvalidate$timer = reactiveTimer(Inf)
})
population <- reactive({
data.frame(x=returns$x, y=returns$y, z=returns$z)
})
grow_freq <- function(df, x, y){
rbind(df, c(x,y))
}
grow <- reactive({
frequencies$df <- grow_freq(frequencies$df, returns$i, returns$freq)
})
drift <- reactive({
returns$z = sample(returns$z, replace=T)
# random locations
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
# calculate frequency
returns$freq = sum(returns$z == 1)/input$population_size
# increase to next generation
returns$i = returns$i+1
})
observeEvent(input$run, {
autoInvalidate$timer = reactiveTimer(1000) # changed to 1 second
drift()
grow()
})
observeEvent(autoInvalidate$timer(), {
if (returns$freq < 1 & returns$freq > 0 & returns$i != 0){
autoInvalidate$timer()
drift()
grow()
}
# else if (returns$freq == 0 | returns$freq == 1) {
# autoInvalidate$timer = reactiveTimer(Inf)
# }
})
observeEvent(input$reset, {
returns$z = rbinom(input$population_size, 1, input$initial_frequency)
returns$x = rnorm(input$population_size)
returns$y = rnorm(input$population_size)
returns$freq = input$initial_frequency
returns$circle=circleFun(center=c(0,0), diameter=10, npoints=100)
returns$i = 0
frequencies$df = data.frame(x=returns$i, y=returns$freq)
autoInvalidate$timer = reactiveTimer(Inf)
})
output$text <- renderText({
#autoInvalidate$timer()
text = paste("Population size: ",input$population_size,"\n",
"Frequency allele 1: ",returns$freq,"\n",
"Generation: ",returns$i, sep="")
print(text)
})
output$pop_plot <- renderPlot({
#autoInvalidate$timer()
ggplot(data=population(), aes(x, y)) +
geom_point(aes(color=factor(z)), size=5, alpha=0.7) +
geom_path(data=returns$circle, color="black", size=2) +
scale_color_brewer(type="qual", palette=1, name="allele") +
theme(axis.title=element_blank(), axis.text=element_blank()) +
theme(legend.title=element_text(size=16), legend.text=element_text(size=14))
},
height = 400, width = 450)
output$freq_plot <- renderPlot({
#autoInvalidate$timer()
if (dim(frequencies$df)[1] == 1){
ggplot(data=frequencies$df, aes(x, y)) +
geom_hline(yintercept=0.5) +
geom_point() +
labs(x="generation",y="frequency") +
ylim(0,1)
} else {
ggplot(data=frequencies$df, aes(x, y)) +
geom_hline(yintercept=0.5) +
geom_point() +
geom_line() +
labs(x="generation",y="frequency") +
ylim(0,1)
}
},
height = 300, width = 500)
}
shinyApp(ui = ui, server = server)

Related

R Shiny recoloring of points

I would like click-select points and group them based on color.
I can save selected points with color information into a new data frame and plot it, however I would like to keep track and see what was already selected on the interactive plot.
How can I show/label already selected points or make it permanent after "Add selection"?
library(shiny)
library(tidyverse)
library(DT)
library(colourpicker)
ui = fluidPage(
colourInput("col", "Select colour", "purple"),
actionButton("addToDT", "Add selection", icon = icon("plus")), hr(),
plotOutput("plot", click = "plot_click", dblclick = "plot_reset"),
DT::dataTableOutput('plot_DT'), hr(),
textOutput("clickcoord"),
DT::dataTableOutput('final_DT'),
plotOutput("plotSelected")
)
server = function(input, output, session) {
selectedPoint = reactiveVal(rep(FALSE, nrow(mtcars)))
output$clickcoord <- renderPrint({
print(input$plot_click)
})
observeEvent(input$plot_click, {
clicked = nearPoints(mtcars, input$plot_click, allRows = TRUE)$selected_
selectedPoint(clicked | selectedPoint())
})
observeEvent(input$plot_reset, {
selectedPoint(rep(FALSE, nrow(mtcars)))
})
output$plot_DT = DT::renderDataTable({
mtcars$sel = selectedPoint()
mtcars = dplyr::filter(mtcars, sel == TRUE) %>% mutate(group_color = input$col)
})
final_DT = reactiveValues()
final_DT$df = data.frame()
FinalData = eventReactive(input$addToDT, {
mtcars$sel = selectedPoint()
mtcars = dplyr::filter(mtcars, sel == TRUE) %>% mutate(group_color = input$col)
final_DT$df = bind_rows(final_DT$df, mtcars)
})
output$final_DT = renderDataTable({FinalData()})
output$plot = renderPlot({
mtcars$sel = selectedPoint()
ggplot(mtcars, aes(wt, mpg, color = mtcars$sel, fill=mpg)) +
geom_point(shape = 21, size = 6, stroke = 2) +
scale_color_manual(values = c("#ffffff00", input$col)) +
scale_fill_viridis_c() +
theme_bw()
})
output$plotSelected = renderPlot({
sel_df = FinalData()
ggplot(sel_df, aes(wt, mpg, fill = group_color, colour = group_color)) +
geom_point(shape = 21, size = 6, stroke = 2) +
scale_color_manual(values = unique(sel_df$group_color)) +
scale_fill_manual(values = unique(sel_df$group_color)) +
theme_bw()
})
observeEvent(input$addToDT, {
selectedPoint(rep(FALSE, nrow(mtcars)))
})
}
shinyApp(ui, server)
I think this is the "crux" of what your are looking for. I used a very similar example that I found in the help for entitled:
A demonstration of clicking, hovering, and brushing
(https://shiny.rstudio.com/reference/shiny/0.13.1/plotoutput)
It is very similar to your example.
I create a matrix of T/F elements where the rows are the observations and the columns are in which batch the observation is selected. So when you launch the whole matrix is False, but as you click on observations the switch to positive in the first column. Then if you click addSelection and continue you start switching the observations in the next column.
Could you confirm that this what you are looking for?
Below is the code.
shinyApp(
ui = basicPage(
fluidRow(
column(
width = 4,
plotOutput("plot",
height = 300,
click = "plot_click", # Equiv, to click=clickOpts(id='plot_click')
),
actionButton("addToDT", "Add selection", icon = icon("plus")), hr(),
h4("Clicked points"),
tableOutput("plot_clickedpoints"),
),
column(
width = 4,
verbatimTextOutput("counter"),
),
)
),
server = function(input, output, session) {
data <- reactive({
input$newplot
# Add a little noise to the cars data so the points move
cars + rnorm(nrow(cars))
})
output$plot <- renderPlot({
d <- data()
plot(d$speed, d$dist, main = paste("No of Sets Chosen", input$addToDT))
})
output$plot_clickinfo <- renderPrint({
cat("Click:
")
str(input$plot_click)
})
selectedPoints <- reactiveVal(rep(FALSE, nrow(cars)))
selectionMatrix <- reactiveVal(matrix(data = F, nrow = nrow(cars), ncol = 7))
observeEvent(input$plot_click, {
clicked <- nearPoints(data(), input$plot_click, "speed", "dist", allRows = TRUE)$selected
selectedPoints(clicked | selectedPoints())
tmp <- unlist(selectionMatrix())
tmp[, (input$addToDT + 1)] <- selectedPoints()
selectionMatrix(tmp)
})
observeEvent(input$addToDT, {
selectedPoints(rep(FALSE, nrow(cars)))
})
output$plot_clickedpoints <- renderTable({
# if (input$addToDT==0) {
res <- selectionMatrix()
return(res)
})
}
)

Graphs not appearing on my Rshiny Dasboard

I am a beginner coder and am working on a Rshiny Dashboard for the Rdata "BOD". Unfortunatley, non of my graphs are showing up on the Dashboard. I was wondering what mistakes i have made in my script as no errors are returned if I run the code.
This is my first post on stack overflow so I appologise if I haven't followed proper etiquette.
Thanks,
My Code:
library(shiny)
library(shinythemes)
library(tidyverse)
library(ggplot2)
library(DT)
library(forecast)
library(fpp)
library(tsbox)
library(hrbrthemes)
#Data preperation
data(BOD)
summary(BOD)
BODFrame <- data.frame(BOD)
Time <- BODFrame$Time
Demand <- BODFrame$demand
tsdata <- ts(BODFrame, frequency = 2)
ddata <- decompose(tsdata, "multiplicative")
plot(ddata)
seasonal <- (ddata$seasonal)
trend <- (ddata$trend)
random <- (ddata$random)
plot(seasonal)
plot(trend)
plot(random)
forecastframe <- auto.arima(BODFrame[,1])
myforecastframe <- forecast(forecastframe, level=c(60), h = 12)
plot(myforecastframe)
#creating UI
ui <- fluidPage(
titlePanel("Effect of Time on BOD (mg/1) Levels"),
navbarPage("Select Tab",
tabPanel("Explore The Data",
mainPanel(
tabsetPanel(
tabPanel("Line Graph", plotOutput("line"), br(), radioButtons("filetype", "Select file type", c("png", "jpeg")), downloadButton("dwd", "Download Graph")),
tabPanel("Histogram", plotOutput("hist"), br(), radioButtons("filetype", "Select file type", c("png", "jpeg")), downloadButton("dwd", "Download Graph"))
)
)
),
tabPanel("Stastical Decomposition of the Data",
sidebarLayout(
sidebarPanel(width = 3,
selectInput(inputId = "Models",
label = "Select Model:",
choices = c("Trend"=1, "Seasonality"=2, "Noise"=3),
selected = 1),
textOutput("text1")
),
mainPanel(
plotOutput("plot2"),
br(), radioButtons("filetype", "Select file type", c("png", "jpeg")), downloadButton("dwd", "Download Graph")
)
)
),
tabPanel("Forecasting BOD",
mainPanel(
plotOutput("Forecast"),
br(), radioButtons("filetype", "Select file type", c("png", "jpeg")), downloadButton("dwd", "Download Graph")
)
)
)
)
#Writing Server Logic
server <- function(input, output){
output$line <- renderPlot({
ggplot(BODFrame, aes(x = Time, y = Demand)) + geom_line(col = "cadetblue4", size = 2) + theme_ipsum() + ggtitle("BOD (mg/1) vs Time") +
geom_point(col = "red", size = 3)
})
output$hist <- renderPlot({
ggplot(data = BODFrame) + geom_histogram(bins = 6, breaks = c(1:7), mapping = aes(x = Time, y = ..Demand..),
col = "cadetblue4") + theme_ipsum() + ggtitle("BOD (mg/1) vs Time Histogram")
})
output$plot2 <- renderPlot({
if (input$Models == 1){
plot(trend, main = "Decomposed Trend - BOD")
}
else if (input$Models == 2){
plot(seasonal, main = "Decomposed Seasonality - BOD")
}
else if (input$Models == 3){
plot(random, main = "Decomposed Noise - BOD")
}
})
output$Forecast <- renderPlot({
plot(myforecastframe, main = "Forecasted Level of BOD (mg/1)")
})
output$text1 <- renderText({
if (input$Models == 1){
paste0("The increasing or decreasing value of BOD in the series")
}
else if (input$Models == 2){
paste0("The repeating short term cycle of the BOD series.")
}
else if (input$Models == 3){
paste0("The random variation in the BOD series.")
}
})
}
shinyApp(ui = ui, server = server)

Saving Dynamically Generated Plots in Shiny

so I have recently adapted some code that I found on StackOverflow to create a dynamic number of plots based on user input. However, I now cannot figure out how to save all of those dynamic plots in one file; when I use ggsave() in downloadHandler, it only saves the last plot generated, as the plots are created inside of a for loop, inside of an observe function. I have tried saving the for loop as a separate function and saving that instead of last plot, I have tried saving the observe() as a function and calling that inside ggsave(), but nothing works. Any idea how I can save all of the generated plots to one file?
ui <- fluidPanel(
sidebarLayout(
sidebarPanel(
#this is the input widget for dataset selection
selectInput(inputId = "dataset_selec",
label = "Choose which Dataset to explore:",
choices = list("NK AD Dataset (Zhang, 2020)",
"APPPS1 Dataset (Van Hove, 2019)",
"Aging T Cell Dataset (Dulken, 2019)"),
selected = "APPPS1 Dataset (Van Hove, 2019)"))
mainPanel(
fluidRow(
column(4,
textInput(inputId = "gene_fp",
label = "Enter gene(s) of interest here, separated by commas: ")
),
column(4,
br(),
checkboxInput("split_fp", "Split the graph?")
),
column(4,
conditionalPanel(condition = "input.split_fp == true",
#display choices to split by
selectInput(inputId = "metadata_split_fp",
label = "Choose how to split the Seurat data: ",
choices = list("Genotype", "Timepoint")))
)
),
#ask users if they want to split the graphs
br(),
fluidRow(
column(4,
textInput("save_name_fp",
label = "Enter a file name: ")
),
column(4,
conditionalPanel(condition = "input.save_name_fp.length > 0",
selectInput("fp_device",
label = "Select file type: ",
choices = list("PNG", "JPEG", "PDF", "TIFF",
"BMP", "SVG")))
),
column(4,
br(),
conditionalPanel(condition = "input.save_name_fp.length > 0",
downloadButton("fp_save", label = "Save Feature Plot"))
)
),
#plot the actual plot
uiOutput("fp_plots")
)
)
)
server <- function(input, output, session) {
datasetInput <- reactive({
switch(input$dataset_selec,
"NK AD Dataset (Zhang, 2020)" = nk_data,
"APPPS1 Dataset (Van Hove, 2019)" = appps1_data,
"Aging T Cell Dataset (Dulken, 2019)" = tcellinfil_data)
})
output$fp_plots <- renderUI({
#validate is to prevent an error message from being displayed when a gene hasn't been entered yet
validate(
need(input$gene_fp !="", "Please enter a gene.")
)
fp_genes <- input$gene_fp
fp_genes <- gsub(" ", "", fp_genes)
fp_genes <- unlist(strsplit(fp_genes, split = ","))
n <- length(fp_genes)
plot_output_list <- lapply(1:n, function(i) {
plotname <- paste("plot", i, sep = "")
if (input$split_fp == TRUE) {plotOutput(plotname, height = 580, width = 1100)}
else {plotOutput(plotname, height = 580, width = 550)}
})
do.call(tagList, plot_output_list)
})
#Here, we take the input of genes, and turn it into a character vector, so that we can iterate
#over it. This needs to be under observe({}) because it involves an input.
#Next, we iterate through the list of genes using a for loop, and within that for loop we assign
#the plots that we want to be displayed to each plotname, which is also sequentially created within
#this for loop, and assign it to the tagList we generated earlier. Basically, we're adding objects to
#list of names we made earlier.
#This needs to be under local({}) control, otherwise each graph doesn't get its own number,
#because of when renderPlot is evaluated
observe({
fp_genes <- input$gene_fp
fp_genes <- gsub(" ", "", fp_genes)
fp_genes <- unlist(strsplit(fp_genes, split = ","))
for (i in 1:length(fp_genes)) {
local({
plotname <- paste("plot", i, sep = "")
gene <- fp_genes[i]
output[[plotname]] <- renderPlot({
if (input$split_fp == TRUE) {FeaturePlot(datasetInput(), features = gene, split.by = input$metadata_split_fp)}
else {FeaturePlot(datasetInput(), features = gene)}
})
})
}
})
output$fp_save <- downloadHandler(
filename = function() {
paste(input$save_name_fp, tolower(input$fp_device), sep = ".")
},
content = function(file) {
ggsave(file, device = tolower(input$fp_device))
}
)
}
Create a list of plots, use grid.arrange to save it in a format you wish, and then save it. Perhaps you can adapt this code.
library(shiny)
library(dplyr)
library(tidyr)
library(ggplot2)
library(gridExtra)
mean_data <- data.frame(
Name = c(paste0("Group_", LETTERS[1:20])),
matx <- matrix(sample(1:1000, 1000, replace = T), nrow = 20)
)
names(mean_data)[-1] <- c(paste0("Gene_", 1:50))
sd_data <- data.frame(
Name = c(paste0("Group_", LETTERS[1:20])),
matx <- matrix(runif(1000, 5, 10), nrow = 20)
)
names(sd_data)[-1] <- c(paste0("Gene_", 1:50))
# Prepare dataset.
# 1. Bind mean and sd data
# 2. Reshape
data <- bind_rows(list(
mean = mean_data,
sd = sd_data
), .id = "stat")
data_mean_sd1 <- data %>%
pivot_longer(-c(Name, stat), names_to = "Gene", values_to = "value") %>%
pivot_wider(names_from = "stat", values_from = "value")
###
ui <- fluidPage(
pageWithSidebar(
headerPanel("Gene_FPKM Value Barplot"),
sidebarPanel(
selectInput(
"selectGeneSymbol",
"Select Gene Symbol:",
choices = unique(data_mean_sd1$Gene),
multiple =F,
width = 400,
selected = 1 #"Igfbp7"
),
selectInput(
"selectGeneSymbol2",
"Select Gene Symbol2:",
choices = unique(data_mean_sd1$Gene),
multiple =F,
width = 400,
selected = 1 #"Igfbp7"
),
selectInput("fp_device",
label = "Select file type: ",
choices = list("PNG", "JPEG", "PDF", "TIFF","BMP", "SVG")
),
actionButton(inputId = "plot1", label = "FPKM"),
actionButton(inputId = "plot2", label = "logFC"),
actionButton(inputId = "all",label = "logFC&FPKM"),br(),
downloadButton("fp_save", label = "Save Feature Plot")
),
mainPanel(
uiOutput("all")
)
)
)
server <- function(input, output, session) {
plot_data1 <- reactive({
subset(data_mean_sd1, Gene %in% input$selectGeneSymbol)
})
plot_data2 <- reactive({
subset(data_mean_sd1, Gene %in% input$selectGeneSymbol2)
})
global <- reactiveValues(out = NULL)
observeEvent(input$plot1, {
global$out <- plotOutput("plot1", height=750)
})
observeEvent(input$plot2, {
global$out <- plotOutput("plot2", height=750)
})
observeEvent(input$all, {
global$out <- plotOutput("plot3", height=1150)
})
output$all <- renderUI({
global$out
})
p1 <- eventReactive(list(input$plot1,
input$all), {
ggplot(data = plot_data1(), aes(x = Name, y = mean,fill=Name)) +
geom_bar(stat = "identity", position = position_dodge(0.9), width = 0.9) +
geom_errorbar(aes(ymin = mean - sd, ymax = mean + sd), width = .2, position = position_dodge(0.9)) +
theme_classic2() +
rotate_x_text(angle = 45) +
theme(legend.position = "none") +
labs(title = input$selectGeneSymbol, x = NULL, y = "FPKM_value") +
theme(plot.title = element_text(hjust = 0.5)) +
theme(plot.margin = unit(c(20, 5, 1, 5), "mm"))+
theme(axis.text.x=element_text(vjust=1,size=12))
})
p2 <- eventReactive(list(input$plot2,
input$all), {
ggplot(data = plot_data2(), aes(x = Name, y = mean,fill=Name)) +
geom_bar(stat = "identity", position = position_dodge(0.9), width = 0.9) +
geom_errorbar(aes(ymin = mean - sd, ymax = mean + sd), width = .2, position = position_dodge(0.9)) +
theme_classic2() +
rotate_x_text(angle = 45) +
theme(legend.position = "none") +
labs(title = input$selectGeneSymbol2, x = NULL, y = "FPKM_value") +
theme(plot.title = element_text(hjust = 0.5)) +
theme(plot.margin = unit(c(20, 5, 1, 5), "mm"))+
theme(axis.text.x=element_text(vjust=1,size=12))
})
#plotlist <- do.call(tagList, list(p1(),p2()))
output$plot1 <- renderPlot({ p1() })
output$plot2 <- renderPlot({ p2() })
output$plot3 <- renderPlot({ grid.arrange(p1(),p2(), ncol=1) })
observe({
plots <- list(p1(),p2())
myplots <- do.call(grid.arrange, c(plots, ncol = 1))
output$fp_save <- downloadHandler(
filename = function() {
paste("myplots", tolower(input$fp_device), sep = ".")
},
content = function(file) {
ggsave(file, plot=myplots, device = tolower(input$fp_device))
}
)
})
}
# Create Shiny app ----
shinyApp(ui = ui, server = server)

How to print a list of ggplots in Shiny in a for loop

I want something like this to happen in Shiny (but just 1 plot, not 4):
THe following steps are the ones that I want for my Shiny app plot:
Print an empty plot
Draw the red abline
Sequentially draw n segments with points
Draw blue dotted abline
Stop
But I got 2 problems with my function that I don't know how to fix:
In the slider "n", for example if I put 10, and press "Correr", I get 8 segments first, I want none at first, and when I press the "play" button the pattern I wrote above starts.
Again, say I put 10, as soon as the blue dotted abline appears, I get an error of "subscript out of bounds"
ui:
ui <-fluidPage(
tabsetPanel(
tabPanel("Simulacion 1",
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "a",label = "a",value = 1 ,min = 1,max = 20),
sliderInput(inputId = "b",label = "b",value = 1 ,min = 1,max= 20),
sliderInput(inputId = "sig",label = "sig",value = 1 ,min = 1,max=50),
sliderInput(inputId = "n",label = "n",value = 1 ,min = 1,max=50,step=1,animate=animationOptions(interval=1000)),
actionButton(inputId= "correr", label = "Correr")),
mainPanel(plotOutput(outputId = "simul1")
)))),)
server:
server <-function(input, output) {
z=qnorm(0.975)
intercepto=c()
pendiente=c()
sim.beta=function(a,b,sig,n){
x=runif(n,1,10)
y=a+b*x+rnorm(n,0,sig)
return(data.frame(x,y))
}
Animacion=eventReactive(input$correr,{
set.seed(123)
s1=sim.beta(input$a,input$b,input$sig,input$n)
grafico=vector('list', ncol(s1))
X=s1$x;Y=s1$y
mod1=lm(s1$y~s1$x)
r11=X;r21=input$a+input$b*r11
for (i in 1:c(input$n+3)) {
if(i==1){
grafico[[i]]=ggplot(s1,aes(x,y))+
theme_classic()+
xlim(0,10)+
ylim(input$a-z*input$sig,2*input$a+input$b*10+z*input$sig)
}
if(i==2){
grafico[[i]]=grafico[[i-1]]+
geom_abline(intercept = input$a,slope = input$b,col=2)
}
else if(i>2 & i<=c(input$n+2)){
grafico[[i]]= local({
i=i
ggplot(s1[1:c(i-2),],aes(x,y))+
theme_classic()+
xlim(0,10)+
ylim(input$a-z*input$sig,2*input$a+input$b*10+z*input$sig)+
geom_abline(intercept = input$a,slope = input$b,col=2)+
geom_segment(aes(x=r11[1:c(i-2)],y=r21[1:c(i-2)]+z*input$sig,xend=r11[1:c(i-2)],yend=r21[1:c(i-2)]-z*input$sig),linetype=2)+
geom_point(aes(X[1:c(i-2)],Y[1:c(i-2)]),col=3,size=3)
})
}
else if(i==c(input$n+3)){
grafico[[i]]= grafico[[i-1]]+
geom_abline(aes(intercept=mod1$coefficients[1],slope=mod1$coefficients[2]),col=4,linetype=2)
}
}
grafico
})
output$simul1 <- renderPlot({
Animacion()[[input$n]]
}, height = 650, width = 650)
}
shinyApp(ui = ui, server = server)
Here is a working solution. I've made a few changes:
animacion is now only dependent on a reactiveTimer. This allows you to generate new plot data in a given interval
animacion only returns one plot, which is then plotted -> you don't need to mess around with input$n which plot should be shown
I've added some logic to introduce a timer counter/index, so that the correct output data is sequentially generated
because the parameter change should only applied when "Correr" is pressed, I've added a new eventReactive to store the parameters (otherwise the updated values would be automatically used every time the timer triggers)
library(shiny)
library(ggplot2)
ui <-fluidPage(
tabsetPanel(
tabPanel("Simulacion 1",
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "a",label = "a",value = 1 ,min = 1,max = 20),
sliderInput(inputId = "b",label = "b",value = 1 ,min = 1,max= 20),
sliderInput(inputId = "sig",label = "sig",value = 1 ,min = 1,max=50),
sliderInput(inputId = "n",label = "n",value = 1 ,min = 1,max=50,step=1,animate=animationOptions(interval=1000)),
actionButton(inputId= "correr", label = "Correr")),
mainPanel(plotOutput(outputId = "simul1")
)))))
server <-function(input, output) {
z=qnorm(0.975)
intercepto=c()
pendiente=c()
animation_timer <- reactiveTimer(1500)
timer_index <- 1
grafico <- list()
sim.beta=function(a,b,sig,n){
x=runif(n,1,10)
y=a+b*x+rnorm(n,0,sig)
return(data.frame(x,y))
}
animation_parameters <- eventReactive(input$correr, {
list(a = input$a,
b = input$b,
sig = input$sig,
n = input$n)
})
Animacion = eventReactive(animation_timer(),{
set.seed(123)
s1=sim.beta(animation_parameters()$a,
animation_parameters()$b,
animation_parameters()$sig,
animation_parameters()$n)
X=s1$x;Y=s1$y
mod1=lm(s1$y~s1$x)
r11=X;r21=input$a+input$b*r11
if(timer_index ==1){
grafico <<- ggplot(s1,aes(x,y))+
theme_classic()+
xlim(0,10)+
ylim(animation_parameters()$a-z*animation_parameters()$sig,2*animation_parameters()$a+animation_parameters()$b*10+z*animation_parameters()$sig)
}
if(timer_index ==2){
grafico <<- grafico +
geom_abline(intercept = animation_parameters()$a,slope = animation_parameters()$b,col=2)
}
else if(timer_index>2 & timer_index<=c(animation_parameters()$n+2)){
grafico <<- local({
i= timer_index
ggplot(s1[1:c(i-2),],aes(x,y))+
theme_classic()+
xlim(0,10)+
ylim(animation_parameters()$a-z*animation_parameters()$sig,2*animation_parameters()$a+animation_parameters()$b*10+z*animation_parameters()$sig)+
geom_abline(intercept = animation_parameters()$a,slope = animation_parameters()$b,col=2)+
geom_segment(aes(x=r11[1:c(i-2)],y=r21[1:c(i-2)]+z*animation_parameters()$sig,xend=r11[1:c(i-2)],yend=r21[1:c(i-2)]-z*animation_parameters()$sig),linetype=2)+
geom_point(aes(X[1:c(i-2)],Y[1:c(i-2)]),col=3,size=3)
})
}
else if(timer_index ==c(animation_parameters()$n+3)){
grafico <<- grafico +
geom_abline(aes(intercept=mod1$coefficients[1],slope=mod1$coefficients[2]),col=4,linetype=2)
}
# update timer_index
if (timer_index == animation_parameters()$n + 3) {
timer_index <<- 1
} else {
timer_index <<- timer_index + 1
}
grafico
})
output$simul1 <- renderPlot({
Animacion()
}, height = 650, width = 650)
}
shinyApp(ui = ui, server = server)
P.S. I'm actually not sure if local and animation_parameters work together, maybe you have to tweak that.

Update Graph in R Shiny react

I would like to understand why the graph does not update in the RShiny App generated from the code below.
What I try to do:
Generate a sample of n_data observations from a beta-distribution with certain shape parameters.
Plot the histogram of this sample, together with its mean.
Do this e_samples times and keep the means, show an updated graph every second
Plot the histogram of the vector with e_samples means
library(shiny)
library(ggplot2)
# Design the interface
ui <- fluidPage(titlePanel("Population vs sample"),
sidebarLayout(
# Function to determine the layout
sidebarPanel(
sliderInput(
'shape1',
label = 'Population shape 1:',
min = 1,
max = 9,
value = 2 ,
step = 1
),
sliderInput(
'shape2',
label = 'Population shape 2:',
min = 1,
max = 9,
value = 2 ,
step = 1
),
textInput("n_data", label = "Sample size:",
value = '25'),
textInput("e_samples", label = "number of samples:",
value = '5'),
actionButton("RerunButton", "New sample", icon("play"))
),
mainPanel(plotOutput('Sample'))
))
# Set up the server
server <- function(input, output) {
set.seed(1234)
xvals <- seq(.001, .999, by = 0.001)
# Define reactive values
avgs <- reactiveVal(vector(mode = "list", length = 1))
vals <- reactiveVal(0)
s <- reactiveVal()
sample_plot <- reactiveVal()
# This is where I hope to generate a new graph every second.
observe({
invalidateLater(1000)
cat(paste('vals', vals(), '\n'))
cat(paste('avgs', avgs(), '\n'))
if (vals() < input$e_samples) {
s(data.frame(
d = rbeta(
n = input$n_data,
shape1 = as.numeric(input$shape1),
shape2 = as.numeric(input$shape2)
)
))
temp <- s()
vals(vals() + 1)
averages <- avgs()
averages[vals()] <- mean(temp$d)
avgs(averages)
sample_plot(
ggplot() +
geom_histogram(
data = temp,
aes(x = d),
binwidth = 0.1,
fill = 'white',
col = 'black'
) +
geom_vline(xintercept = mean(temp$d)) +
xlim(0, 1) +
xlab('observation value') +
ylab('count')
)
}
})
output$Sample <- renderPlot({
sample_plot()
})
observeEvent(input$RerunButton, {
vals(0)
})
observeEvent(input$RerunButton, {
avgs(vector(mode = "list", length = 1))
})
}
shinyApp(ui = ui, server = server)
The code above only updates the graph once. Why?
All of your reactiveValues that keep changing are triggering your observe repeatedly right away. This happens five times in succession, well before 1000 msec has elapsed. To prevent this from happening, you would need to use isolate for your reactiveValues. See if this gives the right behavior:
# Set up the server
server <- function(input, output, session) {
set.seed(1234)
xvals <- seq(.001, .999, by = 0.001)
# Define reactive values
avgs <- reactiveVal(vector(mode = "list", length = 1))
vals <- reactiveVal(0)
s <- reactiveVal()
sample_plot <- reactiveVal()
# This is where I hope to generate a new graph every second.
observe({
invalidateLater(1000, session)
#cat(paste('vals', vals(), '\n'))
#cat(paste('avgs', avgs(), '\n'))
if (isolate(vals()) < input$e_samples) {
s(data.frame(
d = rbeta(
n = input$n_data,
shape1 = as.numeric(input$shape1),
shape2 = as.numeric(input$shape2)
)
))
temp <- s()
isolate(vals(vals() + 1))
averages <- isolate(avgs())
averages[vals()] <- mean(temp$d)
avgs(averages)
}
})
sample_plot <- reactive({
ggplot() +
geom_histogram(
data = s(),
aes(x = d),
binwidth = 0.1,
fill = 'white',
col = 'black'
) +
geom_vline(xintercept = mean(s()$d)) +
xlim(0, 1) +
xlab('observation value') +
ylab('count')
})
output$Sample <- renderPlot({
sample_plot()
})
observeEvent(input$RerunButton, {
vals(0)
})
observeEvent(input$RerunButton, {
avgs(vector(mode = "list", length = 1))
})
}

Resources