Fancy moving-nodes network visualization with Python

54 Views Asked by At

I have a network that undergoes a loop, and for each iteration its edges update their weight. Some of them increase their weight, strengthening the link between the two nodes, and some of them decrease their weight, weakening the relation between the component nodes.

Given that the weight is collected as an attribute of the Graph as G[node1][node2]["weight"], is there a way to visualize the network in a way that, for each iteration of the loop, those edges whose weight increased with regard to the previous iteration, see their component nodes get closer (they move closer to each other), whereas those edges that became weaker, see their component nodes separate to a longer distance?

Although I'd need the edges to approach or move away, the position of the nodes should remain static: unless the edge weight changes, nodes stay where they were set originally. Only when their edge weight changed is when they move (towards or away from their associate nodes).

Maybe, then, in the end, collect this into a gif to see the movement.

Any help would be very much appreciated it. Thank you.

1

There are 1 best solutions below

0
Timeless On

IIUC, a possible option would be to use 's animation :

init_pos = nx.spring_layout(G, seed=7)

def movedge(pos, p1, p2, distance):
    nodes = p1, p2
    p1, p2 = itemgetter(p1, p2)(pos)
    curr_distance = np.linalg.norm(p2 - p1)
    scale = distance / curr_distance
    new_p1 = p1 + (p2 - p1) * scale
    new_p2 = p2 + (p1 - p2) * scale
    return {**pos, **dict(zip(nodes, (new_p1, new_p2)))}

# `drawg()` goes here.. (see full code)

fig, ax = plt.subplots(figsize=(10, 6))

N = 20
frames = {}
H = G.copy(); new_pos = init_pos.copy()
for move, rng in zip(moves, np.arange(1, len(moves) * N + 1).reshape(-1, N)):
    p1, p2 = move["p1"], move["p2"]; init = H[p1][p2]["weight"]
    for i, j in zip(rng, np.linspace(init, move["distance"], N).round(2)):
        H[p1][p2]["weight"] = j; move["distance"] = j-init
        new_pos.update(movedge(**move, pos=new_pos))
        frames[i] = {"g": H.copy(), "curr_pos": movedge(**move, pos=new_pos)}

def animate(i):
    ax.clear()
    drawg(**frames[i + 1], ax=ax)

To visuzalize it in notebook :

%matplotlib notebook

ani = FuncAnimation(
    fig, animate, frames=N * len(moves), interval=150, repeat=True
)

To save a (.gif) :

writer = PillowWriter(
    fps=15, metadata=dict(artist="Me"), bitrate=1800,
)
ani.save("output.gif", writer=writer)

enter image description here

Graph used (G) : Example from networkx/docs.

Moves used :

moves = ( # some random moves
    {"p1": "a", "p2": "b", "distance": 0.57},
    {"p1": "c", "p2": "d", "distance": 0.12},
    {"p1": "c", "p2": "f", "distance": 0.88},
    {"p1": "c", "p2": "d", "distance": 0.07},
    {"p1": "c", "p2": "e", "distance": 0.68},
    {"p1": "a", "p2": "b", "distance": 0.59},
)

Full code