You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

R Shiny中DataTable的联动过滤器实现方案咨询

最优实现方案

针对你的需求,核心思路是通过响应式过滤逻辑实现全字段过滤,同时动态更新每个过滤器的可选值,确保联动效果。以下是完整的优化代码:

library(shiny)
library(DT)
library(stringr)
library(dplyr)
library(purrr)

# 原始聚合数据
dataset <- data.frame(
  "person" = c('personA', 'personA','personA', 'personA','personB','personB','personB','personC','personC','personD','personE'),
  "location" = c('ER1','ER1','ER1','ER1','QF5','QF5','QF5','BV9','BV9','ER1','ER3'),
  "field_of_study" = c('Genetics','Biochemistry','Ecophysiology','Phylogeny','Ecology','GIS', 'Ecotoxicology', 'Genetics', 'Ecology', 'GIS', 'Geology'),
  "skills" = c('G01','B04','EC04','P02','E02','G01', 'E07', 'G01', 'E07', 'G02', 'G08')
) %>% 
  group_by(person, location) %>% 
  summarise(
    field_of_study=paste(unique(field_of_study), collapse = " - "),
    skills=paste(skills, collapse = " - ")
  ) %>% 
  ungroup()

ui = fluidPage(
  actionButton(inputId = "filter", label = "Filter"),
  DT::dataTableOutput("skills_directory")
)

