Can one get hierarchical graphs from networkx with python 3?

Can one get hierarchical graphs from networkx with python 3?



I am trying to display a tree graph of my class hierarchy using networkx. I have it all graphed correctly, and it displays fine. But as a circular graph with crossing edges, it is a pure hierarchy, and it seems I ought to be able to display it as a tree.


networkx.



I have googled this extensively, and every solution offered involves using pygraphviz... but PyGraphviz does not work with Python 3 (documentation from the pygraphviz site).


pygraphviz



Has anyone been able to get a tree graph display in Python 3?





With networkx you should be able to use DIGraph with the dot layout. This should display a tree graph.
– rfkortekaas
Apr 12 '15 at 9:34





The development version of pygraphviz does work with Python 3.
– Aric
Apr 12 '15 at 12:34





You might try using the spring layout, networkx.spring_layout()
– Aric
Apr 12 '15 at 12:35





I tried spring layout -- what displays is still circular, with overlapping edges.
– NickDanger66
Apr 12 '15 at 14:06





I've provided an answer, but it won't look particularly nice if the tree has some branches that are very "wide". I think this is where a lot of the effort of pygraphviz happens. Let me know if it works for you. If not, let me know what looks bad about it and I'll see if it's an easy fix.
– Joel
Apr 13 '15 at 6:32




5 Answers
5



edit (27 Aug 2018) If you want to create a plot with the nodes appearing as rings around the root node, the code right at the bottom shows a simple modification to do this



edit (17 Sept 2017) I believe the trouble with pygraphviz that OP was having should be fixed by now. So pygraphviz is likely to be a better solution that what I've got below.



Here is a simple recursive program to define the positions:


import networkx as nx

def hierarchy_pos(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5,
pos = None, parent = None):
'''If there is a cycle that is reachable from root, then this will see infinite recursion.
G: the graph
root: the root node of current branch
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
pos: a dict saying where all nodes go if they have been assigned
parent: parent of this branch.'''
if pos == None:
pos = root:(xcenter,vert_loc)
else:
pos[root] = (xcenter, vert_loc)
neighbors = list(G.neighbors(root))
if parent != None: #this should be removed for directed graphs.
neighbors.remove(parent) #if directed, then parent not in neighbors.
if len(neighbors)!=0:
dx = width/len(neighbors)
nextx = xcenter - width/2 - dx/2
for neighbor in neighbors:
nextx += dx
pos = hierarchy_pos(G,neighbor, width = dx, vert_gap = vert_gap,
vert_loc = vert_loc-vert_gap, xcenter=nextx, pos=pos,
parent = root)
return pos



and an example usage:


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')



enter image description here



Ideally this should rescale the horizontal separation based on how wide things will be beneath it. I'm not attempting that now.



Radial expansion



Let's say you want the plot to look like:



enter image description here



Here's the code for that:


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)



edit - thanks to Deepak Saini for noting an error that arises in directed graphs (comments in the example code now show how to fix that)





Exactly what I was looking for! Awesome dude!
– Clement T.
Dec 12 '16 at 18:57





Needs neighbors = list(G.neighbors(root)) for python 3.
– typingduck
Jan 24 at 19:19


neighbors = list(G.neighbors(root))





@typingduck Can you check if neighbors = G.neighbors(root) and then later if neighbors: rather than if len(neighbors)!=0: works correctly?
– Joel
Jan 25 at 8:04



neighbors = G.neighbors(root)


if neighbors:


if len(neighbors)!=0:





What if there is a loop, can we show it by above graph? Example: For this data [(1,2), (1,3), (1,4), (2,5), (2,6), (2,7), (3,8), (3,9), (4,10),(5,11), (5,12), (6,13),(13,1)]
– DreamerP
Jul 2 at 11:44






@DreamerP : The code is designed for a tree. What I think you could do if there are cycles is have a set of "found nodes" which are also removed from neighbors. You may need to do some modification of the positioning because you would have edges within a hierarchy level, which would lead to a bunch of overlapping edges so it would be hard to tell which nodes are actually connected.
– Joel
Jul 2 at 17:47


neighbors



I modified slightly so that it would not infinitely recurse.


import networkx as nx

def hierarchy_pos(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5 ):
'''If there is a cycle that is reachable from root, then result will not be a hierarchy.

G: the graph
root: the root node of current branch
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
'''

def h_recur(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5,
pos = None, parent = None, parsed = ):
if(root not in parsed):
parsed.append(root)
if pos == None:
pos = root:(xcenter,vert_loc)
else:
pos[root] = (xcenter, vert_loc)
neighbors = G.neighbors(root)
if parent != None:
neighbors.remove(parent)
if len(neighbors)!=0:
dx = width/len(neighbors)
nextx = xcenter - width/2 - dx/2
for neighbor in neighbors:
nextx += dx
pos = h_recur(G,neighbor, width = dx, vert_gap = vert_gap,
vert_loc = vert_loc-vert_gap, xcenter=nextx, pos=pos,
parent = root, parsed = parsed)
return pos

return h_recur(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5)



The simplest way to get a nice-looking tree graph display in Python 2 or 3 without PyGraphviz is to use PyDot (https://pypi.python.org/pypi/pydot). Whereas PyGraphviz provides an interface to the whole of Graphviz, PyDot only provides an interface to Graphviz's Dot tool, which is the only one you need if what you're after is a hierarchical graph / a tree. If you want to create your graph in NetworkX rather than PyDot, you can use NetworkX to export a PyDot graph, as in the following:


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')



Note that Graphviz and PyDot need to be installed for the above to work correctly.



enter image description here



Warning: I have experienced problems when using PyDot to draw graphs with node attribute dictionaries exported from NetworkX - sometimes the dictionaries seem to be exported with quotation marks missing from strings, which causes the write method to crash. This can be avoided by leaving out the dictionaries.


write



Here is a solution for large trees. It is a modification of Joel's recursive approach that evenly spaces nodes at each level.


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)
if parent is not None:
neighbors.remove(parent)
for neighbor in neighbors:
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)
if parent is not None:
neighbors.remove(parent)
for neighbor in neighbors:
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()



Joel's example will look like this:
enter image description here



And this is a more complex graph (rendered using plotly):enter image description here





This would seem to be something that should be easy out-of-the-box. I teach CS, and I would love to use this package to create b-trees, red-black trees, etc.... But it is a little cumbersome right now.
– Gene Callahan
Mar 11 '17 at 20:16



For a directed graph, Since neighbors(x) include only the succesors(x), so you have to remove the lines:


if parent != None:
neighbors.remove(parent)



Also, a better option would be this:


pos=nx.graphviz_layout(G,prog='dot')






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)