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

如何用套索/框选工具选择Plot中所有可见Glyph的点?

我刚好遇到过类似的Bokeh选择场景,咱们可以通过自定义选择逻辑来实现你要的效果:让套索/框选工具只选中当前可见图层里、通过CDSFilter过滤后的点。下面是具体的实现方案:

1. 基础设置:图层与选中样式

首先确保你的每个circle图层都配置了选中样式,并且图例可以控制图层显示隐藏:

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CDSFilter, CustomJS, Circle
from bokeh.layouts import layout

# 创建两个数据源(你可以根据自己的需求调整)
source_green = ColumnDataSource(data=dict(x=[1,2,3], y=[4,5,6], category=["A"]*3))
source_blue = ColumnDataSource(data=dict(x=[4,5,6], y=[1,2,3], category=["B"]*3))

# 定义CDSFilter(这里用category字段过滤,你替换成自己的过滤条件)
filter_green = CDSFilter(column_name='category', value='A')
filter_blue = CDSFilter(column_name='category', value='B')

# 创建绘图对象,添加选择工具
p = figure(tools="lasso_select,box_select,reset", width=600, height=400)

# 定义选中状态的样式(红色高亮,你可以自定义)
selected_glyph = Circle(fill_color='red', line_color='darkred', size=12)

# 绘制绿色点图层,应用过滤和选中样式
circle_green = p.circle(
    'x', 'y', source=source_green, 
    color='green', size=10, alpha=0.8, 
    legend_label="Green Points",
    selection_glyph=selected_glyph
)
circle_green.glyph.filter = filter_green

# 绘制蓝色点图层,同样配置
circle_blue = p.circle(
    'x', 'y', source=source_blue, 
    color='blue', size=10, alpha=0.8, 
    legend_label="Blue Points",
    selection_glyph=selected_glyph
)
circle_blue.glyph.filter = filter_blue

# 开启图例点击隐藏图层的功能
p.legend.click_policy = "hide"

2. 核心:自定义选择回调逻辑

接下来我们要写一段CustomJS代码,监听选择工具的事件,让它只处理可见图层和符合过滤条件的点:

# 收集所有需要参与选择的图层渲染器
renderers = [circle_green, circle_blue]

# 编写选择回调函数
select_callback = CustomJS(args=dict(renderers=renderers, plot=p), code="""
    // 获取最新的选择事件
    const latest_event = plot.tool_events.tool_events.slice(-1)[0];
    if (!latest_event || latest_event.type !== 'selection') return;
    
    const geometry = latest_event.geometry;
    
    // 遍历每个图层
    renderers.forEach(renderer => {
        // 跳过隐藏的图层
        if (!renderer.visible) return;
        
        const source = renderer.data_source;
        const glyph = renderer.glyph;
        const filter = glyph.filter;
        
        const x = source.data.x;
        const y = source.data.y;
        const selected_indices = [];
        
        // 遍历每个点,判断是否符合条件
        for (let i = 0; i < x.length; i++) {
            // 第一步:检查是否通过CDSFilter
            let passes_filter = true;
            if (filter) {
                // 处理普通CDSFilter(如果是CustomJSFilter,需要调整逻辑,见下文)
                if (source.data[filter.column_name][i] !== filter.value) {
                    passes_filter = false;
                }
            }
            if (!passes_filter) continue;
            
            // 第二步:检查是否在选择区域内
            let in_selection = false;
            if (geometry.type === 'rect') {
                // 框选:判断点是否在矩形范围内
                in_selection = (x[i] >= geometry.x0 && x[i] <= geometry.x1) && 
                               (y[i] >= geometry.y0 && y[i] <= geometry.y1);
            } else if (geometry.type === 'poly') {
                // 套索:判断点是否在多边形内(实现点-in-多边形算法)
                function pointInPolygon(point, polygon) {
                    let inside = false;
                    const [px, py] = point;
                    for (let j = polygon.length - 1, i = 0; i < polygon.length; j = i++) {
                        const [xi, yi] = polygon[i];
                        const [xj, yj] = polygon[j];
                        const intersect = ((yi > py) !== (yj > py)) && 
                                          (px < (xj - xi) * (py - yi) / (yj - yi) + xi);
                        if (intersect) inside = !inside;
                    }
                    return inside;
                }
                in_selection = pointInPolygon([x[i], y[i]], geometry.coords);
            }
            
            if (in_selection) {
                selected_indices.push(i);
            }
        }
        
        // 更新当前图层数据源的选中索引
        source.selected.indices = selected_indices;
        source.change.emit();
    });
""")

# 将回调绑定到绘图的工具事件上
p.js_on_event('tool_event', select_callback)

3. 适配复杂过滤器(可选)

如果你的CDSFilter是CustomJSFilter(而非简单的等值过滤),需要修改回调中判断过滤的逻辑,比如:

// 替换原来的过滤判断部分
if (filter) {
    if (filter.type === 'CDSFilter') {
        // 普通等值过滤逻辑
        passes_filter = (source.data[filter.column_name][i] === filter.value);
    } else if (filter.type === 'CustomJSFilter') {
        // 执行CustomJSFilter的过滤函数
        const filterFn = new Function('source', filter.code);
        const filteredFlags = filterFn(source);
        passes_filter = filteredFlags[i];
    }
}

最终效果

现在你运行代码后:

  • 当绿色图层可见时,套索/框选会选中绿色图层中符合过滤条件的点
  • 点击图例隐藏绿色图层后,选择工具会自动选中蓝色图层中可见的点
  • 选中的点会以你定义的红色样式高亮

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:37:14