如何使用 matplotlib 和 networkx 开发矢量化可导航图

How to develop a vectorized navigable graph using matplotlib and networkx

我想生成一个很长的数字层次结构图表,但生成的图表是像素化的,无法缩放以显示其详细信息。我正在使用 networkx 和 matplotlib 生成此图表。 这是我要生成层次结构的数组。

tree = [(1, 2), (2, 4), (4, 8), (8, 16), (16, 5), (5, 10), (10, 3), (3, 6), (10, 20), (20, 40), (40, 13), (13, 26), (26, 52), (52, 17), (17, 34), (34, 11), (11, 22), (22, 7), (7, 14), (14, 28), (28, 9), (6, 12), (40, 80), (80, 160), (160, 53), (53, 106), (106, 35), (35, 70), (70, 23), (23, 46), (46, 15), (9, 18), (22, 44), (44, 88), (88, 29), (29, 58), (58, 19), (16, 32), (32, 64), (64, 21), (12, 24), (19, 38), (38, 76), (76, 25), (46, 92), (92, 184), (184, 61), (61, 122), (122, 244), (244, 488), (488, 976), (976, 325), (325, 650), (650, 1300), (1300, 433), (433, 866), (866, 1732), (1732, 577), (577, 1154), (1154, 2308), (2308, 4616), (4616, 9232), (9232, 3077), (3077, 6154), (6154, 2051), (2051, 4102), (4102, 1367), (1367, 2734), (2734, 911), (911, 1822), (1822, 3644), (3644, 7288), (7288, 2429), (2429, 4858), (4858, 1619), (1619, 3238), (3238, 1079), (1079, 2158), (2158, 719), (719, 1438), (1438, 479), (479, 958), (958, 319), (319, 638), (638, 1276), (1276, 425), (425, 850), (850, 283), (283, 566), (566, 1132), (1132, 377), (377, 754), (754, 251), (251, 502), (502, 167), (167, 334), (334, 668), (668, 1336), (1336, 445), (445, 890), (890, 1780), (1780, 593), (593, 1186), (1186, 395), (395, 790), (790, 263), (263, 526), (526, 175), (175, 350), (350, 700), (700, 233), (233, 466), (466, 155), (155, 310), (310, 103), (103, 206), (206, 412), (412, 137), (137, 274), (274, 91), (91, 182), (182, 364), (364, 121), (121, 242), (242, 484), (484, 161), (161, 322), (322, 107), (107, 214), (214, 71), (71, 142), (142, 47), (47, 94), (94, 31), (31, 62), (62, 124), (124, 41), (41, 82), (82, 27), (15, 30), (25, 50), (50, 100), (100, 33)]

图生成如下:

G=nx.Graph()
G.add_edges_from(tree)
pos = tg.hierarchy_pos(G,1)    
nx.draw(G, pos=pos, with_labels=True)

生成的层次结构如下所示:enter image description here 如图所示,该图是像素化图像,无法缩放以显示细节。如何生成此图的矢量化版本。

hierarchy_pos函数取自

实际上有两个不同的问题:

  1. nodes/labels 的位置重叠,因为图在默认 node/text 尺寸下不够大。
  2. 图像需要以矢量化格式保存。

下面的代码片段调整了绘图大小并保存了矢量化图像,但是包含 hierarchy_pos 定义的完整代码被复制到 post 的底部,用于 post真实。

import matplotlib.pyplot as plt

fig = plt.figure(figsize=(20, 50)) # increase figsize
nx.draw(G, pos=pos, with_labels=True)
fig.savefig('filename.eps', format='eps') #save vectorized image

这里是 .jpg 只是为了显示布局差异。 Imgur(Whosebug 的图像托管提供商)不支持矢量化图像,所以我无法在此处上传。


完整的可复制粘贴代码:

