The hvPlot NetworkX plotting API is meant as a drop-in replacement for the networkx.draw methods. In most cases the existing code will work as is or with minor modifications, returning a HoloViews object rendering an interactive bokeh plot, equivalent to the matplotlib plot the standard API constructs. First let us import the plotting interface and give it the canonical name hvnx:

In [ ]:
import hvplot.networkx as hvnx

import networkx as nx
import holoviews as hv

In this user guide we will follow along with many of the examples in the NetworkX tutorial on drawing graphs.

The hxnx namespace provides all the same plotting functions as nx, this means in most cases one can simply be swapped for the other. This also includes most keywords used to customize the plots. The main difference is in the way multiple plots are composited, like all other hvPlot APIs the networkX functions returns HoloViews objects which can be composited using + and * operations:

In [ ]:
G = nx.petersen_graph()

spring = hvnx.draw(G, with_labels=True)
shell = hvnx.draw_shell(G, nlist=[range(5, 10), range(5)], with_labels=True, font_weight='bold')

spring + shell
In [ ]:
H = nx.triangular_lattice_graph(1, 20)
hvnx.draw_planar(H, node_color='green', edge_color='brown')

The most common layout functions have dedicated drawing methods such as the draw_shell function above, which automatically computes the node positions.

However layout algorithms are not necessarily deterministic, so if we want to plot and overlay subsets of either the nodes or edges using the nodelist and edgelist keywords the node positions should be computed ahead of time and passed in explicitly:

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

hvnx.draw(G, pos, nodelist=[0, 1, 2, 3, 4], node_color='blue') *\
hvnx.draw_networkx_nodes(G, pos, nodelist=[5, 6, 7, 8, 9], node_color='green')

The hvnx namespace also makes save and ``show utilities available to save the plot to HTML or PNG files or display it in a separate browser window when working in a standard Python interpreter.

In [ ]:
G = nx.dodecahedral_graph()

shells = [[2, 3, 4, 5, 6], [8, 1, 0, 19, 18, 17, 16, 15, 14, 7], [9, 10, 11, 12, 13]]
shell = hvnx.draw_shell(G, nlist=shells)

pos = nx.nx_agraph.graphviz_layout(G)
graphviz = hvnx.draw(G, pos=pos)

layout = shell + graphviz

hvnx.save(layout, 'graph_layout.png')

Styling Graphs

The full set of options which are inherited from networkx's API are listed in the hxnx.draw() docstring. Using these the more common styling of nodes and edges can easily be altered through the common set of options that are inherited from networkx. In addition common HoloViews options to control the size of the plots, axes and styling are also supported. Finally, some layout functions also accept special keyword arguments such as the nlist argument for the shell layout which specifies the shells.

In [ ]:
options = {
    'node_color': 'black',
    'node_size': 100,
    'edge_width': 3,
    'width': 300,
    'height': 300
}

random = hvnx.draw_random(G, **options)
circular = hvnx.draw_circular(G, **options)
spectral = hvnx.draw_spectral(G, **options)
shell = hvnx.draw_shell(G, nlist=[range(5,10), range(5)], **options)

(random + circular + spectral + shell).cols(2)

In addition to being able to set scalar style values hvPlot also supports the HoloViews concept of style mapping, which uses so called dim transforms to map attributes of the graph nodes and edges to vary the visual attributes of the plot. For example we might construct a graph with edge weights and node sizes as attributes. The plotting function will extract these attributes which means they can be used to scale visual properties of the plot such as the edge_width, edge_color or node_size:

In [ ]:
G = nx.Graph()

G.add_edge('a', 'b', weight=0.6)
G.add_edge('a', 'c', weight=0.2)
G.add_edge('c', 'd', weight=0.1)
G.add_edge('c', 'e', weight=0.7)
G.add_edge('c', 'f', weight=0.9)
G.add_edge('a', 'd', weight=0.3)

G.add_node('a', size=20)
G.add_node('b', size=10)
G.add_node('c', size=12)
G.add_node('d', size=5)
G.add_node('e', size=8)
G.add_node('f', size=3)

pos = nx.spring_layout(G)  # positions for all nodes

hvnx.draw(G, pos, edge_color='weight', edge_cmap='viridis',
          edge_width=hv.dim('weight')*10, node_size=hv.dim('size')*20)

