Networks with Plotly

In this notebook we illustrate how to generate planar graphs/networks in Plotly, defined as Graph objects in networkx.

The Plotly objects defined in this notebook have some attributes introduced only starting with the Plotly 1.8.6.

Planar graphs defined by the lists of nodes and edges


In [2]:
import networkx as nx

In [3]:
G=nx.Graph()#  G is an empty Graph

In [4]:
Nodes=range(9)
G.add_nodes_from(Nodes)
Edges=[(0,1), (0,2), (1,3), (1,4), (1,7), (2,5), (2,8), (3, 4), (3,5),(4,6), (4,7), (4,8), (5,7)]
G.add_edges_from(Edges)
G.add_edge(6,8)

Graphs are drawn using various layouts. The default layout is spring_layout. Other common layouts are spectral_layout, fruchterman_reingold_layout (for more details see here).

In order to plot a Graph with Python Plotly we extract the node positions calling the layout function, nx.name_layout(G).

Position is a dictionary having as keys the node indices and as values the coordinates assigned to nodes by the layout algorithm:


In [6]:
pos1=nx.spring_layout(G)
print pos1


{0: array([ 0.05778792,  0.        ]), 1: array([ 0.02296447,  0.5083251 ]), 2: array([ 0.4976912 ,  0.08608332]), 3: array([ 0.        ,  0.78987127]), 4: array([ 0.50350874,  0.74679982]), 5: array([ 0.27187797,  0.476265  ]), 6: array([ 1.        ,  0.79157307]), 7: array([ 0.2210117 ,  0.89440326]), 8: array([ 0.85377665,  0.43007917])}

In [7]:
pos2=nx.spectral_layout(G)
print pos2


{0: array([ 0.        ,  0.91005414]), 1: array([ 0.18943709,  0.22856717]), 2: array([ 0.24577346,  0.7128032 ]), 3: array([ 0.22829568,  0.        ]), 4: array([ 0.51022695,  0.18469614]), 5: array([ 0.14790551,  0.12247499]), 6: array([ 1.        ,  0.40985771]), 7: array([  2.28295680e-01,   2.51522722e-16]), 8: array([ 0.73123327,  0.53372517])}

Spring layout and Fruchterman Reingold layout algorithms start with random positions of nodes. That is why two consecutive calls of the corresponding function return different node positions for the same graph.

To illustrate non-uniqueness of positions we call again the function nx.spring_layout() for G:


In [8]:
pos3=nx.spring_layout(G)
print pos3


{0: array([ 0.55271388,  1.        ]), 1: array([ 0.81184324,  0.58139772]), 2: array([ 0.18794049,  0.78577442]), 3: array([ 0.77581885,  0.15018096]), 4: array([ 0.45893696,  0.1967814 ]), 5: array([ 0.56323739,  0.52161378]), 6: array([ 0.0494129,  0.       ]), 7: array([ 0.93248766,  0.33947602]), 8: array([ 0.        ,  0.35521613])}

Spectral layout algorithm returns a unique position at each call for a given graph.

The Plotly plot of a graph involves the Graph nodes and edges, node positions associated by a chosen layout function, and the attributes of nodes and edges defined as Plotly Scatter traces of type (mode) marker, respectively line.


In [5]:
import plotly.plotly as py
from plotly.graph_objs import *

The function scatter_nodes sets the position of nodes and their attributes:


In [6]:
def scatter_nodes(pos, labels=None, color=None, size=20, opacity=1):
    # pos is the dict of node positions
    # labels is a list  of labels of len(pos), to be displayed when hovering the mouse over the nodes
    # color is the color for nodes. When it is set as None the Plotly default color is used
    # size is the size of the dots representing the nodes
    #opacity is a value between [0,1] defining the node color opacity
    L=len(pos)
    trace = Scatter(x=[], y=[],  mode='markers', marker=Marker(size=[]))
    for k in range(L):
        trace['x'].append(pos[k][0])
        trace['y'].append(pos[k][1])
    attrib=dict(name='', text=labels , hoverinfo='text', opacity=opacity) # a dict of Plotly node attributes
    trace=dict(trace, **attrib)# concatenate the dict trace and attrib
    trace['marker']['size']=size
    if color is not None:
        trace['marker']['color']=color
    return trace

The node coordinates are displayed by Plotly by default when hovering the mouse over the node. Because these coordinates have no relevance in the network analysis we hide them setting hoverinfo as 'text'. In the function above text is the list of node labels.

