强制指定 R Shiny 中的渲染顺序

4

我对R Shiny和响应式编程相对较新。根据我的理解(以及这个教程),似乎你不应该告诉Shiny“何时”去执行某些操作(即强制执行顺序),因为它会自己解决这个问题。然而,通常情况下,我希望绘图或其他UI逐个渲染。有没有一个好的方法来实现这一点?

我下面提供了一个最小化的示例。我想先渲染header然后再渲染plot,因为plot需要耗费大量的计算时间。

library(shiny)

ui <- fluidPage(
    tags$h1("My app"),
    uiOutput("header"),
    plotOutput("plot")
)

server <- function(input, output) {
            output$header <- renderUI({
                tagList(tags$h2("Section header"), 
                        tags$p("Some information relevant to the plot below..."))
            })
            
            output$plot <- renderPlot({
                # hypothetical expensive computation
                Sys.sleep(2)
            
                # hypothetical plot
                hist(rnorm(20))
            })
}

shinyApp(ui = ui, server = server)

很明显,我可以用server中的定义替换ui中的uiOutput("header"),这样问题就解决了; 但是,实际上我想让header是动态的。我找到的一个妥协的解决方案是在header中包含一个隐藏的输入,并在plot中使用req()。这有点像在加载时自动单击操作按钮。

server <- function(input, output) {
            output$header <- renderUI({
                tagList(tags$h2("Section header"), 
                        tags$p("Some information relevant to the plot below..."),
                        div(style = "display:none", textInput(inputId = "hidden", label = "", value = "x")))
            })
            
            output$plot <- renderPlot({
                req(input$hidden)
                
                ...
            })
}

然而,如果我想在多种情况下重复这个过程,或者如果我想要强制连续渲染两个以上的输出,则这似乎很繁琐。有没有人可以提供更加优雅的解决方案?

但是,如果您运行上面的代码,您会发现它们同时出现,这不是我想要的。为了更清楚地表明页面正在加载,您可以在 uiOutput("header") 上方添加文本。您会注意到,在绘图准备好之前,标题栏保持空白。 - antsatsui
1
好的,我编辑了我的帖子并使用了Sys.sleep。 - antsatsui
renderPlotrenderUI没有priority参数,但是observeEvent有。也许如果你让你的图表和标题分别依赖于由具有不同优先级的observeEvent生成的反应性,那么这将给你想要的结果。 - Limey
1
我尝试了以下方法:我创建了一个reactiveValues来存储标题和图形数据,然后使用两个observe调用来修改reactiveValues,然后从每个render中调用reactiveValues。不幸的是,尝试改变优先级并没有改变任何东西——标题总是首先执行,但在显示之前等待图形完成。我尝试的另一种方法是只需将output$header <- ...(以及同样的操作)包装在observe语句中——然后priority让我改变执行顺序,但我仍然有它们互相等待加载的问题。 - antsatsui
2个回答

2
由于您的示例代码包括“耗时计算”,我猜最终您并不想控制呈现顺序,而是想避免长时间运行的函数阻塞代码的其他部分(XY问题)。
默认情况下,R是单线程的,因此我们需要子进程来解决这个问题。在中,您可以使用library(future) + library(promises)来解决这个问题。然而,使用异步进程来解除shiny会话中的元素的阻塞需要我们“隐藏”承诺——请在此处阅读更多信息。
请参见下面您示例的异步版本:
library(shiny)
library(promises)
library(future)
plan(multisession)

ui <- fluidPage(
  tags$h1("My app"),
  uiOutput("header"),
  plotOutput("plot")
)

server <- function(input, output) {
  output$header <- renderUI({
    tagList(tags$h2("Section header"), 
            tags$p("Some information relevant to the plot below..."))
  })
  
  data <- reactiveVal()
  
  observe({
    future_promise({
      # hypothetical expensive computation
      Sys.sleep(2)
      # hypothetical plot data
      rnorm(20)
    }, seed=TRUE) %...>% data()
    return(NULL) # "hide" the future_promise
  })
  
  output$plot <- renderPlot({
    req(data(), cancelOutput = TRUE)
    hist(data())
  })
}

shinyApp(ui = ui, server = server)

result


1
在Shiny中,对于给定的会话,直到所有输出都准备好后才会将任何输出发送回客户端:文本呈现函数不会被发送,直到图形呈现函数完成,详见Shiny的flush cycle
一种解决方法是使用reactiveVal跳过绘制图形,以便文本在第一个刷新周期中显示,然后使用invalidateLater()启动新的刷新周期来运行更长的绘图。

library(shiny)

ui <- fluidPage(
  tags$h1("My app"),
  uiOutput("header"),
  plotOutput("plot")
)

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

  skipPlot <- reactiveVal(1)

  output$header <- renderUI({
    tagList(tags$h2("Section header"),
            tags$p("Some information relevant to the plot below..."))
  })

  output$plot <- renderPlot({
    
    if (isolate(skipPlot()==1)) {
      # skip first reactive sequence
      skipPlot(0)
      # launch next reactive sequence
      invalidateLater(1,session)
    } else {
      # hypothetical expensive computation
      Sys.sleep(2)

      # hypothetical plot
      hist(rnorm(20))

    }

  })
}

shinyApp(ui = ui, server = server)

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接