分层层次布局#

注意:我们强烈建议考虑其他布局,如环形布局(pygraphistry)和igraph插件的dot引擎

[ ]:
from graphistry.layout.sugiyama import SugiyamaLayout
from graphistry.layout.graph import Graph, Vertex, Edge
import pandas as pd
import networkx as nx
import matplotlib
import matplotlib.pyplot as plt

[ ]:
def from_networkx(nxg):
    """
        Converts a networkx graph to a sugiyama graph.
    """
    vertices = []
    data_to_v = {}
    for x in nxg.nodes():
        vertex = Vertex(x)
        vertices.append(vertex)
        data_to_v[x] = vertex
    E = [Edge(data_to_v[xy[0]], data_to_v[xy[1]], data = xy) for xy in nxg.edges()]
    g = Graph(vertices, E)
    return g


def to_networkx(g):
    """
        Converts a sugiyama graph to a networkx graph.
    """
    from networkx import MultiDiGraph

    nxg = MultiDiGraph()
    for v in g.vertices():
        nxg.add_node(v.data)
    for e in g.edges():
        # todo: this leads to issues when the data is more than an id
        nxg.add_edge(e.v[0].data, e.v[1].data)
    return nxg


def draw_graph(g, layout_direction = 0, source_column = "source", target_column = "target", root=None):
    """
    Renders the given graph after applying the layered layout.
    :param g: Graphistry Graph or NetworkX Graph.
    """
    if isinstance(g, nx.Graph):
        gg = from_networkx(g)
        nxg = g
    elif isinstance(g, Graph):
        gg = g
        nxg = to_networkx(g)
    elif isinstance(g, pd.DataFrame):
        gg = SugiyamaLayout.graph_from_pandas(g, source_column = source_column, target_column = target_column)
        nxg = to_networkx(gg)
    else:
        raise ValueError
    # apply layout
    positions = SugiyamaLayout.arrange(gg, layout_direction = layout_direction, root=root)
    nx.draw(nxg, pos = positions, with_labels = True, verticalalignment = 'bottom', arrowsize = 3, horizontalalignment = "left", font_size = 20)
    plt.show()


def scatter_graph(g, root=None):
    """
    Renders the given graph as a scatter plot after applying the layered layout.
    :param g: Graphistry Graph or NetworkX Graph.
    """
    if isinstance(g, nx.Graph):
        gg = from_networkx(g)
        nxg = g
    elif isinstance(g, Graph):
        gg = g
        nxg = to_networkx(g)
    # apply layout
    coords = list(SugiyamaLayout.arrange(gg, root=root).values())
    x = [c[0] for c in coords]
    y = [c[1] for c in coords]
    fig, ax = plt.subplots()
    ax.scatter(x, y)

    for i, v in enumerate(gg.vertices()):
        ax.annotate(v.data, (x[i], y[i]))

    plt.axis('off')
    plt.show()


def arrange(g, layout_direction = 0, source_column = "source", target_column = "target", topological_coordinates = False):
    """
    Returns the positions of the given graph after applying the layered layout.
    :param g: Graphistry Graph, Pandas frame or NetworkX Graph.
    """
    if isinstance(g, nx.Graph):
        gg = from_networkx(g)
        nxg = g
    elif isinstance(g, Graph):
        gg = g
        nxg = to_networkx(g)
    elif isinstance(g, pd.DataFrame):
        gg = SugiyamaLayout.graph_from_pandas(g, source_column = source_column, target_column = target_column)
        nxg = to_networkx(gg)
    else:
        raise ValueError
    # apply layout
    positions = SugiyamaLayout.arrange(gg, layout_direction = layout_direction, topological_coordinates = topological_coordinates)
    return positions

显式简单图#

Graph 对象可用于创建显式图:

[ ]:
matplotlib.rc('figure', figsize = [8, 5])
g = Graph()

bosons = Vertex("Boson")
higgs = Vertex("Higgs")
pions = Vertex("Pions")
kaons = Vertex("Kaons")
hadrons = Vertex("Hadrons")

e1 = Edge(bosons, higgs)
e2 = Edge(bosons, kaons)
e3 = Edge(bosons, pions)
e4 = Edge(pions, hadrons)
e5 = Edge(kaons, hadrons)

g.add_edges([e1, e2, e3, e4, e5])
scatter_graph(g)

Pandas 图表#

[ ]:
g = nx.generators.balanced_tree(2, 3)
df = nx.to_pandas_edgelist(g, "source", "target")
df.head()
[ ]:
matplotlib.rc('figure', figsize = [5, 5])
draw_graph(df, 3)

你可以像这样设置根目录

[ ]:
matplotlib.rc('figure', figsize = [5, 5])
draw_graph(df, 3, root=[3,12])

#

一棵真正的树将按预期排列:

[ ]:
matplotlib.rc('figure', figsize = [120, 30])
g = nx.generators.balanced_tree(5, 3)
draw_graph(g, 2)

现实世界的图#

Barabasi-Albert graphs 代表模仿生物和其他现实世界网络的无标度网络:

[ ]:
matplotlib.rc('figure', figsize = [120, 90])
g = nx.generators.barabasi_albert_graph(500, 3)
draw_graph(g)

布局方向#

  • 0: 从上到下

  • 1: 从右到左

  • 2: 从下到上

  • 3: 从左到右

[ ]:
matplotlib.rc('figure', figsize = [10, 5])
g = nx.generators.balanced_tree(3, 2)
draw_graph(g, layout_direction = 2)

拓扑坐标#

除了绝对坐标外,您还可以请求拓扑坐标,这些坐标对应于垂直轴的层索引和水平轴的单位区间内的值。将这些值与实际的总宽度和高度相乘,可以得到给定(宽度,高度)矩形内的坐标。请注意,分层是根据标准坐标系进行的,即向上和向右。当将布局方向设置为水平(layout_direction等于1或3)时,第一个坐标是层索引,第二个坐标将在单位区间内。

[ ]:
g = nx.from_edgelist([(1,2),(1,3),(3,4)])
positions = arrange(g, topological_coordinates=True, layout_direction=0)
print(positions)

拓扑坐标也可以直接与NetworX一起使用:

[ ]:
nx.draw(g, pos = positions, with_labels = True, verticalalignment = 'bottom', arrowsize = 3, horizontalalignment = "left", font_size = 20)

完全图#

分层布局试图最小化交叉,但在像完全图这样的极端例子中,无论算法尝试调整多少次,交叉仍然存在。

[ ]:
matplotlib.rc('figure', figsize = [120, 90])
g = nx.generators.complete_graph(10)
draw_graph(g,root=4)

仅限职位#

你可以通过arrange方法获取节点的位置而不进行可视化。你也可以只绘制点而不绘制边,如下所示:

[ ]:
matplotlib.rc('figure', figsize = [20, 30])
g = nx.generators.random_lobster(100, 0.3, 0.3)
scatter_graph(g)

瓦茨-斯托加茨#

Watts-Strogatz 模型是另一种展示所谓小世界现象的现实世界图模型:

[ ]:
matplotlib.rc('figure', figsize = [120, 70])
g = nx.generators.connected_watts_strogatz_graph(1000, 2, 0.3)
draw_graph(g)
[ ]: