I want to create a multi-layered graph (like in the attached image), by connecting the two graphs written with the following code, using networkx
#Graph1
g1 = nx.read_edgelist('sample.txt', nodetype=str)
pos = nx.shell_layout(g)
plt.figure(figsize=(10, 10))
nx.draw_networkx_edges(g, pos, edge_color='khaki', alpha=1)
nx.draw_networkx_nodes(g,pos,node_color='r',alpha=0.5,node_size=1000)
nx.draw_networkx_labels(g, pos, font_size=10,font_family='IPAexGothic')
plt.axis('off')
#Graph2
g2 = nx.read_edgelist('sample2.txt', nodetype=str)
pos = nx.shell_layout(g)
plt.figure(figsize=(10, 10))
nx.draw_networkx_edges(g, pos, edge_color='khaki', alpha=1)
nx.draw_networkx_nodes(g,pos,node_color='r',alpha=0.5,node_size=1000)
nx.draw_networkx_labels(g, pos, font_size=10,font_family='IPAexGothic')
plt.axis('off')
enter image description here
enter image description here
For NetworkX, a graph with more than 100K nodes may be too large. I'll demonstrate that it can handle a network with 187K nodes in this post, but the centrality calculations were prolonged. Luckily, there are some other packages available to help us with even larger graphs.
An nbunch is a single node, container of nodes or None (representing all nodes). It can be a list, set, graph, etc.. To filter an nbunch so that only nodes actually in G appear, use G. nbunch_iter(nbunch) .
Multigraphs. NetworkX provides classes for graphs which allow multiple edges between any pair of nodes. The MultiGraph and MultiDiGraph classes allow you to add the same edge twice, possibly with different edge data. This can be powerful for some applications, but many algorithms are not well defined on such graphs.
RAPIDS's graph algorithms like PageRank and functions like NetworkX make efficient use of the massive parallelism of GPUs to accelerate analysis of large graphs by over 1000X.
There is no functionality within networkx
that currently supports a layered layout, much less a visualization as shown. So we need to roll our own.
The following implementation LayeredNetworkGraph
assumes that you have a list of graphs [g1, g2, ..., gn]
that represent the different layers. Within a layer, the corresponding (sub-) graph defines the connectivity. Between layers, nodes in subsequent layers are connected if they have the same node ID.
As there are no layout functions (AFAIK) that would compute node positions in three dimensions with the planarity constraint imposed on nodes within a layer, we use a small hack: we create a graph composition across all layers, compute the positions in two dimensions, and then apply these positions to nodes in all layers. One could compute a true force directed layout with the planarity constraints, but that would be a lot of work and since your example only used a shell layout (which would be unaffected), I haven't bothered. The differences would be small in many cases.
If you want to change aspects of the visualisation (sizes, widths, colours), have a look at the draw
method. Most changes that you might require can probably be made there.
#!/usr/bin/env python
"""
Plot multi-graphs in 3D.
"""
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Line3DCollection
class LayeredNetworkGraph(object):
def __init__(self, graphs, node_labels=None, layout=nx.spring_layout, ax=None):
"""Given an ordered list of graphs [g1, g2, ..., gn] that represent
different layers in a multi-layer network, plot the network in
3D with the different layers separated along the z-axis.
Within a layer, the corresponding graph defines the connectivity.
Between layers, nodes in subsequent layers are connected if
they have the same node ID.
Arguments:
----------
graphs : list of networkx.Graph objects
List of graphs, one for each layer.
node_labels : dict node ID : str label or None (default None)
Dictionary mapping nodes to labels.
If None is provided, nodes are not labelled.
layout_func : function handle (default networkx.spring_layout)
Function used to compute the layout.
ax : mpl_toolkits.mplot3d.Axes3d instance or None (default None)
The axis to plot to. If None is given, a new figure and a new axis are created.
"""
# book-keeping
self.graphs = graphs
self.total_layers = len(graphs)
self.node_labels = node_labels
self.layout = layout
if ax:
self.ax = ax
else:
fig = plt.figure()
self.ax = fig.add_subplot(111, projection='3d')
# create internal representation of nodes and edges
self.get_nodes()
self.get_edges_within_layers()
self.get_edges_between_layers()
# compute layout and plot
self.get_node_positions()
self.draw()
def get_nodes(self):
"""Construct an internal representation of nodes with the format (node ID, layer)."""
self.nodes = []
for z, g in enumerate(self.graphs):
self.nodes.extend([(node, z) for node in g.nodes()])
def get_edges_within_layers(self):
"""Remap edges in the individual layers to the internal representations of the node IDs."""
self.edges_within_layers = []
for z, g in enumerate(self.graphs):
self.edges_within_layers.extend([((source, z), (target, z)) for source, target in g.edges()])
def get_edges_between_layers(self):
"""Determine edges between layers. Nodes in subsequent layers are
thought to be connected if they have the same ID."""
self.edges_between_layers = []
for z1, g in enumerate(self.graphs[:-1]):
z2 = z1 + 1
h = self.graphs[z2]
shared_nodes = set(g.nodes()) & set(h.nodes())
self.edges_between_layers.extend([((node, z1), (node, z2)) for node in shared_nodes])
def get_node_positions(self, *args, **kwargs):
"""Get the node positions in the layered layout."""
# What we would like to do, is apply the layout function to a combined, layered network.
# However, networkx layout functions are not implemented for the multi-dimensional case.
# Futhermore, even if there was such a layout function, there probably would be no straightforward way to
# specify the planarity requirement for nodes within a layer.
# Therefor, we compute the layout for the full network in 2D, and then apply the
# positions to the nodes in all planes.
# For a force-directed layout, this will approximately do the right thing.
# TODO: implement FR in 3D with layer constraints.
composition = self.graphs[0]
for h in self.graphs[1:]:
composition = nx.compose(composition, h)
pos = self.layout(composition, *args, **kwargs)
self.node_positions = dict()
for z, g in enumerate(self.graphs):
self.node_positions.update({(node, z) : (*pos[node], z) for node in g.nodes()})
def draw_nodes(self, nodes, *args, **kwargs):
x, y, z = zip(*[self.node_positions[node] for node in nodes])
self.ax.scatter(x, y, z, *args, **kwargs)
def draw_edges(self, edges, *args, **kwargs):
segments = [(self.node_positions[source], self.node_positions[target]) for source, target in edges]
line_collection = Line3DCollection(segments, *args, **kwargs)
self.ax.add_collection3d(line_collection)
def get_extent(self, pad=0.1):
xyz = np.array(list(self.node_positions.values()))
xmin, ymin, _ = np.min(xyz, axis=0)
xmax, ymax, _ = np.max(xyz, axis=0)
dx = xmax - xmin
dy = ymax - ymin
return (xmin - pad * dx, xmax + pad * dx), \
(ymin - pad * dy, ymax + pad * dy)
def draw_plane(self, z, *args, **kwargs):
(xmin, xmax), (ymin, ymax) = self.get_extent(pad=0.1)
u = np.linspace(xmin, xmax, 10)
v = np.linspace(ymin, ymax, 10)
U, V = np.meshgrid(u ,v)
W = z * np.ones_like(U)
self.ax.plot_surface(U, V, W, *args, **kwargs)
def draw_node_labels(self, node_labels, *args, **kwargs):
for node, z in self.nodes:
if node in node_labels:
ax.text(*self.node_positions[(node, z)], node_labels[node], *args, **kwargs)
def draw(self):
self.draw_edges(self.edges_within_layers, color='k', alpha=0.3, linestyle='-', zorder=2)
self.draw_edges(self.edges_between_layers, color='k', alpha=0.3, linestyle='--', zorder=2)
for z in range(self.total_layers):
self.draw_plane(z, alpha=0.2, zorder=1)
self.draw_nodes([node for node in self.nodes if node[1]==z], s=300, zorder=3)
if self.node_labels:
self.draw_node_labels(self.node_labels,
horizontalalignment='center',
verticalalignment='center',
zorder=100)
if __name__ == '__main__':
# define graphs
n = 5
g = nx.erdos_renyi_graph(4*n, p=0.1)
h = nx.erdos_renyi_graph(3*n, p=0.2)
i = nx.erdos_renyi_graph(2*n, p=0.4)
node_labels = {nn : str(nn) for nn in range(4*n)}
# initialise figure and plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
LayeredNetworkGraph([g, h, i], node_labels=node_labels, ax=ax, layout=nx.spring_layout)
ax.set_axis_off()
plt.show()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With