The full set of options that are supported can be accessed on the hvnx.draw function (note this does not include some bokeh specific option to control the styling of selection, nonselection and hover nodes and edges which may also be supplied and follow a pattern like hover_node_fill_color or selection_edge_line_alpha).

For reference here is the docstring listing the main supported option:

In [ ]:
print(hvnx.draw.__doc__)

The main difference to the networkx.draw API are a few options which are not supported (such as font_weight and arrowsize) and the renaming of width (which controls the edge line width) to edge_width since width and height are reserved for defining the screen dimensions of the plot.

Examples

To demonstrate that the API works almost identically this section reproduces various examples from the NetworkX documentation.

Plot properties

Compute some network properties for the lollipop graph.

URL: https://networkx.github.io/documentation/stable/auto_examples/basic/plot_properties.html

In [ ]:
#    Copyright (C) 2004-2018 by
#    Aric Hagberg <[email protected]>
#    Dan Schult <[email protected]>
#    Pieter Swart <[email protected]>
#    All rights reserved.
#    BSD license.

#    Adapted by Philipp Rudiger <[email protected]>

G = nx.lollipop_graph(4, 6)

pathlengths = []

print("source vertex {target:length, }")
for v in G.nodes():
    spl = dict(nx.single_source_shortest_path_length(G, v))
    print('{} {} '.format(v, spl))
    for p in spl:
        pathlengths.append(spl[p])

print('')
print("average shortest path length %s" % (sum(pathlengths) / len(pathlengths)))

# histogram of path lengths
dist = {}
for p in pathlengths:
    if p in dist:
        dist[p] += 1
    else:
        dist[p] = 1

print('')
print("length #paths")
verts = dist.keys()
for d in sorted(verts):
    print('%s %d' % (d, dist[d]))

print("radius: %d" % nx.radius(G))
print("diameter: %d" % nx.diameter(G))
print("eccentricity: %s" % nx.eccentricity(G))
print("center: %s" % nx.center(G))
print("periphery: %s" % nx.periphery(G))
print("density: %s" % nx.density(G))

hvnx.draw(G, with_labels=True)
In [ ]:
G = nx.path_graph(8)
hvnx.draw(G)
In [ ]:
# Author: Aric Hagberg ([email protected])
# Adapted by Philipp Rudiger <[email protected]>

G = nx.cycle_graph(24)
pos = nx.spring_layout(G, iterations=200)

# Preferred API
# hvnx.draw(G, pos, node_color='index', node_size=500, cmap='Blues')

# Original code
hvnx.draw(G, pos, node_color=range(24), node_size=500, cmap='Blues')
In [ ]:
# Author: Aric Hagberg ([email protected])
# Adapted by Philipp Rudiger <[email protected]>

G = nx.star_graph(20)
pos = nx.spring_layout(G)
colors = range(20)
hvnx.draw(G, pos, node_color='#A0CBE2', edge_color=colors,
          edge_width=4, edge_cmap='Blues', with_labels=False)
In [ ]:
# Author: Aric Hagberg ([email protected])
# Adapted by Philipp Rudiger <[email protected]>

G = nx.house_graph()
# explicitly set positions
pos = {0: (0, 0),
       1: (1, 0),
       2: (0, 1),
       3: (1, 1),
       4: (0.5, 2.0)}

hvnx.draw_networkx_nodes(G, pos, node_size=2000, nodelist=[4], padding=0.2) *\
hvnx.draw_networkx_nodes(G, pos, node_size=3000, nodelist=[0, 1, 2, 3], node_color='black') *\
hvnx.draw_networkx_edges(G, pos, alpha=0.5, width=6, xaxis=None, yaxis=None)
In [ ]:
try:
    import pygraphviz  # noqa
    from networkx.drawing.nx_agraph import graphviz_layout
except ImportError:
    try:
        import pydot  # noqa
        from networkx.drawing.nx_pydot import graphviz_layout
    except ImportError:
        raise ImportError("This example needs Graphviz and either "
                          "PyGraphviz or pydot")

G = nx.balanced_tree(3, 5)
pos = graphviz_layout(G, prog='twopi', args='')
hvnx.draw(G, pos, node_size=20, alpha=0.5, node_color="blue", with_labels=False, width=600, height=600)

Spectral Embedding

The spectral layout positions the nodes of the graph based on the eigenvectors of the graph Laplacian L=D−A, where A is the adjacency matrix and D is the degree matrix of the graph. By default, the spectral layout will embed the graph in two dimensions (you can embed your graph in other dimensions using the dim argument to either draw_spectral() or spectral_layout()).

