【问题标题】:R Shiny: observeEvent() behaves differently based on what tab is openR Shiny:observeEvent() 的行为根据打开的选项卡而有所不同
【发布时间】:2017-09-15 22:45:08
【问题描述】:

更新:将示例更改为(希望)不那么抽象的示例

我有一个使用 mtcars 数据集的带有两个选项卡的闪亮应用程序。 “Displacement”包含一个散点图,显示 disp 与 mpg,“horsepower”包含一个散点图,显示 hp 与 mpg。两个选项卡都包含一个复选框组输入,允许用户根据气缸数过滤结果。 “生成”按钮会生成图形,这样它就不会在输入更改后立即更改。

每个选项卡都有两个附加按钮。 “比较”是一个切换按钮,它将一个选项卡的复选框输入中的值复制到另一个选项卡的输入并生成结果图,因此您可以轻松比较两个选项卡如何查找相同的输入。 “恢复默认值”将复选框输入重置为其初始值。

这两个按钮的工作方式相同。每个按钮都有自己的观察者,一旦按下按钮,就会触发更新功能。此函数首先更新输入,然后通过更新为此目的创建的反应值生成图,然后切换到包含该图的选项卡。

但是,只有“比较”按钮才能正常工作,只需单击一下即可更新输入并生成图表。另一方面,恢复默认按钮仅更新输入。您还需要按两次才能更新绘图。

我尝试将“恢复默认值”按钮从一个选项卡移动到另一个选项卡,然后它确实在第一次单击时更新绘图,因此行为会根据受影响的绘图是否在与按钮相同的选项卡(下面提供了应用程序,因此您可以自己尝试)。我对这种行为感到困惑,因为将按钮链接到更新函数的 observeEvent() 函数的编码相同。按钮位于哪个选项卡中并不重要。

谁能解释造成这种情况的原因以及我应该更改什么以使两个按钮在一次单击中更新复选框输入和绘图输出?

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)

【问题讨论】:

    标签: r tabs shiny observers reactive


    【解决方案1】:

    如果您将代码更改为:

    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")
    })
    

    您会注意到,在您将数字输入值更新为 2 之后,或者即使在您更新 generateTalk 的值之后,input$talkNumber 仍然是触发函数时的值,在您的情况下是 3,而不是 2。所以当renderPrint 函数运行,它仍将使用旧值。我不确定底层的工作流程,但我猜它只会在当前函数(响应式调用)完成时更改为新值。

    但是当第一个选项卡不可见时,它会被强制重新渲染,因此它会获得更新所有 UI 元素的更改,因此设置为数字输入的新值由 renderPrint 函数读取。

    顺便说一句,我觉得你的例子有点太抽象了,我认为如果你能向我们展示你真正想要做的事情,那么你目前的方式会有更好的解决方案。

    编辑:

    我发现this 的帖子可能会有所帮助。基本上,update*Input 函数只会在 observeEvent 结束后对 UI 进行更改,因此 generateDisp 触发的 renderPlot 使用的是旧的输入值。

    【讨论】:

    • 感谢您的回复。我已将问题中的示例更改为希望不那么抽象的示例。我想要的是有允许用户在选项卡之间“切换”的按钮,从一个选项卡复制输入值并将它们粘贴到另一个选项卡中,这样他就可以轻松地比较相同输入的不同图表。我还想要将输入更改为某些预定义值的按钮。第二种按钮通常与绘图位于同一选项卡中,这就是我遇到此问题的地方。
    • @FrankvanDorp 如果您将 browser() 语句添加为 renderPlot 的第一行并开始调试您的应用程序。你会注意到恢复默认按钮实际上是通过触发 renderPlot 运行两次而起作用的。这很奇怪,您可能想尝试不使用 reactiveVal。我会尝试使用 reactiveValues 看看是否有区别
    • @FrankvanDorp 正如我在帖子中更新的那样,是 update*Input 函数导致了延迟。在我看来,当用户点击恢复默认按钮时只更新 selectInput,然后让你的情节完全依赖 selectInput 并摆脱生成按钮,这似乎更自然。
    • 谢谢,我现在明白出了什么问题。也许有一种方法可以强制 updateInput 在observeEvent 结束之前立即进行更改?那将解决它。关于您的解决方案,它肯定会解决此示例应用程序的问题,但我正在处理的实际应用程序包含每个选项卡的十几个输入,并且适用于巨大的数据集。如果它在每次输入更改时自动更新绘图,它会浪费大量时间渲染用户不感兴趣的绘图,应用程序会变慢并且使用它会变得非常麻烦。这就是为什么我需要生成按钮。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-29
    • 1970-01-01
    相关资源
    最近更新 更多