I have a dataset of ordered concept-categories and I'm graphing them by the order that they appear, with labels for conceptual distance and thickness based on times where connections between nodes occur multiple times.
I've got a graph in networkx that I've produced by:
It took me a lot to figure out how to get the graph right, but I'm now happy with it. My issue is that I want to take a completed graph G and reproduce it one edge at a time so that I can animate it as I go.
This is how I've done it, but it is very glitchy because we get labels appearing before their edges and suchlike. Any idea how I can do this properly?
###############################################
# Doing the bit where we output the video of the graph being constructed
# Create an empty directed graph for animation
positions = pos_dic
G_animated = nx.DiGraph()
# Initialize figure and axis for animation
fig, ax = plt.subplots(figsize=(20, 12))
plt.title(f'Graph for {key} at {timestamp}',font)
# Step 2: Extract the edges, colors, weights, and labels from the original graph
edges = list(G.edges(data=True))
edge_colors = [attr['color'] for u, v, attr in edges]
edge_weights = [attr['weight'] for u, v, attr in edges]
edge_labels = {(u, v): attr['label'] for u, v, attr in edges}
# Step 3: Function to update the graph step by step
def update_graph(num):
ax.clear() # Clear the plot for the next frame
if num < len(edges):
u, v, attr = edges[num]
# Add the nodes if they don't exist yet in G_animated
if u not in G_animated.nodes:
G_animated.add_node(u)
if v not in G_animated.nodes:
G_animated.add_node(v)
# Now, add the edge with the attributes
G_animated.add_edge(u, v, **attr)
# Draw the updated graph with custom positions
edge_color_list = [edge_colors[i] for i in range(len(G_animated.edges))]
nx.draw(G_animated, pos=positions, ax=ax, with_labels=False, node_color='lightblue',
edge_color=edge_color_list, width=edge_weights[:len(G_animated.edges)],
node_size=500, arrows=True)
nx.draw_networkx_labels(G_animated, pos_dic, font_size=11, font_color='black', font_weight='bold', bbox=dict(facecolor='white', edgecolor='black', boxstyle='round,pad=0.3'))
# Draw edge labels (showing the label attributes)
nx.draw_networkx_edge_labels(G_animated, pos=positions, edge_labels=edge_labels)
# Step 4: Create animation object
ani = FuncAnimation(fig, update_graph, frames=len(edges), repeat=False, interval=3000)
# Step 5: Save the animation as a video (e.g., .mp4)
writer = FFMpegWriter(fps=1, metadata=dict(artist='Nick Kelly'), bitrate=1800)
ani.save(f"graph_animation_{key}.mp4", writer=writer)
# plt.show()
plt.close()
# End video here
##################################################
This gives an animation that is very glitchy, see this file for an example: https://1drv.ms/v/s!AuaSDysD-RqIhOsFSP6pYZCUmvDYvQ?e=iz2k8L
Networkx resizes the axis data limits to fit the plot elements on each draw. As you are expanding the graph, the data limits are changing, giving the impression that graph elements are "jumping" around.
The simplest fix is to enforce constant data limits at the end of each update:
...
xy = np.array(positions.values())
xmin, ymin = np.min(xy, axis=0)
xmax, ymax = np.max(xy, axis=0)
pad_by = 0.05 # may need adjusting
pad_x, pad_y = pad_by * np.ptp(xy, axis=0)
def update_graph(num):
...
ax.set_xlim(xmin - pad_x, xmax + pad_x)
ax.set_ylim(ymin - pad_y, ymax + pad_y)
ax.set_aspect("equal")
ani = ...