简体   繁体   English

在 python networkx 图中为节点添加工具提示

[英]adding tooltip for nodes in python networkx graph

I created a directed graph using networkx.DiGraph then plotted it using networkx.draw_spring(graph) , so all the nodes of the graph have some details stored in a list of dictionaries.我使用networkx.DiGraph创建了一个有向图,然后使用networkx.draw_spring(graph)绘制它,因此该图的所有节点都有一些详细信息存储在字典列表中。

How to add something like a "tooltip" to view these details on mouse hover on each node?如何添加类似“工具提示”的内容以在每个节点上的鼠标 hover 上查看这些详细信息? If this is possible, how to make this "tooltip" always visible for all nodes, not just by hovering?如果这是可能的,如何使这个“工具提示”对所有节点始终可见,而不仅仅是通过悬停?

Always visible始终可见

To label all the nodes, you just need to use annotate .对于 label 所有节点,您只需要使用annotate即可。 Something like this像这样的东西

import matplotlib.pyplot as plt
import networkx as nx

G = nx.path_graph(5)
attrs = {0: {'attr1': 20, 'attr2': 'nothing'}, 1: {'attr2': 3}, 2: {'attr1': 42}, 3: {'attr3': 'hello'}, 4: {'attr1': 54, 'attr3': '33'}}
nx.set_node_attributes(G, attrs)
nx.draw(G)

for node in G.nodes:
    xy = pos[node]
    annot.xy = xy
    node_attr = G.nodes[node]
    text = '\n'.join(f'{k}: {v}' for k, v in G.nodes[node].items())
    text = f'node {node}\n' + text
    ax.annotate(text, xy=xy)

On hover在 hover

Here's a working example of getting tooltip on hover.这是在 hover 上获取工具提示的工作示例。 This is based off tooltips using standard matplotlib plots here .这是基于使用标准 matplotlib 图工具提示。 I used draw_networkx_nodes to get the objects used for hovering and displaying tooltips instead of using draw_spring .我使用draw_networkx_nodes来获取用于悬停和显示工具提示的对象,而不是使用draw_spring But you can manually define the position with spring_layout .但是您可以使用 spring_layout 手动定义spring_layout

import matplotlib.pyplot as plt
import networkx as nx

G = nx.path_graph(5)
attrs = {0: {'attr1': 20, 'attr2': 'nothing'}, 1: {'attr2': 3}, 2: {'attr1': 42}, 3: {'attr3': 'hello'}, 4: {'attr1': 54, 'attr3': '33'}}
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()

在此处输入图像描述

FYI @busybear @ sudofix仅供参考@busybear @sudofix

This only works if you have your nodes starting from 0.这仅在您的节点从 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并将 rest 保留为

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).但是,如果您将nodes = list(range(5))更改为nodes = list(range(1,6))它不起作用,因为node = ind["ind"][0]返回 position 中的节点G.nodes而不是节点的名称,因此访问pos[node]G.nodes[node]会得到错误的 position(它移动了 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:并修复 function 以将其用作:

def update_annot(ind):
    node_idx = ind["ind"][0]
    node = idx_to_node_dict[node_idx]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM