2
votes

UPDATE: Changed example to a (hopefully) less abstract one

I have a shiny app with two tabs using the mtcars dataset. "Displacement" contains a scatterplot showing disp against mpg, "horsepower" contains a scatterplot showing hp against mpg. Both tabs contain a checkbox group input that allows the user to filter the results based on the number of cylinders. The "Generate" button generates the graph so that it doesn't change immediately as soon as the input is changed.

Each tab has two additional buttons. "Compare" is a switching button that copies the values in the checkbox inputs of one tab to the inputs of the other tab and generates the resulting plot, so you can easily compare how both tabs look for the same input. "Restore Defaults" resets a checkbox input to its initial value.

Both of these buttons work in the same manner. Each button has its own observer that triggers an Update function once the button is pressed. This function first updates the input, then generates the plot by updating a reactive value created for this purpose, then switches to the tab containing that plot.

However, only the Compare buttons work correctly, updating the inputs and generating the graphs with a single click. The Restore Default buttons, on the other hand, only update the inputs. You need to press it twice to update the plots as well.

I have tried to move a Restore Default button from one tab to another and then it does update the plot on the first click, so the behavior changes based on whether or not the affected plot is in the same tab as the button (app is provided below so you can try this for yourself). I am baffled by this behavior, because the observeEvent() functions linking the buttons to the update functions are coded identically. It should not matter in which tab a button is located.

Can anyone explain what causes this and what I should change to make both buttons update the checkbox input and the plot output in a single click?

library(shiny)
library(ggplot2)

ui <- fluidPage(
  tabsetPanel(id="App",
    tabPanel(
      "displacement",
      checkboxGroupInput("cyl1", "Number of cylinders", choices=c("4","6","8"), selected="4"),
      actionButton("genDisp", "Generate"),
      actionButton("switchToHp", "Compare"),
      actionButton("resDefDisp", "Restore Default"),
      plotOutput("disp")
    ),
    tabPanel(
      "horsepower",
      checkboxGroupInput("cyl2", "Number of cylinders", choices=c("4","6","8"), selected="8"),
      actionButton("genHp", "Generate"),
      actionButton("switchToDisp", "Compare"),
      actionButton("resDefHp", "Restore Default"),
      plotOutput("hp")
    )
  )
)

server <- function(input, output, session){

  # reactive values used to trigger plot generation
  generateDisp  <- reactiveVal(0)
  generateHp  <- reactiveVal(0)

  # "Generate" Buttons
  observeEvent({
    input$genDisp
  },
  {
    generateDisp(generateDisp()+1)
  })
  observeEvent({
    input$genHp
  },
  {
    generateHp(generateHp()+1)
  })

  # "Compare"/"Restore Defaults" buttons
  observeEvent({
    input$switchToHp
  },{
    updateHp(cyl=input$cyl1)
  })
  observeEvent({
    input$resDefDisp
  },{
    updateDisp(cyl="4")
  })
  observeEvent({
    input$switchToDisp
  },{
    updateDisp(cyl=input$cyl2)
  })
  observeEvent({
    input$resDefHp
  },{
    updateHp(cyl="8")
  })

  # Functions updating graphs
  updateDisp <- function(cyl=NULL)
  {
    updateCheckboxGroupInput(session, "cyl1", "Number of cylinders", selected = cyl)
    generateDisp(generateDisp()+1)
    updateTabsetPanel(session, "App", selected = "displacement")
  }
  updateHp <- function(cyl=NULL)
  {
    updateCheckboxGroupInput(session, "cyl2", "Number of cylinders", selected = cyl)
    generateHp(generateHp()+1)
    updateTabsetPanel(session, "App", selected = "horsepower")
  }

  # output plots
  output$disp <- renderPlot({
    generateDisp()
    isolate({
      data <- filter(mtcars, cyl %in% input$cyl1) %>%
        mutate(cyl=as.factor(cyl))
      ggplot(data, aes(x=mpg, y=disp, color=cyl)) +
        geom_point()
    })
  })
  output$hp <- renderPlot({
    generateHp()
    isolate({
      data <- filter(mtcars, cyl %in% input$cyl2) %>%
        mutate(cyl=as.factor(cyl))
      ggplot(data, aes(x=mpg, y=hp, color=cyl)) +
        geom_point()
    })
  })

}


shinyApp(ui, server)
1

1 Answers

1
votes

If you change your code to this:

observeEvent({
  c(input$updateTalk1, input$updateTalk2)
}, {
  updateNumericInput(session, "talkNumber", "Choose number", value = 2)
  print(input$talkNumber)
  generateTalk(generateTalk() + 1)
  print(input$talkNumber)

  updateTabsetPanel(session, "App", selected = "talk")
})

You will notice after you updated the numeric input value to 2, or even after you updated the value of generateTalk, the input$talkNumber is still the value when the function is triggered, in your case 3, not 2. So when the renderPrint function runs, it will still use the old value. I'm not sure about the underlying workflow, but I guess it will only change to the new value when the current function(reactive call) finished.

However when the first tab is invisible, it is forced to be re-rendered, thus it gets the change to update all the UI elements, so the new value set to the numeric input is read by the renderPrint function.

By the way, I feel your example is a little bit too abstract and I think if you can show us what you are actually trying to do, there would be a better solution to your current way.

EDIT:

I found this post that might be helpful. Basically, update*Input function will only make the change to the UI after observeEvent ends, therefore the renderPlot triggered by the generateDisp is using the old input values.