【问题标题】:Matplotlib cross hair cursor in PyQt5PyQt5 中的 Matplotlib 十字准线光标
【发布时间】:2021-09-06 21:32:21
【问题描述】:

我想添加一个捕捉数据点并在鼠标移动时更新的十字准线。我发现this example 效果很好:

import numpy as np
import matplotlib.pyplot as plt

class SnappingCursor:
    """
    A cross hair cursor that snaps to the data point of a line, which is
    closest to the *x* position of the cursor.

    For simplicity, this assumes that *x* values of the data are sorted.
    """
    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
        # text location in axes coords
        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
            index = min(np.searchsorted(self.y, y), len(self.y) - 1)
            if index == self._last_index:
                return  # still on the same data point. Nothing to do.
            self._last_index = index
            x = self.x[index]
            y = self.y[index]
            # update the line positions
            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()


y = np.arange(0, 1, 0.01)
x = np.sin(2 * 2 * np.pi * y)

fig, ax = plt.subplots()
ax.set_title('Snapping cursor')
line, = ax.plot(x, y, 'o')
snap_cursor = SnappingCursor(ax, line)
fig.canvas.mpl_connect('motion_notify_event', snap_cursor.on_mouse_move)
plt.show()

但是当我想用 PyQt5 调整代码并在 GUI 中显示绘图时,我遇到了麻烦。我的代码是:

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:
    """
    A cross hair cursor that snaps to the data point of a line, which is
    closest to the *x* position of the cursor.

    For simplicity, this assumes that *x* values of the data are sorted.
    """
    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
        # text location in axes coords
        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
            index = min(np.searchsorted(self.y, y), len(self.y) - 1)
            if index == self._last_index:
                return  # still on the same data point. Nothing to do.
            self._last_index = index
            x = self.x[index]
            y = self.y[index]
            # update the line positions
            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')

        snap_cursor = SnappingCursor(ax, line)
        plot1.mpl_connect('motion_notify_event', 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())

通过运行上述代码,数据被正确绘制,但十字准线仅显示在其初始位置,不会因鼠标移动而移动。数据值也不显示。

我也发现了一个类似的问题here,但是问题没有回答清楚。

【问题讨论】:

    标签: matplotlib pyqt5 cursor cross-hair-label


    【解决方案1】:

    有2个问题:

    • snap_cursor 是一个局部变量,当__init__ 执行完毕时将被删除。你必须让他成为班级成员。

    • 教程的初始代码被设计成显示信息的点是穿过光标并与曲线相交的水平线。在您的初始代码中,它与示例不同,也不适用于您的新曲线,因此我恢复了教程的逻辑。

    import sys
    
    from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
    
    import numpy as np
    
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    
    
    class SnappingCursor:
        """
        A cross hair cursor that snaps to the data point of a line, which is
        closest to the *x* position of the cursor.
    
        For simplicity, this assumes that *x* values of the data are sorted.
        """
    
        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
            # text location in axes coords
            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
                index = min(np.searchsorted(self.x, x), len(self.x) - 1)
                if index == self._last_index:
                    return  # still on the same data point. Nothing to do.
                self._last_index = index
                x = self.x[index]
                y = self.y[index]
                # update the line positions
                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(widget)
    
            x = np.arange(0, 1, 0.01)
            y = np.sin(2 * 2 * np.pi * x)
    
            canvas = FigureCanvas(Figure(tight_layout=True, linewidth=3))
            ax = canvas.figure.subplots()
            ax.set_title("Snapping cursor")
            (line,) = ax.plot(x, y, "o")
            self.snap_cursor = SnappingCursor(ax, line)
            canvas.mpl_connect("motion_notify_event", self.snap_cursor.on_mouse_move)
    
            vbox.addWidget(canvas)
            self.setCentralWidget(widget)
            self.setWindowTitle("Example")
    
    
    app = QApplication(sys.argv)
    w = Window()
    w.show()
    app.exec()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-10
      • 2014-07-24
      • 1970-01-01
      • 1970-01-01
      • 2021-02-03
      • 2014-11-04
      • 1970-01-01
      • 2014-05-15
      相关资源
      最近更新 更多