In RShiny, using actionButton values within conditionalPanel causing many headaches - r

I have spent the last half hour reading about conditionalPanel, as well as actionButtons and how their value increments by 1 each time the button is pressed. With that said, here's what I'm trying to do below and what problem I am facing:
The problem
I am using conditionalPanel to toggle between two different renderPlots. The reason I am using two different renderPlots, rather than both plots within the same renderPlot, is because it is important that the two plots have different height / width dimensions. I am using the actionButton's value within the conditionalPanel, which IS BAD.
What I want
I want movement.chart to be displaying if togglemovement actionButton was more recently pressed of the 2 buttons. I want shot.chart to be displaying if toggleshotchart was more recently pressed.
What I wish I could do
If I could only use plotData$value within the conditional, I would be all set. plotData$value is being used to enter if statements in the renderPlots to determine which plots should be plotted, but I can't use them in conditionalPanel.
With all of that said, here's a shortened version of my project.
library(shiny)
# 2. UI layout
# ============
ui <- fluidPage(
fluidRow(
column(width = 4, align = 'center',
actionButton(inputId = 'toggleshotchart', label = 'Launch Shots'),
actionButton(inputId = 'togglemovement', label = 'Launch Movements')
),
# This displays either the shot or movement chart
# ===============================================
column(width = 8, align = 'left',
conditionalPanel("input.togglemovement > input.toggleshotchart",
plotOutput('movement.chart', height = 650, width = 1128)
),
conditionalPanel("input.togglemovement <= input.toggleshotchart",
plotOutput('shot.chart', height = 846, width = 900)
)
)
)
)
# 3. Server Layout
# ================
server <- shinyServer(function(input, output) {
# Create some reactive stuff to toggle charts
plotData <- reactiveValues(value = NULL)
observeEvent(input$toggleshotchart, {
plotData$value <- 0
})
observeEvent(input$togglemovement, {
plotData$value <- 1
})
# create the first chart
output$shot.chart <- renderPlot({
# this chart is displayed at launch
if (is.null(plotData$value)) {
plot(c(1,2,3,4,5), c(1,2,3,4,5))
}
# this chart SHOULD BE displayed after clicking toggleshotchart
else if(plotData$value == 0) {
hist(rnorm(10))
}
# Do nothing (prev displayed motion chart here)
else {
# do nothing
}
})
# this chart SHOULD BE displayed after clicking togglemovementchart
output$movement.chart <- renderPlot({
if(plotData$value == 1) {
hist(c(1,2,3,2,1))
}
})
})
shinyApp(ui = ui, server = server)
I read a lot about resetting the actionButton's value, but couldn't find anything that would fix my problem. It seems like resetting actionButton values to 0 is not easy / cannot be done. Any thoughts would be greatly appreciated on this task of mine!
EDIT - I asked this related question earlier - In RShiny, change plot width / height for separate plots within same renderPlot() - where the solution works, but is for a slightly different question. The more I worked on this issue, the more I realized that my initial question did not address my actual problem.

What about using radioButtons to toggle between two plots?
library(shiny)
# 2. UI layout
# ============
ui <- fluidPage(
fluidRow(
column(width = 4,
radioButtons("choice_plot","Launch",choices=list("Shots","Movements"), selected="Shots")),
# This displays either the shot or movement chart
# ===============================================
column(width = 8, align = 'left', uiOutput("plot_ui"))
)
)
# 3. Server Layout
# ================
server <- shinyServer(function(input, output) {
output$plot_ui <- renderUI({
if(input$choice_plot == 'Shots') {
plot.width = 1128
plot.height = 650
}else{
plot.width = 900
plot.height = 846
}
plotOutput('plot', width = plot.width, height = plot.height)
})
output$plot <- renderPlot({
if(input$choice_plot == 'Shots'){
hist(rnorm(10))
}else{
hist(c(1,2,3,2,1))
}
})
})
shinyApp(ui = ui, server = server)
Here is as well my try with actionButton:
library(shiny)
# 2. UI layout
# ============
ui <- fluidPage(
fluidRow(
column(width = 4,
actionButton("button1", "Shots"),
actionButton("button2", "Movements")),
# This displays either the shot or movement chart
# ===============================================
column(width = 8, align = 'left', uiOutput("plot_ui"))
)
)
# 3. Server Layout
# ================
server <- shinyServer(function(input, output) {
output$plot_ui <- renderUI({
if(input$button1 == 1) {
plot.width = 1128
plot.height = 650
}else{
plot.width = 900
plot.height = 846
}
plotOutput('plot', width = plot.width, height = plot.height)
})
v <- reactiveValues(data = NULL)
observeEvent(input$button1, {
v$data <- rnorm(10)
})
observeEvent(input$button2, {
v$data <- c(1,2,3,2,1)
})
output$plot <- renderPlot({
if (is.null(v$data)) return()
hist(v$data)
})
})
shinyApp(ui = ui, server = server)

