简体   繁体   中英

networkx: drawing in Layered manner

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      

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()

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM