Как нарисовать сложную сеть DiGraph в удобочитаемом виде? - PullRequest
0 голосов
/ 11 апреля 2020

Я хотел бы использовать 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()

Как вы можете видеть, рисование графика таким наивным способом даст совершенно бесполезный и нечитаемый результат, такой как:

enter image description here

Дело в том, что после всех моих исследований читая учебник / документы по networkx, проверяя некоторые ссылки на Google, я не смог найти правильный способ выполнить задачу sh. У меня установлен graphviz, но я с треском провалился, пытаясь собрать и запустить pygraphivz / pydot на windows, в любом случае ...

Вопрос : Как нарисовать сложный граф с помощью networkx каким-то иерархическим и чистым способом, где узлы равномерно распределены между ними? Ниже вы можете увидеть тип вывода, который я хотел бы получить здесь:

enter image description here

Как видите, узлы рассредоточены, циклы отображаются правильно и различные уровни иерархии полностью ясны сверху вниз / было бы здорово, если бы что-то подобное (или подобное) могло быть достигнуто с помощью networkx.

Фактически, то, что описано в этом paper - это именно тот тип вывода, который я хотел бы получить здесь

Ns. Пример изображения, заимствованный с этого сайта

1 Ответ

1 голос
/ 13 апреля 2020

Различные функции draw в сети x принимают аргумент pos, который представляет собой словарь с именем узла в качестве ключа и координатами x, y в качестве значений.

Вы можете создать это самостоятельно. Если вы знаете иерархию, которую хотите навязать, вы можете перевести иерархию в y-позиции, а затем просто добавить заполненные x-позиции, когда вы go:

# 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()

enter image description here

Метки узлов несколько перекрываются, но это можно отрегулировать с помощью размера шрифта, с дополнительным смещением по размерам рисунка

РЕДАКТИРОВАТЬ:

Поворачивать метки узлов, чтобы избежать наложения текста:

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

enter image description here

...