【问题标题】:reset selectizeInputs each time I modify a numericInput in RShiny每次我在 RShiny 中修改 numericInput 时重置 selectizeInputs
【发布时间】:2018-05-20 13:19:16
【问题描述】:

我正在学习闪亮并使用连接到许多 selectizeInputs 的 numericInput。

  • 如果数字输入等于 1 或 2,我想分别创建 1 和 2 个 selectizeInputs,并为每个 selectizeInput EDIT 选择名为“modalities”的向量的第“i”个模态:和选择 = modalities[i] only(而不是modalities)

  • 如果数字输入等于 3 或 4,我想分别创建 3 和 4 个相互连接的 selectizeInputs(选择 = 模态)。换句话说:如果在其中一个 selectizeinputs 中选择了一个项目,我希望它从其他 selectizeinputs 的选择中消失。

此外(这是我遇到的问题)我想在每次修改 numericInput 时“重置”所有选定的 SelectizeInputs。我尝试使用下面的 observeEvent 并尝试使用隔离(输入$ui_number),但我没有找到任何解决我的问题的方法,因为我不明白该怎么做......!

感谢您的帮助!

      library(shiny)

      modalities <- LETTERS[1:10]

      ui = tabPanel("Change modalities",
                    numericInput("ui_number", label = "Number of modalities",
                                 min = 1, max = 4, value = 3),
                    uiOutput("renderui")
      )

      server = function(input, output, session) {

        # Generate modalities select lists
        output$renderui <- renderUI({
          output = tagList()
          for (i in seq_len(input$ui_number)) {
            output[[i]] = selectizeInput(paste0("ui_mod_choose", i), 
                                         label = paste0("Modality ", i),
                                         choices = modalities, multiple = TRUE)
          }
          return(output)
        })


        # if input$ui_number is modified to 3 or 4 : set selected to NULL ##### NOT WORKING
        observeEvent({input$ui_number},
                     {
                       n <- input$ui_number
                       if(n%in%c(3,4)){
                         for (i in seq_len(n)) {
                           updateSelectizeInput(session, paste0("ui_mod_choose",i),selected=NULL)
                         }
                       }

                     }

        )


        observe({

          n <- input$ui_number

          if(n%in%c(1,2)){ #if n=1 or 2 =>  Select the "i"th modality for each selectizeInput
            for (i in seq_len(n)) {
              updateSelectizeInput(session, paste0("ui_mod_choose",i),
                                   choices = modalities[i],
                                   selected = modalities[i]
              )
            }

          } else{   # if n=3 or 4 => Remove selected modalities from other select lists
            for (i in seq_len(n)) {
              vecteur <- unlist(lapply((1:n)[-i], function(i) 
                input[[paste0("ui_mod_choose",i)]]))
              updateSelectizeInput(session, paste0("ui_mod_choose",i),
                                   choices = setdiff(modalities, vecteur),
                                   selected = input[[paste0("ui_mod_choose",i)]])
            }

          }




        })

      }

      runApp(shinyApp(ui, server))  

本期对应以下问题:

lapply function using a numericInput parameter around an observeEvent in RShiny

EDIT2:感谢@Aurèle 的提示,新尝试。 唯一剩下的问题是 lapply 中的 1:100 可能需要一些时间来加载(没有找到解决方案来在条件面板周围添加诸如 1:input&ui_number 之类的反应性内容)

      library(shiny)

      modalities <- LETTERS[1:10]

      make_conditional_selectizeInputs <- function() {
        do.call(
          div,
          lapply(1:100, function(i)
            conditionalPanel(
              condition = sprintf("%d <= input.ui_number", i),
              selectizeInput(sprintf("ui_mod_choose%d", i), 
                             label = sprintf("Modality %d", i),
                             choices = character(0), multiple = TRUE, selected = NULL)
            )
          )
        )
      }



      ui <- tabPanel(
        "Change modalities",
        uiOutput("rendernumeric"),
        #numericInput("ui_number", label = "Number of modalities", min = 1L, max = max, value = 1L),
        make_conditional_selectizeInputs()
      )


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

        max <- 4

        output$rendernumeric <- renderUI({
          numericInput("ui_number", label = "Number of modalities", min = 1L, max = max, value = 1L)
        })





        n <- reactive({
          n <- input$ui_number
          if (is.null(n) || is.na(n) || !n >= 0) 0 else n
        })

        # Reset all
        observeEvent(
          eventExpr = n(), 
          handlerExpr = for (i in seq_len(max))
            updateSelectizeInput(
              session, sprintf("ui_mod_choose%d", i),
              choices = if (n() %in% 1:2 && i <= n()) modalities[i] else modalities, 
              selected = if (n() %in% 1:2 && i <= n()) modalities[i] else NULL
            )
        )

        all_selected <- reactive({
          unlist(lapply(seq_len(max), function(i) 
            input[[sprintf("ui_mod_choose%d", i)]]))
        })

        # Update available modalities
        observeEvent(
          eventExpr = all_selected(),
          handlerExpr = if (!n() %in% 1:2) for (i in seq_len(n())) {
            x <- input[[sprintf("ui_mod_choose%d", i)]]
            other_selected <- setdiff(all_selected(), x)
            updateSelectizeInput(session, sprintf("ui_mod_choose%d", i),
                                 choices = setdiff(modalities, other_selected),
                                 selected = x)
          }
        )

      }

      runApp(shinyApp(ui, server))

