【问题标题】:How to modify reactivity chain so last object modified controls other chained objects?如何修改反应链以便最后修改的对象控制其他链接对象?
【发布时间】:2026-01-12 17:45:01
【问题描述】:

[新注释 1:在最底部发布的最终解析代码反映了 2021 年 12 月 3 日的 ismirsehregal 解决方案,以及一些标记为“# ADDED”和“# MODIFIED”的小调整。 ADD 是为了解决我在矩阵 2 添加了值后从矩阵 1 中删除行时遇到的错误(如下所述),并且“修改”是为了符合矩阵 1 和 2 的列标题(它们没有不同的意义列标题)。

运行以下代码时,我希望反应链中最后一个修改的对象能够“控制”或“支配”该反应链中的其他对象。在此代码中,链接的反应对象是“matrix1”和“matrix2”。输入到matrix1下游到matrix2,并输入到matrix2的前2列到matrix1上游。根据草案,矩阵 2 的输入胜过矩阵 1 的输入。我希望最后输入的矩阵胜过另一个矩阵。有人可以帮我解决这个问题吗?

底部帮助说明的图像。

我已经搞砸了isolate() 和其他东西,试图让它按照我想要的方式工作。我还遇到了矩阵陷入循环的问题,其中值在两个矩阵之间来回反弹。我还没有完全掌握isolate()

代码:

library(dplyr)
library(ggplot2)
library(shiny)
library(shinyMatrix)

sumMat <- function(x){return(rep(sum(x,na.rm = TRUE), 10))}

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      matrixInput("matrix1",
                  label ="Matrix 1 (scenario 1):",
                  value = matrix(c(60,5),ncol=2,dimnames=list(NULL,c("X","Y"))),
                  rows = list(extend = TRUE, delete = TRUE),
                  class = "numeric"),
      actionButton(inputId = "showMat2", "Add scenarios"),br(),br(),
    ),
    mainPanel(plotOutput("plot"))
  )
)

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

  observeEvent(input$matrix1, {
    tmpMat1 <- input$matrix1
    if(any(rownames(input$matrix1) == "")){rownames(tmpMat1) <- paste("Row", seq_len(nrow(input$matrix1)))    }
    isolate(updateMatrixInput(session, inputId = "matrix1", value = tmpMat1))
  })
  
  observeEvent(input$showMat2,{
    showModal(
      modalDialog(
        matrixInput("matrix2",
                    label = "Matrix 2:",
                    value = input$matrix1,
                    rows = list(extend = TRUE, delete = TRUE),
                    cols = list(extend = TRUE, delta = 2, delete = TRUE, multiheader = TRUE),
                    class = "numeric"),
        footer = tagList(modalButton("Close"))
      ))
    observeEvent(input$matrix2, {
      tmpMat2 <- input$matrix2
      rownames(tmpMat2) <- paste("Row", seq_len(nrow(input$matrix2)))
      colnames(tmpMat2) <- paste("Scenario",rep(1:ncol(tmpMat2),each=2,length.out=ncol(tmpMat2)))
      isolate(updateMatrixInput(session, inputId = "matrix2", value = tmpMat2))
      isolate(updateMatrixInput(session, inputId = "matrix1", value = tmpMat2[,1:2]))
    })
  })
  
  plotData <- reactive({
    tryCatch(
      lapply(seq_len(ncol(input$matrix1)/2), 
             function(i){
               tibble(
                 Scenario= colnames(input$matrix1)[i*2-1],X=seq_len(10),
                 Y=sumMat(input$matrix1[,(i*2-1):(i*2), drop = FALSE])
               )
             }) %>% bind_rows(),
      error = function(e) NULL
    )
  })
  
  output$plot <- renderPlot({
    plotData() %>% ggplot() + 
      geom_line(aes(x = X, y = Y, colour = as.factor(Scenario))) +
      theme(legend.title=element_blank())
  })
}

shinyApp(ui, server)

新注释1:下面的最终解析代码

sumMat <- function(x) {return(rep(sum(x, na.rm = TRUE), 10))}

