如何检测点击了 Matplotlib PathCollection 中的哪个项目?

How to detect which item in a Matplotlib PathCollection has been clicked on?

我可以使用 Matplotlib 绘制有向网络图。现在我希望能够响应鼠标事件,以便用户可以与网络进行交互。例如,一个节点可以在用户点击它时改变它的颜色。这只是一个简单的例子,但它说明了这一点。我还想知道点击了哪个节点(标签);我对它在 space.

中的 x,y 坐标并不感兴趣

到目前为止,这是我的代码:

import matplotlib.pyplot as plt
from matplotlib.collections import PathCollection
import networkx as nx

def createDiGraph():
    G = nx.DiGraph()

    # Add nodes:
    nodes = ['A', 'B', 'C', 'D', 'E']
    G.add_nodes_from(nodes)

    # Add edges or links between the nodes:
    edges = [('A','B'), ('B','C'), ('B', 'D'), ('D', 'E')]
    G.add_edges_from(edges)
    return G

G = createDiGraph()

# Get a layout for the nodes according to some algorithm.
pos = nx.layout.spring_layout(G, random_state=779)

node_size = 300
nodes = nx.draw_networkx_nodes(G, pos, node_size=node_size, node_color=(0,0,0.9),
                                edgecolors='black')
# nodes is a matplotlib.collections.PathCollection object
nodes.set_picker(5)
#nodes.pick('button_press_event')
#print(nodes.get_offsets())

nx.draw_networkx_edges(G, pos, node_size=node_size, arrowstyle='->',
                                arrowsize=15, edge_color='black', width=1)

nx.draw_networkx_labels(G, pos, font_color='red', font_family='arial',
                                font_size=10)

def onpick(event):
    #print(event.mouseevent)

    if isinstance(event.artist, PathCollection):
        #nodes = event.artist
        print (event.ind)

fig = plt.gcf()

# Bind our onpick() function to pick events:
fig.canvas.mpl_connect('pick_event', onpick)

# Hide the axes:
plt.gca().set_axis_off()
plt.show()

绘制时网络看起来像这样:

例如,如果我用鼠标单击节点 C,程序会打印出 [1];或节点 E 的 [3]。请注意,索引不对应于 A 的 0、B 的 1、C 的 2,等等,即使原始节点已按该顺序添加到 networkx 有向图。

那么当我点击节点C时如何获取值'C'呢?以及如何获得图中代表 C 的对象以便更改其颜色?

我试过 PathCollection.pick,但我不确定要将什么传递给它,而且我不确定这是否是正确的使用方法。

看起来 G 在创建时不保留节点的顺序,但是,节点的顺序似乎存储在 pos 中。我会推荐以下内容:

添加到您的导入语句:

from ast import literal_eval

nx.draw_networkx_nodes 中定义 label 并在函数 update_plot 中创建绘图,该函数将 Gposcolor 作为参数:

def update_plot(pos, G, colors):
    # the keys of the pos dictionary contains the labels that you are interested in
    # or label = [*pos]
    nodes = nx.draw_networkx_nodes(G, pos, node_size=node_size, node_color=colors,edgecolors='black', label=list(pos.keys()))
    # nodes is a matplotlib.collections.PathCollection object
    nodes.set_picker(5)
    nx.draw_networkx_edges(G, pos, node_size=node_size, arrowstyle='->', arrowsize=15, edge_color='black', width=1)
    nx.draw_networkx_labels(G, pos, font_color='red', font_family='arial', font_size=10)

你的 onpick 函数应该是:

def onpick(event):
    if isinstance(event.artist, PathCollection):
        #index of event
        ind = event.ind[0]
        #convert the label list from a string back to a list
        label_list = literal_eval(event.artist.get_label())
        print(label_list[ind])
        colors = [(0, 0, 0.9)] * len(pos)
        colors[ind]=(0.9,0,0)
        update_plot(pos, G, colors)

你的代码主体就是:

G = createDiGraph()
# Get a layout for the nodes according to some algorithm.
pos = nx.layout.spring_layout(G, random_state=779)

node_size = 300
colors=[(0,0,0.9)]*len(pos)
update_plot(pos, G, colors)

在@apogalacticon 的有用评论后,我最终找到了这个解决方案。

import matplotlib.pyplot as plt
from matplotlib.collections import PathCollection
import networkx as nx

def createDiGraph():
    G = nx.DiGraph()

    # Add nodes:
    nodes = ['A', 'B', 'C', 'D', 'E']
    G.add_nodes_from(nodes)

    # Add edges or links between the nodes:
    edges = [('A','B'), ('B','C'), ('B', 'D'), ('D', 'E')]
    G.add_edges_from(edges)
    return G

G = createDiGraph()

# Get a layout for the nodes according to some algorithm.
pos = nx.layout.spring_layout(G, random_state=779)

node_size = 300
nodes = nx.draw_networkx_nodes(G, pos, node_size=node_size, node_color=(0,0,0.9),
                                edgecolors='black')
# nodes is a matplotlib.collections.PathCollection object
nodes.set_picker(5)

nx.draw_networkx_edges(G, pos, node_size=node_size, arrowstyle='->',
                                arrowsize=15, edge_color='black', width=1)

nx.draw_networkx_labels(G, pos, font_color='red', font_family='arial',
                                font_size=10)

def onpick(event):

    if isinstance(event.artist, PathCollection):
        all_nodes = event.artist
        ind = event.ind[0] # event.ind is a single element array.
        this_node_name = pos.keys()[ind]
        print(this_node_name)

        # Set the colours for all the nodes, highlighting the picked node with
        # a different colour:
        colors = [(0, 0, 0.9)] * len(pos)
        colors[ind]=(0, 0.9, 0)
        all_nodes.set_facecolors(colors)

        # Update the plot to show the change:
        fig.canvas.draw() # plt.draw() also works.
        #fig.canvas.flush_events() # Not required? See 

fig = plt.gcf()

# Bind our onpick() function to pick events:
fig.canvas.mpl_connect('pick_event', onpick)

# Hide the axes:
plt.gca().set_axis_off()
plt.show()

当您单击一个节点时,其表面颜色变为绿色,而其他节点设置为蓝色:

不幸的是,网络中的节点似乎由单个 matplotlib.artist.Artist 对象表示,该对象恰好是没有任何艺术家子节点的路径集合。这意味着您无法获取单个节点来更改其属性。相反,您被迫更新所有节点,只需确保所选节点的属性(在本例中为颜色)与其他节点不同。