Solución:
[scroll down a bit to see what kind of output the code produces]
editar (7 de noviembre de 2019) Puse una versión más refinada de esto en un paquete que he estado escribiendo: https://epidemicsonnetworks.readthedocs.io/en/latest/_modules/EoN/auxiliary.html#hierarchy_pos. La principal diferencia entre el código aquí y la versión allí es que el código aquí le da a todos los hijos de un nodo dado el mismo espacio horizontal, mientras que el código que sigue a ese enlace también considera cuántos descendientes tiene un nodo al decidir cuánto espacio asignarlo. .
editar (19 de enero de 2019) He actualizado el código para que sea más robusto: ahora funciona para gráficos dirigidos y no dirigidos sin ninguna modificación, ya no requiere que el usuario especifique la raíz y prueba que el gráfico es un árbol antes de ejecutarse (sin la prueba lo haría tienen recursividad infinita: consulte la respuesta de user2479115 para conocer una forma de manejar los no árboles).
editar (27 de agosto de 2018) Si desea crear un gráfico con los nodos que aparecen como anillos alrededor del nodo raíz, el código de la parte inferior muestra una modificación simple para hacer esto.
editar (17 de septiembre de 2017) Creo que el problema con pygraphviz que OP estaba teniendo ya debería estar solucionado. Entonces, es probable que pygraphviz sea una mejor solución que la que tengo a continuación.
Aquí hay un programa recursivo simple para definir las posiciones. La recursividad ocurre en _hierarchy_pos
, que es llamado por hierarchy_pos
. El papel principal de hierarcy_pos
es hacer un poco de prueba para asegurarse de que el gráfico sea apropiado antes de ingresar a la recursividad:
import networkx as nx
import random
def hierarchy_pos(G, root=None, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5):
'''
From Joel's answer at https://stackoverflow.com/a/29597209/2966723.
Licensed under Creative Commons Attribution-Share Alike
If the graph is a tree this will return the positions to plot this in a
hierarchical layout.
G: the graph (must be a tree)
root: the root node of current branch
- if the tree is directed and this is not given,
the root will be found and used
- if the tree is directed and this is given, then
the positions will be just for the descendants of this node.
- if the tree is undirected and not given,
then a random choice will be used.
width: horizontal space allocated for this branch - avoids overlap with other branches
vert_gap: gap between levels of hierarchy
vert_loc: vertical location of root
xcenter: horizontal location of root
'''
if not nx.is_tree(G):
raise TypeError('cannot use hierarchy_pos on a graph that is not a tree')
if root is None:
if isinstance(G, nx.DiGraph):
root = next(iter(nx.topological_sort(G))) #allows back compatibility with nx version 1.11
else:
root = random.choice(list(G.nodes))
def _hierarchy_pos(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5, pos = None, parent = None):
'''
see hierarchy_pos docstring for most arguments
pos: a dict saying where all nodes go if they have been assigned
parent: parent of this branch. - only affects it if non-directed
'''
if pos is None:
pos = {root:(xcenter,vert_loc)}
else:
pos[root] = (xcenter, vert_loc)
children = list(G.neighbors(root))
if not isinstance(G, nx.DiGraph) and parent is not None:
children.remove(parent)
if len(children)!=0:
dx = width/len(children)
nextx = xcenter - width/2 - dx/2
for child in children:
nextx += dx
pos = _hierarchy_pos(G,child, width = dx, vert_gap = vert_gap,
vert_loc = vert_loc-vert_gap, xcenter=nextx,
pos=pos, parent = root)
return pos
return _hierarchy_pos(G, root, width, vert_gap, vert_loc, xcenter)
y un ejemplo de uso:
import matplotlib.pyplot as plt
import networkx as nx
G=nx.Graph()
G.add_edges_from([(1,2), (1,3), (1,4), (2,5), (2,6), (2,7), (3,8), (3,9), (4,10),
(5,11), (5,12), (6,13)])
pos = hierarchy_pos(G,1)
nx.draw(G, pos=pos, with_labels=True)
plt.savefig('hierarchy.png')
Idealmente, esto debería cambiar la escala de la separación horizontal en función de qué tan anchas serán las cosas debajo de ella. No estoy intentando eso, pero esta versión lo hace: https://epidemicsonnetworks.readthedocs.io/en/latest/_modules/EoN/auxiliary.html#hierarchy_pos
Expansión radial
Digamos que quieres que la trama se vea así:
Aquí está el código para eso:
pos = hierarchy_pos(G, 0, width = 2*math.pi, xcenter=0)
new_pos = {u:(r*math.cos(theta),r*math.sin(theta)) for u, (theta, r) in pos.items()}
nx.draw(G, pos=new_pos, node_size = 50)
nx.draw_networkx_nodes(G, pos=new_pos, nodelist = [0], node_color="blue", node_size = 200)
editar – gracias a Deepak Saini por señalar un error que solía aparecer en los gráficos dirigidos
Aquí hay una solución para árboles grandes. Es una modificación del enfoque recursivo de Joel que espacia los nodos de manera uniforme en cada nivel.
def hierarchy_pos(G, root, levels=None, width=1., height=1.):
'''If there is a cycle that is reachable from root, then this will see infinite recursion.
G: the graph
root: the root node
levels: a dictionary
key: level number (starting from 0)
value: number of nodes in this level
width: horizontal space allocated for drawing
height: vertical space allocated for drawing'''
TOTAL = "total"
CURRENT = "current"
def make_levels(levels, node=root, currentLevel=0, parent=None):
"""Compute the number of nodes for each level
"""
if not currentLevel in levels:
levels[currentLevel] = {TOTAL : 0, CURRENT : 0}
levels[currentLevel][TOTAL] += 1
neighbors = G.neighbors(node)
for neighbor in neighbors:
if not neighbor == parent:
levels = make_levels(levels, neighbor, currentLevel + 1, node)
return levels
def make_pos(pos, node=root, currentLevel=0, parent=None, vert_loc=0):
dx = 1/levels[currentLevel][TOTAL]
left = dx/2
pos[node] = ((left + dx*levels[currentLevel][CURRENT])*width, vert_loc)
levels[currentLevel][CURRENT] += 1
neighbors = G.neighbors(node)
for neighbor in neighbors:
if not neighbor == parent:
pos = make_pos(pos, neighbor, currentLevel + 1, node, vert_loc-vert_gap)
return pos
if levels is None:
levels = make_levels({})
else:
levels = {l:{TOTAL: levels[l], CURRENT:0} for l in levels}
vert_gap = height / (max([l for l in levels])+1)
return make_pos({})
El ejemplo de Joel se verá así:
Y este es un gráfico más complejo (renderizado usando plotly):
La forma más sencilla de obtener un gráfico de árbol atractivo en Python 2 o 3 sin PyGraphviz es usar PyDot (https://pypi.python.org/pypi/pydot). Mientras que PyGraphviz proporciona una interfaz para todo Graphviz, PyDot solo proporciona una interfaz para la herramienta Dot de Graphviz, que es la única que necesita si lo que busca es un gráfico jerárquico / un árbol. Si desea crear su gráfico en NetworkX en lugar de PyDot, puede usar NetworkX para exportar un gráfico PyDot, como se muestra a continuación:
import networkx as nx
g=nx.DiGraph()
g.add_edges_from([(1,2), (1,3), (1,4), (2,5), (2,6), (2,7), (3,8), (3,9),
(4,10), (5,11), (5,12), (6,13)])
p=nx.drawing.nx_pydot.to_pydot(g)
p.write_png('example.png')
Tenga en cuenta que es necesario instalar Graphviz y PyDot para que lo anterior funcione correctamente.
Advertencia: He tenido problemas al usar PyDot para dibujar gráficos con diccionarios de atributos de nodo exportados desde NetworkX; a veces, los diccionarios parecen exportarse con comillas que faltan en las cadenas, lo que hace que el write
método para estrellarse. Esto se puede evitar omitiendo los diccionarios.