import networkx as nx
import random
import matplotlib.pyplot as plt

    
def hierarchy_pos(G, root=None, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5):

    '''
    From Joel's answer at   
    Licensed under Creative Commons Attribution-Share Alike 
    
    If the graph is a tree this will return the positions to plot this in a 
    hierarchical layout.
    
    G: the graph (must be a tree)
    
    root: the root node of current branch 
    - if the tree is directed and this is not given, 
      the root will be found and used
    - if the tree is directed and this is given, then 
      the positions will be just for the descendants of this node.
    - if the tree is undirected and not given, 
      then a random choice will be used.
    
    width: horizontal space allocated for this branch - avoids overlap with other branches
    
    vert_gap: gap between levels of hierarchy
    
    vert_loc: vertical location of root
    
    xcenter: horizontal location of root
    '''
    if not nx.is_tree(G):
        raise TypeError('cannot use hierarchy_pos on a graph that is not a tree')

    if root is None:
        if isinstance(G, nx.DiGraph):
            root = next(iter(nx.topological_sort(G)))  #allows back compatibility with nx version 1.11
        else:
            root = random.choice(list(G.nodes))

    def _hierarchy_pos(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5, pos = None, parent = None):
        '''
        see hierarchy_pos docstring for most arguments

        pos: a dict saying where all nodes go if they have been assigned
        parent: parent of this branch. - only affects it if non-directed

        '''
    
        if pos is None:
            pos = {root:(xcenter,vert_loc)}
        else:
            pos[root] = (xcenter, vert_loc)
        children = list(G.neighbors(root))
        if not isinstance(G, nx.DiGraph) and parent is not None:
            children.remove(parent)  
        if len(children)!=0:
            dx = width/len(children) 
            nextx = xcenter - width/2 - dx/2
            for child in children:
                nextx += dx
                pos = _hierarchy_pos(G,child, width = dx, vert_gap = vert_gap, 
                                    vert_loc = vert_loc-vert_gap, xcenter=nextx,
                                    pos=pos, parent = root)
        return pos

            
    return _hierarchy_pos(G, root, width, vert_gap, vert_loc, xcenter)
            

tree = [(1, 2), (2, 4), (4, 8), (8, 16), (16, 5), (5, 10), (10, 3), (3, 6), (10, 20), (20, 40), (40, 13), (13, 26), (26, 52), (52, 17), (17, 34), (34, 11), (11, 22), (22, 7), (7, 14), (14, 28), (28, 9), (6, 12), (40, 80), (80, 160), (160, 53), (53, 106), (106, 35), (35, 70), (70, 23), (23, 46), (46, 15), (9, 18), (22, 44), (44, 88), (88, 29), (29, 58), (58, 19), (16, 32), (32, 64), (64, 21), (12, 24), (19, 38), (38, 76), (76, 25), (46, 92), (92, 184), (184, 61), (61, 122), (122, 244), (244, 488), (488, 976), (976, 325), (325, 650), (650, 1300), (1300, 433), (433, 866), (866, 1732), (1732, 577), (577, 1154), (1154, 2308), (2308, 4616), (4616, 9232), (9232, 3077), (3077, 6154), (6154, 2051), (2051, 4102), (4102, 1367), (1367, 2734), (2734, 911), (911, 1822), (1822, 3644), (3644, 7288), (7288, 2429), (2429, 4858), (4858, 1619), (1619, 3238), (3238, 1079), (1079, 2158), (2158, 719), (719, 1438), (1438, 479), (479, 958), (958, 319), (319, 638), (638, 1276), (1276, 425), (425, 850), (850, 283), (283, 566), (566, 1132), (1132, 377), (377, 754), (754, 251), (251, 502), (502, 167), (167, 334), (334, 668), (668, 1336), (1336, 445), (445, 890), (890, 1780), (1780, 593), (593, 1186), (1186, 395), (395, 790), (790, 263), (263, 526), (526, 175), (175, 350), (350, 700), (700, 233), (233, 466), (466, 155), (155, 310), (310, 103), (103, 206), (206, 412), (412, 137), (137, 274), (274, 91), (91, 182), (182, 364), (364, 121), (121, 242), (242, 484), (484, 161), (161, 322), (322, 107), (107, 214), (214, 71), (71, 142), (142, 47), (47, 94), (94, 31), (31, 62), (62, 124), (124, 41), (41, 82), (82, 27), (15, 30), (25, 50), (50, 100), (100, 33)]

G=nx.Graph()
G.add_edges_from(tree)
pos = hierarchy_pos(G,1)

fig = plt.figure(figsize=(20, 50)) # increase figsize
nx.draw(G, pos=pos, with_labels=True)

fig.savefig('filename.eps', format='eps') #save vectorized image