如何在Python的Bokeh中为交互式网络图添加自环边与有向箭头边
在Bokeh中实现状态转移图的自环边与有向箭头边
一、实现带箭头的有向边
Bokeh原生提供Arrow组件,可直接为状态转移边添加有向箭头,步骤如下:
- 先确定所有节点的坐标(可通过布局算法计算或手动指定)
- 遍历每条状态转移关系,创建
Arrow对象,指定起点、终点,并配置箭头头部和箭身样式 - 将所有箭头添加到绘图figure中
示例代码:
from bokeh.models import Arrow, NormalHead, Line, ColumnDataSource from bokeh.plotting import figure, show # 节点坐标与标签数据源 nodes = ColumnDataSource(data=dict( x=[1, 2, 3], y=[1, 2, 1], labels=['状态1', '状态2', '状态3'] )) # 转移边数据:起点节点索引、终点节点索引、转移概率 edges = ColumnDataSource(data=dict( start_idx=[0, 1, 2], end_idx=[1, 2, 0], prob=[0.3, 0.5, 0.2] )) p = figure(tools="hover,pan,zoom", x_range=(0, 4), y_range=(0, 3)) # 绘制节点 p.circle('x', 'y', size=20, source=nodes, fill_color='lightblue') # 批量生成有向边 for i in range(len(edges.data['start_idx'])): start_x = nodes.data['x'][edges.data['start_idx'][i]] start_y = nodes.data['y'][edges.data['start_idx'][i]] end_x = nodes.data['x'][edges.data['end_idx'][i]] end_y = nodes.data['y'][edges.data['end_idx'][i]] p.add_layout(Arrow( end=NormalHead(fill_color='black', size=8), line=Line(color='gray', width=2), x_start=start_x, y_start=start_y, x_end=end_x, y_end=end_y )) show(p)
二、实现自环边
自环边需要绘制环绕节点的圆弧,可通过Bokeh的Arc组件实现,同时在圆弧末端添加箭头指向节点:
- 针对有自环的节点,设置自环的半径(略大于节点尺寸)和圆弧角度范围(比如右侧半圆、上侧半圆)
- 绘制圆弧后,在圆弧终点添加箭头,微调位置避免与节点重叠
示例代码(接续上述节点数据源):
import math from bokeh.models import Arc # 给状态2添加自环,转移概率0.4 self_loop_idx = 1 node_x = nodes.data['x'][self_loop_idx] node_y = nodes.data['y'][self_loop_idx] loop_radius = 0.3 # 自环半径 # 绘制右侧半圆自环(角度范围π/2到3π/2) p.arc( x=node_x, y=node_y, radius=loop_radius, start_angle=math.pi/2, end_angle=3*math.pi/2, line_color='gray', line_width=2 ) # 添加自环箭头,指向节点中心 p.add_layout(Arrow( end=NormalHead(fill_color='black', size=8), line=Line(color='gray', width=2), x_start=node_x, y_start=node_y - loop_radius, x_end=node_x, y_end=node_y - loop_radius + 0.05 # 微调位置避免重叠 ))
调整start_angle和end_angle可改变自环的位置(比如0到math.pi对应上侧半圆)。
三、结合悬停高亮交互
要实现悬停节点时高亮关联边和节点,可通过HoverTool配合CustomJS回调实现:
- 给节点和边元素添加标识名称,方便JS定位
- 编写回调代码,重置所有元素样式后,高亮当前节点的关联边
示例代码片段:
from bokeh.models import HoverTool, CustomJS # 给节点添加名称标识 p.circle('x', 'y', size=20, source=nodes, fill_color='lightblue', name='state_nodes') # 给边添加名称标识(创建Arrow时统一设置name) # 这里需要将所有Arrow存入一个渲染器列表,或者用MultiLine替代Arrow实现批量边渲染 # 以下是简化的回调示例: hover_callback = CustomJS(args=dict(nodes=nodes, edges=edges, p=p), code=""" const hovered_idx = cb_data.index.indices[0]; if (!hovered_idx) return; // 重置所有边样式 p.select_all('Arrow').forEach(arrow => { arrow.line.line_color = 'gray'; arrow.line.line_width = 2; }); // 高亮关联边 for (let i=0; i<edges.data.start_idx.length; i++) { const start = edges.data.start_idx[i]; const end = edges.data.end_idx[i]; if (start === hovered_idx || end === hovered_idx) { p.select_all('Arrow')[i].line.line_color = 'red'; p.select_all('Arrow')[i].line.line_width = 3; } } """) hover_tool = HoverTool( tooltips=[("状态", "@labels"), ("转移概率", "@prob")], callback=hover_callback, renderers=[p.select(name='state_nodes')] ) p.add_tools(hover_tool)
内容的提问来源于stack exchange,提问作者Shreyans Jain
相关产品推荐
相关产品推荐