server = function(input, output, session) {
  
  # 响应式计算过滤后的数据
  filtered_data <- reactive({
    data <- dataset
    
    # 过滤person字段
    if (!is.null(input$person) && length(input$person) > 0) {
      data <- data %>% filter(person %in% input$person)
    }
    
    # 过滤location字段
    if (!is.null(input$location) && length(input$location) > 0) {
      data <- data %>% filter(location %in% input$location)
    }
    
    # 过滤field_of_study字段:匹配选中的任意一个领域
    if (!is.null(input$field_of_study) && length(input$field_of_study) > 0) {
      data <- data %>% filter(
        map_lgl(field_of_study, ~ any(str_detect(.x, fixed(input$field_of_study, ignore_case = TRUE))))
      )
    }
    
    # 过滤skills字段:匹配选中的任意一个技能
    if (!is.null(input$skills) && length(input$skills) > 0) {
      data <- data %>% filter(
        map_lgl(skills, ~ any(str_detect(.x, fixed(input$skills, ignore_case = TRUE))))
      )
    }
    
    data
  })
  
  # 渲染DataTable
  output$skills_directory <- DT::renderDataTable({
    DT::datatable(
      filtered_data(),
      selection = 'single',
      rownames = FALSE,
      editable = FALSE,
      options = list(dom = 't')
    )
  })
  
  # 打开过滤模态框
  observeEvent(input$filter,{
    showModal(
      modalDialog(
        title = "搜索条件",
        selectizeInput("person", "人员", choices = c("", sort(unique(dataset$person))), multiple = TRUE),
        selectizeInput("location", "地点", choices = c("", sort(unique(dataset$location))), multiple = TRUE),
        selectizeInput("field_of_study", "研究领域", choices = c("", sort(unique(unlist(str_split(dataset$field_of_study, " - "))))), multiple = TRUE),
        selectizeInput("skills", "技能", choices = c("", sort(unique(unlist(str_split(dataset$skills, " - "))))), multiple = TRUE),
        actionButton(inputId = "reset_filter", label = "重置"),
        actionButton(inputId = "confirm_filter", label = "确认"),
        footer = modalButton("取消"),
        easyClose = TRUE,
        size = "l"
      )
    )
  })
  
  # 重置过滤器
  observeEvent(input$reset_filter, {
    updateSelectizeInput(session, "person", selected = "")
    updateSelectizeInput(session, "location", selected = "")
    updateSelectizeInput(session, "field_of_study", selected = "")
    updateSelectizeInput(session, "skills", selected = "")
  })
  
  # 联动更新person过滤器选项(基于其他三个过滤器的选择)
  observe({
    temp_data <- dataset
    
    # 应用location、field_of_study、skills的过滤条件
    if (!is.null(input$location) && length(input$location) > 0) {
      temp_data <- temp_data %>% filter(location %in% input$location)
    }
    if (!is.null(input$field_of_study) && length(input$field_of_study) > 0) {
      temp_data <- temp_data %>% filter(
        map_lgl(field_of_study, ~ any(str_detect(.x, fixed(input$field_of_study, ignore_case = TRUE))))
      )
    }
    if (!is.null(input$skills) && length(input$skills) > 0) {
      temp_data <- temp_data %>% filter(
        map_lgl(skills, ~ any(str_detect(.x, fixed(input$skills, ignore_case = TRUE))))
      )
    }
    
    # 更新选项
    new_choices <- c("", sort(unique(temp_data$person)))
    updateSelectizeInput(session, "person", choices = new_choices, selected = input$person)
  })
  
  # 联动更新location过滤器选项(基于其他三个过滤器的选择)
  observe({
    temp_data <- dataset
    
    # 应用person、field_of_study、skills的过滤条件
    if (!is.null(input$person) && length(input$person) > 0) {
      temp_data <- temp_data %>% filter(person %in% input$person)
    }
    if (!is.null(input$field_of_study) && length(input$field_of_study) > 0) {
      temp_data <- temp_data %>% filter(
        map_lgl(field_of_study, ~ any(str_detect(.x, fixed(input$field_of_study, ignore_case = TRUE))))
      )
    }
    if (!is.null(input$skills) && length(input$skills) > 0) {
      temp_data <- temp_data %>% filter(
        map_lgl(skills, ~ any(str_detect(.x, fixed(input$skills, ignore_case = TRUE))))
      )
    }
    
    # 更新选项
    new_choices <- c("", sort(unique(temp_data$location)))
    updateSelectizeInput(session, "location", choices = new_choices, selected = input$location)
  })
  
  # 联动更新field_of_study过滤器选项(基于其他三个过滤器的选择)
  observe({
    temp_data <- dataset
    
    # 应用person、location、skills的过滤条件
    if (!is.null(input$person) && length(input$person) > 0) {
      temp_data <- temp_data %>% filter(person %in% input$person)
    }
    if (!is.null(input$location) && length(input$location) > 0) {
      temp_data <- temp_data %>% filter(location %in% input$location)
    }
    if (!is.null(input$skills) && length(input$skills) > 0) {
      temp_data <- temp_data %>% filter(
        map_lgl(skills, ~ any(str_detect(.x, fixed(input$skills, ignore_case = TRUE))))
      )
    }
    
    # 拆分拼接字段,提取唯一值
    new_choices <- temp_data$field_of_study %>%
      str_split(" - ") %>%
      unlist() %>%
      unique() %>%
      sort() %>%
      c("", .)
    
    updateSelectizeInput(session, "field_of_study", choices = new_choices, selected = input$field_of_study)
  })
  
  # 联动更新skills过滤器选项(基于其他三个过滤器的选择)
  observe({
    temp_data <- dataset
    
    # 应用person、location、field_of_study的过滤条件
    if (!is.null(input$person) && length(input$person) > 0) {
      temp_data <- temp_data %>% filter(person %in% input$person)
    }
    if (!is.null(input$location) && length(input$location) > 0) {
      temp_data <- temp_data %>% filter(location %in% input$location)
    }
    if (!is.null(input$field_of_study) && length(input$field_of_study) > 0) {
      temp_data <- temp_data %>% filter(
        map_lgl(field_of_study, ~ any(str_detect(.x, fixed(input$field_of_study, ignore_case = TRUE))))
      )
    }
    
    # 拆分拼接字段,提取唯一值
    new_choices <- temp_data$skills %>%
      str_split(" - ") %>%
      unlist() %>%
      unique() %>%
      sort() %>%
      c("", .)
    
    updateSelectizeInput(session, "skills", choices = new_choices, selected = input$skills)
  })
  
  # 确认过滤后关闭模态框
  observeEvent(input$confirm_filter, {
    removeModal()
  })
}

shinyApp(ui, server)

关键实现细节

  1. 响应式过滤逻辑

    • 用filtered_data()响应式表达式实时计算过滤结果,覆盖四个字段的过滤规则
    • 对于拼接字段(field_of_study/skills),使用map_lgl+str_detect实现多选OR匹配(即行包含任意一个选中值即被保留),若需AND匹配可将any替换为all
  2. 过滤器联动更新

    • 每个过滤器的observe监听其他三个输入的变化,基于过滤后的数据提取可选值
    • 拼接字段的选项通过str_split拆分字符串后去重排序,确保选项是当前数据中存在的单个值
  3. 用户体验优化

    • 添加了「重置」按钮,一键清空所有过滤器选择
    • 确认过滤后自动关闭模态框,操作更流畅
    • 选项默认按字母排序,提升可读性

内容的提问来源于stack exchange,提问作者Linda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 20:45:34