用Python绘制有向图,以显示所有边的方法

34
我正在使用Python模拟在有向图上进行的过程。我希望能够生成这个过程的动画。
我遇到的问题是,大多数Python图形可视化库将成对的有向边组合成单个边。例如,NetworkX在显示以下图形时仅绘制两条边,而我希望将四条边分别显示:
import networkx as nx
import matplotlib.pyplot as plt 

G = nx.MultiDiGraph()

G.add_edges_from([
    (1, 2),
    (2, 3),
    (3, 2),
    (2, 1),
])

plt.figure(figsize=(8,8))
nx.draw(G)

Output from NetworkX; parallel edges are overlapping, so only two lines are displayed

我希望能够展示类似于这样的内容,每个平行边都单独绘制:

Desired output format; parallel edges are drawn separately

这个问题 R reciprocal edges in igraph in R 似乎涉及到相同的问题,但那里的解决方案是针对R igraph库而不是Python库。

是否有一种简单的方法可以使用现有的Python图形可视化库来生成这种样式的图?如果它能支持多重图就更好了。

我接受调用外部程序来生成图像的解决方案。我想生成整个系列的动画帧,因此解决方案必须是自动化的。


相关:https://dev59.com/WGUp5IYBdhLWcg3wx5tu - 0 _
4个回答

34

Graphviz工具似乎可以显示不同的边缘。

例如,给出以下内容:

digraph G {
  A -> B;
  A -> B;
  A -> B;
  B -> C;

  B -> A;
  C -> B;
}

转换为 会产生:

example graph

Graphviz的输入语言非常简单,因此您可以自己生成它,尽管搜索“python graphviz”会出现一些库,包括PyPI上的graphviz模块
以下是使用graphviz模块生成上述图形的Python代码:
from graphviz import Digraph

dot = Digraph()
dot.node('A', 'A')
dot.node('B', 'B')
dot.node('C', 'C')
dot.edges(['AB', 'AB', 'AB', 'BC', 'BA', 'CB'])

print(dot.source)
dot.render(view=True)

3
原文:It turns out that NetworkX can export .dot files using pydot or pygraphviz, so this works perfectly. Thanks!翻译:原来NetworkX可以使用pydot或pygraphviz导出.dot文件,所以这很完美地解决了问题。谢谢! - Josh Rosen
两个链接都失效了@JoshRosen - alper
我遇到了以下错误:格式:“pdf”未被识别。请使用以下之一@Laurence Gonsalves - alper
@alper 我相信在构建时选择输出格式是可能的。您可能需要安装不同版本的Graphviz或自己构建。 - Laurence Gonsalves

17

也许我有点晚了,但我找到了另一个解决方案来解决你的问题。我发帖是为了如果有人有同样的问题,它可以有所帮助。

可以使用networkxmatplotlib绘制有向图,使边缘单独显示,方法是将参数connectionstyle传递给函数networkx.drawing.nx_pylab.draw

import matplotlib.pyplot as plt
import networkx as nx


# create a directed multi-graph
G = nx.MultiDiGraph()
G.add_edges_from([
    (1, 2),
    (2, 3),
    (3, 2),
    (2, 1),
])
# plot the graph
plt.figure(figsize=(8,8))
nx.draw(G, connectionstyle='arc3, rad = 0.1')
plt.show()  # pause before exiting

这里是结果:

结果

关于参数connectionstyle,请查看matplotlib.patches.ConnectionStyle的文档


这似乎会为仅连接单个有向边的顶点创建弧。有没有简单的解决方法? - Mehdi Saffar

13

使用 NetworkX,一种可能的解决方法是避免文件 I/O 并使用通过 pydot 进行布局的点:

import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from io import BytesIO

g = nx.dodecahedral_graph()
d = nx.drawing.nx_pydot.to_pydot(g)
    # `d` is a `pydot` graph object,
    # `dot` options can be easily set
    # attributes get converted from `networkx`,
    # use set methods to control
    # `dot` attributes after creation
png_str = d.create_png()
sio = BytesIO() # file-like string, appropriate for imread below
sio.write(png_str)
sio.seek(0)
img = mpimg.imread(sio)
imgplot = plt.imshow(img)
plt.show()  # added to make the script wait before exiting

关于为什么需要使用 seek(0),请查看如何在Python中从字符串创建图像

如果在IPython (qt)控制台中,则上述内容将内联打印,并且更直接的方法是:

import networkx as nx
from IPython.display import Image

g = nx.dodecahedral_graph()
d = nx.drawing.nx_pydot.to_pydot(g)

png_str = d.create_png()
Image(data=png_str)

这个例子需要更新:nx.to_pydot已经不再可用。相反,您应该pip install pydotplus,然后使用nx.drawing.nx_pydot.to_pydot - ogrisel
1
感谢您提出更新此示例的需求,我已经进行了更新。在上游的networkx中,pydotplus已经被还原为pydot,请参见此PR - 0 _
1
如果pydotplus未找到您的graphviz安装程序,它将抛出“InvocationException: GraphViz's executables not found”。解决方法是将其添加到当前执行的系统路径中,就像这样: import os然后 os.environ["PATH"] += r";C:\Anaconda\envs\testes\Library\bin\graphviz",其中您应该替换存放graphviz可执行文件的文件夹。 - mvbentes
pydotpluspydot的一个未维护的分支。pydot == 1.2.3只会在$PATH中查找GraphViz可执行文件,原因在于此问题和其中引用的问题所述。将相关路径添加到环境的$PATH变量中不是一种解决方法,而是声明GraphViz位置的正确方式。请考虑使用.bashrc或等效方式,而不是在运行时修改os.environ - 0 _

3
您可以通过使用Matplotlib接口来完成:

您可以通过使用Matplotlib接口来完成:

G=nx.MultiGraph ([(1, 2),
   (2, 3),
   (3, 2),
   (2, 1)])
pos = {1: (0.4,0.5), 2: (0.5,0.5), 3: (0.6,0.5)}
nx.draw_networkx_nodes(G, pos, node_color = 'k', node_size = 100, alpha = 1)
ax = plt.gca()
for e in G.edges:
    ax.annotate("",
                xy=pos[e[0]], xycoords='data',
                xytext=pos[e[1]], textcoords='data',
                arrowprops=dict(arrowstyle="->", color="0.5",
                                shrinkA=5, shrinkB=5,
                                patchA=None, patchB=None,
                                connectionstyle="arc3,rad=rrr".replace('rrr',str(2*(e[2]-0.5))
                                ),
                                ),
                )
plt.axis('off')
plt.show()

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接