如何让Matplotlib的button_press_event忽略按钮控件?event.inaxes无效
解决Matplotlib点击捕获模式下按钮误触发坐标记录的问题
我来帮你搞定这个头疼的小问题!你遇到的核心问题其实很清晰:matplotlib里的Button组件是建在独立的axes对象上的,所以你原来用event.inaxes判断时,点击按钮也会返回一个有效的axes,导致按钮点击被当成了绘图区域的点击,进而误添加坐标。
最直接的修复方法
只需要在捕获点击事件时,明确判断点击的axes就是你用来绘图的那个axes,而不是随便哪个axes都行。修改ClickCatcher类里的__call__方法的条件:
把原来的:
if event.button == 1 and event.inaxes:
改成:
if event.button == 1 and event.inaxes is self.ax:
这样只有点击你指定的绘图坐标轴(self.window_object.ax)时,才会触发坐标记录,按钮所在的axes就不会被识别成有效区域了。
顺便优化个小隐患
你的代码还有个小问题:多次点击Mark按钮会重复创建ClickCatcher实例,绑定多个相同的事件回调,导致一次点击触发好几次记录。我给你加个小优化,让Mark按钮可以切换开启/关闭捕获模式,同时避免重复绑定:
在Window类的__init__里加个变量保存捕获器实例:
self.click_catcher = None # 用来保存捕获器实例,避免重复创建
然后修改activate_marking方法:
def activate_marking(self, event): if self.is_click_catcher_working: # 关闭捕获模式 self.is_click_catcher_working = False self.fig.canvas.mpl_disconnect(self.click_catcher.cid) self.click_catcher = None print("点击捕获已关闭") else: # 开启捕获模式 self.is_click_catcher_working = True self.click_catcher = ClickCatcher(self) print("点击捕获已开启,请在绘图区域点击记录坐标")
这样点击Mark按钮可以来回切换开关,体验更好。
还有个小细节要改
你原来的print_points方法里,字符串格式化的写法有点问题,会先输出所有x坐标再输出所有y坐标,改成配对输出更合理:
def print_points(self, event): if not self.click_data: print("还没有捕获任何点") return output = '({:.2f}, {:.2f}) ; '*len(self.click_data[0]) # 把x和y配对后再格式化 points_flat = [] for x, y in zip(self.click_data[0], self.click_data[1]): points_flat.extend([x, y]) print(output.format(*points_flat))
修改后的完整代码
import numpy as np import matplotlib.pyplot as plt from matplotlib.widgets import Button class ClickCatcher: def __init__(self, window_object): self.window_object = window_object self.fig = self.window_object.fig self.ax = self.window_object.ax self.window_object.is_click_catcher_working = True self.cid = window_object.fig.canvas.mpl_connect('button_press_event', self) self.points, = self.ax.plot([], [], marker='.', ls='None', color='red') self.data_x = [] self.data_y = [] def __call__(self, event): # 关键修改:只响应绘图区域的axes点击 if event.button == 1 and event.inaxes is self.ax: self.data_x.append(event.xdata) self.data_y.append(event.ydata) self.update_plot() print('{:8.2f} {:8.2f}'.format(event.xdata, event.ydata)) self.window_object.click_data = (self.data_x, self.data_y) def update_plot(self): self.points.set_data(self.data_x, self.data_y) self.fig.canvas.draw() class Window: def __init__(self): self.is_click_catcher_working = False self.click_data = () self.click_catcher = None # 保存捕获器实例,避免重复绑定 self.fig, self.ax = plt.subplots() self.configure_appearance() self._plot, = plt.plot(np.arange(0, 10, 0.1), np.sin(np.arange(0, 10, 0.1)), drawstyle='steps-mid', color='black', lw=1) self._add_buttons() def configure_appearance(self): self.fig.set_size_inches(10, 5) self.ax.tick_params(axis='both', labelsize=14) self.ax.set_xlabel('Energy (keV)', fontsize=12) self.ax.set_ylabel('Number of counts', fontsize=12) plt.subplots_adjust(bottom=0.25) def _add_buttons(self): self.button_mark = Button(plt.axes([0.4, 0.05, 0.1, 0.075]), 'Mark') self.button_mark.on_clicked(self.activate_marking) self.button_print = Button(plt.axes([0.6, 0.05, 0.1, 0.075]), 'Print') self.button_print.on_clicked(self.print_points) def print_points(self, event): if not self.click_data: print("还没有捕获任何点") return output = '({:.2f}, {:.2f}) ; '*len(self.click_data[0]) points_flat = [] for x, y in zip(self.click_data[0], self.click_data[1]): points_flat.extend([x, y]) print(output.format(*points_flat)) def activate_marking(self, event): if self.is_click_catcher_working: self.is_click_catcher_working = False self.fig.canvas.mpl_disconnect(self.click_catcher.cid) self.click_catcher = None print("点击捕获已关闭") else: self.is_click_catcher_working = True self.click_catcher = ClickCatcher(self) print("点击捕获已开启,请在绘图区域点击记录坐标") if __name__ == '__main__': win = Window() plt.show()
测试一下效果
现在你可以:
- 点击Mark开启捕获,在绘图区点几下记录坐标
- 点击Print,只会输出已捕获的坐标,不会新增
- 再点Mark可以关闭捕获,此时点绘图区也不会记录了
- 捕获模式下Print完全正常工作,不需要先退出模式
完美解决你的问题啦!
内容的提问来源于stack exchange,提问作者Florence Foster of Physics
相关产品推荐
相关产品推荐

