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

基于DT搜索的Crosstalk联动Plotly动态过滤实现问询

解决方案

一、实现DT搜索/选行联动高亮Plotly散点

crosstalk默认仅支持图表到表格的单向联动,要实现表格到图表的反向高亮,需要借助JavaScript监听DT的操作事件,再动态修改Plotly散点样式。以下是完整的修改代码:

---
title: "文本与散点联动分析"
output: html_document
---

```{r setup, include=FALSE}
library(tidyverse)
library(crosstalk)
library(plotly)
library(DT)
set.seed(0)
n = 20
survey <- data.frame(id = 1:n,
                     date = as.Date("2023-01-01") + runif(n = n, max = 100),
                     score = sample(0:10, n, replace = TRUE),
                     sentence = wakefield::sentence(n = n) |> str_wrap(40))

survey_shared <- SharedData$new(survey, key = ~id)
# 给散点绑定唯一id标识,用于JS定位
p <- ggplot(survey_shared, aes(date, score, text = sentence, customdata = id)) +
  geom_point()

p_plotly <- ggplotly(p, tooltip = "text") %>% 
  layout(hoverlabel = list(align = "left")) 

# 开启DT的多选和顶部搜索功能
dt_table <- datatable(survey_shared, 
                      rownames = FALSE, 
                      width = "100%",
                      selection = "multiple",
                      filter = "top")
document.addEventListener('DOMContentLoaded', function() {
  // 获取Plotly和DT的DOM对象
  const plotDiv = document.querySelector('.plotly');
  const dtTable = $('#DataTables_Table_0').DataTable();
  
  // 定义高亮逻辑:根据id列表修改点的颜色和大小
  function highlightPoints(targetIds) {
    if (!plotDiv.data) return;
    const originalColor = plotDiv.data[0].marker.color;
    const originalSize = plotDiv.data[0].marker.size || 6;
    
    // 生成高亮后的样式数组
    const newColors = plotDiv.data[0].customdata.map(id => 
      targetIds.includes(id) ? '#ff0000' : originalColor
    );
    const newSizes = plotDiv.data[0].customdata.map(id => 
      targetIds.includes(id) ? originalSize + 3 : originalSize
    );
    
    // 更新Plotly图表
    Plotly.restyle(plotDiv, 
                   {marker: {color: newColors, size: newSizes}}, 
                   [0]);
  }
  
  // 监听DT行选择事件
  dtTable.on('select', function(e, dt, type, indexes) {
    if (type === 'row') {
      const selectedIds = indexes.map(idx => dt.row(idx).data().id);
      highlightPoints(selectedIds);
    }
  });
  
  // 监听DT取消选择事件
  dtTable.on('deselect', function(e, dt, type, indexes) {
    if (type === 'row') {
      const selectedIds = dt.rows({selected: true}).data().toArray().map(row => row.id);
      highlightPoints(selectedIds);
    }
  });
  
  // 监听DT搜索事件
  dtTable.on('search.dt', function() {
    const visibleIds = dtTable.rows({search: 'applied'}).data().toArray().map(row => row.id);
    highlightPoints(visibleIds);
  });
});

r p_plotly

r dt_table

### 代码说明:
- 在ggplot的`aes`中添加`customdata = id`,为每个散点绑定唯一标识,方便JS精准定位。
- DT表格启用`selection = "multiple"`支持多选,`filter = "top"`添加顶部搜索框。
- JavaScript监听DT的行选择、取消选择、搜索三个事件,通过`Plotly.restyle`动态修改匹配点的颜色和大小,实现高亮效果。

## 二、基于sentence字段的文本过滤实现方式

### 方式1:使用crosstalk的`filter_text`(推荐)
crosstalk提供了原生的文本过滤组件,可直接与SharedData绑定,实现Plotly和DT的全局同步过滤:

```r
---
title: "文本过滤联动分析"
output: html_document
---

```{r}
library(tidyverse)
library(crosstalk)
library(plotly)
library(DT)
set.seed(0)
n = 20
survey <- data.frame(id = 1:n,
                     date = as.Date("2023-01-01") + runif(n = n, max = 100),
                     score = sample(0:10, n, replace = TRUE),
                     sentence = wakefield::sentence(n = n) |> str_wrap(40))

survey_shared <- SharedData$new(survey, key = ~id)

r filter_text("sentence_filter", "搜索句子内容:", survey_shared, column = ~sentence)

p <- ggplot(survey_shared, aes(date, score, text = sentence)) +
  geom_point()

ggplotly(p, tooltip = "text") %>% layout(hoverlabel = list(align = "left")) 

datatable(survey_shared, rownames = FALSE, width = "100%")
### 方式2:DT原生搜索+JS高亮
如果需要保留DT原生搜索框,同时联动Plotly高亮,直接使用第一部分的代码即可——DT的搜索操作会自动触发JS逻辑,高亮Plotly中匹配的散点。

### 两种方式对比:
- **crosstalk filter_text**:全局过滤,输入后Plotly和DT仅显示匹配数据,适合需要精准筛选的场景。
- **DT原生搜索+JS高亮**:保留全量数据视图,仅高亮匹配项,适合需要对比全量数据的场景。

内容的提问来源于stack exchange,提问作者Jon Spring
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 10:30:44