17
votes

I have a graph of nodes with specific attributes and I want to draw the graph by networkx in Python with several attributes as labels of nodes outside the node.

Can someone help me how can I write my code to achieve this aim?

There is a loop in my code which generate "interface_?" attribute for each input from firewall list (fwList)

for y in fwList:
    g.add_node(n, type='Firewall')
    print 'Firewall ' + str(n) + ' :' 
    for x in fwList[n]:
        g.node[n]['interface_'+str(i)] = x
        print 'Interface '+str(i)+' = '+g.node[n]['interface_'+str(i)]
        i+=1
    i=1
    n+=1

Then, later on I draw nodes and edges like:

pos=nx.spring_layout(g)
nx.draw_networkx_edges(g, pos)
nx.draw_networkx_nodes(g,pos,nodelist=[1,2,3],node_shape='d',node_color='red')

and will extended it to some new nodes with other shape and color later.

For labeling a single attribute I tried below code, but it didn't work

labels=dict((n,d['interface_1']) for n,d in g.nodes(data=True))

And for putting the text out of the node I have no idea...

4

4 Answers

11
votes

You have access to the node positions in the 'pos' dictionary. So you can use matplotlib to put text wherever you like. e.g.

In [1]: import networkx as nx

In [2]: G=nx.path_graph(3)

In [3]: pos=nx.spring_layout(G)

In [4]: nx.draw(G,pos)

In [5]: x,y=pos[1]

In [6]: import matplotlib.pyplot as plt

In [7]: plt.text(x,y+0.1,s='some text', bbox=dict(facecolor='red', alpha=0.5),horizontalalignment='center')
Out[7]: <matplotlib.text.Text at 0x4f1e490>

enter image description here

10
votes

In addition to Aric's answer, the pos dictionary contains x, y coordinates in the values. So you can manipulate it, an example might be:

pos_higher = {}
y_off = 1  # offset on the y axis

for k, v in pos.items():
    pos_higher[k] = (v[0], v[1]+y_off)

Then draw the labels with the new position:

nx.draw_networkx_labels(G, pos_higher, labels)

where G is your graph object and labels a list of strings.

1
votes

NetworkX's documentation on draw_networkx_labels also shows that you can use the horizontalalignment and verticalalignment parameters to get an easy, out-of-the-box solution without manually nudging the labels.

From the docs:

  • horizontalalignment ({‘center’, ‘right’, ‘left’}) – Horizontal alignment (default=’center’)

  • verticalalignment ({‘center’, ‘top’, ‘bottom’, ‘baseline’, ‘center_baseline’}) – Vertical alignment (default=’center’)

Even more conveniently, you can use this with higher-level NetworkX functions, like nx.draw() or one of the nx.draw_spectral (or its variants) because these higher function arguments accept keyword arguments which is in turn passed into its lower-level functions.

So the following code is a minimally-viable code that does what you asked for, which is to push / nudge the labels outside of the node:

G = nx.DiGraph(name='Email Social Network')
nx.draw(G, arrowsize=3, verticalalignment='bottom')
# or:
nx.draw(G, arrowsize=3, verticalalignment='top')
plt.show()

The result of that verticalalignment is visualized below: enter image description here

1
votes

I like to create a nudge function that shifts the layout by an offset.

import networkx as nx
import matplotlib.pyplot as plt

def nudge(pos, x_shift, y_shift):
    return {n:(x + x_shift, y + y_shift) for n,(x,y) in pos.items()}

G = nx.Graph()
G.add_edge('a','b')
G.add_edge('b','c')
G.add_edge('a','c')

pos = nx.spring_layout(G)
pos_nodes = nudge(pos, 0, 0.1)                              # shift the layout

fig, ax = plt.subplots(1,2,figsize=(12,6))
nx.draw_networkx(G, pos=pos, ax=ax[0])                      # default labeling
nx.draw_networkx(G, pos=pos, with_labels=False, ax=ax[1])   # default nodes and edges
nx.draw_networkx_labels(G, pos=pos_nodes, ax=ax[1])         # nudged labels
ax[1].set_ylim(tuple(i*1.1 for i in ax[1].get_ylim()))      # expand plot to fit labels
plt.show()

Two graph representations, side by side, of three nodes in a triangle. The one on the left has labels placed directly on the nodes per the networkx defaults, and the one on the right has labels shifted slightly up