PyQt5嵌入Matplotlib时可吸附数据点的十字光标无法随鼠标移动问题
PyQt5嵌入Matplotlib吸附十字光标失效修复方案
错误原因
- 实例被垃圾回收:
SnappingCursor的实例snap_cursor是__init__方法内的局部变量,方法执行结束后就被销毁,绑定的鼠标事件回调随之失效,需要将其绑定为窗口类的成员属性避免回收。 - 索引查找逻辑不匹配:原示例中y轴数据是升序排列的,所以用y值查找最近点索引;适配后的代码x轴是升序序列、y轴是无序的正弦值,原索引查找逻辑会返回错误结果,需要调整查找逻辑匹配当前数据分布。
修复后完整代码
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout import sys from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import numpy as np class SnappingCursor: """ 可吸附到最近数据点的十字光标,默认适配x轴升序排列的数据 """ def __init__(self, ax, line): self.ax = ax self.horizontal_line = ax.axhline(color='k', lw=0.8, ls='--') self.vertical_line = ax.axvline(color='k', lw=0.8, ls='--') self.x, self.y = line.get_data() self._last_index = None # 文本显示位置,基于坐标轴相对坐标 self.text = ax.text(0.72, 0.9, '', transform=ax.transAxes) def set_cross_hair_visible(self, visible): need_redraw = self.vertical_line.get_visible() != visible self.vertical_line.set_visible(visible) self.horizontal_line.set_visible(visible) self.text.set_visible(visible) return need_redraw def on_mouse_move(self, event): if not event.inaxes: self._last_index = None need_redraw = self.set_cross_hair_visible(False) if need_redraw: self.ax.figure.canvas.draw() else: self.set_cross_hair_visible(True) x, y = event.xdata, event.ydata # 适配x轴升序的数据,查找最近点索引 index = min(np.searchsorted(self.x, x), len(self.x) - 1) if index == self._last_index: return # 仍在同一个数据点上,无需更新 self._last_index = index x = self.x[index] y = self.y[index] # 更新十字线位置 self.horizontal_line.set_ydata(y) self.vertical_line.set_xdata(x) self.text.set_text('x=%1.2f, y=%1.2f' % (x, y)) self.ax.figure.canvas.draw() class Window(QMainWindow): def __init__(self): super().__init__() widget=QWidget() vbox=QVBoxLayout() plot1 = FigureCanvas(Figure(tight_layout=True, linewidth=3)) ax = plot1.figure.subplots() x = np.arange(0, 1, 0.01) y = np.sin(2 * 2 * np.pi * x) line, = ax.plot(x, y, 'o') # 将光标实例绑定为类属性,避免被垃圾回收 self.snap_cursor = SnappingCursor(ax, line) plot1.mpl_connect('motion_notify_event', self.snap_cursor.on_mouse_move) vbox.addWidget(plot1) widget.setLayout(vbox) self.setCentralWidget(widget) self.setWindowTitle('Example') self.show() App = QApplication(sys.argv) window = Window() sys.exit(App.exec())
内容的提问来源于stack exchange,提问作者Amir
相关产品推荐
相关产品推荐

