如何以可读的方式绘制复杂的 networkx DiGraph?

How to draw complex networkx DiGraph in a readable way?

我想使用 networkx 来研究一个相当大的项目的架构,但我已经完成了测试 到目前为止还不太好,这是我所有研究的一个最小例子:

import matplotlib.pyplot as plt
import networkx as nx
from networkx.readwrite import node_link_graph

G = node_link_graph({'directed': True, 'multigraph': False, 'graph': {}, 'nodes': [{'id': 'build'}, {'id': 'root'}, {'id': 'utils'}, {'id': 'codegen'}, {'id': 'codegen.templates'}, {'id': 'nodes.shapes'}, {'id': 'codegen.c_types'}, {'id': 'nodes'}, {'id': 'containers'}, {'id': 'distutils'}, {'id': 'wheel'}, {'id': 'tools.testing'}, {'id': 'finalizations'}, {'id': 'importing'}, {'id': 'plugins'}, {'id': 'freezer'}, {'id': 'tree'}, {'id': 'specs'}, {'id': 'optimizations'}, {'id': 'plugins.standard'}, {'id': 'tools.general.dll_report'}, {'id': 'tools.specialize'}, {'id': 'tools.testing.compare_with_cpython'}, {'id': 'tools.testing.find_sxs_modules'}, {'id': 'tools.testing.measure_construct_performance'}, {'id': 'tools.testing.run_root_tests'}, {'id': 'tools'}], 'links': [{'source': 'build', 'target': 'root'}, {'source': 'build', 'target': 'utils'}, {'source': 'root', 'target': 'root'}, {'source': 'root', 'target': 'containers'}, {'source': 'root', 'target': 'utils'}, {'source': 'root', 'target': 'finalizations'}, {'source': 'root', 'target': 'freezer'}, {'source': 'root', 'target': 'plugins'}, {'source': 'root', 'target': 'nodes.shapes'}, {'source': 'utils', 'target': 'root'}, {'source': 'utils', 'target': 'utils'}, {'source': 'codegen', 'target': 'codegen'}, {'source': 'codegen', 'target': 'codegen.templates'}, {'source': 'codegen', 'target': 'root'}, {'source': 'codegen', 'target': 'codegen.c_types'}, {'source': 'codegen', 'target': 'utils'}, {'source': 'codegen', 'target': 'nodes.shapes'}, {'source': 'codegen', 'target': 'nodes'}, {'source': 'codegen', 'target': 'containers'}, {'source': 'codegen.templates', 'target': 'root'}, {'source': 'nodes.shapes', 'target': 'codegen.c_types'}, {'source': 'nodes.shapes', 'target': 'codegen'}, {'source': 'nodes.shapes', 'target': 'root'}, {'source': 'nodes.shapes', 'target': 'nodes.shapes'}, {'source': 'codegen.c_types', 'target': 'codegen.templates'}, {'source': 'codegen.c_types', 'target': 'codegen.c_types'}, {'source': 'codegen.c_types', 'target': 'codegen'}, {'source': 'nodes', 'target': 'containers'}, {'source': 'nodes', 'target': 'utils'}, {'source': 'nodes', 'target': 'nodes.shapes'}, {'source': 'nodes', 'target': 'importing'}, {'source': 'nodes', 'target': 'root'}, {'source': 'nodes', 'target': 'optimizations'}, {'source': 'nodes', 'target': 'tree'}, {'source': 'nodes', 'target': 'nodes'}, {'source': 'nodes', 'target': 'specs'}, {'source': 'containers', 'target': 'root'}, {'source': 'distutils', 'target': 'wheel'}, {'source': 'distutils', 'target': 'tools.testing'}, {'source': 'tools.testing', 'target': 'root'}, {'source': 'tools.testing', 'target': 'utils'}, {'source': 'tools.testing', 'target': 'tools.testing'}, {'source': 'finalizations', 'target': 'finalizations'}, {'source': 'finalizations', 'target': 'root'}, {'source': 'finalizations', 'target': 'importing'}, {'source': 'finalizations', 'target': 'plugins'}, {
                    'source': 'importing', 'target': 'containers'}, {'source': 'importing', 'target': 'plugins'}, {'source': 'importing', 'target': 'root'}, {'source': 'importing', 'target': 'utils'}, {'source': 'importing', 'target': 'importing'}, {'source': 'importing', 'target': 'tree'}, {'source': 'plugins', 'target': 'root'}, {'source': 'plugins', 'target': 'containers'}, {'source': 'plugins', 'target': 'utils'}, {'source': 'plugins', 'target': 'plugins'}, {'source': 'freezer', 'target': 'codegen'}, {'source': 'freezer', 'target': 'codegen.templates'}, {'source': 'freezer', 'target': 'root'}, {'source': 'freezer', 'target': 'utils'}, {'source': 'freezer', 'target': 'containers'}, {'source': 'freezer', 'target': 'importing'}, {'source': 'freezer', 'target': 'nodes'}, {'source': 'freezer', 'target': 'plugins'}, {'source': 'freezer', 'target': 'tree'}, {'source': 'freezer', 'target': 'freezer'}, {'source': 'tree', 'target': 'root'}, {'source': 'tree', 'target': 'plugins'}, {'source': 'tree', 'target': 'utils'}, {'source': 'tree', 'target': 'tree'}, {'source': 'tree', 'target': 'nodes'}, {'source': 'tree', 'target': 'optimizations'}, {'source': 'tree', 'target': 'freezer'}, {'source': 'tree', 'target': 'importing'}, {'source': 'tree', 'target': 'specs'}, {'source': 'specs', 'target': 'root'}, {'source': 'specs', 'target': 'specs'}, {'source': 'specs', 'target': 'utils'}, {'source': 'optimizations', 'target': 'root'}, {'source': 'optimizations', 'target': 'importing'}, {'source': 'optimizations', 'target': 'nodes'}, {'source': 'optimizations', 'target': 'nodes.shapes'}, {'source': 'optimizations', 'target': 'tree'}, {'source': 'optimizations', 'target': 'utils'}, {'source': 'optimizations', 'target': 'optimizations'}, {'source': 'optimizations', 'target': 'plugins'}, {'source': 'plugins.standard', 'target': 'root'}, {'source': 'plugins.standard', 'target': 'plugins'}, {'source': 'plugins.standard', 'target': 'containers'}, {'source': 'plugins.standard', 'target': 'utils'}, {'source': 'tools.general.dll_report', 'target': 'freezer'}, {'source': 'tools.general.dll_report', 'target': 'utils'}, {'source': 'tools.specialize', 'target': 'codegen'}, {'source': 'tools.specialize', 'target': 'root'}, {'source': 'tools.testing.compare_with_cpython', 'target': 'root'}, {'source': 'tools.testing.compare_with_cpython', 'target': 'tools.testing'}, {'source': 'tools.testing.compare_with_cpython', 'target': 'utils'}, {'source': 'tools.testing.find_sxs_modules', 'target': 'tools.testing'}, {'source': 'tools.testing.find_sxs_modules', 'target': 'root'}, {'source': 'tools.testing.find_sxs_modules', 'target': 'utils'}, {'source': 'tools.testing.measure_construct_performance', 'target': 'tools.testing'}, {'source': 'tools.testing.run_root_tests', 'target': 'tools'}, {'source': 'tools.testing.run_root_tests', 'target': 'tools.testing'}, {'source': 'tools.testing.run_root_tests', 'target': 'utils'}]})
