有没有办法在选择必须不相交的情况下进行多个闪亮的选择器输入?

Is there a way to make multiple Shiny picker inputs where the selections must be disjoint?

我希望在 Shiny 中为 50 个州中的每一个州进行一些选择器输入,但我想将它们分成三个不同的组,以便没有组具有相同的状态。我只是想知道是否有一种方法可以确保三个选择器输入不会都 select 相同的状态,或者在 R 中是否有一种我不知道的更好的方法来做到这一点。谢谢!

我假设您所说的“选择器输入”是指 selectInput/selectizeInput

有多种方法可以做到这一点。一种方法是在选择 first/second 后使用 updateSelectInput() 更新提醒输入。可供选择的可能状态将是除已选择的状态之外的所有状态。这将使得无法在 UI.

的多个输入中选择相同的状态

但是,这可能会有点涉及您的需要。在这种情况下,我建议您:

  • 将您的三个输入替换为一个 selectInput(..., multiple = TRUE),然后使用 validate() 检查用户是否恰好选择了三个状态
  • 或者如果用户在三个输入中的任何一个中多次选择相同状态,则只需使用 validate() 向用户抛出错误。

设置需要一些工作,但您可以通过更新 当一个改变时,其他输入的可用选择。如果你只有两个或 应该像这样 linked 的三个输入,它可能很诱人 写出观察者并完成它。但实际上,这是一个 泛化模式,所以我认为使用辅助函数是有意义的 反而。这样,您可以 link 无论您需要多少输入,也可以 re-use 不同应用程序中的逻辑。

辅助函数只需要知道参与的 ID 输入,以及一组共享的选择。这里不是绝对必要的,但是 还使 choices 具有反应性,让它们动态变化。

selectPool <- function(inputIds, choices = reactive(NULL)) {
  stopifnot(is.reactive(choices))
  session <- getDefaultReactiveDomain()
  input <- session$input
  
  # Keep track of all selected values in the pool
  alreadySelected <- reactive({
    Reduce(union, lapply(inputIds, \(id) input[[id]]))
  })
  
  # ... and based on that, what's left to select from.
  remainingChoices <- reactive({
    setdiff(choices(), alreadySelected())
  })
  
  # When an input changes, update remaining choices for others
  lapply(inputIds, \(id) {
    observe({
      lapply(setdiff(inputIds, id), \(otherId) {
        otherSelected <- input[[otherId]]
        updateSelectInput(
          session = session,
          inputId = otherId,
          # Anything already selected must remain a choice
          choices = c(remainingChoices(), otherSelected),
          selected = otherSelected
        )
      })
    }) |> bindEvent(input[[id]], ignoreNULL = FALSE)
  })
}

一旦我们花时间这样做,在应用程序中使用它就非常简单了:

library(shiny)

ui <- fluidPage(
  titlePanel("Star Wars Alliance Builder"),
  selectInput("alliance1", "Alliance 1", NULL, multiple = TRUE),
  selectInput("alliance2", "Alliance 2", NULL, multiple = TRUE),
  selectInput("alliance3", "Alliance 3", NULL, multiple = TRUE),
)

server <- function(input, output, session) {
  selectPool(
    inputIds = c("alliance1", "alliance2", "alliance3"),
    choices = reactive(unique(dplyr::starwars$species))
  )
}

shinyApp(ui, server)