The function scatter_edges defines the Graph edges as a Scatter Plotly trace.

hoverinfo is set as 'none', because otherwise hovering the mouse over the points of coordinates $(x,y)$, i.e. over the nodes, their coordinates are displayed.


In [7]:
def scatter_edges(G, pos, line_color=None, line_width=1):
    trace = Scatter(x=[], y=[], mode='lines')
    for edge in G.edges():
        trace['x'] += [pos[edge[0]][0],pos[edge[1]][0], None]
        trace['y'] += [pos[edge[0]][1],pos[edge[1]][1], None]  
        trace['hoverinfo']='none'
        trace['line']['width']=line_width
        if line_color is not None: # when it is None a default Plotly color is used
            trace['line']['color']=line_color
    return trace

Let us plot the Graph, $G$, defined above using node positions assigned by Fruchterman Reingold layout algorithm:


In [9]:
pos=nx.fruchterman_reingold_layout(G)

In [10]:
labels=[str(k)  for k in range(len(pos))] # labels are  set as the nodes indices in the list of nodes
trace1=scatter_edges(G, pos)
trace2=scatter_nodes(pos, labels=labels)

Settings for Plotly layout of the plot:


In [11]:
width=500
height=500
axis=dict(showline=False, # hide axis line, grid, ticklabels and  title
          zeroline=False,
          showgrid=False,
          showticklabels=False,
          title='' 
          )
layout=Layout(title= 'Fruchterman Reingold  layout',  
    font= Font(),
    showlegend=False,
    autosize=False,
    width=width,
    height=height,
    xaxis=XAxis(axis),
    yaxis=YAxis(axis),
    margin=Margin(
        l=40,
        r=40,
        b=85,
        t=100,
        pad=0,
       
    ),
    hovermode='closest',
    plot_bgcolor='#EFECEA', #set background color            
    )

data=Data([trace1, trace2])
fig = Figure(data=data, layout=layout)

To get a good looking graph it is recommended to define data as a list having the first element the trace corresponding to edges, and the second the trace for nodes. Otherwise the lines will be traced over the node dots.

If we want to annotate the nodes we should update the layout with the corresponding annotations. The function below defines the annotations:


In [12]:
def make_annotations(pos, text, font_size=14, font_color='rgb(25,25,25)'):
    L=len(pos)
    if len(text)!=L:
        raise ValueError('The lists pos and text must have the same len')
    annotations = Annotations()
    for k in range(L):
        annotations.append(
            Annotation(
                text=text[k], 
                x=pos[k][0], y=pos[k][1],
                xref='x1', yref='y1',
                font=dict(color= font_color, size=font_size),
                showarrow=False)
        )
    return annotations

In [17]:
fig['layout'].update(annotations=make_annotations(pos, [str(k) for k in range(len(pos))]))  
py.sign_in('empet', '')
py.iplot(fig, filename='Network-12')


Out[17]:

Network defined by adjacency matrix


In [13]:
import numpy as np

In [14]:
Ad=np.array([[0,1,1,1,0,0,0,1], # Adjacency matrix
             [1,0,1,0,1,1,1,0],
             [1,1,0,0,0,0,1,1],
             [1,0,0,0,1,1,1,1],
             [0,1,0,1,0,1,1,0],
             [0,1,0,1,1,0,1,0],
             [0,1,1,1,1,1,0,1],
             [1,0,1,1,0,0,1,0]], dtype=float)
Gr=nx.from_numpy_matrix(Ad)

In [15]:
position=nx.spring_layout(Gr)

Interpreting the graph as a friendship graph, we set its labels as being the friend names:


In [16]:
labels=['Fred', 'Alice', 'Bob', 'Anna', 'Frida', 'Andrew', 'Jack', 'Maria']

In [17]:
traceE=scatter_edges(Gr, position)
traceN=scatter_nodes(position, labels=labels)

Now we update the above layout of the plot with information on the new graph:


In [25]:
layout.update(title='Friendship Network')
data1=Data([traceE, traceN])
fig = Figure(data=data1, layout=layout)
fig['layout'].update(annotations=make_annotations(position, [str(k) for k in range(len(position))]))  
py.iplot(fig, filename='Network-2N')


Out[25]:

In [26]:
from IPython.core.display import HTML
def  css_styling():
    styles = open("./custom.css", "r").read()
    return HTML(styles)
css_styling()


Out[26]: