Reputation: 7476
I want to draw a layered graph If you can look at the graph below, it can be ordered the following way
95 85
21 31
42 52 62 22 32
13
14
I can provide hints which node in which layer goes if needed.
In the past I had similar need, but it does not work in this case : NetworkX: draw graph in layers
I tried all standard layouts and all graph_viz layouts : ‘dot’, ‘twopi’, ‘fdp’, ‘sfdp’, ‘circo’
By layered I mean where every group of nodes are on the same layer (vertical coordinates : Y)
Like I've ordered them above ...
this my first crack on it ...now i have to do the x-axis more clearly based on edges. thanks mathfux
def layered_layout(nodes, base):
nodes = np.array(nodes)
ncols = np.unique( nodes % base, return_counts=True)[1].max()
nlayers = np.unique(nodes % base).size
ys = np.linspace(0, 1, nlayers)
xs = np.linspace(0, 1, ncols)
pos = {}
for j,b in enumerate(np.unique(nodes % base)):
ixs = np.where(nodes % base == b)[0]
for i,x in enumerate(ixs) :
node = nodes[x]
pos[node] = (xs[i], ys[j])
return pos
Upvotes: 1
Views: 935
Reputation: 5949
In case you can't find any default layout, you can define your custom layout. It seems not that complicated for layered manner.
def layered_layout(layers, stretch=0, alignment='c'):
sizes = np.array([len(n) for n in layers])
lfill = [np.linspace(0, 1, n) for n in sizes]
scales = (sizes - 1)/(max(sizes) - 1)
if alignment == 'l':
x_coord_levels = [(x - x[0]) * (s + (1 - s) * stretch) for x, s in zip(lfill, scales)]
elif alignment == 'r':
x_coord_levels = [(x - x[-1]) * (s + (1 - s) * stretch) for x, s in zip(lfill, scales)]
elif alignment == 'c':
x_coord_levels = [(x - (x[0]+x[-1])/2) * (s + (1 - s) * stretch) for x, s in zip(lfill, scales)]
else:
raise AttributeError('invalid alignment attribute')
y_coord_levels = [np.repeat(-y, times) for y, times in zip(np.arange(len(sizes)), sizes)]
pos_levels = [dict(zip(l, zip(*p))) for l,p in zip(layers, zip(x_coord_levels, y_coord_levels))]
pos = {k: v for d in pos_levels for k, v in d.items()}
return pos
After you create a graph :
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
G = nx.DiGraph()
G.add_edges_from([(21, 42), (52, 13), (42, 13), (95, 21), (31, 52), (31, 62), (95, 31), (62, 13), (32, 95), (13, 22), (85, 31), (22, 14), (14, 32)])
nx.set_edge_attributes(G, {(21, 42): 0.20, (52, 13): 0.52, (42, 13): 0.49, (95, 21): 0.15, (31, 52): 0.52, (31, 62): 0.42, (95, 31): 0.47, (62, 13): 0.42, (32, 95): 0.42, (13, 22): 0.71, (85, 31): -0.00, (22, 14): 0.74, (14, 32): 0.74}, 'w')
layered_layout
should be used to define values of pos
parameter:
layers = [[95, 85], [21, 31, 42], [52, 62, 22, 32], [13], [14]]
pos = layered_layout(layers, stretch=..., alignment=...)
Example of usage:
layers = [[95, 85], [21, 31, 42], [52, 62, 22, 32], [13], [14]]
fig = plt.figure(figsize=(20,10))
for i in range(3):
for j in range(3):
ax = fig.add_subplot(3, 3, 1+3*j+i)
ax.set_title(f'stretch = {[0, 0.5, 1][j]}, alignment={"lcr"[i]}')
pos = layered_layout(layers, stretch = [0, 0.5, 1][j], alignment='lcr'[i])
nx.draw_networkx(G, pos, with_labels=True, font_size=7, node_size=100)
nx.draw_networkx_edge_labels(G, pos, edge_labels = nx.get_edge_attributes(G,'w'), font_size=7)
plt.axis('on'); plt.tick_params(left=True, bottom=True, labelleft=True, labelbottom=True)
plt.show()
Upvotes: 3