我想使用networkx(或其他包,如果你知道更好的包)来创建一个图,其节点位于固定位置,但图的边不应重叠(例如下面的AD、DA)。如何确保边缘被绘制为“圆形”以便它们不会重叠?
我以前的代码如下所示:
#!/usr/bin/env python3
import networkx as nx
import matplotlib.pyplot as plt
# Graph data
names = ['A', 'B', 'C', 'D', 'E']
positions = [(0, 0), (0, 1), (1, 0), (0.5, 0.5), (1, 1)]
edges = [('A', 'B'), ('A', 'C'), ('A', 'D'), ('A', 'E'), ('D', 'A')]
# Matplotlib figure
plt.figure('My graph problem')
# Create graph
G = nx.MultiDiGraph(format='png', directed=True)
for index, name in enumerate(names):
G.add_node(name, pos=positions[index])
labels = {}
for edge in edges:
G.add_edge(edge[0], edge[1])
labels[(edge[0], edge[1])] = '{} -> {}'.format(edge[0], edge[1])
layout = dict((n, G.node[n]["pos"]) for n in G.nodes())
nx.draw(G, pos=layout, with_labels=True, node_size=300)
nx.draw_networkx_edge_labels(G, layout, edge_labels=labels)
plt.show()
并给出以下结果
connectionstyle
参数指定为 nx.draw_networkx_edges
例如,如果我想要一个具有弯曲边缘的网络,我可以写:
# Compute position of nodes
pos = nx.kamada_kawai_layout(G)
# Draw nodes and edges
nx.draw_networkx_nodes(G, pos)
nx.draw_networkx_edges(
G, pos,
connectionstyle="arc3,rad=0.1" # <-- THIS IS IT
)
要使边缘更加弯曲,只需增加“rad=x”的x即可。
注意:代码不会生成具有所有颜色和箭头的图形,为此需要更多代码。
图形的边缘不应重叠
使用
connectionstyle
参数绘制具有预定曲率的圆弧不会减少边-边或节点-边重叠。
对于直边,与其他情节元素的重叠通常是不可避免的。但是,如果允许我们绘制弯曲边缘,我们可以对每条边缘进行布线,使其尽可能避开其他节点和边缘。这种效果可以通过将控制点插入到每条边中,有效地将每条边分割成短子边来实现。然后,我们可以使用标准 Fruchterman-Reingold 算法(在
spring_layout
中称为 networkx
)将图模拟为弹簧系统,其中所有节点和控制点相互排斥,但连接的节点和控制点也相互吸引。 (原始)节点的位置保持固定,同时我们让边缘控制点的位置退火到其平衡位置。
我在这里实现了这种方法。该代码是一个名为 netgraph 的 Python 网络绘图库的一部分,我是该库的作者。
netgraph
与networkx和igraph图形对象完全兼容,因此应该可以轻松快速地生成美观的图形。
#!/usr/bin/env python
import matplotlib.pyplot as plt
import networkx as nx
from netgraph import Graph # pip install netgraph
g = nx.florentine_families_graph()
Graph(g, edge_layout='curved')
plt.show()
我认为你不能直接使用networkx函数来做到这一点。但是您可以直接使用已计算的节点位置来使用 matplotlib。
调整您的代码:
import networkx as nx
import matplotlib.pyplot as plt
# Graph data
names = ['A', 'B', 'C', 'D', 'E']
positions = [(0, 0), (0, 1), (1, 0), (0.5, 0.5), (1, 1)]
edges = [('A', 'B'), ('A', 'C'), ('A', 'D'), ('A', 'E'), ('D', 'A')]
# Matplotlib figure
plt.figure('My graph problem')
# Create graph
G = nx.MultiDiGraph(format='png', directed=True)
for index, name in enumerate(names):
G.add_node(name, pos=positions[index])
labels = {}
layout = dict((n, G.node[n]["pos"]) for n in G.nodes())
nx.draw(G, pos=layout, with_labels=True, node_size=300)
ax = plt.gca()
for edge in edges:
ax.annotate("",
xy=layout[edge[0]], xycoords='data',
xytext=layout[edge[1]], textcoords='data',
arrowprops=dict(arrowstyle="->", color="0.5",
shrinkA=5, shrinkB=5,
patchA=None, patchB=None,
connectionstyle="arc3,rad=-0.3",
),
)
plt.show()
给予:
另请参阅此。
正如 Paul 提到的,现在可以选择在
FancyArrowPatch
中使用 draw_networkx_edges
,尽管它仅适用于有向图并且速度也非常慢。
无论如何,我打包了一些旧代码,这些代码使用
bezier
包从 NetworkX 图(或任何边列表,实际上)生成漂亮的弯曲边并绘制它们。它可能有用:https://github.com/beyondbeneath/bezier-curved-edges-networkx
示例图像,使用 SNAP Facebook 数据集和 ForceAtlas2 布局: