You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 17:57:04