Related

Trying to use an actionButton to get next random image nested within random tab

The problem
I am building a shiny app with a questionnaire, about dogs breeds and living spaces. And for the questions each dog breed appears in a tab in a random order, and within those tabs I show different houses where you could have those dog breeds.
The main idea is to randomize both the dog breeds (by tab) and each of the housing possibilities nested within that tab.
What I have so far
This is my best working example so far in a working shiny app and all the files and scripts are in this github repository so you can clone it to test all the example code
In this app the tabs do show on random order (by breed). The first house option also shows well, my idea is that when you press the next house action button it shows the next random house without repeating (it is important that the person answering does not see the next house beforehand for them not to be influenced by that)
I have tried two different approaches to this problem included in the github repository in the folders FirstTry and SecondTry.
First try
This is the working shiny app, here is the code for it, working in this link and with the code in this link
Here is the code of the ui which is really simple, just a tabpanel with the uiOutput mytabs which contain all the tabs:
library(shiny)
shinyApp(
ui = tagList(
navbarPage("",
tabPanel("DogImages",
fluidRow(
column(width = 6,
uiOutput('mytabs')
) )
)#cerrado tab panel 2
) #cerrado de navbar Panel
), #cerrado ui
Then the server is where all the magic happens
server <- function(input, output) {
#####################################
# Tabs breeds
## This renderUI generates each tab with a lapply
output$mytabs = renderUI({
## I have three breeds
Breeds <- c(1:3)
## And then create a random order of them
Random <- sample(Breeds, 3)
#### And now loop through each random breed
myTabs = lapply(Random, function(i){
### Starts with the first random breed
tabPanel(paste("Breed", i),
###
fluidPage(
fluidRow(
column(6,
wellPanel(
###Here it sources random breed i
img(src = paste0("Dog",i, ".jpg"), height = 300, width = 300))),
##then within that I want to have the
##Random houses but here is where I run
##into problems
renderUI({
##Starting with random habitats
Habitats <- c(1:3)
RandomH <- sample(Habitats, 3)
## The idea is that I use this new button to show the next house
column(6,actionButton("New_Button", "Next house"),
wellPanel(
uiOutput(paste0("PlotHouse",RandomH[1]))) )
})
)
))
####
})
do.call(tabsetPanel, myTabs)
})
and this is just the source of each house and closure of the server and shiny:
output$PlotHouse1 <- renderUI({
img(src = "House1.jpg", height = 300, width = 300)
})
output$PlotHouse2 <- renderUI({
img(src = "House2.jpg", height = 300, width = 300)
})
output$PlotHouse3 <- renderUI({
img(src = "House3.jpg", height = 300, width = 300)
})
}
) #Close shiny app
So this works for dog breeds but not for not , and I have to make the next button to show the next random house, which is what I try to do in the folder SecondTry
Second try
for this one I change the code within the second renderUI within the lapply:
renderUI({Habitats <- c(1:3)
RandomH <- sample(Habitats, 3)
### Here I add the reactive value j = 1 to move the RandomH along
Values <- reactiveValues(j = 1)
###This is to show j just for the question
output$Numb <- renderText(Values$j)
### Here I state that every time I press new button it adds 1 to j
observe({input$New_Button
isolate(Values$j <- Values$j + 1)
})
## and this is the action button for next house
column(6,actionButton("New_Button", "Next house"),
textOutput("Numb"),
## Here I say show me img PlotHouse[j]
wellPanel(uiOutput(paste0("PlotHouse",RandomH[Values$j])))
)
})
Expected solution
Just that when I press the next house button I see the next random house
Besides storing the counting in reactive values the main challenge would be to differentiate between tabs: Two ways come two mind.
Using modules
Listen on tab changes.
To count the clicks simply use reactiveValues().
global <- reactiveValues(nr = 1)
observeEvent(input$New_Button1, {
global$nr <- min(global$nr + 1, maxHouseNr)
})
In order to reset the counter when switching to the new tab you want to listen on tab changes. You could give the tabsetPanel() an id and listen on input$ID.
Set id:
do.call(tabsetPanel, c(id = "whichTab", myTabs))
Listen on tab change:
observeEvent(input$whichTab, {
global$nr <- 1
})
Code:
library(shiny)
maxHouseNr <- 3
Habitats <- 1:maxHouseNr
RandomH <-lapply(rep(maxHouseNr, 3), sample, size = maxHouseNr, replace = FALSE)
shinyApp(
ui = tagList(
navbarPage("",
tabPanel("DogImages",
fluidRow(
column(width = 6,
uiOutput('mytabs')
) )
)#cerrado tab panel 2
) #cerrado de navbar Panel
), #cerrado ui
server <- function(input, output) {
global <- reactiveValues(nr = 1)
observeEvent(input$whichTab, {
global$nr <- 1
})
observeEvent(input$New_Button1, {
global$nr <- min(global$nr + 1, maxHouseNr)
})
observeEvent(input$New_Button2, {
global$nr <- min(global$nr + 1, maxHouseNr)
})
observeEvent(input$New_Button3, {
global$nr <- min(global$nr + 1, maxHouseNr)
})
#####################################
# Tabs breeds
output$mytabs = renderUI({
Breeds <- c(1:3)
Random <- sample(Breeds, 3)
myTabs = lapply(Random, function(i){
tabPanel(paste("Breed", i),
###
fluidPage(
fluidRow(
column(6,
wellPanel(
img(src = paste0("Dog",i, ".jpg"), height = 300, width = 300))),
renderUI({
column(6, actionButton(inputId = paste0("New_Button", i), "Next house"),
wellPanel(
uiOutput(paste0("PlotHouse", i)))
)
})
)
))
####
})
do.call(tabsetPanel, c(id = "whichTab", myTabs))
})
output$PlotHouse1 <- renderUI({
img(src = paste0("House", RandomH[[1]][global$nr],".jpg"), height = 300, width = 300)
})
output$PlotHouse2 <- renderUI({
img(src = paste0("House", RandomH[[2]][global$nr],".jpg"), height = 300, width = 300)
})
output$PlotHouse3 <- renderUI({
img(src = paste0("House", RandomH[[3]][global$nr],".jpg"), height = 300, width = 300)
})
}
) #cerrado de shiny
Just convert the random order into reactive values and trigger the calculation by the button in a reactive enviorment (ojala sea esto lo que quieres)
library(shiny)
shinyApp(
ui = tagList(
navbarPage("",
tabPanel("DogImages",
fluidRow(
column(width = 6,
uiOutput('mytabs')
) )
)#cerrado tab panel 2
) #cerrado de navbar Panel
), #cerrado ui
server <- function(input, output) {
Breeds <- c(1:3)
Values =reactiveValues()
#####################################
# Tabs breeds
observe({
input$New_Button
Values$Random <- sample(Breeds, 3)
})
output$mytabs = renderUI({
myTabs = lapply(Values$Random, function(i){
tabPanel(paste("Breed", i),
###
fluidPage(
fluidRow(
column(6,
wellPanel(
img(src = paste0("Dog",i, ".jpg"), height = 300, width = 300))),
renderUI({
Habitats <- c(1:3)
RandomH <- sample(Habitats, 3)
column(6,actionButton("New_Button", "Next house"),
wellPanel(
uiOutput(paste0("PlotHouse",RandomH[1])))
)
})
)
))
####
})
do.call(tabsetPanel, myTabs)
})
output$PlotHouse1 <- renderUI({
img(src = "House1.jpg", height = 300, width = 300)
})
output$PlotHouse2 <- renderUI({
img(src = "House2.jpg", height = 300, width = 300)
})
output$PlotHouse3 <- renderUI({
img(src = "House3.jpg", height = 300, width = 300)
})
}
) #cerrado de shiny

