使用NetworkX时如何获取FancyArrowPatch端点的精确位置
问题
我用NetworkX和matplotlib复现指定图形,已创建好网络结构:
graph = nx.Graph() elist = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0), (5, 1), (5, 2)] graph.add_edges_from(elist) pos = {0: (-1, 2), 1: (2, 2), 2: (3, 0.5), 3: (2, -1.5), 4: (-1.5, -1), 5: (0, 0)}
我想先绘制节点再单独绘制边,从边的位置获取端点来做散点标记,但用FancyArrowPatch绘制带箭头的边时,调用FancyArrowPatch.get_path()拿不到端点的精确位置。相关代码如下:
fig, ax = plt.subplots(figsize=(8, 6), dpi=200) ax.set_axis_off() node_object = nx.draw_networkx_nodes(graph, pos=pos, node_size=3000, node_color="#d3d3d3") edge_object = nx.draw_networkx_edges( graph, pos=pos, node_size=10000, edge_color="#808080", width=2, arrows=True ) node_object.set_zorder(-1) for el in edge_object: paths = el.get_path().vertices[0], el.get_path().vertices[2] x, y = np.transpose(paths) middle = (paths[0] + paths[1]) / 2 dist = np.abs(paths[1] - paths[0]) print(middle, dist) print(el, x, y, fig.dpi) ax.scatter(x, y, s=100, c="#3a649d", edgecolors="#808080", linewidths=2)
请问问题原因是什么?有没有办法获取端点的精确位置?
问题原因
get_path()返回局部坐标路径:FancyArrowPatch的get_path()方法返回的是箭头补丁自身局部坐标系下的路径,并非画布或数据坐标系的全局位置。这些顶点坐标是相对补丁自身原点的,无法直接对应实际的边端点。- 边端点被自动偏移:NetworkX的
draw_networkx_edges在绘制带箭头的边时,会根据node_size参数自动调整边的端点位置,让边停在节点边缘而非完全覆盖节点。此时FancyArrowPatch的实际端点是偏移后的位置,但get_path()无法直接输出这个全局坐标。
解决方法
方法1:手动计算偏移后端点
利用节点大小和原始坐标,计算边端点的偏移位置:
import networkx as nx import matplotlib.pyplot as plt import numpy as np graph = nx.Graph() elist = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0), (5, 1), (5, 2)] graph.add_edges_from(elist) pos = {0: (-1, 2), 1: (2, 2), 2: (3, 0.5), 3: (2, -1.5), 4: (-1.5, -1), 5: (0, 0)} fig, ax = plt.subplots(figsize=(8, 6), dpi=200) ax.set_axis_off() node_size = 3000 node_object = nx.draw_networkx_nodes(graph, pos=pos, node_size=node_size, node_color="#d3d3d3") edge_object = nx.draw_networkx_edges( graph, pos=pos, node_size=node_size, edge_color="#808080", width=2, arrows=True ) node_object.set_zorder(-1) for u, v in graph.edges(): # 获取原始节点坐标 x1, y1 = pos[u] x2, y2 = pos[v] # 计算节点半径(node_size为面积,半径需转换为数据坐标系单位) node_radius = np.sqrt(node_size / np.pi) / fig.dpi * ax.get_window_extent().width # 计算边的向量与长度 dx = x2 - x1 dy = y2 - y1 length = np.sqrt(dx**2 + dy**2) # 计算偏移量,让边端点停在节点边缘 if length > 0: offset_x = dx * node_radius / length offset_y = dy * node_radius / length start = (x1 + offset_x, y1 + offset_y) end = (x2 - offset_x, y2 - offset_y) # 绘制端点散点 ax.scatter([start[0], end[0]], [start[1], end[1]], s=100, c="#3a649d", edgecolors="#808080", linewidths=2) plt.show()
方法2:通过变换矩阵转换局部坐标
利用FancyArrowPatch的变换矩阵,将局部路径坐标转换为数据坐标系的全局坐标:
import networkx as nx import matplotlib.pyplot as plt import numpy as np graph = nx.Graph() elist = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0), (5, 1), (5, 2)] graph.add_edges_from(elist) pos = {0: (-1, 2), 1: (2, 2), 2: (3, 0.5), 3: (2, -1.5), 4: (-1.5, -1), 5: (0, 0)} fig, ax = plt.subplots(figsize=(8, 6), dpi=200) ax.set_axis_off() node_object = nx.draw_networkx_nodes(graph, pos=pos, node_size=3000, node_color="#d3d3d3") edge_object = nx.draw_networkx_edges( graph, pos=pos, node_size=10000, edge_color="#808080", width=2, arrows=True ) node_object.set_zorder(-1) for el in edge_object: # 获取箭头局部路径与变换矩阵 path = el.get_path() transform = el.get_transform() # 将局部顶点转换为数据坐标系坐标 data_vertices = transform.transform(path.vertices) # 箭头起点为第一个顶点,终点为倒数第二个顶点(最后一个为箭头尖端) start = data_vertices[0] end = data_vertices[-2] # 绘制端点散点 ax.scatter([start[0], end[0]], [start[1], end[1]], s=100, c="#3a649d", edgecolors="#808080", linewidths=2) plt.show()
内容的提问来源于stack exchange,提问作者Alessandro Romancino
相关产品推荐
相关产品推荐