ui <- fluidPage(sidebarLayout(
  sidebarPanel(
    matrixInput(
      "matrix1",
      label = "Matrix 1:", # MODIFIED HEADER
      value = matrix(c(60,5),ncol=2,dimnames=list(NULL,rep("Scenario 1",2))), # MODIFIED HEADER
      rows = list(extend = TRUE, delete = TRUE),
      cols = list(multiheader = TRUE), # ADD
      class = "numeric"
    ),
    actionButton(inputId = "showMat2", "Add scenarios"),br(),br(),
  ),
  mainPanel(plotOutput("plot"))
))

server <- function(input, output, session) {
  
  currentMat <- reactiveVal(isolate(input$matrix1))
  
  observeEvent(input$matrix1, {
    tmpMat1 <- input$matrix1
    if(any(rownames(input$matrix1)=="")){rownames(tmpMat1)<-paste("Row",seq_len(nrow(input$matrix1)))}
    updateMatrixInput(session, inputId = "matrix1", value = tmpMat1)
    
    tmpMat2 <- currentMat()
    
    if(nrow(tmpMat1) > nrow(tmpMat2)){tmpMat2 <- rbind(tmpMat2, rep(NA, ncol(tmpMat2)))}
  
    # ADDED
    if(nrow(tmpMat2) > nrow(tmpMat1)){tmpMat1 <- rbind(tmpMat1, rep(NA, ncol(tmpMat1)))}
  
    currentMat(cbind(tmpMat1[drop=FALSE], tmpMat2[,-1:-2,drop=FALSE]))
  })
  
  observeEvent(input$showMat2, {
    showModal(modalDialog(
      matrixInput(
        "matrix2",
        label = "Matrix 2:",
        value = currentMat(),
        rows = list(extend = TRUE, delete = TRUE),
        cols = list(extend = TRUE,delta = 2,delete = TRUE,multiheader = TRUE),
        class = "numeric"
      ),
      footer = tagList(modalButton("Close"))
    ))
  })
  
  observeEvent(input$matrix2, {
    tmpMat2 <- input$matrix2
    rownames(tmpMat2) <- paste("Row", seq_len(nrow(input$matrix2)))
    colnames(tmpMat2) <-
      paste("Scenario", rep(1:ncol(tmpMat2),each = 2,length.out = ncol(tmpMat2)))
    currentMat(tmpMat2)
    updateMatrixInput(session, inputId = "matrix2", value = tmpMat2)
    updateMatrixInput(session, inputId = "matrix1", value = tmpMat2[, 1:2, drop = FALSE])
  })
  
  plotData <- reactive({
    tryCatch(
      lapply(seq_len(ncol(input$matrix1) / 2),
             function(i) {
               tibble(
                 Scenario = colnames(input$matrix1)[i * 2 - 1],
                 X = seq_len(10),
                 Y = sumMat(input$matrix1[, (i * 2 - 1):(i * 2), drop = FALSE])
               )
             }) %>% bind_rows(),
      error = function(e)
      NULL
    )
  })
  
  output$plot <- renderPlot({
    plotData() %>% ggplot() +
      geom_line(aes(
        x = X,
        y = Y,
        colour = as.factor(Scenario)
      )) +
      theme(legend.title = element_blank())
  })
}

shinyApp(ui, server)

