Bokeh中如何在HoverToolTip显示过滤后的DataFrame?
问题
在Bokeh 3.2.2版本中,尝试通过回调函数在Hover工具提示中显示过滤后的pandas DataFrame时,发现cb_data.source、cb_data.x、cb_data.y均为undefined。请问如何在JavaScript中获取当前的x、y属性?
错误信息
cb_data.source = undefined cb_data.value = undefined cb_data.x/cb_data.y = undefined/undefined
代码示例
import pandas as pd from bokeh.models import HoverTool, ColumnDataSource, DatetimeTickFormatter, CustomJS from bokeh.plotting import figure, show data = { "Time": ["12:03", "12:03", "12:03", "12:03", "12:12", "12:12", "12:12", "12:12", "12:17", "12:17", "12:17", "12:17"], "Spot": [19653, 19653, 19653, 19653, 19629, 19629, 19629, 19629, 19640, 19640, 19640, 19640], "Strike": [19500, 19600, 19700, 19800, 19500, 19600, 19700, 19800, 19500, 19600, 19700, 19800], "cLtp": [99, 174, 167, 167, 91, 165, 161, 93, 95, 16, 12, 31], "pLtp": [49, 24, 42, 25, 51, 74, 36, 53, 21, 29, 75, 32], "cSellOi": [851500, 267500, 379050, 382350, 1324350, 389700, 438850, 1318250, 1365900, 449000, 433200, 433200], "pSellOi": [2530350, 1615600, 1656000, 1606500, 2542100, 1598450, 1646950, 2519050, 2544450, 1488200, 1460400, 1460400], } df = pd.DataFrame(data) # Convert the 'Time' column to datetime df['Time'] = pd.to_datetime(df['Time'], format='%H:%M') df.sort_values(by='Time', inplace=True) source = ColumnDataSource(df) plot = figure( x_axis_label="Time", y_axis_label="Spot", x_axis_type="datetime", title="Dataframe in Tooltip", ) plot.line(x='Time', y='Spot', source=source, line_width=3, line_color="blue") plot.xaxis.formatter = DatetimeTickFormatter( days="%Y-%m-%d", hours="%H:%M:%S", minutes="%H:%M" ) # Create a custom JavaScript callback for the hover tool jsHover = """ var data = source.data; var hover = hover; hover.tooltips = []; var tooltipString = "" console.log( "cb_data.source = " + cb_data['source'] ); console.log( "cb_data.value = " + cb_data['value']); console.log( "cb_data.x/cb_data.y = " + cb_data['x'] +"/" + cb_data['y']); for (var i = 0; i < data['Strike'].length; i++) { tooltipString += data['Strike'][i] + ": (" + data['cLtp'][i] + ", " + data['pLtp'][i] + ")["; tooltipString += data['cSellOi'][i] + ", " + data['pSellOi'][i] + "]\\n"; } hover.tooltips = tooltipString; hover.change.emit(); console.log('jsHover Callback Finished'); console.log(hover.tooltips); """ hoverTool = HoverTool(tooltips=None) cb = CustomJS(args={'source': source,'hover': hoverTool}, code=jsHover) hoverTool.callback = cb plot.add_tools(hoverTool) show(plot)
解决方案
1. 适配Bokeh 3.x的Hover回调参数
Bokeh 3.x版本中cb_data的结构已变更,不再直接提供x、y字段,需通过cb_data.geometry获取鼠标屏幕坐标,再结合图表的刻度转换方法得到实际数据值。
2. 修改后的完整实现代码
import pandas as pd from bokeh.models import HoverTool, ColumnDataSource, DatetimeTickFormatter, CustomJS from bokeh.plotting import figure, show data = { "Time": ["12:03", "12:03", "12:03", "12:03", "12:12", "12:12", "12:12", "12:12", "12:17", "12:17", "12:17", "12:17"], "Spot": [19653, 19653, 19653, 19653, 19629, 19629, 19629, 19629, 19640, 19640, 19640, 19640], "Strike": [19500, 19600, 19700, 19800, 19500, 19600, 19700, 19800, 19500, 19600, 19700, 19800], "cLtp": [99, 174, 167, 167, 91, 165, 161, 93, 95, 16, 12, 31], "pLtp": [49, 24, 42, 25, 51, 74, 36, 53, 21, 29, 75, 32], "cSellOi": [851500, 267500, 379050, 382350, 1324350, 389700, 438850, 1318250, 1365900, 449000, 433200, 433200], "pSellOi": [2530350, 1615600, 1656000, 1606500, 2542100, 1598450, 1646950, 2519050, 2544450, 1488200, 1460400, 1460400], } df = pd.DataFrame(data) df['Time'] = pd.to_datetime(df['Time'], format='%H:%M') df.sort_values(by='Time', inplace=True) source = ColumnDataSource(df) plot = figure( x_axis_label="Time", y_axis_label="Spot", x_axis_type="datetime", title="Dataframe in Tooltip", ) plot.line(x='Time', y='Spot', source=source, line_width=3, line_color="blue") plot.xaxis.formatter = DatetimeTickFormatter( days="%Y-%m-%d", hours="%H:%M:%S", minutes="%H:%M" ) # 调整后的JavaScript回调 jsHover = """ var data = source.data; var hover = hover; var plot = plot; // 将鼠标屏幕坐标转换为数据域的x、y值 var x = plot.x_scale.invert(cb_data.geometry.x); var y = plot.y_scale.invert(cb_data.geometry.y); // 对齐时间戳精度(pandas存储的datetime为毫秒级) var targetTime = Math.round(x.getTime() / 1000) * 1000; var targetSpot = Math.round(y); // 构建格式化的提示文本 var tooltipString = `<div>时间: ${new Date(targetTime).toLocaleTimeString()}</div>`; tooltipString += `<div>Spot: ${targetSpot}</div>`; tooltipString += "<div>---</div>"; // 筛选匹配当前时间和Spot的记录 for (var i = 0; i < data['Time'].length; i++) { if (data['Time'][i] === targetTime && data['Spot'][i] === targetSpot) { tooltipString += `<div>${data['Strike'][i]}: (${data['cLtp'][i]}, ${data['pLtp'][i]})[${data['cSellOi'][i]}, ${data['pSellOi'][i]}]</div>`; } } hover.tooltips = [["", tooltipString]]; hover.change.emit(); """ hoverTool = HoverTool(tooltips=None) # 必须传入plot对象用于刻度转换 cb = CustomJS(args={'source': source, 'hover': hoverTool, 'plot': plot}, code=jsHover) hoverTool.callback = cb plot.add_tools(hoverTool) show(plot)
关键要点
- 需将
plot对象传入CustomJS的参数中,才能调用x_scale.invert和y_scale.invert完成坐标转换 - 时间值需要做毫秒级对齐,匹配ColumnDataSource中存储的时间戳格式
- 动态提示文本需用HTML格式,以数组形式赋值给
hover.tooltips
内容的提问来源于stack exchange,提问作者srt111
相关产品推荐
相关产品推荐

