Clicking through plots in Shiny app - r

In my Shiny app, I use a for loop to make different plots and I would like the user to be able to click through each one. How would I achieve this as it currently just goes to the final plot?
library(shiny)
server <- function(input, output, session) {
# data
v <- c(9,8,7,8,9,5,6,7,4,3)
w <- c(3,4,2,3,3,3,2,3,4,5)
x <- c(1,3,4,6,2,4,6,8,6,3)
y <- c(4,5,2,4,2,1,2,5,7,8)
z <- c(5,9,8,6,4,6,8,9,6,7)
df <- data.frame(v, w, x, y, z)
# initial plot that will allow user to change parameters (haven't implemented yet)
output$plot <- renderPlot(plot(df[[1]],df[[2]]))
# wait until the button is triggered
observeEvent(input$run, {
for (i in 5){
output$plot <- renderPlot(plot(df[[1]],df[[i]], main = i))
}
})
}
ui <- fluidPage(
actionButton("run", "Generate"),
plotOutput("plot")
)
shinyApp(ui = ui, server = server)

You just need to use the variable that will maintain the count for each click:
library(shiny)
server <- function(input, output, session) {
# data
v <- c(9,8,7,8,9,5,6,7,4,3)
w <- c(3,4,2,3,3,3,2,3,4,5)
x <- c(1,3,4,6,2,4,6,8,6,3)
y <- c(4,5,2,4,2,1,2,5,7,8)
z <- c(5,9,8,6,4,6,8,9,6,7)
df <- data.frame(v, w, x, y, z)
# initial plot that will allow user to change parameters (haven't implemented yet)
output$plot <- renderPlot(plot(df[[1]],df[[2]]))
count<-0 # This is the counter which keeps track on button count
observeEvent(input$run, {
count <<- count + 1 # Increment the counter by 1 when button is click
if(count<6){
# Draw the plot if count is less than 6
output$plot <- renderPlot(plot(df[[1]],df[[count]],main = count))
}
else{
# Reset the counter if it is more than 5
count <- 0
}
})
}
ui <- fluidPage(
actionButton("run", "Generate"),
plotOutput("plot")
)
shinyApp(ui = ui, server = server)

Instead of using for loop you can try by directly using the action button click count i.e. input$run.The following code will generate the plots one by one till the click count is less than or equal to 5 and then returns to initial plot as soon the as click count exceeds 5. You can modify the else statement as per your wish.
observeEvent(input$run, {
if(input$run <= 5){
output$plot <- renderPlot(plot(df[[1]],df[[input$run]], main = input$run))
}else output$plot <- renderPlot(plot(df[[1]],df[[2]]))
})
Hope this might be helpful

Related

Click Interactive Plot in R Shiny

