使用 Networkx 高效地在图形的 matplotlib 上制作动画

Making animation on matplotlib of graph with Networkx efficiently

我正在尝试为边宽和颜色随时间变化的图表制作动画。我的代码有效,但速度非常慢。我想还有更有效的实现。

def minimal_graph(datas, pos):
    frames = len(datas[0])
    fig, axes = plt.subplots(1, 2)
    axes = axes.flatten()
    for j, dat in enumerate(datas):
        G = nx.from_numpy_matrix(dat[0])
        nx.draw(G, pos, ax=axes[j])
    def update(it, data, pos, ax):
        print(it)
        for i, dat in enumerate(data):
            # This is the problematic line, because I clear the axis hence
            # everything has to be drawn from scratch every time.
            ax[i].clear()
            G = nx.from_numpy_matrix(dat[it])
            edges, weights = zip(*nx.get_edge_attributes(G, 'weight').items())
            nx.draw(
                G,
                pos,
                node_color='#24FF00',
                edgelist=edges,
                edge_color=weights,
                width=weights,
                edge_vmin=-5,
                edge_vmax=5,
                ax=ax[i])
    ani = animation.FuncAnimation(fig, update, frames=frames, fargs=(
        datas, pos, axes), interval=100)
    ani.save('temp/graphs.mp4')
    plt.close()


dataset1 = []
dataset2 = []
for i in range(100):
    arr1 = np.random.rand(400, 400)
    arr2 = np.random.rand(400, 400)
    dataset1.append(arr1)
    dataset2.append(arr2)

datasets = [dataset1, dataset2]
G = nx.from_numpy_matrix(dataset1[0])
pos = nx.spring_layout(G)

minimal_graph(datasets, pos)

正如代码中所指出的,问题在于我在每一帧都从“头”重新绘制图形。在 matplotlib 中使用动画时,我通常会尝试创建线条并使用函数“'line.set_data()'”,我知道这要快得多。只是我不知道如何使用 networkx 为图形设置它。我在这里发现 问题,但他们也使用相同的 ax.clear 并为每一帧重绘所有内容。那么,有没有一种方法可以将线对象设置为不在每次迭代时重绘所有内容?例如,在我的例子中,节点总是相同的(颜色、位置、大小保持不变)。

nx.draw 不公开用于表示节点和边的 matplotlib 艺术家,因此您无法更改艺术家的属性 in-place。从技术上讲,如果您单独绘制边缘,您确实会得到一些艺术家的集合,但是 non-trivial 将艺术家列表映射回边缘,in particular if there are self-loops present

如果你愿意使用其他库来制作动画,我前段时间写过netgraph。对于您的问题至关重要的是,它可以轻松地将所有艺术家暴露给索引形式,以便可以更改其属性 in-place 而无需重新绘制其他所有内容。 netgraph 接受 full-rank 矩阵和 networkx Graph 对象作为输入,因此输入数据应该很简单。

下面是一个简单的可视化示例。如果我 运行 具有 400 个节点和 1000 个边的相同脚本,它需要 30 秒才能在我的笔记本电脑上完成。

#!/usr/bin/env python
"""
MWE for animating edges.
"""
import numpy as np
import matplotlib.pyplot as plt

from netgraph import Graph # pip install netgraph
from matplotlib.animation import FuncAnimation

total_nodes = 10
total_frames = 100

adjacency_matrix = np.random.rand(total_nodes, total_nodes) < 0.2
weight_matrix = 5 * np.random.randn(total_frames, total_nodes, total_nodes)

# precompute normed weight matrix, such that weights are on the interval [0, 1];
# weights can then be passed directly to matplotlib colormaps (which expect float on that interval)
vmin, vmax = -5, 5
weight_matrix[weight_matrix<vmin] = vmin
weight_matrix[weight_matrix>vmax] = vmax
weight_matrix -= vmin
weight_matrix /= vmax - vmin

cmap = plt.cm.RdGy

plt.ion()

fig, ax = plt.subplots()
g = Graph(adjacency_matrix, arrows=True, ax=ax)

def update(ii):
    artists = []
    for jj, kk in zip(*np.where(adjacency_matrix)):
        w = weight_matrix[ii, jj, kk]
        g.edge_artists[(jj, kk)].set_facecolor(cmap(w))
        g.edge_artists[(jj, kk)].width = 0.01 * np.abs(w-0.5) # assuming large negative edges should be wide, too
        g.edge_artists[(jj, kk)]._update_path()
        artists.append(g.edge_artists[(jj, kk)])
    return artists

animation = FuncAnimation(fig, update, frames=total_frames, interval=100, blit=True)
animation.save('edge_animation.mp4')