FYI @busybear @ sudofix
This only works if you have your nodes starting from 0.
if you do:
import matplotlib.pyplot as plt
import networkx as nx
nodes = list(range(5))
edges = []
for e1,e2 in zip(nodes[:-1],nodes[1:]):
edges.append((e1,e2))
G = nx.Graph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
attrs = {}
for node in G.nodes:
attrs[node] = {'attr1': node, 'attr2': 'hello', 'attr3': 33}
and leave the rest as
nx.set_node_attributes(G, attrs)
fig, ax = plt.subplots()
pos = nx.spring_layout(G)
nodes = nx.draw_networkx_nodes(G, pos=pos, ax=ax)
nx.draw_networkx_edges(G, pos=pos, ax=ax)
annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(ind):
node = ind["ind"][0]
xy = pos[node]
annot.xy = xy
node_attr = {'node': node}
node_attr.update(G.nodes[node])
text = '\n'.join(f'{k}: {v}' for k, v in node_attr.items())
annot.set_text(text)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = nodes.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()
everything works great.
But if you change
nodes = list(range(5))
to be
nodes = list(range(1,6))
it does not work, because node = ind["ind"][0]
returns the position of the nodes in G.nodes
and not the name of the node, so accessing pos[node]
and G.nodes[node]
gets the wrong position (it is shifted by 1).
The solution is to create a mapping like
idx_to_node_dict = {}
for idx, node in enumerate(G.nodes):
idx_to_node_dict[idx] = node
and fix the function to use it as:
def update_annot(ind):
node_idx = ind["ind"][0]
node = idx_to_node_dict[node_idx]