I'm trying to create a plot with a bunch of boxes and then when a box gets clicked on it gets colored in up. I'm having two issues with this. 1. I can't figure out a way for the figure to update dynamically when I click. 2. I can't figure out how to store the values that come out of the click input variable so that I have stored all previous clicks and would be able to color in multiple boxes. You can see a few ways I've tried to solve and test either of the two issues and I'm not having any luck. Any help with either issue would be appreciated.
ui <- fluidPage(
# Application title
titlePanel("Boxes"),
sidebarLayout(
sidebarPanel(
textOutput("text")),
# Get it it's a pun
mainPanel(
plotOutput("boxPlot",click = "test")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
vals <- reactiveValues(x=NA,y=NA,test=NA)
observeEvent(input$click, {
vals$x <- c(vals$x,input$test$x)
vals$y <- c(vals$y,input$test$y)
vals$test <- input$test$x
})
output$boxPlot <- renderPlot({
par(mai=c(0,0,0,0))
plot(1,ylim=c(2,15),xlim=c(2,15),type='n',yaxs='i',xaxs='i',ylab='',xlab='',axes=F)
for (i in 2:15) {
abline(v=i)
abline(h=i)
}
observeEvent(input$click, { rect(floor(input$test$x),floor(input$test$y),ceiling(input$test$x),ceiling(input$test$y),col='blue')})
# if (length(vals$x) > 0) {
# rect(floor(vals$x[1]),floor(vals$y[1]),ceiling(vals$x[1]),ceiling(vals$y[1]),col='blue')
# }
})
# output$text <- renderText(vals$x[length(vals$x)])
output$text <- renderText(vals$test)
}
# Run the application
shinyApp(ui = ui, server = server)
This might be a simple solution.
You should only have one single observeEvent for your click event. In that observeEvent, store your x and y values as reactiveValues as you current are doing.
Then, your renderPlot should plot the grid lines and filled in rectangles based on your reactiveValues. By including input$boxPlot_click (as I called it) in renderPlot the plot will be redrawn each click.
library(shiny)
ui <- fluidPage(
titlePanel("Boxes"),
sidebarLayout(
sidebarPanel(
textOutput("text")),
mainPanel(
plotOutput("boxPlot", click = "boxPlot_click")
)
)
)
server <- function(input, output) {
vals <- reactiveValues(x=NA,y=NA)
observeEvent(input$boxPlot_click, {
vals$x <- c(vals$x,input$boxPlot_click$x)
vals$y <- c(vals$y,input$boxPlot_click$y)
})
output$boxPlot <- renderPlot({
input$boxPlot_click
par(mai=c(0,0,0,0))
plot(1,ylim=c(2,15),xlim=c(2,15),type='n',yaxs='i',xaxs='i',ylab='',xlab='',axes=F)
for (i in 2:15) {
abline(v=i)
abline(h=i)
}
for (i in seq_along(length(vals$x))) {
rect(floor(vals$x),floor(vals$y),ceiling(vals$x),ceiling(vals$y),col='blue')
}
})
output$text <- renderText(paste0(vals$x, ', ' , vals$y, '\n'))
}
shinyApp(ui = ui, server = server)

How to wait for two blocks of code to run in R Shiny

Suppose I have the following blocks of codes in Shiny:
library(shiny)
rv <- reactiveValues()
observe({
# Event A
# Code Block A
# The code below signals the end of Code Block A
rv$event_a <- F
rv$event_a <- T
})
observe({
# Event B
# Code Block B
# The code below signals the end of Code Block B
rv$event_b <- F
rv$event_b <- T
})
observe({
rv$event_a
rv$event_b
if(rv$event_a & rv$event_b) {
# Do something only after both Code Blocks A and B finish running.
# Code Block C
}
})
As you can see, I'm toggling the reactive values in Blocks A and B from FALSE to TRUE to trigger Block C to run.
I want to write the code so that the cycle can repeat itself:
Some trigger -> Block A & B -> C ->
Some trigger -> Block A & B -> C ...
My questions are the following:
How can I make Code Block C run only once, when both Code Block A and B finished running?
How else can I achieve triggering Code Block C without the weird toggling of reactive values (between FALSE and TRUE) that I am currently relying on?
I have accomplished this before by eventObserving or eventReacting to the reactive objects or reactiveValues generated by 'code-block-a' or 'code-block-b'. I have attached 3 small shiny app examples to give insight into this approach using different methods (hopefully these will help answer the original question - or at least give some ideas).
This app will create a table in 'code-block-a' with as many rows as the sliderInput has selected. Once this 'event_a()' reactive is updated 'code-block-b' subsets one row. Once 'code-block-b' updates its object 'event_b()' a modal is displayed showing the selected row in a table.
library(shiny)
library(tidyverse)
ui <- fluidPage(
sliderInput("slide", "slide", value = 5, min = 1, max = 10),
actionButton("go", "go"),
)
server <- function(input, output, session) {
rv <- reactiveValues(tr1 = 0, el = 0)
final <- reactiveValues()
#CODE BLOCK A#
#takes slider input and makes a table with that many rows
event_a <- eventReactive(input$go,{
nums <- seq(1, input$slide, by = 1)
l <- 1:length(nums)
tibble(Letter = letters[l],
Value = nums)
})
#trigger next series of events in response to event_a()
#observeEvent(event_a(),{
# rv$el <- rv$el + 1
# })
##CODE BLOCK B##
# this will subset a row of data based on the value of the reactive
event_b <- eventReactive(event_a(), {
row <- sample(1:nrow(event_a()), 1)
event_a()[row,]
})
#look for changes in event_b() to trigger event C
#the loading of event_b will trigger the modal via rv$tr1
# observeEvent(event_b(), {
# rv$tr1 <- rv$tr1 + 1
# })
#side effect make a table from event_b() to be shown in modal
output$modal_plot <- renderTable({
event_b()
})
##CODE BLOCK C##
#launch modal showing table
observeEvent(event_b(), {
showModal(modalDialog(title = "Table",
"This is a table",
tableOutput("modal_plot"),
inline = T))
})
}
shinyApp(ui, server)
Or if all your 'code-block' are observers you can use reactive values that are updated inside of an observer. I have found this flexible if multiple things need to happen to trigger something downstream:
library(shiny)
library(tidyverse)
ui <- fluidPage(
sliderInput("slide", "slide", value = 5, min = 1, max = 10),
actionButton("go", "go"),
)
server <- function(input, output, session) {
rv <- reactiveValues(tr1 = 0, el = 0)
final <- reactiveValues()
#CODE BLOCK A#
#takes slider input and makes a table with that many rows
event_a <- eventReactive(input$go,{
nums <- seq(1, input$slide, by = 1)
l <- 1:length(nums)
tibble(Letter = letters[l],
Value = nums)
})
#trigger next series of events in response to event_a()
observeEvent(event_a(),{
rv$el <- rv$el + 1
})
##CODE BLOCK B##
# this will subset a row of data based on the value of the reactive
event_b <- eventReactive(rv$el, ignoreInit = T, {
row <- sample(1:nrow(event_a()), 1)
event_a()[row,]
})
#look for changes in event_b() to trigger event C
#the loading of event_b will trigger the modal via rv$tr1
observeEvent(event_b(), {
rv$tr1 <- rv$tr1 + 1
})
#side effect make a table from event_b() to be shown in modal
output$modal_plot <- renderTable({
event_b()
})
##CODE BLOCK C##
#launch modal showing table
observeEvent(rv$tr1, ignoreInit = T, {
showModal(modalDialog(title = "Table",
"This is a table",
tableOutput("modal_plot"),
inline = T))
})
}
shinyApp(ui, server)
Furthermore, if you are wanting something that iterates like a loop here is an example that describes the above process, but plots each row of data in a modal one row at a time and asking for user input each time:
library(shiny)
library(tidyverse)
ui <- fluidPage(
sliderInput("slide", "slide", value = 5, min = 1, max = 10),
actionButton("go", "go"),
tableOutput("df"),
tableOutput("user_choices_table")
)
server <- function(input, output, session) {
rv <- reactiveValues(tr1 = 0, el = 0)
final <- reactiveValues()
#STEP 1
#some function/series of events that gives us a some data
data1 <- eventReactive(input$go,{
c <- seq(1, input$slide, by = 1)
l <- 1:length(c)
out_table <- tibble(Letter = letters[l],
Value = c)
return(out_table)
})
#side effect - return data1 to UI
output$df <- renderTable({
data1()
})
#number of max iterations we will go though (dependent number of rows in data1)
num_iterations <- reactive({
nrow(data1())
})
#trigger next series of events in response to data1()
#this will get us from 0 to 1 and another observer will be used to add
#all the way up to the max_iterations
observeEvent(data1(),{
rv$el <- rv$el + 1
})
#this ^ observer is like entering the loop on the first iteration
##STEP 2##
##start/continue the "disjointed-loop".
#Subset data1 into smaller piece we want based on rv$el
#this will be our 'i' equivalent in for(i in ...)
#data subset
data2 <- eventReactive(rv$el, ignoreInit = TRUE, {
data2 <- data1()[rv$el,]
return(data2)
})
#side effect make a plot based on data2 to be shown in modal
output$modal_plot <- renderPlot({
d <- data2()
ggplot()+
geom_col(data = d, aes(x = Letter, y = Value, fill = Letter))+
theme_linedraw()
})
#once we get our data2 subset ask the user via modal if this is what they want
#the loading of data2 will trigger the modal via rv$tr1
observeEvent(data2(), {
rv$tr1 <- rv$tr1 + 1
})
##STEP 3##
#launch modal showing plot and ask for user input
observeEvent(rv$tr1, ignoreInit = TRUE, {
showModal(modalDialog(title = "Make a Choice!",
"Is this a good selection?",
plotOutput("modal_plot"),
checkboxGroupInput("check", "Choose:",
choices = c("Yes" = "yes",
"No" = "no"),
inline = T),
footer = actionButton("modal_submit", "Submit")))
})
#when user closes modal the response is saveed to final[[character representing number of iteration]]
observeEvent(input$modal_submit, {
final[[as.character(rv$el)]] <- input$check
if(rv$el < num_iterations()){
rv$el <- rv$el + 1 #this retriggers step2 to go again
} else {
rv$done <- rv$done + 1
} #breaks the disjointed loop and trigger start of next reactions
})
#and the modal is closed
observeEvent(input$modal_submit, {
removeModal()
})
final_choice <- eventReactive(rv$done, ignoreInit = TRUE,{
enframe(isolate(reactiveValuesToList(final))) %>%
mutate(name = as.numeric(name),
value = unlist(value)) %>%
arrange(name)
})
output$user_choices_table <- renderTable({
final_choice()
})
}
shinyApp(ui, server)

Challenge with dynamically generated, interactive R Shiny plots (mostly functioning code)

Below is the minimum code. It works, but there is a weird problem. Here is what works:
User can select a number of plots (default is 3).
User can click in a plot and have that value represented (partly works).
Steps to reproduce the "partly works":
At launch, click in plot #3, no problem.
Click in plot #2, nothing happens.
Reduce the number of plots from 3 to 2 and then back to 3.
Click in plot #2, now it works.
Click in plot #1, nothing happens.
Reduce the number of plots from 3 to 1 and then back to 3.
Click in plot #1, now it works.
If you reload the app, and start with step 6 above, all plots are interactive as expected.
rm(list=ls())
library(shiny)
#
# Dynamic number of plots: https://stackoverflow.com/questions/26931173/shiny-r-renderplots-on-the-fly
# That can invalidate each other: https://stackoverflow.com/questions/33382525/how-to-invalidate-reactive-observer-using-code
#
ui <- (fluidPage(sidebarLayout(
sidebarPanel(
numericInput("np", "Plots:", min=0, max=10, value=3, step=1)
)
,mainPanel(
fluidRow(uiOutput("plots"))
)
)))
server <- function(input, output, session) {
val <- reactiveValues()
dum <- reactiveValues(v=0)
obs <- list()
### This is the function to break the whole data into different blocks for each page
plotInput <- reactive({
print("Reactive")
np <- input$np
for(i in 1:np) {
cx <- paste0("clk_p",i); dx <- paste0("dbl_p",i); px <- paste0("p",i)
obs[[cx]] <- observeEvent(input[[cx]], {
req(input[[cx]]); val[[px]] <- input[[cx]]$x; dum$v <- dum$v+1; print(paste("Dum",dum$v))
})
obs[[dx]] <- observeEvent(input[[dx]], {
req(input[[dx]]); val[[px]] <- NULL
})
}
return (list(np=np))
})
##### Create divs######
output$plots <- renderUI({
print("Tag plots")
pls <- list()
for(i in 1:plotInput()$np) {
pls[[i]] <- column(4,
plotOutput(paste0("p",i), height=200, width=200
,click=paste0("clk_p",i)
,dblclick=paste0("dbl_p",i))
)
}
tagList(pls)
})
observe({
print("Observe")
lapply(1:plotInput()$np, function(i){
output[[paste("p", i, sep="") ]] <- renderPlot({
print(paste("Plot",dum$v))
x <- val[[paste0("p",i)]]
x <- ifelse(is.null(x),"NA",round(x,2))
par(mar=c(2,2,2,2))
plot(x=runif(20), y=runif(20), main=i, xlim=c(0,1), ylim=c(0,1), pch=21, bg="gray", cex=1.5)
if(is.numeric(x)) abline(v=x, col="blue")
rm(x)
})
})
})
}
shinyApp(ui, server)
Here is a working version of what you're trying to do:
library(shiny)
ui <- fluidPage(
sidebarPanel(
numericInput("num", "Plots:", 3)
),
mainPanel(
uiOutput("plots")
)
)
server <- function(input, output, session) {
obs <- list()
val <- reactiveValues()
observe({
lapply(seq(input$num), function(i){
output[[paste0("plot", i) ]] <- renderPlot({
click_id <- paste0("clk_p",i);
plot(x = runif(20), y = runif(20), main=i)
if (!is.null(val[[click_id]])) {
abline(v = val[[click_id]], col = "blue")
}
})
})
})
observe({
lapply(seq(input$num), function(i){
id <- paste0("clk_p",i);
if (!is.null(obs[[id]])) {
obs[[id]]$destroy()
}
val[[id]] <- NULL
obs[[id]] <<- observeEvent(input[[id]], {
cat('clicked ', id, ' ', input[[id]]$x, '\n')
val[[id]] <- input[[id]]$x
}, ignoreInit = TRUE)
})
})
output$plots <- renderUI({
lapply(seq(input$num), function(i) {
id <- paste0("plot", i)
plotOutput(id, height=200, width=200, click=paste0("clk_p",i))
})
})
}
shinyApp(ui,server)
A few main pointers for anyone who sees this in the future:
The main issue with the original code was that all the observers were registering only for the last ID. This is a bit of an advanced concept and has to do with the way environments in R work and because they were created in a for loop. The fix for this is to use lapply() instead of a for loop to create the observers
Another issue is that obs was overwriting the observers in the list, but the previous observers still exist and can still fire, so I added logic to destroy() existing observers.
One of the most important rules in shiny is to not place side effects inside reactives (plotInput has side effects) so I rewrote the code in a way that avoids that

shiny reactive object not found

I think I'm missing something with respect to reactives in my Shiny app. Here's a MRE that shows my problem printing y.
library(shiny)
ui <- fluidPage(
titlePanel("Test"),
textAreaInput("text", "Text", "", width = "400px"),
verbatimTextOutput("text"),
actionButton("do", "Run"),
textOutput("result")
)
server <- function(input, output) {
observeEvent(input$do, {
y <- reactive({
x <- ""
t <- 1
while (t < 5) {
x <- paste(input$text, x, sep=",")
t <- t + 1
}
})
})
output$result <- renderPrint({y})
}
shinyApp(ui = ui, server = server)
You shouldn't put a reactive value inside of an observeEvent or observe call. In fact, it has been advised by Joe Cheng to never nest either observe or reactive functions within either themselves or the other. They are separate things used for different purposes. Since you want the reactive y to be created based on when input$do is clicked, you should use eventReactive:
server <- function(input, output) {
y <- eventReactive(input$do, {
x <- ""
t <- 1
while (t < 5) {
x <- paste(input$text, x, sep=",")
t <- t + 1
}
return(x)
})
output$result <- renderText({y()})
}
I changed renderPrint() to renderText() so that it displays your desired output. Also, since y is a reactive, you need to add y() after it in the renderText() call. I also added return(x) to the eventReactive call, otherwise t would be the value returned.
I the problem is that your call to reactive() does not return anything. Wrapping an expression inside reactive assigns the return value of the expression to a variable each time a reactive value inside the expression is changed. Reactive values are usually all input$... variables, and those that you store in reactiveValues() objects.
If I get you right you want to change and print y every time the "run" button is hit. Save this to a reactiveValue() collection (similarly accessible like a list), and then put this inside your renderPrint function.
From your code I reckon that you want y to be the value of x after the while loop.
library(shiny)
ui <- fluidPage(
titlePanel("Test"),
textAreaInput("text", "Text", "", width = "400px"),
verbatimTextOutput("text"),
actionButton("do", "Run"),
textOutput("result")
)
server <- function(input, output) {
values <- reactiveValues()
observeEvent(input$do, {
x <- ""
t <- 1
while (t < 5) {
x <- paste(input$text, x, sep=",")
t <- t + 1
}
values$y <- x
})
output$result <- renderPrint({values$y})
}
shinyApp(ui = ui, server = server)

How to return multiple values in R ShinyServer

I am doing the following:
using R ShinyUI, get client inputs on ranges of variables A, B, C;
in R ShinyServer, read in a csv file, and using the client inputs to slice the csv, and get the portion that I need;
Perform a loop calculation on the csv, calculate various statistics from the loop output, and plot all these statistics.
Pseudo code:
data = read.csv('file.csv')
shinyServer(function(input, output) {
data <- reactive({
data = data[data$A<INPUT1 & data$B> INPUT2 & data$C<INPUT3,]
})
for (i in 1:dim(data)[1]){
result1[i] = xxx
result2[i] = xxx
}
output$plot <- renderPlot({
plot(result1)
})
})
The above code does not work. I want to know:
How to correctly incorporate user input and get the variable "data,"
How to plot result1 and result2 from output$plot
Thanks!
The for loop should be inside a the renderPlot, so each time the input$month changes, the reactive data will change and then the for lop will update your variables. If you have the for loop outside a reactive expression, it will be executed only once when the app starts, but after changes in the input.
Below is simple example based on the pseudo code you provide in your original question to illustrate the possible solution.
library(shiny)
ui <- shinyUI( fluidPage(
fluidRow(
column(4,
numericInput("input1", "Speed >", 8),
numericInput("input2", "Dist >", 15)
),
column(8,
plotOutput("plot")
)
)
))
server <- shinyServer(function(input, output) {
dat0 <- cars
data <- reactive({
dat0[dat0$speed > input$input1 & dat0$dist > input$input2,]
})
output$plot <- renderPlot({
s <- dim(data())[1]
result1 <- numeric(s)
result2 <- numeric(s)
for (i in 1:s){
result1[i] <- data()[i, 1]
result2[i] <- data()[i, 2]
}
plot(result1, result2)
})
})
shinyApp(ui = ui, server = server)

Resources