使用滑块与CustomJS实时更新Bokeh ColumnDataSource遇阻求助
解决Bokeh滑块实时更新颜色和数据表格下载的问题
我帮你排查了代码里的几个关键问题,这就是你的CustomJS未生效的核心原因,下面是问题分析和修正后的完整方案:
核心问题梳理
- 字段不匹配:你的颜色映射依赖
position字段,但回调里错误地修改了不存在的color字段,Bokeh无法识别这个字段来更新标记颜色。 - 循环逻辑错误:回调中没有遍历数组的每个元素(缺少
[i]索引),而且直接给整个字段赋值单个字符串,导致无法逐个更新数据点的状态。 - 回调未关联滑块:你编写了更新逻辑,但没有将回调绑定到三个滑块的
value变化事件上,滑块拖动时根本不会触发颜色更新。 - 缺少下载功能:原代码没有实现数据表格的下载按钮,需要添加
DownloadButton配合CustomJS来导出数据。
修正后的完整代码
from bokeh.plotting import figure, show, ColumnDataSource from bokeh.models import (HoverTool, Span, Slider, CustomJS, CategoricalColorMapper, TableColumn, DataTable, DownloadButton) from bokeh.layouts import row, column import numpy as np # 初始化数据源,确保position字段存在 source = ColumnDataSource( data=dict( x=[0.154228, 0.118865, 0.071774, 0.230292, 0.070180, 0.010010, 0.141877, 0.085712, 0.166331, 0.101799], y=[1.358454, 2.736966, 1.556393, 1.286304, 1.785875, 0.168123, 2.120294, 2.556393, 5.058894, 6.671531], accession=[158489.0, 442616758.0, 349669.0, 320544370.0, 161077523.0, 24648576.0, 320545678.0, 17137000.0, 442625663.0, 17647529.0], position=["normal"] * 10 # 初始化所有点为normal状态 ) ) # 颜色映射保持使用position字段 color_mapper = CategoricalColorMapper(factors=["up", "normal", "down"], palette=['yellow', 'green', 'blue']) hover = HoverTool(tooltips=[ ("accession", "@accession"), ("log(fc)", "@x"), ("-log(pvalue)", "@y") ]) TOOLS = ",pan,wheel_zoom,box_zoom,reset,box_select,lasso_select" p = figure( x_axis_label='log(fc)', y_axis_label='-log(pvalue)', tools=TOOLS, ) p.add_tools(hover) # 初始化滑块和参考线 h_slider = Slider(start=0, end=5, value=1, step=.1, title="variation of log(pvalue)") v_slider_right = Slider(start=0.1, end=0.2, value=0.15, step=.01, title="right fold change") v_slider_left = Slider(start=0, end=0.1, value=0.05, step=.01, title="left log fold change") hline = Span(location=h_slider.value, dimension='width', line_color='green', line_width=2) vline1 = Span(location=v_slider_right.value, dimension='height', line_color='blue', line_width=2) vline2 = Span(location=v_slider_left.value, dimension='height', line_color='black', line_width=2) p.renderers.extend([vline1, vline2, hline]) # 绘制圆形标记,绑定position字段的颜色映射 p.circle('x', 'y', source=source, color=dict(field='position', transform=color_mapper), legend='position') # 1. 参考线更新的回调 h_slider.callback = CustomJS(args=dict(span=hline, slider=h_slider), code=""" span.location = slider.value; """) v_slider_right.callback = CustomJS(args=dict(span=vline1, slider=v_slider_right), code=""" span.location = slider.value; """) v_slider_left.callback = CustomJS(args=dict(span=vline2, slider=v_slider_left), code=""" span.location = slider.value; """) # 2. 颜色更新的回调:修正循环逻辑,修改position字段的每个元素 color_callback = CustomJS(args=dict(source=source, v_left=v_slider_left, h=h_slider, v_right=v_slider_right), code=""" const data = source.data; const low = v_left.value; const up = v_right.value; const back_val = h.value; const x = data['x']; const y = data['y']; const pos = data['position']; // 遍历每个数据点,更新position状态 for (let i = 0; i < x.length; i++) { if (x[i] < low && y[i] > back_val) { pos[i] = 'down'; } else if (x[i] > up && y[i] > back_val) { pos[i] = 'up'; } else { pos[i] = 'normal'; } } // 通知数据源更新 source.change.emit(); """) # 将颜色回调绑定到三个滑块的value变化事件 v_slider_left.js_on_change('value', color_callback) v_slider_right.js_on_change('value', color_callback) h_slider.js_on_change('value', color_callback) # 3. 数据表格与下载功能 columns = [ TableColumn(field="accession", title="numero d'accession"), TableColumn(field="x", title="log(fc)"), TableColumn(field="y", title="-log(pvalue)"), TableColumn(field="position", title="status") # 添加状态列展示当前分类 ] data_table = DataTable(source=source, columns=columns, width=500, height=300) # 下载按钮:将数据源转换为CSV格式下载 download_button = DownloadButton(label="Download Data", button_type="success") download_callback = CustomJS(args=dict(source=source), code=""" const data = source.data; const headers = Object.keys(data).join(','); const rows = []; // 构建CSV行 for (let i = 0; i < data.x.length; i++) { const row = Object.values(data).map(col => col[i]).join(','); rows.push(row); } // 创建CSV文件并下载 const csv = headers + '\\n' + rows.join('\\n'); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'data_points.csv'; a.click(); URL.revokeObjectURL(url); """) download_button.js_on_click(download_callback) # 布局:表格和下载按钮放在一列 widget_col = column(v_slider_left, v_slider_right, h_slider, data_table, download_button) layout = row(p, widget_col) show(layout)
关键修改说明
- 字段统一:所有颜色相关逻辑都使用
position字段,和颜色映射保持一致,避免字段不匹配的问题。 - 循环修正:使用
x[i]和y[i]遍历每个数据点,给pos[i]逐个赋值,确保每个点的状态正确更新。 - 回调绑定:用
js_on_change('value', color_callback)将颜色更新回调绑定到三个滑块上,滑块拖动时立即触发状态更新。 - 下载功能:添加
DownloadButton,通过CustomJS将数据源转换为CSV格式,实现一键下载所有数据。
现在拖动滑块,你会看到:
- 参考线实时跟随滑块位置移动
- 数据点的颜色根据滑块阈值自动切换(up/normal/down)
- 数据表格实时展示每个点的状态,点击下载按钮可以导出所有数据为CSV文件
内容的提问来源于stack exchange,提问作者fabien
相关产品推荐
相关产品推荐

