无JS经验:基于Bokeh JSCallback实现下拉框更新图表
Fixing Bokeh CustomJS Callback for Dropdown Selection in Static HTML
我明白你在做静态Bokeh HTML文件时卡在了下拉框回调上——不能用Bokeh Server确实得靠CustomJS硬刚,咱们一步步把你的代码理顺、修好。
先说说你现有代码里的核心问题:
- 数据源逻辑搞反了:你把空的
true_source当数据源容器,却让图表用了全量数据的source,这完全搞反了分工; - 回调逻辑错误:循环里的
if(x[i]==f)完全没关联到学期和对应数据,变量也混淆了; - 初始图表显示不对:默认加载了所有学期的叠加数据,不符合下拉框初始选中值的预期。
下面是修正后的完整可运行代码,我会逐段解释:
Step 1: 整理数据结构,准备合适的数据源
首先我们把数据按学期分组,把所有学期的完整数据存在一个ColumnDataSource里,方便JS直接调取;图表用的数据源只初始加载默认学期的数据:
import pandas as pd from bokeh.models import ColumnDataSource, Select, CustomJS from bokeh.plotting import figure, save from bokeh.io import output_file from bokeh.transform import factor_cmap from bokeh.palettes import Category10_4 # 你的测试数据 data_test = {'Term': ['Fall 2018', 'Fall 2018', 'Fall 2018', 'Fall 2018', 'Spring 2018', 'Spring 2018', 'Spring 2018', 'Spring 2018', 'Fall 2017','Fall 2017','Fall 2017','Fall 2017'], 'Grade': ['A+','A','B','C','A+','A','B','C','A+','A','B','C'], 'grade_count': [4500, 500, 6000, 250, 103, 1345, 2503, 324, 684,230, 566,2344]} df = pd.DataFrame(data_test) # 按学期分组,把每个学期的Grade和grade_count整理成字典 grouped = df.groupby('Term') all_data = ColumnDataSource(data={ term: { 'x': list(group['Grade']), 'y': list(group['grade_count']) } for term, group in grouped }) # 图表用的数据源,初始加载默认学期数据 default_term = 'Fall 2018' source = ColumnDataSource(data=all_data.data[default_term])
Step 2: 修正下拉框和CustomJS回调
现在all_data里存了每个学期的完整数据,回调只需要根据选中的学期,把对应数据赋值给图表数据源即可,逻辑非常直接:
# 下拉框选项:提取所有学期 Terms = list(grouped.groups.keys()) dropdown = Select(title='Term Dropdown', value=default_term, options=Terms) # 修正后的CustomJS回调 callback = CustomJS(args=dict(source=source, all_data=all_data), code=""" // 获取当前选中的学期 const selected_term = cb_obj.value; // 从全量数据里取出对应学期的x/y值 const new_data = all_data.data[selected_term]; // 更新图表数据源 source.data = { x: new_data.x, y: new_data.y }; // 触发图表更新 source.change.emit(); """) # 给下拉框绑定回调 dropdown.js_on_change('value', callback)
Step 3: 创建图表并输出静态HTML
最后把图表和下拉框组合(如果需要显示下拉框,记得用layout),输出静态HTML:
# 定义颜色映射(如果之前没定义的话) grade_list = ['A+', 'A', 'B', 'C'] grade_cmap = factor_cmap('x', palette=Category10_4, factors=grade_list) # 创建柱状图 p1 = figure(x_range=grade_list, title='Grade Distribution by Term', x_axis_label='Grade', y_axis_label='Count') p1.vbar(x='x', top='y', width=.75, bottom=0, source=source, line_color=grade_cmap, fill_color=grade_cmap) # (可选)如果需要在页面显示下拉框,用layout组合 from bokeh.layouts import column layout = column(dropdown, p1) # 输出静态HTML文件 output_file('grade_distribution.html') save(layout) # 或者用show(layout)直接在浏览器打开预览
为什么这样改能解决问题?
- 数据源分工明确:
all_data存全量的分组数据,source只存当前要显示的学期数据,避免了循环筛选的麻烦; - 回调逻辑简洁:直接根据选中的学期调取对应数据,没有多余的循环判断,减少出错概率;
- 初始显示符合预期:默认加载下拉框选中的学期数据,不会出现所有学期叠加的混乱图表。
内容的提问来源于stack exchange,提问作者gtay
相关产品推荐
相关产品推荐

