You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Bokeh中Tap工具回调函数编写求助:实现交互式双图联动更新

解决Bokeh Tap工具回调实现交互式图表更新的问题

我看了你的代码,问题主要出在Tap工具的回调逻辑数据传递方式以及图表坐标匹配这几个地方。下面我会一步步帮你修复这些问题,实现你想要的功能:点击Graph1的任意位置,Graph2更新显示对应x列的d[:,x]曲线。

核心问题分析

  • Image Glyph的Tap回调不返回indices:你用的是image glyph,它是单个元素,所以cb_data.source.selected.indices是空的,应该通过cb_data.geometries获取点击的坐标值。
  • Python数组无法直接传给CustomJSd是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)

关键修改说明

  1. 坐标范围匹配:把Graph1的x_range设为(0, N)y_range设为(0, len_d),这样点击的x坐标直接对应d的列索引,不用额外转换。
  2. 数据传递方式:创建d_source把numpy数组d转换成二维列表,这样CustomJS可以正常读取。
  3. 回调逻辑调整:通过cb_data.geometries[0].x获取点击的x坐标,取整后作为列索引,提取d的对应列更新Graph2的数据源。
  4. Graph2的轴对应:调整了x/y的映射,让y轴对应行索引、x轴对应d的数值,逻辑更直观。

现在运行代码,点击Graph1的任意位置,Graph2就会实时更新显示对应x列的曲线啦!

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

火山引擎 最新活动