2

I am trying to print the binary tree using networkx library in python.

But, I am unable to preserve the left and right childs. Is there a way to tell the Graph to print left child first and then the right child?

import networkx as nx
G = nx.Graph()
G.add_edges_from([(10,20), (11,20)])
nx.draw_networkx(G)

enter image description here

EDIT 1: On using the pygraphwiz, it results in a directed graph atleast. So, I have better picture of the root node.

Below is the code I am using:

import pygraphviz as pgv
G = pgv.AGraph()
G.add_node('20')
G.add_node('10')
G.add_node('11')
G.add_edge('20','10')
G.add_edge('20','11')
G.add_edge('10','7')
G.add_edge('10','12')

G.layout()
G.draw('file1.png')
from IPython.display import Image
Image('file1.png')

But, this is still far from a structured format. I will post on what I find out next. The new graph looks like below (atleast we know the root):

Root to Leaf Binary tree

EDIT 2: For those who are facing issues with installation, please refer to this post. The answer to this - its very helpful if you want to install pygraphviz on windows 64 bit.

Community
  • 1
  • 1
ForeverLearner
  • 1,901
  • 2
  • 28
  • 51
  • 2
    I don't know anything about networkx, but graphs nodes usually don't have disctinctions for "left" and "right" childs. – Karoly Horvath Oct 30 '15 at 15:48
  • Agreed Karoly. I will keep everyone posted about what I find out. – ForeverLearner Oct 30 '15 at 18:31
  • May be worth at least looking at http://stackoverflow.com/questions/11479624/is-there-a-way-to-guarantee-hierarchical-output-from-networkx - not directly an answer, but relevant. – Joel Oct 31 '15 at 10:02
  • also http://stackoverflow.com/questions/29586520/can-one-get-hierarchical-graphs-from-networkx-with-python-3 – Joel Oct 31 '15 at 10:06

2 Answers2

5

I believe Networkx is not suited to binary trees but you can set the node positions yourself. I have wrote the following algorithm to setup node positions but it works fine for full or complete binary trees where key nodes are ordered [0,1,...].

def full_tree_pos(G):
    n = G.number_of_nodes()
    if n == 0 : return {}
    # Set position of root
    pos = {0:(0.5,0.9)}
    if n == 1:
        return pos
    # Calculate height of tree
    i = 1
    while(True):
        if n >= 2**i and n<2**(i+1):
            height = i 
            break
        i+=1
    # compute positions for children in a breadth first manner
    p_key = 0
    p_y = 0.9
    p_x = 0.5
    l_child = True # To indicate the next child to be drawn is a left one, if false it is the right child
    for i in xrange(height):
        for j in xrange(2**(i+1)):
            if 2**(i+1)+j-1 < n:
                print 2**(i+1)+j-1
                if l_child == True:
                    pos[2**(i+1)+j-1] = (p_x - 0.2/(i*i+1) ,p_y - 0.1)
                    G.add_edge(2**(i+1)+j-1,p_key)
                    l_child = False
                else:
                    pos[2**(i+1)+j-1] = (p_x + 0.2/(i*i+1) ,p_y - 0.1)
                    l_child = True
                    G.add_edge(2**(i+1)+j-1,p_key)
                    p_key += 1
                    (p_x,p_y) = pos[p_key]

    return pos

G = nx.Graph()
G.add_nodes_from(xrange(25))
pos = full_tree_pos(G)
nx.draw(G, pos=pos, with_labels=True)
plt.show()

which gave the following graph. enter image description here

Abdallah Sobehy
  • 2,881
  • 1
  • 15
  • 28
  • 1
    related: http://stackoverflow.com/questions/29586520/can-one-get-hierarchical-graphs-from-networkx-with-python-3 – Joel Oct 31 '15 at 10:03
  • @joel I believe your code, is more general and would be better to use. Anyway it was just a trial to find a solution. – Abdallah Sobehy Oct 31 '15 at 17:33
1

Note If you use networkx version 1.11 or earlier, see the note at the end.

The following assumes that each node has an attribute assigned to it telling whether it is the left or right child of its parent. So you'll have to assign this - by default a graph does not have any concept of this. Perhaps it might be possible to convince the networkx people to make a new class of graph which is a binary tree and stores this information automatically, but at present, it's not there. I don't know whether there would be enough interest to justify this.

import networkx as nx

def binary_tree_layout(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.
       each node has an attribute "left: or "right"'''
    if pos == None:
        pos = {root:(xcenter,vert_loc)}
    else:
        pos[root] = (xcenter, vert_loc)
    neighbors = list(G.neighbors(root))
    if parent != None:
        neighbors.remove(parent)
    if len(neighbors)!=0:
        dx = width/2.
        leftx = xcenter - dx/2
        rightx = xcenter + dx/2
        for neighbor in neighbors:
            if G.nodes[neighbor]['child_status'] == 'left':
                pos = binary_tree_layout(G,neighbor, width = dx, vert_gap = vert_gap, 
                                    vert_loc = vert_loc-vert_gap, xcenter=leftx, pos=pos, 
                    parent = root)
            elif G.nodes[neighbor]['child_status'] == 'right':
                pos = binary_tree_layout(G,neighbor, width = dx, vert_gap = vert_gap, 
                                    vert_loc = vert_loc-vert_gap, xcenter=rightx, pos=pos, 
                    parent = root)
    return pos

Here is a sample call where I made even nodes into left children.

G= nx.Graph()
G.add_edges_from([(0,1),(0,2), (1,3), (1,4), (2,5), (2,6), (3,7)])
for node in G.nodes():
    if node%2==0:
        G.nodes[node]['child_status'] = 'left'  #assign even to be left
    else:
        G.nodes[node]['child_status'] = 'right' #and odd to be right
pos = binary_tree_layout(G,0)
nx.draw(G, pos=pos, with_labels = True)

enter image description here


edit notes An earlier version of this answer worked for networkx version 1.11 and earlier. If you need it, please open the edit history and use the 2nd version of this answer.

Joel
  • 22,598
  • 6
  • 69
  • 93
  • Thanks for your suggestion Joel. Accepted your answer as it gave me an idea that a level-order traversal can do the trick. I will try to give a no to each node while i do a BFS. Then use this during nx.draw_drawnetworkx(G) call. I will share my results soon. Sorry about my long absence. – ForeverLearner Nov 06 '15 at 13:52
  • Great Answer, TypeError: object of type 'dict_keyiterator' has no len() https://github.com/pgmpy/pgmpy/issues/927 – Max Base Mar 13 '20 at 01:14
  • `G.node[node]['child_status'] = 'left'` produces **AttributeError: 'Graph' object has no attribute 'node'** – Apostolos Nov 09 '21 at 09:14
  • @Apostolos I've updated the code. The error you saw is because of updates to networkx's library that make it incompatible with older versions. The code should now work for version 2.x – Joel Nov 10 '21 at 10:13
  • 1
    @MaxBase Sorry I didn't see this. I've updated the code (see comment above). I study infectious disease control. Mid-Mar '20 was a busy time for me. – Joel Nov 10 '21 at 10:14
  • OK, @Joel. Good patch. – Apostolos Nov 10 '21 at 17:41