【问题讨论】:

    标签: r shiny


    【解决方案1】:

    基本上,只要您重新生成selectizeInputs,您只需要多一行:selected = if (n %in% 1:2) modalities[i] else NULL

    library(shiny)
    
    modalities <- LETTERS[1:10]
    
    ui = tabPanel("Change modalities",
                  numericInput("ui_number", label = "Number of modalities",
                               min = 1, max = 4, value = 3),
                  uiOutput("renderui"))
    
    server = function(input, output, session) {
    
      # Generate modalities select lists
      output$renderui <- renderUI({
        output = tagList()
        n <- input$ui_number
        n <- if (is.null(n) || is.na(n) || ! n >= 0) 0 else n
        for (i in seq_len(n)) {
          output[[i]] = selectizeInput(paste0("ui_mod_choose", i), 
                                       label = paste0("Modality ", i),
                                       choices = if (n %in% 1:2) modalities[i] else modalities, 
                                       multiple = TRUE,
                                       # Add this
                                       selected = if (n %in% 1:2) modalities[i] else NULL)
        }
        output
      })
    
      # Remove selected modalities from other select lists
      observe({
        n <- isolate(input$ui_number)
        if (!n %in% 1:2) for (i in seq_len(n)) {
          vecteur <- unlist(lapply((1:n)[-i], function(i) 
            input[[paste0("ui_mod_choose",i)]]))
          updateSelectizeInput(session, paste0("ui_mod_choose",i),
                               choices = setdiff(modalities, vecteur),
                               selected = input[[paste0("ui_mod_choose",i)]])
        }
      })
    
    }
    
    runApp(shinyApp(ui, server))
    

    【讨论】:

    • 感谢您的提示,但它仍然不是我想要的(我的错:我一开始没有正确提及):当 n %in c(1,2) 我想要选择 = modalities[i] (你做了什么),但我也想要选择 = modalities[i] 而不是 modalities ......而且我尝试过的仍然不起作用......(见编辑)。我真的很难知道在 renderUI 中,在 updateSelectizeInput 中,当隔离 n...
    • 查看我的编辑:if (!n %in% 1:2) for (.... 而不是 for (....。此处使用isolate() 以避免对input$ui_number 的不必要依赖。力求简单(说起来容易做起来难):只在真正需要时添加依赖项。不要太担心不理解updateSelectizeInput,它更像是一个边缘案例/Shiny 中的一个hack。为了更好地理解 Shiny 的 reactivity,shiny.rstudio.com 上的资源非常宝贵。另外:设置options(shiny.reactlog=TRUE),然后运行您的应用程序,玩一下它,然后点击Ctrl+F3 获取正在发生的图形模型
    • 感谢您的技巧,我会尝试学习如何使用 shiny.reactlog。我开始玩这个应用程序。我将 numericInput(value = 3) 替换为 numericInput(value = 1) 并且应用程序停止工作:( => 当我选择 input$ui_number=3 时,selectizeinputs 不再相互连接,我不明白为什么会这样!
    • 好的,我可以重现了。这是因为if (!n %in% 1:2)不知何故让我们失去了对input$ui_mod_chooseis的依赖。
    • 这就是为什么我在过去 3 天里每次尝试某些东西时都失败的部分原因。我真的不明白这种失去依赖性。您认为有可能找到一种适用于所有情况的解决方案吗?
    【解决方案2】:

    (这足以作为单独的答案)。

    https://shiny.rstudio.com/articles/dynamic-ui.html 中,建议了四种不同的 Shiny 动态 UI 方法,按难度排序:

    • conditionalPanel 函数,在 ui.R 中使用,封装了一组需要动态显示/隐藏的 UI 元素。
    • server.R 中的renderUI 函数与ui.R 中的uiOutput 函数一起使用,可让您生成对 UI 函数的调用,并使结果显示在 UI 中的预定位置。
    • insertUIremoveUI 函数,在 server.R 中使用,允许您添加和删除任意 UI 代码块(全部相互独立),只要您想,想多少次都行,随心所欲。
    • 使用JavaScript修改网页

    您的尝试使用第二种方法,此答案使用第一种方法(尽管它应该适用于其中任何一种):

    library(shiny)
    
    modalities <- LETTERS[1:10]
    max <- 4L
    

    首先,构建 UI 的辅助函数。 selectizeInputs 的数量不再是动态的,而是固定为max,并且它们根据input$ui_number 交替显示/隐藏:

    make_conditional_selectizeInputs <- function(max) {
      do.call(
        div,
        lapply(seq_len(max), function(i)
          conditionalPanel(
            condition = sprintf("%d <= input.ui_number", i),
            selectizeInput(sprintf("ui_mod_choose%d", i), 
                           label = sprintf("Modality %d", i),
                           choices = character(0), multiple = TRUE, selected = NULL)
          )
        )
      )
    }
    
    ui <- tabPanel(
      "Change modalities",
      numericInput("ui_number", label = "Number of modalities",
                   min = 1L, max = max, value = 1L),
      make_conditional_selectizeInputs(max)
    )
    

    服务器函数有两个响应式表达式,有助于模块化代码,但对其逻辑不是必需的(n()all_expected())。

    不再有renderUI()selectizeInputs 已经一劳永逸地生成了)。

    有一个 observeEvent() 依赖于 input$ui_number 并在它更改时重置所有选择和选择。

    最后一个 observeEvent() 依赖于所有 input$ui_mod_choose[i] 并在有新选择时更新所有选择。

    server <- function(input, output, session) {
    
      n <- reactive({
        n <- input$ui_number
        if (is.null(n) || is.na(n) || !n >= 0) 0 else n
      })
    
      # Reset all
      observeEvent(
        eventExpr = n(), 
        handlerExpr = for (i in seq_len(max))
          updateSelectizeInput(
            session, sprintf("ui_mod_choose%d", i),
            choices = if (n() %in% 1:2 && i <= n()) modalities[i] else modalities, 
            selected = if (n() %in% 1:2 && i <= n()) modalities[i] else NULL
          )
      )
    
      all_selected <- reactive({
        unlist(lapply(seq_len(max), function(i) 
          input[[sprintf("ui_mod_choose%d", i)]]))
      })
    
      # Update available modalities
      observeEvent(
        eventExpr = all_selected(),
        handlerExpr = if (!n() %in% 1:2) for (i in seq_len(n())) {
          x <- input[[sprintf("ui_mod_choose%d", i)]]
          other_selected <- setdiff(all_selected(), x)
          updateSelectizeInput(session, sprintf("ui_mod_choose%d", i),
                               choices = setdiff(modalities, other_selected),
                               selected = x)
        }
      )
    
    }
    

    本质上,它与第二种方法(使用renderUI)的不同之处在于,它删除了input$ui_numberinput$ui_mod_choose[i] 之间的部分依赖关系,至少在它们生成时是这样(但是当它们生成时存在残留依赖关系)因为updateSelectizeInput 而重新重置。我不完全清楚为什么我可以使用这种方法而不是renderUI)。

    runApp(shinyApp(ui, server))
    

    这是 reactlog 的截图,虽然它没有显示全图,因为 updateSelectizeInput() 的必要杂质混合了 UI 和服务器逻辑,并创建了循环依赖项可能很难推理:

    【讨论】:

    • 我已经说过了,但首先:非常感谢您的帮助,感谢您帮助我理解我将定义为闪亮的高水平 :) 我真的很喜欢学习这种新方法。而且这种方法效果很好!但是(再次抱歉,但是我想了解反射的每一步)在我的真实应用程序中,我的最大值不是固定值,而是取决于另一个 selectInput 的值(反应值)。由于我必须在您的示例中将其声明为全局变量,因此我不确定是否可以为我的真实应用程序重现此方法。
    • 我找到了一种方法(参见 EDIT 2)用 lapply(1:100..想办法!
    猜你喜欢
    • 2017-11-13
    • 2018-12-03
    • 2019-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多