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

如何在Bokeh中用CustomJS动态联动更新下拉菜单选项

实现Bokeh下拉菜单联动:先选物种再选对应样方

我来帮你搞定这个联动需求!核心是要在选中物种后动态过滤出该物种对应的可用样方(quadrat),然后更新第二个下拉菜单的选项,同时最终的绘图要结合两个选择器的条件来过滤数据。下面是完整的修正方案,我会先给你可运行的代码,再解释关键细节:

完整可运行代码

from bokeh.models import CustomJS, ColumnDataSource, MultiSelect, Column
from bokeh.plotting import figure, show
from bokeh.layouts import row, column
import pandas as pd

# 数据准备
data = dict(
    common_name=['A','A','B','C','B','B','A','C','C','B','A','B','C'],
    length=[10,20,10,20,30,20,20,30,20,30,30,20,30],
    weight=[100,200,100,300,100,400,100,300,100,400,500,600,450],
    quadrat=['N','N','N','N','M','M','M','M','O','O','M','N','O']
)
df = pd.DataFrame(data)
data_source = ColumnDataSource(df)

# 空数据源,初始绘图为空
plot_source = ColumnDataSource(dict(length=[], weight=[]))

# 创建绘图
plot = figure(title="Length vs Weight", x_axis_label='Length', y_axis_label='Weight')
plot.circle(x='length', y='weight', source=plot_source, size=10)

# 物种选择器
available_species = sorted(df['common_name'].unique())
multiselect_species = MultiSelect(title='Select Species:', value=[], options=available_species, width=240)

# 预处理:建立物种到对应可用样方的映射(后端提前处理,前端直接用)
species_quadrat_map = {sp: sorted(df[df['common_name'] == sp]['quadrat'].unique()) for sp in available_species}

# 样方选择器(初始为空,等待物种选择后更新)
multiselect_quadrat = MultiSelect(title='Select Quadrat:', value=[], options=[], width=240)

# 物种选择变化的回调:更新样方选项 + 绘制选中物种的所有数据
species_callback = CustomJS(
    args={
        'data_source': data_source,
        'plot_source': plot_source,
        'quadrat_select': multiselect_quadrat,
        'species_quadrat_map': species_quadrat_map
    },
    code="""
    // 获取当前选中的物种数组
    const selected_species = cb_obj.value;
    
    // 收集所有选中物种对应的可用样方,去重后排序
    let available_quadrats = [];
    selected_species.forEach(sp => {
        available_quadrats = available_quadrats.concat(species_quadrat_map[sp]);
    });
    available_quadrats = [...new Set(available_quadrats)].sort();
    
    // 更新样方选择器的选项,同时清空选中值(避免无效选项残留)
    quadrat_select.options = available_quadrats;
    quadrat_select.value = [];
    
    // 更新绘图:显示选中物种的所有数据(此时样方未选)
    const data = data_source.data;
    const plot_data = plot_source.data;
    plot_data.length = [];
    plot_data.weight = [];
    
    for (let i = 0; i < data.common_name.length; i++) {
        if (selected_species.includes(data.common_name[i])) {
            plot_data.length.push(data.length[i]);
            plot_data.weight.push(data.weight[i]);
        }
    }
    
    plot_source.change.emit();
    """
)

# 样方选择变化的回调:结合物种和样方的选择过滤数据
quadrat_callback = CustomJS(
    args={
        'data_source': data_source,
        'plot_source': plot_source,
        'species_select': multiselect_species
    },
    code="""
    const selected_species = species_select.value;
    const selected_quadrats = cb_obj.value;
    
    const data = data_source.data;
    const plot_data = plot_source.data;
    plot_data.length = [];
    plot_data.weight = [];
    
    // 同时满足物种和样方的条件才加入绘图数据
    for (let i = 0; i < data.common_name.length; i++) {
        if (selected_species.includes(data.common_name[i]) && selected_quadrats.includes(data.quadrat[i])) {
            plot_data.length.push(data.length[i]);
            plot_data.weight.push(data.weight[i]);
        }
    }
    
    plot_source.change.emit();
    """
)

# 绑定回调函数
multiselect_species.js_on_change('value', species_callback)
multiselect_quadrat.js_on_change('value', quadrat_callback)

# 页面布局
widgets_layout = column(multiselect_species, multiselect_quadrat)
page_layout = row(widgets_layout, plot)
show(page_layout)

关键细节解释

1. 预处理物种-样方映射

我在Python端提前生成了每个物种对应的可用样方列表species_quadrat_map,这样前端JS可以直接快速获取对应选项,不用在前端做复杂的数据过滤,提升性能和代码简洁性。

2. 物种选择回调的核心逻辑

  • 当你选中/取消选中物种时,会自动收集所有选中物种对应的样方,去重排序后更新第二个下拉菜单的options。
  • 同时清空样方选择器的选中值,避免出现已经不存在的无效选项。
  • 此时绘图会显示选中物种的所有数据(因为还没选样方)。

3. 样方选择回调的逻辑

当你选择样方时,回调会同时获取当前选中的物种和样方,只有同时满足两个条件的数据才会被过滤出来更新绘图,实现了双条件筛选。

原代码的问题修正

  • 原代码中更新样方选项的JS语法错误:multiselect_quadrat.options = "available_quadrat[cb_obj.value]"这种字符串赋值完全不符合JS语法,正确的做法是直接赋值数组。
  • 原代码没有根据选中物种过滤样方,只是用了所有样方选项,不符合你的需求。
  • 原代码的两个回调是独立的,没有结合双条件筛选,导致选样方时会覆盖物种的过滤逻辑,现在改成了双条件叠加筛选。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:04:32