nx.draw(G, with_labels=True)
plt.show()

如您所见,以这种天真的方式绘制图形将产生完全无用且不可读的输出,例如:

事实是,在阅读了 networkx tutorial/docs、检查了一些 google 参考文献之后,我一直无法找到完成任务的正确方法。我已经安装了 graphviz,但我在 windows 上尝试构建&运行 pygraphivz/pydot 时惨遭失败......

问题:如何使用 networkx 以某种分层且干净的方式绘制复杂的图形,其中节点均匀分布在它们之间?您可以在下面看到我想要在此处实现的输出类型:

如您所见,节点分散,循环显示正确,层次结构的不同级别完全清晰top/down...如果这样(或类似)的东西可以用networkx实现。

事实上,paper中描述的正是我想要在此处实现的输出类型

Ns。图片例子借用这个site

networkx 的各种 draw 函数采用 pos 参数,这是一个以节点名称作为键、x、y 坐标作为值的字典。

你可以自己生成这个。如果您知道要施加的层次结构,则可以将层次结构转换为 y 位置,然后只需添加填充 x 位置即可:

# exctracting nodes from dictionary into list:
nodes = [{'id': 'build'}, {'id': 'root'}, {'id': 'utils'}, {'id': 'codegen'}, {'id': 'codegen.templates'}, {'id': 'nodes.shapes'}, {'id': 'codegen.c_types'}, {'id': 'nodes'}, {'id': 'containers'}, {'id': 'distutils'}, {'id': 'wheel'}, {'id': 'tools.testing'}, {'id': 'finalizations'}, {'id': 'importing'}, {'id': 'plugins'}, {'id': 'freezer'}, {'id': 'tree'}, {'id': 'specs'}, {'id': 'optimizations'}, {'id': 'plugins.standard'}, {'id': 'tools.general.dll_report'}, {'id': 'tools.specialize'}, {'id': 'tools.testing.compare_with_cpython'}, {'id': 'tools.testing.find_sxs_modules'}, {'id': 'tools.testing.measure_construct_performance'}, {'id': 'tools.testing.run_root_tests'}, {'id': 'tools'}]

nodelist = []
for n in nodes:
    for k, v in n.items():
        nodelist.append(v)

# hierarchy here is arbitrarily defined based on the index of hte node in nodelist. 
# {hierarchy_level : number_of_nodes_at_that_level}
hierarchy = {
    0:4,
    1:10,
    2:5,
    3:5,
    4:3
}

coords = []
for y, v in hierarchy.items():
    coords += [[x, y] for x in list(range(v))]

# map node names to positions 
# this is based on index of node in nodelist.
# can and should be tailored to your actual hierarchy    
positions = {}
for n, c in zip(nodelist, coords):
    positions[n] = c

fig = plt.figure(figsize=(15,5))
nx.draw_networkx_nodes(G, pos=positions, node_size=50)
nx.draw_networkx_edges(G, pos=positions, alpha=0.2)

# generate y-offset for the labels, s.t. they don't lie on the nodes
label_positions = {k:[v0, v1-.25] for k, (v0,v1) in positions.items()}
nx.draw_networkx_labels(G, pos=label_positions, font_size=8)
plt.show()

节点标签有些重叠,但这可以通过字体大小进行调整,通过图形尺寸进行额外偏移

编辑:

旋转节点标签以避免文本重叠:

text = nx.draw_networkx_labels(G, pos=label_positions, font_size=8)
for _, t in text.items():
    t.set_rotation(20)