如何让Shiny中非首标签页未渲染图表响应PlotlyProxy更新?
解决Shiny中未渲染的Plotly图表无法响应plotlyProxy更新的问题
问题说明
当Plotly图表位于Shiny的非初始激活标签页时,若用户在未切换到该标签页(图表未完成前端渲染)的情况下修改输入值,后续打开图表所在标签页时,plotlyProxy的更新操作不会生效。只有先打开过一次图表标签页,图表才会开始响应输入变化。
原因分析
plotlyProxy的更新指令需要依赖前端已存在的Plotly实例。默认情况下,Shiny会暂停隐藏元素的渲染(suspendWhenHidden = TRUE),所以未激活标签页中的plotlyOutput不会生成前端Plotly实例,此时plotlyProxy发送的更新找不到目标,指令直接丢失。
解决方案
通过outputOptions()取消图表输出的隐藏暂停渲染设置,让图表在后台完成初始化,确保plotlyProxy始终能找到对应的前端实例。
修改后的完整代码
library(shiny) library(plotly) library(shinyWidgets) library(data.table) trace_1 <- rnorm(100, mean = 5) trace_2 <- rnorm(100, mean = 0) trace_3 <- rnorm(100, mean = -5) x <- c(1:100) data <- data.table(x, trace_1, trace_2, trace_3) ui <- fluidPage( radioGroupButtons("tracenum", label = "Trace number to change marker colour", choices = 1:3, selected = NULL), tabsetPanel(id = "tabs", tabPanel("First tab", textOutput("text")), tabPanel("Second tab", plotlyOutput("plot")) ) ) server <- function(input, output, session) { # 关键修改:取消图表隐藏时的渲染暂停 outputOptions(output, "plot", suspendWhenHidden = FALSE) output$text <- renderText({ paste("Trace number", input$tracenum, "has been selected") }) output$plot <- renderPlotly({ fig <- plot_ly(data, x = ~x, y = ~trace_1, name = 'trace 1', type = 'scatter', mode = 'lines+markers') fig <- fig %>% add_trace(y = ~trace_2, name = 'trace 2', mode = 'lines+markers') fig <- fig %>% add_trace(y = ~trace_3, name = 'trace 3', mode = 'lines+markers') fig }) observeEvent(input$tracenum, { if (!is.na(input$tracenum)) { data[, updatevar := get(paste0("trace_", input$tracenum))] plotlyProxy("plot", session) %>% plotlyProxyInvoke("addTraces", list(x = c(data$x, data$x), y = c(data$updatevar, data$updatevar), type = "scatter", mode = "markers", marker = list(color = "black"))) } }) } shinyApp(ui, server)
补充方案(性能友好型)
如果担心后台渲染图表影响性能,可采用状态记录+标签页切换触发更新的方案:
- 用
reactiveVal记录用户的输入选择历史 - 监听标签页切换事件,当切换到图表页时,检查是否有未应用的更新并执行操作
示例代码如下:
server <- function(input, output, session) { # 记录未应用的trace选择 pending_update <- reactiveVal(NULL) output$text <- renderText({ paste("Trace number", input$tracenum, "has been selected") }) output$plot <- renderPlotly({ fig <- plot_ly(data, x = ~x, y = ~trace_1, name = 'trace 1', type = 'scatter', mode = 'lines+markers') fig <- fig %>% add_trace(y = ~trace_2, name = 'trace 2', mode = 'lines+markers') fig <- fig %>% add_trace(y = ~trace_3, name = 'trace 3', mode = 'lines+markers') # 渲染时检查是否有未应用的更新 if (!is.null(pending_update())) { data[, updatevar := get(paste0("trace_", pending_update()))] fig <- fig %>% add_trace(x = c(data$x, data$x), y = c(data$updatevar, data$updatevar), type = "scatter", mode = "markers", marker = list(color = "black")) pending_update(NULL) # 清空状态 } fig }) # 记录输入变化,若图表未渲染则暂存 observeEvent(input$tracenum, { if (!is.na(input$tracenum)) { # 检查图表是否已初始化 if (!is.null(output$plot)) { data[, updatevar := get(paste0("trace_", input$tracenum))] plotlyProxy("plot", session) %>% plotlyProxyInvoke("addTraces", list(x = c(data$x, data$x), y = c(data$updatevar, data$updatevar), type = "scatter", mode = "markers", marker = list(color = "black"))) } else { pending_update(input$tracenum) } } }) # 切换到图表标签页时触发渲染 observeEvent(input$tabs, { if (input$tabs == "Second tab" && !is.null(pending_update())) { output$plot <- renderPlotly({ fig <- plot_ly(data, x = ~x, y = ~trace_1, name = 'trace 1', type = 'scatter', mode = 'lines+markers') fig <- fig %>% add_trace(y = ~trace_2, name = 'trace 2', mode = 'lines+markers') fig <- fig %>% add_trace(y = ~trace_3, name = 'trace 3', mode = 'lines+markers') data[, updatevar := get(paste0("trace_", pending_update()))] fig <- fig %>% add_trace(x = c(data$x, data$x), y = c(data$updatevar, data$updatevar), type = "scatter", mode = "markers", marker = list(color = "black")) pending_update(NULL) fig }) } }) }
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

