Bokeh中Tap工具回调函数编写求助:实现交互式双图联动更新
解决Bokeh Tap工具回调实现交互式图表更新的问题
我看了你的代码,问题主要出在Tap工具的回调逻辑、数据传递方式以及图表坐标匹配这几个地方。下面我会一步步帮你修复这些问题,实现你想要的功能:点击Graph1的任意位置,Graph2更新显示对应x列的d[:,x]曲线。
核心问题分析
- Image Glyph的Tap回调不返回indices:你用的是
imageglyph,它是单个元素,所以cb_data.source.selected.indices是空的,应该通过cb_data.geometries获取点击的坐标值。 - Python数组无法直接传给CustomJS:
d是numpy数组,不能直接作为参数传给CustomJS,需要把它放到ColumnDataSource里转换成可序列化的格式。 - Graph1的坐标范围不匹配:原代码里Graph1的x范围是0-20,但
d有500列,需要调整坐标让点击的x值对应d的列索引。
修正后的完整代码
import numpy as np from bokeh.plotting import figure, show, ColumnDataSource from bokeh.layouts import row from bokeh.models import TapTool, CustomJS N = 500 x = np.linspace(0, 10, N) y = np.linspace(0, 10, N) xx, yy = np.meshgrid(x, y) d = np.sin(xx)*np.cos(yy) len_d = len(d) # --- 初始化两个图表 --- # Graph1:调整坐标范围,让x对应d的列索引(0到N) plot1 = figure( tooltips=[("x", "$x{0}"), ("y", "$y"), ("value", "@image")], plot_height=350, plot_width=400, title="Graph 1: Click to select x column", tools="tap,hover", x_range=(0, N), # x范围匹配d的列数 y_range=(0, len_d) # y范围匹配d的行数 ) # Graph2:初始化数据源,先显示第一列数据 plot2_source = ColumnDataSource({'x': d[:, 0], 'y': np.arange(len_d)}) plot2 = figure( plot_height=350, plot_width=400, title="Graph 2: d[:, selected x]", tools="pan,box_select,crosshair,box_zoom,reset,save,wheel_zoom,hover" ) lines = plot2.line(x='y', y='x', source=plot2_source) # y是行索引,x是d的数值 # --- 准备传递给CustomJS的数据 --- # 把d转换成二维列表,存到CDS里(CustomJS只能处理JSON可序列化的数据) d_source = ColumnDataSource(data={'d': d.tolist()}) # --- 编写Tap回调函数 --- code = ''' // 获取点击的坐标 const click_x = cb_data.geometries[0].x; // 转换成整数索引(因为d的列是从0到N-1) const col_index = Math.round(click_x); // 确保索引在有效范围内 if (col_index >=0 && col_index < d_source.data.d[0].length) { // 提取d的对应列 const new_data = { y: Array.from({length: d_source.data.d.length}, (_, i) => i), x: d_source.data.d.map(row => row[col_index]) }; // 更新Graph2的数据源 plot2_source.data = new_data; plot2_source.change.emit(); } ''' # 绑定Tap工具回调 tap_tool = plot1.select(TapTool) tap_tool.callback = CustomJS( args={'d_source': d_source, 'plot2_source': plot2_source}, code=code ) # --- 添加Image到Graph1 --- plot1.image( image=[d], x=0, y=0, dw=N, # 宽度匹配列数 dh=len_d, # 高度匹配行数 palette='Spectral11', level="image" ) plot1.grid.grid_line_width = 0.5 # --- 显示布局 --- plots = row(plot1, plot2) show(plots)
关键修改说明
- 坐标范围匹配:把Graph1的
x_range设为(0, N),y_range设为(0, len_d),这样点击的x坐标直接对应d的列索引,不用额外转换。 - 数据传递方式:创建
d_source把numpy数组d转换成二维列表,这样CustomJS可以正常读取。 - 回调逻辑调整:通过
cb_data.geometries[0].x获取点击的x坐标,取整后作为列索引,提取d的对应列更新Graph2的数据源。 - Graph2的轴对应:调整了x/y的映射,让y轴对应行索引、x轴对应
d的数值,逻辑更直观。
现在运行代码,点击Graph1的任意位置,Graph2就会实时更新显示对应x列的曲线啦!
内容的提问来源于stack exchange,提问作者NadeY




