【发布时间】:2021-05-21 15:53:29
【问题描述】:
我正在尝试创建一个显示,其中您在后台有一个 matplotlib 画布,在前台有一个重叠的小部件,显示有关绘制数据的一些任意信息。这本身我基本上已经实现了,但是我在对齐方面遇到了麻烦。
我希望重叠小部件(在QGroupBox 下方的示例中)与axes 的左下角对齐,并在窗口大小更改时做出响应。问题是我不知道如何正确更改两个重叠小部件的相对位置。
我发现this answer(下面称为方法1),它使用QAlignment,但一旦设置,QGroupBox 似乎对任何类型的位置变化都没有反应。也许可以添加边距并动态更改它们?
我发现的另一种方法是this one(以下称为方法2),它使用绝对定位,因此不会随着调整窗口大小而改变。也许这个更有意义?但是,每次调整窗口大小时,都需要进行一些转换和信号处理来重新定位QGroupBox。但不知何故,我没能正确地进行转换。
最后,我还找到了this,处理锚点,但我不知道它们是如何工作的,也不知道它们是否是常规 PyQt5 中的东西。
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import \
FigureCanvasQTAgg as FigureCanvas
import matplotlib.patheffects as PathEffects
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QDialog, QApplication, QGridLayout, QGroupBox, \
QLabel, QLineEdit
class MainWindow(QDialog):
def __init__(self):
super().__init__()
# this is just for context
self.fig, self.ax = plt.subplots()
self.canvas = FigureCanvas(self.fig)
self.data = np.random.uniform(0, 1, (50, 2))
self.artists = []
for point in self.data:
artist = self.ax.plot(*point, 'o', c='orange')[0]
artist.set_pickradius = 5
self.artists.append(artist)
self.zoom_factor = 1.2
self.x_press = 0
self.y_press = 0
self.last_artist = None
self.cid_motion = self.canvas.mpl_connect(
'motion_notify_event', self.motion_event
)
self.cid_button = self.canvas.mpl_connect(
'button_press_event', self.pan_press
)
self.cid_zoom = self.canvas.mpl_connect('scroll_event', self.zoom)
self.mainLayout = QGridLayout(self)
self.mainLayout.addWidget(self.canvas, 0, 0)
self.setLayout(self.mainLayout)
self.statsBox = QGroupBox('Stats:')
self.statsLayout = QGridLayout(self.statsBox)
self.posLabel = QLabel('Pos:')
self.statsLayout.addWidget(self.posLabel, 0, 0)
self.posEdit = QLineEdit()
self.posEdit.setReadOnly(True)
self.posEdit.setAlignment(Qt.AlignHCenter)
self.statsLayout.addWidget(self.posEdit, 0, 1)
# here is what's interesting
# method 1
# self.mainLayout.addWidget(
# self.statsBox, 0, 0, Qt.AlignRight | Qt.AlignBottom
# )
# method 2
self.statsBox.setParent(self)
self.statsBox.setFixedSize(self.statsBox.sizeHint())
self.position_statsBox()
def resizeEvent(self, a0):
super().resizeEvent(a0)
self.position_statsBox()
def position_statsBox(self):
x, y = self.ax.get_xlim()[1], self.ax.get_ylim()[0]
pos = QPoint(*self.ax.transData.transform((x, y)))
self.statsBox.move(pos)
# below here is just for context again
def motion_event(self, event):
if event.inaxes == self.ax and event.button == 1:
self.pan_move(event)
else:
self.hover(event)
def pan_press(self, event):
if event.inaxes == self.ax and event.button == 1:
self.x_press = event.xdata
self.y_press = event.ydata
def pan_move(self, event):
xdata = event.xdata
ydata = event.ydata
cur_xlim = self.ax.get_xlim()
cur_ylim = self.ax.get_ylim()
dx = xdata - self.x_press
dy = ydata - self.y_press
new_xlim = [cur_xlim[0] - dx, cur_xlim[1] - dx]
new_ylim = [cur_ylim[0] - dy, cur_ylim[1] - dy]
self.ax.set_xlim(new_xlim)
self.ax.set_ylim(new_ylim)
self.canvas.draw_idle()
def zoom(self, event):
if event.inaxes == self.ax:
xdata, ydata = event.xdata, event.ydata
xlim = self.ax.get_xlim()
ylim = self.ax.get_ylim()
x_left = xdata - xlim[0]
x_right = xlim[1] - xdata
y_bottom = ydata - ylim[0]
y_top = ylim[1] - ydata
scale_factor = np.power(self.zoom_factor, -event.step)
new_xlim = xdata-x_left*scale_factor, xdata+x_right*scale_factor
new_ylim = ydata-y_bottom*scale_factor, ydata+y_top*scale_factor
self.ax.set_xlim(new_xlim)
self.ax.set_ylim(new_ylim)
self.canvas.draw_idle()
def hover(self, event):
ind = 0
cont = None
while ind in range(len(self.artists)) and not cont:
artist = self.artists[ind]
cont, _ = artist.contains(event)
if cont and artist is not self.last_artist:
if self.last_artist is not None:
self.last_artist.set_path_effects(
[PathEffects.Normal()]
)
self.last_artist = None
artist.set_path_effects(
[PathEffects.withStroke(
linewidth=7, foreground="c", alpha=0.4
)]
)
self.last_artist = artist
x, y = artist.get_data()
pos = f'({x[0]:.2f}, {y[0]:.2f})'
self.posEdit.setText(pos)
ind += 1
if not cont and self.last_artist:
self.last_artist.set_path_effects([PathEffects.Normal()])
self.last_artist = None
self.posEdit.clear()
self.canvas.draw_idle()
if __name__ == '__main__':
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())
【问题讨论】:
标签: python matplotlib pyqt pyqt5