Two conditionalPanel's in Shiny - r

I have a Shiny application and I've created the following conditionalPanel's:
conditionalPanel(condition="output.levels",
numericInput("centerpoints", "Number of center points", value=0, min=0, max=25)),
conditionalPanel(condition="!output.levels",
numericInput("centerpoints", "Number of center points", value=0, min=0, max=0))
If output.levels is TRUE I want to select between 0 and 25 center points. Otherwise, the number of center points must be 0.
The problem is that if the condition is TRUE, I select more than 0 center points and then the condition becomes FALSE, Shiny keeps the center points selected before in stead of 0.
Is there a way to fix it?
Thanks.

The problem you are facing comes from having two almost identical elements in your ui, that share the same id. Conditional panels only hide your elements, but they are still there in the document. So your first numericInput will always be the one registered by Shiny and the second one will not be bound (i.e. will not send its input) to Shiny because of the duplicate id.
I advise you to implement the situation in a different way. There are functions in Shiny that let you change some existing ui element when the app is all set up. Those functions are updateXxxInput and you can change any variable there is in the corresponding input element. Since your only target is to change the max value of the numericInput, we can do this easily from the server, where we can observe the levels or any other variable. In the code below, I used a simple checkbox. The command for changing the max value is updateNumericInput(session, "centerpoints", max = 0). Note that this only changes the one attribute of you input. This is a huge advantage over re-rendering UI elements, since you don't have to keep track what all other attributes are.
The updateXxxInput functions are very useful, so give it a try!
library(shiny)
ui <- shinyUI(
fluidPage(
numericInput("centerpoints", "Number of center points", value=0, min=0, max=25),
checkboxInput("changeCenterpoints", "Cap maximum at 0")
)
)
server <- function(input, output, session){
observeEvent(input$changeCenterpoints, {
if(input$changeCenterpoints){
updateNumericInput(session, "centerpoints", max = 0, value = 0)
}else{
updateNumericInput(session, "centerpoints", max = 25)
}
})
}
shinyApp(ui, server)

Related

Shiny observeEvent shows first event, but not second, third