How to display different infoBox on button clock

I am working on an application in sinydashboard in which the user generates a random number on the click of a button. The random number corresponds to a row in a dataframe which I need to display on the dashboard using an infoBox. Each infoBox needs to persist on the screen until the user closes the application.
I tried generating a new output variable on each click in server.R, however I could not find a way of referencing it in ui.R. Minimal example below. I've not included generating a name for an output variable on each button click as that's not working at all.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(dashboardHeader(title = ""),
dashboardSidebar(),
dashboardBody(fluidRow(
box(
width = 3,
actionButton(inputId = "generateButton",
label = "Generate")
),
box(infoBoxOutput("rnum1"))
)))
server <- function(input, output) {
randomData <- data.frame("RN" = runif(100, 1, 100))
observeEvent(input$generateButton, {
randomNumber <- floor(runif(1, 1, 100))
dataRow <- randomData[randomNumber,]
# Display dataRow in a persistent infoBox
# in a way that 5 clicks will produce 5 boxes
# Number of clicks is not known in advance
output$rnum1 <- renderInfoBox({
infoBox("Number", dataRow)
})
})
}
shinyApp(ui = ui, server = server)
Maybe this is what you want, at leat this a draft. You'll need a reactive variable to store the already generated numbers to be able to have something persistent.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(dashboardHeader(title = ""),
dashboardSidebar(),
dashboardBody(actionButton(inputId = "generateButton",
label = "Generate")
,
uiOutput('infoBoxes'))
)
server <- function(input, output) {
randomData <- data.frame("RN" = runif(100, 1, 100))
rv <- reactiveValues()
observeEvent(input$generateButton, {
randomNumber <- floor(runif(1, 1, 100))
dataRow <- randomData[randomNumber,]
print(dataRow)
rv$persistent <- c(rv$persistent, dataRow)
# Display dataRow in a persistent infoBox
})
output$infoBoxes = renderUI({
if(length(rv$persistent) > 0 ) {
fluidRow(
Map(function(x) infoBox('title', rv$persistent[x]), 1:length(rv$persistent))
)
}
})
}
shinyApp(ui = ui, server = server)