【问题讨论】:

    标签: r shiny shiny-reactivity


    【解决方案1】:

    以下似乎有效:

    • 记得使用drop = FALSE
    • 从不嵌套观察者

    library(dplyr)
    library(ggplot2)
    library(shiny)
    library(shinyMatrix)
    
    sumMat <- function(x) {
      return(rep(sum(x, na.rm = TRUE), 10))
    }
    
    ui <- fluidPage(sidebarLayout(
      sidebarPanel(
        matrixInput(
          "matrix1",
          label = "Matrix 1 (scenario 1):",
          value = matrix(c(60, 5), ncol = 2, dimnames = list(NULL, c("X", "Y"))),
          rows = list(extend = TRUE, delete = TRUE),
          class = "numeric"
        ),
        actionButton(inputId = "showMat2", "Add scenarios"),
        br(),
        br(),
      ),
      mainPanel(plotOutput("plot"))
    ))
    
    server <- function(input, output, session) {
      
      currentMat <- reactiveVal(isolate(input$matrix1))
      
      observeEvent(input$matrix1, {
        tmpMat1 <- input$matrix1
        if (any(rownames(input$matrix1) == "")) {
          rownames(tmpMat1) <- paste("Row", seq_len(nrow(input$matrix1)))
        }
        updateMatrixInput(session, inputId = "matrix1", value = tmpMat1)
        
        tmpMat2 <- currentMat()
        if(nrow(tmpMat1) > nrow(tmpMat2)){
          tmpMat2 <- rbind(tmpMat2, rep(NA, ncol(tmpMat2)))
        }
        if(nrow(tmpMat2) > nrow(tmpMat1)){
          tmpMat1 <- rbind(tmpMat1, rep(NA, ncol(tmpMat1)))
        }
        currentMat(cbind(tmpMat1, tmpMat2[,-1:-2]))
      })
      
      observeEvent(input$showMat2, {
        showModal(modalDialog(
          matrixInput(
            "matrix2",
            label = "Matrix 2:",
            value = currentMat(),
            rows = list(extend = TRUE, delete = TRUE),
            cols = list(
              extend = TRUE,
              delta = 2,
              delete = TRUE,
              multiheader = TRUE
            ),
            class = "numeric"
          ),
          footer = tagList(modalButton("Close"))
        ))
      })
      
      observeEvent(input$matrix2, {
        tmpMat2 <- input$matrix2
        rownames(tmpMat2) <- paste("Row", seq_len(nrow(input$matrix2)))
        colnames(tmpMat2) <-
          paste("Scenario", rep(
            1:ncol(tmpMat2),
            each = 2,
            length.out = ncol(tmpMat2)
          ))
        currentMat(tmpMat2)
        updateMatrixInput(session, inputId = "matrix2", value = tmpMat2)
        updateMatrixInput(session, inputId = "matrix1", value = tmpMat2[, 1:2, drop = FALSE])
      })
      
      plotData <- reactive({
        tryCatch(
          lapply(seq_len(ncol(input$matrix1) / 2),
                 function(i) {
                   tibble(
                     Scenario = colnames(input$matrix1)[i * 2 - 1],
                     X = seq_len(10),
                     Y = sumMat(input$matrix1[, (i * 2 - 1):(i * 2), drop = FALSE])
                   )
                 }) %>% bind_rows(),
          error = function(e)
            NULL
        )
      })
      
      output$plot <- renderPlot({
        plotData() %>% ggplot() +
          geom_line(aes(
            x = X,
            y = Y,
            colour = as.factor(Scenario)
          )) +
          theme(legend.title = element_blank())
      })
    }
    
    shinyApp(ui, server)
    

    【讨论】:

    • 效果很好!我注意到的一件事是在我的原始代码中,当用户再次单击操作按钮时,矩阵 2 中的任何其他场景输入都会被保留和检索,这是应该的。随着您的更改,此 >1 方案保留不再有效。所有 >1 个场景输入都丢失。
    • 请检查我的编辑。您上面的代码没有正确保留这些值。
    • 您好,“场景 1”之外的其他场景现在保留在您修改后的代码中。但是,最后输入的对象不再占主导地位。例如,当我在矩阵 1 中输入值,然后单击操作按钮添加更多场景时,矩阵 1 的输入不会像应有的那样出现在矩阵 2 的“场景 1”中。然而,矩阵 2 的输入确实会在矩阵 1 中正确反应性地呈现。
    • @CuriousJorge-user9788072 - 我再次更新了代码。
    • 嗨!几乎!它工作正常,除了从矩阵 1 中删除行时。它崩溃并显示消息“警告:cbind 中的错误:矩阵的行数必须匹配(参见参数 2)......”您可以毫无问题地从矩阵 2 中删除行和列,并且它们正确反应地反映在矩阵 1 中。我可以毫无问题地将行插入到矩阵 1 中,并且它们正确反应地反映在矩阵 2 中。我将使用我在另一个草案中使用的一些代码行来修改我的帖子以符合尺寸 矩阵,虽然我更喜欢你的 cbind() 和 rbind() 解决方案,但它们要干净得多。