有没有办法在选择必须不相交的情况下进行多个闪亮的选择器输入?
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)
我希望在 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)