When the edges of the graph represent similarity between the incident nodes, the spectral embedding will place highly similar nodes closer to one another than nodes which are less similar.

This is particularly striking when you spectrally embed a grid graph. In the full grid graph, the nodes in the center of the graph are pulled apart more than nodes on the periphery. As you remove internal nodes, this effect increases.

URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_spectral_grid.html

In [ ]:
options = {
    'node_size': 100,
    'width': 250, 'height': 250
}

G = nx.grid_2d_graph(6, 6)
spectral1 = hvnx.draw_spectral(G, **options)

G.remove_edge((2, 2), (2, 3))
spectral2 = hvnx.draw_spectral(G, **options)

G.remove_edge((3, 2), (3, 3))
spectral3 = hvnx.draw_spectral(G, **options)

G.remove_edge((2, 2), (3, 2))
spectral4 = hvnx.draw_spectral(G, **options)

G.remove_edge((2, 3), (3, 3))
spectral5 = hvnx.draw_spectral(G, **options)

G.remove_edge((1, 2), (1, 3))
spectral6 = hvnx.draw_spectral(G, **options)

G.remove_edge((4, 2), (4, 3))
spectral7 = hvnx.draw_spectral(G, **options)

(hv.Empty() + spectral1 + hv.Empty() +
 spectral2 + spectral3 + spectral4 +
 spectral5 + spectral6 + spectral7).cols(3)
In [ ]:
# Author: Aric Hagberg ([email protected])
# Adapted by Philipp Rudiger <[email protected]>

#    Copyright (C) 2004-2018
#    Aric Hagberg <[email protected]>
#    Dan Schult <[email protected]>
#    Pieter Swart <[email protected]>
#    All rights reserved.
#    BSD license.

G = nx.grid_2d_graph(4, 4)  # 4x4 grid

pos = nx.spring_layout(G, iterations=100)

g1 = hvnx.draw(G, pos, font_size=8)

g2 = hvnx.draw(G, pos, node_color='black', node_size=0, with_labels=False)

g3 = hvnx.draw(G, pos, node_color='green', node_size=250, with_labels=False, edge_width=6)

H = G.to_directed()
g4 = hvnx.draw(H, pos, node_color='blue', node_size=20, with_labels=False)

(g1 + g2 + g3 + g4).cols(2)

Ego Graph

Example using the NetworkX ego_graph() function to return the main egonet of the largest hub in a Barabási-Albert network.

URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_ego_graph.html

In [ ]:
# Author:  Drew Conway ([email protected])
# Adapted by Philipp Rudiger <[email protected]>

from operator import itemgetter

# Create a BA model graph
n = 1000
m = 2
G = nx.generators.barabasi_albert_graph(n, m)
# find node with largest degree
node_and_degree = G.degree()
(largest_hub, degree) = sorted(node_and_degree, key=itemgetter(1))[-1]
# Create ego graph of main hub
hub_ego = nx.ego_graph(G, largest_hub)
# Draw graph
pos = nx.spring_layout(hub_ego)
g = hvnx.draw(hub_ego, pos, node_color='blue', node_size=50, with_labels=False)
# Draw ego as large and red
gnodes = hvnx.draw_networkx_nodes(hub_ego, pos, nodelist=[largest_hub], node_size=300, node_color='red')

g * gnodes
In [ ]:
G = nx.random_geometric_graph(200, 0.125)
# position is stored as node attribute data for random_geometric_graph
pos = nx.get_node_attributes(G, 'pos')

# find node near center (0.5,0.5)
dmin = 1
ncenter = 0
for n in pos:
    x, y = pos[n]
    d = (x - 0.5)**2 + (y - 0.5)**2
    if d < dmin:
        ncenter = n
        dmin = d

# color by path length from node near center
p = nx.single_source_shortest_path_length(G, ncenter)

hvnx.draw_networkx_edges(G, pos, nodelist=[ncenter], alpha=0.4, width=600, height=600) *\
hvnx.draw_networkx_nodes(G, pos, nodelist=list(p.keys()),
                         node_size=80,
                         node_color=list(p.values()),
                         cmap='Reds_r')

Weighted Graph

An example using Graph as a weighted network.

URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_weighted_graph.html

In [ ]:
# Author: Aric Hagberg ([email protected])
# Adapted by Philipp Rudiger <[email protected]>