Bootstrap Modal Multiple Conditions R Shiny

I need to only display a BS modal when a button is pressed and and a condition on a variable is met.
This is a simple app that demonstrates what the challenge is. I need to display a BS modal when num_rows >= 500, and the submit button is fired, not just when the submit button is fired.
I am aware this could be done with a conditionalPanel using input.slider as one of the conditions, but in my real project it is much more complicated than this, and the BS modal/conditional panel needs to depend on both a button (user input) and a variable assigned in the server.
library(shiny)
library(shinyBS)
data = matrix(rnorm(1000*10, 0, 1), nrow = 1000)
ui <- fluidPage(
fluidRow(
column(width = 4,
sliderInput("slider", "Choose Number of Rows to Display", 0, 1000, value = NULL),
submitButton('Submit'),
bsModal("modalExample", "Yes/No", "submit", size = "small", wellPanel(
p(div(HTML("<strong>Warning: </strong> you have chosen to display a large
number of rows. Are you sure you want to proceed?"))),
actionButton("no_button", "Yes"),
actionButton("yes_button", "No")
))
),
column(width = 8,
tableOutput('data')
)
)
)
server <- shinyServer(function(input, output, server){
observe({
num_rows <- input$slider
if(num_rows >= 500){
#
# ACTIVATE MODAL PANEL
#
observeEvent(input$no_button, {
# Do not show table
})
observeEvent(input$yes_button, {
output$table <- renderTable(data)
})
} else{ # Display table normally if number of rows is less than 500
output$table <- renderTable(data)
}
})
})
shinyApp(ui, server)
Have a look at the following code. I disabled the action button if num_rows<500 with the package shinyjs. If num_rows>=500 the action button becomes available to trigger the popup. To update the number of rows selected with the slider you'll have to press the submit button every time. Hope this helps or gets you some ideas. For now I have not implemented your warning message (that did not work for me). Another issue: the slider and display for the pop up only work towards increasing number of rows, not decreasing afterwards. If you find a solution for that, pls share =)
library(shiny)
library(shinyBS)
library(shinyjs)
data = matrix(rnorm(1000*10, 0, 1), nrow = 1000)
data1=data[(1:500),]
head(data)
ui <- fluidPage(
fluidRow(
column(width = 4,
sliderInput("slider", "Choose Number of Rows to Display", 0, 1000, value = NULL),
submitButton('Submit'),
actionButton('Show','Show'),
useShinyjs(),
bsModal("modalExample",'Yes/No','Show', size = "large",tableOutput("tab")
# wellPanel(
# p(div(HTML("<strong>Warning: </strong> you have chosen to display a large
# number of rows. Are you sure you want to proceed?")
# )))
)),
column(width = 8,tableOutput('table'))))
server <- function(input, output)({
observe({
num_rows = input$slider
if(num_rows<500 &num_rows!=0) {
shinyjs::disable('Show')
output$table <- renderTable({
data = data1[(1:num_rows),]
print(head(data1))
data})
}else{
shinyjs::enable('Show')
output$tab = renderTable({
data = data[(1:num_rows),]
data}) }
})
})
shinyApp(ui, server)

In RShiny, change plot width / height for separate plots within same renderPlot()

I am building an RShiny app that includes 1 renderPlot call (in server) with its 1 corresponding plotOutput call (in ui). However, within the renderPlot code, there is a toggle from the ui that switches between two different plots. I would like the plots to have different coordinates. Below is a reproducible RShiny app using generic plots to highlight the aspects of my question:
selector = c("one", "two")
names(selector) = c("one", "two")
plot.width = 600
plot.height = 600
ui <- fluidPage(
fluidRow(
# Organizes the title of the whole shiny app
# ==========================================
column(width = 12, align = 'center',
h2('NBA Shot Chart and Movement Tracking Application'))
),
fluidRow(
# This coordinates the location of the LHS widgets
# ================================================
column(width = 4, align = 'center',
selectInput(inputId = 'shooter.input', label = 'Select Shooter:', multiple = FALSE,
choices = selector, selected = 'one')),
column(width = 8, align = 'left',
plotOutput('shot.chart', width = plot.width, height = plot.height)
)
)
)
server <- shinyServer(function(input, output) {
# renderPlot for the charts (shot charts and movement charts)
output$shot.chart <- renderPlot({
if(input$shooter.input == "one") {
plot(c(1,2,3,4,5), c(6,7,8,9,10))
}
else {
plot(c(1,2,3,4,5), c(1,1,1,1,1))
}
})
})
shinyApp(ui = ui, server = server)
Okay, my question has to do with the plot.width and plot.height parameters set in plotOutput in ui. I want these parameters to change for each of the two plots. When selectInput is set == "one", I want the parameters to be 600 and 600, and when the selectInput is set == "two", I want the parameters to be 600 and 800.
Has anybody run into this problem before, and knows how to deal with it?
Thanks!
Here is the solution:
library(shiny)
selector = c("one", "two")
names(selector) = c("one", "two")
ui <- fluidPage(
fluidRow(
# Organizes the title of the whole shiny app
# ==========================================
column(width = 12, align = 'center',
h2('NBA Shot Chart and Movement Tracking Application'))
),
fluidRow(
# This coordinates the location of the LHS widgets
# ================================================
column(width = 4, align = 'center',
selectInput(inputId = 'shooter.input', label = 'Select Shooter:', multiple = FALSE,
choices = selector, selected = 'one')),
column(width = 8, align = 'left',
uiOutput('shot.chart_ui')
)
)
)
server <- shinyServer(function(input, output) {
output$shot.chart_ui <- renderUI({
if(input$shooter.input == "one") {
plot.width = 600
plot.height = 600
}else{
plot.width = 600
plot.height = 800
}
plotOutput('shot.chart', width = plot.width, height = plot.height)
})
# renderPlot for the charts (shot charts and movement charts)
output$shot.chart <- renderPlot({
if(input$shooter.input == "one") {
plot(c(1,2,3,4,5), c(6,7,8,9,10))
}
else {
plot(c(1,2,3,4,5), c(1,1,1,1,1))
}
})
})
shinyApp(ui = ui, server = server)
I have moved the plotOutput to the server and furthermore i have put plot.width and plot.height into reactive context.

Interactive plotting in shiny using mouse clicks

I am doing a project where I use the shiny server and connect R to mongodb to fetch results from database and display it dynamically.
However, I face the following problem in it. I initially get the results from db and make a plot. After this plot is done, I want the user to make make two mouse clicks on the plot based on which it should take the two values as xlim and plot a zoomed version of the previous plot. However, I am not able to do it successfully.
Here is the code that I have written.
ui.R
library(shiny)
shinyUI(fluidPage(
titlePanel("LOAD AND PERFORMANCE DASHBOARD"),
sidebarLayout(
sidebarPanel(
fluidRow(
selectInput("select", label = h3("Select type of testing"),
choices = list("Performance Testing"=1, "Capacity Testing"=2)),
radioButtons("radio", label = h3("Select parameter to plot"),
choices = list("Disk" = 1, "Flit" = 2,"CPU" = 3,"Egress" =4,
"Memory" = 5))
)),
mainPanel(
plotOutput("plot",clickId="plot_click"),
textOutput("text1"),
plotOutput("plot2")
)
)
))
server.R
library(shiny)
library(rmongodb)
cursor <- vector()
shinyServer(function(input, output) {
initialize <- reactive({
mongo = mongo.create(host = "localhost")
})
calculate <- reactive({
if(input$radio==1)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "disk")
else if(input$radio==2)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "flit")
else if(input$radio==3)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "cpu")
else if(input$radio==4)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "egress")
else if(input$radio==5)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "memory")
})
output$plot <- renderPlot({
initialize()
value <- calculate()
plot(value,xlab="Time",ylab="% Consumed")
lines(value)
cursor <- value
})
output$text1 <- renderText({
paste("You have selected",input$plot_click$x)
})
output$plot2 <- renderPlot({
plot(cursor[cursor<input$plot_click$x && cursor>first_click ],xlab="Time",ylab="% Consumed") lines(cursor)
first_click <- input$plot_click$x
})
})
Thanks in advance for the help :)
Here's a simple example that demonstrates the behavior you want, just run this code (or save as a file and source it). This code uses the new observeEvent function that debuted in Shiny 0.11, which just hit CRAN over the weekend.
The basic idea is that we track two reactive values, click1 and range. click1 represents the first mouse click, if any exists; and range represents the x-values of both mouse clicks. Clicking on the plot simply manipulates these two reactive values, and the plotting operation reads them.
library(shiny)
ui <- fluidPage(
h1("Plot click demo"),
plotOutput("plot", clickId = "plot_click"),
actionButton("reset", "Reset zoom")
)
server <- function(input, output, session) {
v <- reactiveValues(
click1 = NULL, # Represents the first mouse click, if any
range = NULL # After two clicks, this stores the range of x
)
# Handle clicks on the plot
observeEvent(input$plot_click, {
if (is.null(v$click1)) {
# We don't have a first click, so this is the first click
v$click1 <- input$plot_click
} else {
# We already had a first click, so this is the second click.
# Make a range from the previous click and this one.
v$range <- range(v$click1$x, input$plot_click$x)
# And clear the first click so the next click starts a new
# range.
v$click1 <- NULL
}
})
observeEvent(input$reset, {
# Reset both the range and the first click, if any.
v$range <- NULL
v$click1 <- NULL
})
output$plot <- renderPlot({
plot(cars, xlim = v$range)
if (!is.null(v$click1$x))
abline(v = v$click1$x)
})
}
shinyApp(ui, server)

Resources