mat*_*fux 6 python matplotlib event-handling mouseevent networkx
我正在做一个项目,我需要创建一个预览nx.Graph()
,允许更改节点的位置,用鼠标拖动它们。如果单击特定节点,我当前的代码能够在鼠标每次移动后立即重绘整个图形。但是,这会显着增加延迟。我怎样才能只更新需要的艺术家,它是,点击节点,它的标签文本和相邻的边缘,而不是刷新每个艺术家plt.subplots()
?我至少可以得到所有需要搬迁的艺术家的参考吗?
我从显示图表的标准方式开始networkx
:
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import scipy.spatial
def refresh(G):
plt.axis((-4, 4, -1, 3))
nx.draw_networkx_labels(G, pos = nx.get_node_attributes(G, 'pos'),
bbox = dict(fc="lightgreen", ec="black", boxstyle="square", lw=3))
nx.draw_networkx_edges(G, pos = nx.get_node_attributes(G, 'pos'), width=1.0, alpha=0.5)
plt.show()
nodes = np.array(['A', 'B', 'C', 'D', 'E', 'F', 'G'])
edges = np.array([['A', 'B'], ['A', 'C'], ['B', 'D'], ['B', 'E'], ['C', 'F'], ['C', 'G']])
pos = np.array([[0, 0], [-2, 1], [2, 1], [-3, 2], [-1, 2], [1, 2], [3, 2]])
G = nx.Graph()
# IG = InteractiveGraph(G) #>>>>> add this line in the next step
G.add_nodes_from(nodes)
G.add_edges_from(edges)
nx.set_node_attributes(G, dict(zip(G.nodes(), pos.astype(float))), 'pos')
fig, ax = plt.subplots()
# fig.canvas.mpl_connect('button_press_event', lambda event: IG.on_press(event))
# fig.canvas.mpl_connect('motion_notify_event', lambda event: IG.on_motion(event))
# fig.canvas.mpl_connect('button_release_event', lambda event: IG.on_release(event))
refresh(G) # >>>>> replace it with IG.refresh() in the next step
Run Code Online (Sandbox Code Playgroud)
在下一步中,我更改了先前脚本的 5 行(4 行未注释,1 行被替换)加上使用的InteractiveGraph
实例以使其具有交互性:
class InteractiveGraph:
def __init__(self, G, node_pressed=None, xydata=None):
self.G = G
self.node_pressed = node_pressed
self.xydata = xydata
def refresh(self, show=True):
plt.clf()
nx.draw_networkx_labels(self.G, pos = nx.get_node_attributes(self.G, 'pos'),
bbox = dict(fc="lightgreen", ec="black", boxstyle="square", lw=3))
nx.draw_networkx_edges(self.G, pos = nx.get_node_attributes(self.G, 'pos'), width=1.0, alpha=0.5)
plt.axis('off')
plt.axis((-4, 4, -1, 3))
fig.patch.set_facecolor('white')
if show:
plt.show()
def on_press(self, event):
if event.inaxes is not None and len(self.G.nodes()) > 0:
nodelist, coords = zip(*nx.get_node_attributes(self.G, 'pos').items())
kdtree = scipy.spatial.KDTree(coords)
self.xydata = np.array([event.xdata, event.ydata])
close_idx = kdtree.query_ball_point(self.xydata, np.sqrt(0.1))
i = close_idx[0]
self.node_pressed = nodelist[i]
def on_motion(self, event):
if event.inaxes is not None and self.node_pressed:
new_xydata = np.array([event.xdata, event.ydata])
self.xydata += new_xydata - self.xydata
#print(d_xy, self.G.nodes[self.node_pressed])
self.G.nodes[self.node_pressed]['pos'] = self.xydata
self.refresh(show=False)
event.canvas.draw()
def on_release(self, event):
self.node_pressed = None
Run Code Online (Sandbox Code Playgroud)
相关来源:
为了扩展我上面的评论,在 中netgraph
,您的示例可以复制为
import numpy as np
import matplotlib.pyplot as plt; plt.ion()
import networkx as nx
import netgraph
nodes = np.array(['A', 'B', 'C', 'D', 'E', 'F', 'G'])
edges = np.array([['A', 'B'], ['A', 'C'], ['B', 'D'], ['B', 'E'], ['C', 'F'], ['C', 'G']])
pos = np.array([[0, 0], [-2, 1], [2, 1], [-3, 2], [-1, 2], [1, 2], [3, 2]])
G = nx.Graph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
I = netgraph.InteractiveGraph(G,
node_positions=dict(zip(nodes, pos)),
node_labels=dict(zip(nodes,nodes)),
node_label_bbox=dict(fc="lightgreen", ec="black", boxstyle="square", lw=3),
node_size=12,
)
# move stuff with mouse
Run Code Online (Sandbox Code Playgroud)
关于您编写的代码,如果您拥有所有艺术家的句柄,则不需要 kd 树。一般来说,matplotlib 艺术家有一种contains
方法,这样当您记录按钮按下事件时,您可以简单地检查artist.contains(event)
按钮按下是否发生在艺术家身上。当然,如果您使用 networkx 进行绘图,则无法以良好的可查询形式获取句柄(ax.get_children()
两者都不是),因此这是不可能的。