TL;DR: observeEvent is only working on the first instance, but not subsequent.
Explanation:
In a small shiny app below, I dynamically build a URL based on user input, which points to the corresponding pre-rendered "timer" gif images I have hosted on GH. In this shiny app code below, observeEvent works great to pull the sliderInput value (e.g. 5sec), build a URL, and then clicking 'go' will show the timer using a clever shinyjs package. However, if I do not change the input (still 5sec), clicking go doesn't rebuild the image. If I change the value to a different number (4sec), it will show the correct gif. If I change back to the original number (5sec), no gif. If I go to a new number (3sec), correct gif. If I print the value of input$time or of rv$time in my observeEvent, then each of those values are updating correctly (both print the corresponding value).
Goal: to show the gif that corresponds to the input$time upon each update of input$go
Reprex:
library(shiny)
library(tidyverse)
library(glue)
library(shinyjs)
# Define UI
ui <- navbarPage(title = "Reprex",
## RHYME TIME -----
tabPanel("Time",
useShinyjs(),
sidebarLayout(
sidebarPanel(
sliderInput("time",
"Seconds",
min = 3,
max = 10,
value = 5
),
actionButton(inputId = "go",
label = "Go"),
),
mainPanel(
HTML("<center>"),
shinyjs::hidden(htmlOutput("simple_timer")),
HTML("</center>")
)
)
)
)
# Define server logic
server <- function(input, output, session) {
#a container to store my time var
rv <- reactiveValues(
time = 0
)
#the event that is triggered by input$go getting clicked
observeEvent(input$go, {
rv$time <- input$time #this should update rv$time after go is clicked
shinyjs::show("simple_timer") #this is the clever package with simple show/hide funs
})
#the reactive text that generates my HTML
output$simple_timer <- renderText({
glue::glue('<img src ="https://github.com/matthewhirschey/time_timer/raw/main/data/{rv$time}_sec.gif",
align = "center",
height="50%",
width="50%">')
})
}
# Run the application
shinyApp(ui = ui, server = server)
There are a couple of issues with your code:
renderText won't refire if you press input$go again (w/o changing the slider). Becasue the idea is that observer/render fires whenever their reactives change. As your renderText depends on rv$time which does not change when input$time does not change, the render function is not fired on subsequent button presses. This can be remedied by including input$go in the render function setting an additional dependency on the button.
This will not, however, solve your problem, because the browser uses caching. It sees that the <img> tag did not change (same src), thus it does not reload the picture. To circumvent that you can use the trick from Disable cache for some images by adding a timestamp to the src.
To make a long story short, this code does the trick:
output$simple_timer <- renderText({
input$go # make code dependent on the button
# add `?timestamp=<timestamp>`to the src URL to convince the browser to reload the pic
glue::glue('<img src ="https://github.com/matthewhirschey/time_timer/raw/main/data/{rv$time}_sec.gif?timestamp={Sys.time()}",
align = "center",
height="50%",
width="50%">')
})

Is there a way to trigger consecutive, timed events with single action button click in Shiny?

I am building an application that is meant to help students understand sampling. The application allows students to choose a sample size and then see how many 1's they get, given some probability. When sample size is small (i.e., 1), it often occurs that the same value (either a 0 or 1) is sampled multiple times in a row. However, if students click the action button to re-sample and get the same value as in their last random draw, I'm having trouble making it visually clear that the value has updated (i.e., that something happened, even though it gave the same result as before). Ideally, I would like the action button click to cause this field on the main panel to go blank for a fraction of a second and then display the new value.
I have tried using Sys.sleep to get the system to sleep for a second before proceeding, but this does not work. I can get it to (sort of) work by trying to print something else in the same output field before using Sys.sleep, but I feel like there must be an easier way.
ui <- fluidPage(
titlePanel("Example"),
sidebarLayout(
sidebarPanel(
actionButton("run", "Pick a number (0 or 1)")),
mainPanel(
h4("Your Number!"),
textOutput("result")
)
)
)
server <- function(input, output,session) {
observeEvent(input$run, {
x <- sample(c(0,1),size=1,prob=c(.5,.5),replace=T)
###Including these commented lines achieves what I want,
###but doesn't seem very elegant:
# output$result <- renderText({
# print("something")
# })
Sys.sleep(1)
output$result <- renderText({
paste("You got the number ",x)
})
})
}
shinyApp(ui, server)

R Shiny Create Dynamic Numeric Input Based off Checkboxes

The program I'm writing takes a spreadsheet of grades and then creates tables, plots, and summaries of the data it receives. However, I want multiple users to be able to use this. This means the weight of each type of grade (Homework, Tests, Quizzes, etc) will be different for each user.
What I'm trying to do is create a checkboxGroupInput that generates a list of the different types of grades (read from 'Type' column in the spreadsheet), then when each checkbox is checked a numericInput will pop up allowing the user to select a weight. Here is an example picture.
Example Pic
(Sorry, I don't have enough reputation to post an image right now)
However, I don't know how to render the numericInput to a True/False value of each checkbox. I know how to render a table to show columns based on group input, like in this example, but I don't know how to initiate an unknown number of numericInputs since each one will be its own variable. Is this possible? I can't think of another way to achieve what I want.
Here is the code (snippet) of what I have so far.
ui <- fluidPage(
dashboardBody(
tabBox(
id = 'tabset1',
width = '100%',
tabPanel('Enter weights',
splitLayout(uiOutput('type'), uiOutput('weight'))),
tabPanel('Grades Graph', plotOutput('individualGraph')),
tabPanel('Grades Table', dataTableOutput('summaryDT')),
tabPanel('Summary by Unit', plotOutput('summaryBarGraph'), br(), dataTableOutput('summaryUnitDT')),
tabPanel('Class Averages', plotOutput('classAverageGraph'), br(), dataTableOutput('classAverageTable'))
)
)
server <- function(input, output, session) {
output$type <- renderUI({
df <- data.frame(grades())
choices <- unique(pull(df, Type))
checkboxGroupInput(inputId = 'typeVar',
label = 'Select Type',
choices = choices,
selected = choices)
})
output$weight <- renderUI({
numericInput(inputId = 'weightVar',
label = 'Select Weight %', value = 50,
width = 50)
})
}
Any help/guidance would be appreciated! If I'm wrong in thinking this is the best way to achieve what I want please let me know. I'm open to more suggestions.
First of all, does the checkboxGroupInput() make sense here? This seems like the radio buttons UI element match what you're trying to do better (because radio buttons imply to the user that only one can be checked at a time). Second, instead of having many numericInputs created dynamically, could you try to use one numeric input and use updateNumericInput() to change it whenever the radio button changes? Then in your handling of the number change your conditional statement would just need to look at the number AND what radio button is checked.
I'm envisioning something like this:
observeEvent(input$RadioButton, {
updateNumericInput(session, "NumericInputName", value=theVariable)
}
where theVariable is the value previously entered for that category (test, quiz, etc), or 0 if nothing has ever been entered for that category.

Connecting values of sliderInput in Shiny

I've been stuck with this simple problem:
Shiny app uses several sliderInputs that are placed in different tabs in standard fluidPage. When one slider is moved, other sliders should move accordingly.
I've been working with updateSliderInput() function placed in observe()
Are there any other functions or procedures that can change values of selected sliders? I've also been experiencing that when I swich to other tab, the initial value of slider is set as in the beggining even though it should have been changed by updateSliderInput().
I'd be really thankful if someone could reference me to some function or algorithm that could help me.
Bonus points: I also need to do this for selectInput and radioButtons
I have a similar problem and followed an example from
R shiny bi-directional reactive widgets
It works for me. Please see the following:
R code
ui <- fluidPage(
titlePanel(""),
fluidRow(
column(3,
wellPanel(
uiOutput('slider1'),
uiOutput('text1')
))
))
server <- function(input, output) {
output$slider1<-renderUI({
text1.value<-input$slider1
default.slider1<-if(is.null(text1.value)) 15 else text1.value
textInput("text1", label="slider",
value = default.slider1)
})
output$text1<-renderUI({
slider1.value<-input$text1
default.text1<-if(is.null(slider1.value)) 15 else slider1.value
sliderInput("slider1", label = h5("text"),
min = -10, max = 10, value = default.text1,step = 1)
})}
Just realized this question is posted a long time ago. Still hope it helps!

Can I save the old value of a reactive object when it changes?

Note: After coming up with the answer I reworded the question to make if clearer.
Sometimes in a shiny app. I want to make use of a value selected by the user for a widget, as well as the previous value selected for that same widget. This could apply to reactive values derived from user input, where I want the old and the new value.
The problem is that if I try to save the value of a widget, then the variable containing that value has to be reactive or it will not update every time the widget changes. But, if I save the the value in a reactive context it will always give me the current value, not the previous one.
How can I save the previous value of a widget, but still have it update every time the user changes the widget?
Is there a way that does not require the use of an actionButton every time the user changes things? Avoiding an actionButton can be desirable with adding one is otherwise unnecessary and creates excess clicking for the user.
Seeing as the session flush event method seems to be broken for this purpose, here is an alternative way to do it using an observeEvent construct and a reactive variable.
library(shiny)
ui <- fluidPage(
h1("Memory"),
sidebarLayout(
sidebarPanel(
numericInput("val", "Next Value", 10)
),
mainPanel(
verbatimTextOutput("curval"),
verbatimTextOutput("lstval")
)
)
)
server <- function(input,output,session) {
rv <- reactiveValues(lstval=0,curval=0)
observeEvent(input$val, {rv$lstval <- rv$curval; rv$curval <- input$val})
curre <- reactive({req(input$val); input$val; rv$curval})
lstre <- reactive({req(input$val); input$val; rv$lstval})
output$curval <- renderPrint({sprintf("cur:%d",curre())})
output$lstval <- renderPrint({sprintf("lst:%d",lstre())})
}
options(shiny.reactlog = TRUE)
shinyApp(ui, server)
Yielding:
Update This answer was posted before the advent of the reactiveValues/observeEvent model in shiny. I think that #MikeWise 's answer is the better way to do this.
After some playing around this is what I came up with. The ui.r is nothing special
ui.r
library(shiny)
ui <- shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput(inputId="XX", label="Choose a letter",choices=letters[1:5])
),
mainPanel(
textOutput("Current"),
textOutput("old")
)
)
))
"Current" will display the current selection and "old" displays the previous selection.
In the server.r I made use of three key functions: reactiveValues, isolate and session$onFlush.
server.r
library(shiny)
server <- function(input, output,session) {
Values<-reactiveValues(old="Start")
session$onFlush(once=FALSE, function(){
isolate({ Values$old<-input$XX })
})
output$Current <- renderText({paste("Current:",input$XX)})
output$old <- renderText({ paste("Old:",Values$old) })
}
The server.r works like this.
First, Values$old is created using the reactiveValues function. I gave it the value "Start" to make it clear what was happening on load up.
Then I added a session$onFlush function. Note that I have session as an argument in my server function. This will run every time that shiny flushes the reactive system - such as when the selectizeInput is changed by the user. What is important is that it will run before input$XX gets a new value - so the value has changed at the selectizeInput but not at XX.
Inside the session$onFlush I then assign the outgoing value of XX to Values$old. This is done inside an isolate() as this will prevent any problems with input$XX gets updated with the new values. I can then use input$XX and Values$old in the renderText() functions.

Resources