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

Bokeh技术实现:如何隐藏同组/同类别线条的图例

实现Bokeh分组图例交互:按年份/进出口批量控制数据显示

我完全懂你想要的效果——把6个单独数据集的图例换成按年份、进出口类型分组的5个图例,既简洁(数量线性增长)又能从不同维度筛选数据。下面是实现这个交互功能的完整代码,我会顺便解释关键部分:

修改后的可运行代码

from bokeh.io import output_notebook, show, reset_output
from bokeh.models import ColumnDataSource, Legend, LegendItem, CustomJS
from bokeh.palettes import GnBu3, OrRd3
from bokeh.plotting import figure

reset_output()
output_notebook()

fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
years = ["2015", "2016", "2017"]

# 原始数据定义
exports = {'fruits' : fruits, '2015' : [2, 1, 4, 3, 2, 4], '2016' : [5, 3, 4, 2, 4, 6], '2017' : [3, 2, 4, 4, 5, 3]}
imports = {'fruits' : fruits, '2015' : [-1, 0, -1, -3, -2, -1], '2016' : [-2, -1, -3, -1, -2, -2], '2017' : [-1, -2, -1, 0, -2, -2]}

# 初始化图表
p = figure(y_range=fruits, plot_height=250, x_range=(-16, 16), 
           title="Fruit import/export, by year", toolbar_location=None)

# 整合所有数据到一个数据源,添加年份和类型标记
data = {'fruits': fruits}
renderers = []
color_mapper = {
    ('export', '2015'): GnBu3[0],
    ('export', '2016'): GnBu3[1],
    ('export', '2017'): GnBu3[2],
    ('import', '2015'): OrRd3[0],
    ('import', '2016'): OrRd3[1],
    ('import', '2017'): OrRd3[2],
}

# 准备数据并创建渲染器
for trade_type, trade_data in [('export', exports), ('import', imports)]:
    for year in years:
        col_name = f"{trade_type}_{year}"
        data[col_name] = trade_data[year]
        # 绘制hbar,给每个渲染器设置name属性用于识别分组
        r = p.hbar(y='fruits', right=col_name, height=0.9, 
                   color=color_mapper[(trade_type, year)], 
                   source=ColumnDataSource(data))
        r.name = f"{trade_type}_{year}"
        renderers.append(r)

# 创建自定义图例项
legend_items = []
# 年份图例
for year in years:
    legend_items.append(LegendItem(label=year, renderers=[r for r in renderers if year in r.name]))
# 进出口类型图例
for trade_type in ['export', 'import']:
    label = '出口' if trade_type == 'export' else '进口'
    legend_items.append(LegendItem(label=label, renderers=[r for r in renderers if trade_type in r.name]))

# 添加图例到图表
legend = Legend(items=legend_items, location="top_left", click_policy='hide')
p.add_layout(legend)

# 添加JS回调实现分组控制
callback = CustomJS(args=dict(renderers=renderers), code="""
    const clicked_label = cb_obj.label;
    // 判断是年份还是类型
    const is_year = ["2015", "2016", "2017"].includes(clicked_label);
    
    renderers.forEach(r => {
        const [trade_type, year] = r.name.split('_');
        let should_hide = false;
        if (is_year) {
            // 如果是年份点击,切换该年份所有数据的可见性
            should_hide = (year === clicked_label) ? !r.visible : r.visible;
        } else {
            // 如果是类型点击,切换该类型所有数据的可见性
            const type_match = (clicked_label === '出口') ? (trade_type === 'export') : (trade_type === 'import');
            should_hide = type_match ? !r.visible : r.visible;
        }
        r.visible = should_hide;
    });
""")

# 给每个图例项绑定点击回调
for item in legend.items:
    item.label.js_on_click(callback)

# 图表样式设置
p.y_range.range_padding = 0.1
p.ygrid.grid_line_color = None
p.axis.minor_tick_line_color = None
p.outline_line_color = None

show(p)

关键功能说明

  • 数据整合与渲染器标记:把出口、进口的所有年份数据合并到一个数据源,每个数据列用{类型}_{年份}命名,同时给每个hbar渲染器设置name属性,方便后续识别它所属的分组。
  • 自定义分组图例:分别创建年份(2015/2016/2017)和进出口类型(出口/进口)的图例项,每个图例项关联对应分组的所有渲染器。
  • 分组交互逻辑:通过CustomJS回调实现批量切换:
    • 点击年份标签时,切换该年份下所有进出口数据的可见状态;
    • 点击进出口类型标签时,切换该类型下所有年份数据的可见状态;
    • 保持Bokeh默认的"点击切换"交互逻辑,符合用户使用习惯。

这样修改后,你就能实现想要的5个图例、多维度筛选数据的效果了!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 07:42:54