NetworkX#
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
:
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:
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
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:
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.
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
layout
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.
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
:
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:
print(hvnx.draw.__doc__)
Draw the graph G using hvPlot.
Draw the graph with hvPlot with options for node positions,
labeling, titles, and many other drawing features.
Parameters
----------
G : graph
A networkx graph
pos : dictionary, optional
A dictionary with nodes as keys and positions as values.
If not specified a spring layout positioning will be computed.
See :py:mod:`networkx.drawing.layout` for functions that
compute node positions.
arrows : bool, optional (default=True)
For directed graphs, if True draw arrowheads.
Note: Arrows will be the same color as edges.
arrowhead_length : float, optional (default=0.025)
The length of the arrows as fraction of the overall extent of
the graph
with_labels : bool, optional (default=True)
Set to True to draw labels on the nodes.
nodelist : list, optional (default G.nodes())
Draw only specified nodes
edgelist : list, optional (default=G.edges())
Draw only specified edges
node_size : scalar or array, optional (default=300)
Size of nodes. If an array is specified it must be the
same length as nodelist.
node_color : color string, node attribute, or array of floats, (default='r')
Can be a single color, the name of an attribute on the nodes or
sequence of colors with the same length as nodelist. If the
node_color references an attribute on the nodes or is a list of
values they will be colormapped using the cmap and vmin, vmax
parameters.
node_shape : string, optional (default='o')
The shape of the node. Specification is as valid bokeh marker.
alpha : float, optional (default=1.0)
The node and edge transparency
cmap : Colormap, optional (default=None)
Colormap for mapping intensities of nodes
vmin,vmax : float, optional (default=None)
Minimum and maximum for node colormap scaling
linewidths : [None | scalar | sequence]
Line width of symbol border (default =1.0)
edge_width : float, optional (default=1.0)
Line width of edges
edge_color : color string, or array of floats (default='r')
Can be a single color, the name of an attribute on the edges or
sequence of colors with the same length as the edges. If the
edge_color references an attribute on the edges or is a list of
values they will be colormapped using the edge_cmap and
edge_vmin, edge_vmax parameters.
edge_cmap : Matplotlib colormap, optional (default=None)
Colormap for mapping intensities of edges
edge_vmin,edge_vmax : floats, optional (default=None)
Minimum and maximum for edge colormap scaling
style : string, optional (default='solid')
Edge line style (solid|dashed|dotted,dashdot)
labels : dictionary or string, optional (default=None)
Node labels in a dictionary keyed by node of text labels or
a string referencing a node attribute
font_size : int, optional (default=12)
Font size for text labels
font_color : string, optional (default='black')
Font color string
font_family : string, optional (default='sans-serif')
Font family
label : string, optional
Label for graph legend
selection_policy : string, optional (default='nodes')
Whether to select 'nodes', 'edges' or None on tap and selection
events.
inspection_policy : string, optional (default='nodes')
Whether to select 'nodes', 'edges' or None on tap and selection
events.
geo : boolean, optional (default=False)
Whether to return a GeoViews graph
crs : cartopy.crs.CRS
A cartopy coordinate reference system (enables a geographic plot)
height : int, optional (default=400)
The height of the plot in pixels
width : int, optional (default=400)
The width of the plot in pixels
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
# Copyright (C) 2004-2018 by
# Aric Hagberg <hagberg@lanl.gov>
# Dan Schult <dschult@colgate.edu>
# Pieter Swart <swart@lanl.gov>
# All rights reserved.
# BSD license.
# Adapted by Philipp Rudiger <prudiger@anaconda.com>
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(f'{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(f"eccentricity: {nx.eccentricity(G)}")
print(f"center: {nx.center(G)}")
print(f"periphery: {nx.periphery(G)}")
print(f"density: {nx.density(G)}")
hvnx.draw(G, with_labels=True)
source vertex {target:length, }
0 {0: 0, 1: 1, 2: 1, 3: 1, 4: 2, 5: 3, 6: 4, 7: 5, 8: 6, 9: 7}
1 {1: 0, 0: 1, 2: 1, 3: 1, 4: 2, 5: 3, 6: 4, 7: 5, 8: 6, 9: 7}
2 {2: 0, 0: 1, 1: 1, 3: 1, 4: 2, 5: 3, 6: 4, 7: 5, 8: 6, 9: 7}
3 {3: 0, 0: 1, 1: 1, 2: 1, 4: 1, 5: 2, 6: 3, 7: 4, 8: 5, 9: 6}
4 {4: 0, 5: 1, 3: 1, 6: 2, 0: 2, 1: 2, 2: 2, 7: 3, 8: 4, 9: 5}
5 {5: 0, 4: 1, 6: 1, 3: 2, 7: 2, 0: 3, 1: 3, 2: 3, 8: 3, 9: 4}
6 {6: 0, 5: 1, 7: 1, 4: 2, 8: 2, 3: 3, 9: 3, 0: 4, 1: 4, 2: 4}
7 {7: 0, 6: 1, 8: 1, 5: 2, 9: 2, 4: 3, 3: 4, 0: 5, 1: 5, 2: 5}
8 {8: 0, 7: 1, 9: 1, 6: 2, 5: 3, 4: 4, 3: 5, 0: 6, 1: 6, 2: 6}
9 {9: 0, 8: 1, 7: 2, 6: 3, 5: 4, 4: 5, 3: 6, 0: 7, 1: 7, 2: 7}
average shortest path length 2.86
length #paths
0 10
1 24
2 16
3 14
4 12
5 10
6 8
7 6
radius: 4
diameter: 7
eccentricity: {0: 7, 1: 7, 2: 7, 3: 6, 4: 5, 5: 4, 6: 4, 7: 5, 8: 6, 9: 7}
center: [5, 6]
periphery: [0, 1, 2, 9]
density: 0.26666666666666666
Simple Path#
Draw a graph with hvPlot.
URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_simple_path.html
G = nx.path_graph(8)
hvnx.draw(G)
Node colormap#
Draw a graph with hvPlot, color by degree.
URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_node_colormap.html
# Author: Aric Hagberg (hagberg@lanl.gov)
# Adapted by Philipp Rudiger <prudiger@anaconda.com>
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')
Edge Colormap#
Draw a graph with hvPlot, color edges.
URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_edge_colormap.html
# Author: Aric Hagberg (hagberg@lanl.gov)
# Adapted by Philipp Rudiger <prudiger@anaconda.com>
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)
House With Colors#
Draw a graph with hvPlot.
URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_house_with_colors.html
# Author: Aric Hagberg (hagberg@lanl.gov)
# Adapted by Philipp Rudiger <prudiger@anaconda.com>
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)
Circular Tree#
URL: https://networkx.org/documentation/stable/auto_examples/graphviz_layout/plot_circular_tree.html
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
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)
Plot four grids#
Draw a graph with hvPlot.
URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_four_grids.html
# Author: Aric Hagberg (hagberg@lanl.gov)
# Adapted by Philipp Rudiger <prudiger@anaconda.com>
# Copyright (C) 2004-2018
# Aric Hagberg <hagberg@lanl.gov>
# Dan Schult <dschult@colgate.edu>
# Pieter Swart <swart@lanl.gov>
# 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
# Author: Drew Conway (drew.conway@nyu.edu)
# Adapted by Philipp Rudiger <prudiger@anaconda.com>
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
Random Geometric Graph#
URL: https://networkx.github.io/documentation/stable/auto_examples/drawing/plot_random_geometric_graph.html
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
# Author: Aric Hagberg (hagberg@lanl.gov)
# Adapted by Philipp Rudiger <prudiger@anaconda.com>
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
# Author: Rodrigo Dorantes-Gilardi (rodgdor@gmail.com)
# Adapted by Philipp Rudiger <prudiger@anaconda.com>
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.org/documentation/stable/auto_examples/graphviz_layout/plot_giant_component.html
# Copyright (C) 2006-2018
# Aric Hagberg <hagberg@lanl.gov>
# Dan Schult <dschult@colgate.edu>
# Pieter Swart <swart@lanl.gov>
# All rights reserved.
# BSD license.
# Adapted by Philipp Rudiger <prudiger@anaconda.com>
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(f"p = {p:6.3f}"))
hv.Layout(plots).cols(2)