python - 重叠小部件的定位
问题描述
我正在尝试创建一个显示,其中您matplotlib
在后台有一个画布,在前台有一个重叠的小部件,显示有关绘制数据的一些任意信息。这本身我基本上已经实现了,但是,我在对齐方面遇到了麻烦。
我希望重叠的小部件(在下面的示例中QGroupBox
)与 的左下角对齐axes
,并且在窗口大小更改时也响应。问题是我不知道如何正确更改两个重叠小部件的相对位置。
我找到了这个答案(下面称为方法 1),它使用QAlignment
,但是一旦设置,QGroupBox
似乎对任何类型的位置变化都没有反应。也许可以添加边距并动态更改它们?
我发现的另一种方法是这个(下面称为方法 2),它使用绝对定位,因此不会随着调整窗口大小而改变。也许这个更有意义?但是,QGroupBox
每次调整窗口大小时,都需要进行一些转换和信号处理来重新定位。但不知何故,我没有设法正确地进行转换。
最后,我还发现了这个,处理锚点,但我不知道它们是如何工作的,也不知道它们是否是常规 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_())
解决方案
解决方案是将 QGroupBox 设置为画布的子级,并使用轴 bbox 的位置更改位置:
import sys
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.patheffects as PathEffects
from PyQt5.QtCore import Qt, QPoint, QTimer
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 = Figure()
self.canvas = FigureCanvas(self.fig)
self.ax = self.canvas.figure.subplots()
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.statsBox = QGroupBox("Stats:", self.canvas)
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)
self.statsBox.setFixedSize(self.statsBox.sizeHint())
self.position_statsBox()
def resizeEvent(self, event):
super().resizeEvent(event)
self.position_statsBox()
def position_statsBox(self):
x0, y0, x1, y1 = self.ax.bbox.extents
p = QPoint(int(x0), int(y1))
p -= QPoint(0, self.statsBox.height())
p += QPoint(0, 6) # FIXME
self.statsBox.move(p)
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_())
推荐阅读
- reactjs - 动态链接在 react-app 的构建中不起作用
- python - 如何在python中对字符串进行子串化?
- excel - VBA:将子程序包含在条件中
- android - 插页式广告关闭后如何进行下一个活动而不是相同的活动?
- javascript - Vue JS 2:在 beforeCreate 之后停止生命周期
- java - 将 Intellij 键盘设置映射到 Visual Studio
- karate - 如何根据条件将值为 Null 的新键添加到动态响应体中
- python - 没有找到 PyJWT<3.0.0,>=2.0.1 的匹配分布
- javascript - 在 useEffect 调用后,React Hooks 改变了 asycn 函数内部数组的状态
- python - 如何在 numba 中使用 numpy 函数