PyQt5 中的 Matplotlib 十字准线光标
Posted
技术标签:
【中文标题】PyQt5 中的 Matplotlib 十字准线光标【英文标题】:Matplotlib cross hair cursor in PyQt5 【发布时间】: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,但是问题没有回答清楚。
【问题讨论】:
【参考方案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()
【讨论】:
以上是关于PyQt5 中的 Matplotlib 十字准线光标的主要内容,如果未能解决你的问题,请参考以下文章