import networkx as nx

G = nx.Graph()

G.add_edge('a', 'b', weight=0.6)
G.add_edge('a', 'c', weight=0.2)
G.add_edge('c', 'd', weight=0.1)
G.add_edge('c', 'e', weight=0.7)
G.add_edge('c', 'f', weight=0.9)
G.add_edge('a', 'd', weight=0.3)

elarge = [(u, v) for (u, v, attr) in G.edges(data=True) if attr['weight'] > 0.5]
esmall = [(u, v) for (u, v, attr) in G.edges(data=True) if attr['weight'] <= 0.5]

pos = nx.spring_layout(G)  # positions for all nodes

# nodes
nodes = hvnx.draw_networkx_nodes(G, pos, node_size=700)

# edges
edges1 = hvnx.draw_networkx_edges(
    G, pos, edgelist=elarge, edge_width=6)
edges2 = hvnx.draw_networkx_edges(
    G, pos, edgelist=esmall, edge_width=6, alpha=0.5, edge_color='blue', style='dashed')
labels = hvnx.draw_networkx_labels(G, pos, font_size=20, font_family='sans-serif')

edges1 * edges2 * nodes * labels

Directed Graph

Draw a graph with directed edges using a colormap and different node sizes.

Edges have different colors and alphas (opacity). Drawn using matplotlib.

URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_directed.html

In [ ]:
# Author: Rodrigo Dorantes-Gilardi ([email protected])
# Adapted by Philipp Rudiger <[email protected]>

G = nx.generators.directed.random_k_out_graph(10, 3, 0.5)
pos = nx.layout.spring_layout(G)

node_sizes = [3 + 10 * i for i in range(len(G))]
M = G.number_of_edges()
edge_colors = range(2, M + 2)
edge_alphas = [(5 + i) / (M + 4) for i in range(M)]

nodes = hvnx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color='blue')
edges = hvnx.draw_networkx_edges(G, pos, node_size=node_sizes, arrowstyle='->',
                               arrowsize=10, edge_color=edge_colors,
                               edge_cmap='Blues', edge_width=2, colorbar=True)

nodes * edges

Giant Component

This example illustrates the sudden appearance of a giant connected component in a binomial random graph.

https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_giant_component.html

In [ ]:
#    Copyright (C) 2006-2018
#    Aric Hagberg <[email protected]>
#    Dan Schult <[email protected]>
#    Pieter Swart <[email protected]>
#    All rights reserved.
#    BSD license.

# Adapted by Philipp Rudiger <[email protected]>

import math

try:
    import pygraphviz  # noqa
    from networkx.drawing.nx_agraph import graphviz_layout
    layout = graphviz_layout
except ImportError:
    try:
        import pydot  # noqa
        from networkx.drawing.nx_pydot import graphviz_layout
        layout = graphviz_layout
    except ImportError:
        print("PyGraphviz and pydot not found;\n"
              "drawing with spring layout;\n"
              "will be slow.")
        layout = nx.spring_layout

n = 150  # 150 nodes
# p value at which giant component (of size log(n) nodes) is expected
p_giant = 1.0 / (n - 1)
# p value at which graph is expected to become completely connected
p_conn = math.log(n) / float(n)

# the following range of p values should be close to the threshold
pvals = [0.003, 0.006, 0.008, 0.015]

region = 220  # for pylab 2x2 subplot layout
plots = []
for p in pvals:
    G = nx.binomial_graph(n, p)
    pos = layout(G)
    region += 1
    g = hvnx.draw(G, pos, with_labels=False, node_size=15)
    # identify largest connected component
    Gcc = sorted([G.subgraph(c) for c in nx.connected_components(G)], key=len, reverse=True)
    G0 = Gcc[0]
    edges = hvnx.draw_networkx_edges(
        G0, pos, with_labels=False, edge_color='red', edge_width=6.0)
    
    # show other connected components
    other_edges = []
    for Gi in Gcc[1:]:
        if len(Gi) > 1:
            edge = hvnx.draw_networkx_edges(Gi, pos,
                                   with_labels=False,
                                   edge_color='red',
                                   alpha=0.3,
                                   edge_width=5.0
                                  )
            other_edges.append(edge)
    plots.append((g*edges*hv.Overlay(other_edges)).relabel("p = %6.3f" % (p)))

hv.Layout(plots).cols(2)