如何在Bokeh中为多ColumnDataSource数据配置HoverTool及相关功能?
Bokeh散点图优化与问题解决
目标
基于包含x、y、type(取值为a、b、c)三列的pandas DataFrame生成散点图,要求:
- 数据点按type显示不同颜色
- 所有数据点均有Hover效果
- type为c的数据点额外添加Tap效果
数据文件data_file.csv示例:
| x | y | type |
|---|---|---|
| 1 | 4 | a |
| 2 | 3 | b |
| 3 | 2 | a |
| 4 | 4 | c |
| .. | .. | .. |
我的尝试
导入DataFrame并拆分为type为c的数据集和其余数据集,创建两个ColumnDataSource绘图,但存在两个未解决问题,同时想知道是否有更优实现方式。
代码
from bokeh.plotting import figure, show from bokeh.models import ColumnDataSource, OpenURL, TapTool from bokeh.models.tools import HoverTool from bokeh.transform import factor_cmap import pandas as pd # 补充原代码缺失的pandas导入 file = "data.csv" df = pd.read_csv(file, skiprows=1, header=None, sep="\t") # 拆分数据集:a&b为一组,c为另一组 c_df = df.drop(df[df[2] != 'c'].index) ab_df = df.drop(df[df[2] == 'c'].index) ab_source = ColumnDataSource(data=dict( Independent = ab_df[0], Dependent = ab_df[1], Type = ab_df[2] )) c_source = ColumnDataSource(data=dict( Independent = c_df[0], Dependent = c_df[1], Type = c_df[2], link = "http://example.com/" + c_df[0].apply(str) + ".php" )) p = figure(title="Random PLot") p.circle('Independent', 'Dependent', size=10, source=ab_source, color=factor_cmap('Type', ['red', 'blue'], ['a', 'b']), legend_group='Type' ) p.circle('Independent', 'Dependent', size=12, source=c_source, color=factor_cmap('Type', ['green'], ['c']), name='needsTapTool' ) p.legend.title = "Type" hover = HoverTool() hover.tooltips = """ <div> <h3>Type: @Type</h3> <p> @Independent and @Dependent </p> </div> """ p.add_tools(hover) url = "@link" tap = TapTool(names=['needsTapTool']) tap.callback = OpenURL(url=url) p.add_tools(tap) show(p)
问题解答
1. 实现不同类型数据点的差异化Hover效果
Bokeh的HoverTool支持通过names参数指定作用的图形元素,以此实现分组差异化提示:
- 给a&b组的circle添加
name标识,比如name='ab_points' - 创建两个
HoverTool实例,分别绑定对应names并设置不同tooltips
示例代码片段:
# 绘制a&b组时添加name参数 p.circle('Independent', 'Dependent', size=10, source=ab_source, color=factor_cmap('Type', ['red', 'blue'], ['a', 'b']), legend_group='Type', name='ab_points' # 添加分组标识 ) # 针对a&b组的HoverTool hover_ab = HoverTool(names=['ab_points']) hover_ab.tooltips = """ <div> <h3>Type: @Type</h3> <p>X: @Independent, Y: @Dependent</p> </div> """ # 针对c组的HoverTool hover_c = HoverTool(names=['needsTapTool']) hover_c.tooltips = """ <div> <h3>Type: @Type</h3> <p>X: @Independent, Y: @Dependent</p> <p>可点击跳转</p> </div> """ # 添加两个HoverTool p.add_tools(hover_ab, hover_c)
2. 截取ColumnDataSource中的数据生成新字段
不要直接在ColumnDataSource中处理数据,应先在pandas DataFrame层面完成转换,再传入数据源:
- 假设原数据有带
http://www前缀的href列,先通过pandas字符串方法清理:
# 假设df[3]是带前缀的href列 df['clean_link'] = df[3].str.replace('http://www.', '', regex=False) # 拆分c_df时保留处理后的字段 c_df = df[df[2] == 'c'].copy() # 用copy避免链式索引警告 # 构建数据源时直接使用清理后的字段 c_source = ColumnDataSource(data=dict( Independent = c_df[0], Dependent = c_df[1], Type = c_df[2], link = c_df['clean_link'] ))
如果是拼接链接,建议在DataFrame中完成操作,避免索引错误:
c_df['link'] = "http://example.com/" + c_df[0].astype(str) + ".php"
更优实现方式:无需拆分数据集
可以只用一个ColumnDataSource,通过多次绘制circle分别对应不同type,每个图形元素设置独立name来绑定工具,代码更简洁:
import pandas as pd from bokeh.plotting import figure, show from bokeh.models import ColumnDataSource, OpenURL, TapTool, HoverTool from bokeh.transform import factor_cmap # 读取数据并命名列 df = pd.read_csv("data.csv", skiprows=1, header=None, sep="\t", names=['x', 'y', 'type']) # 预处理link字段 df['link'] = "http://example.com/" + df['x'].astype(str) + ".php" source = ColumnDataSource(df) p = figure(title="Random Plot") # 绘制type=a的点 p.circle('x', 'y', size=10, source=source, color=factor_cmap('type', ['red'], ['a']), legend_group='type', name='a_points') # 绘制type=b的点 p.circle('x', 'y', size=10, source=source, color=factor_cmap('type', ['blue'], ['b']), legend_group='type', name='b_points') # 绘制type=c的点 p.circle('x', 'y', size=12, source=source, color=factor_cmap('type', ['green'], ['c']), legend_group='type', name='c_points') p.legend.title = "Type" # a、b组的Hover效果 hover_ab = HoverTool(names=['a_points', 'b_points']) hover_ab.tooltips = """ <div> <h3>Type: @type</h3> <p>X: @x, Y: @y</p> </div> """ # c组的Hover和Tap效果 hover_c = HoverTool(names=['c_points']) hover_c.tooltips = """ <div> <h3>Type: @type</h3> <p>X: @x, Y: @y</p> <p>点击跳转</p> </div> """ tap_c = TapTool(names=['c_points']) tap_c.callback = OpenURL(url="@link") p.add_tools(hover_ab, hover_c, tap_c) show(p)
内容的提问来源于stack exchange,提问作者AbdulMuhaymin
相关产品推荐
相关产品推荐

