将垂直滚动条添加到嵌入式 matplotlib canvas,同时保持其水平大小占据整个 QScrollArea
Add vertical scroll bar to embedded matplotlib canvas while keeping its horizontal size occupying the entire QScrollArea
我在 pyside2
中创建了一个带有嵌入式 matplotlib
canvas 的 GUI。我打算在这个 canvas 上显示很多信息,所以我需要更多的垂直 space。我想我可以将 canvas 放在 QScrollArea
中并创建一个 "tall" 图,其中一些子图彼此下方。
接近我想要的,但我根本不想要水平滚动。相反,我希望根据 GUI,我的 canvas 到 expand/shrink,完全占据 QScrollArea
内可用的水平 space , 同时仍然只允许垂直滚动图形。
以下代码是没有任何滚动条的 MRE:
import sys
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (QApplication, QHBoxLayout, QMainWindow,
QPushButton, QVBoxLayout, QWidget)
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as Canvas,
NavigationToolbar2QT as Navbar)
from matplotlib.pyplot import Figure
class MyApp(QMainWindow):
def __init__(self) -> None:
super().__init__()
# Main Window setup
self.showMaximized()
self.frame = QWidget(self)
self.setCentralWidget(self.frame)
main_layout = QHBoxLayout()
self.frame.setLayout(main_layout)
# Buttons (left column)
buttons_row = QHBoxLayout()
self.plot_above_button = QPushButton(self, text='Plot Data Above')
self.plot_above_button.clicked.connect(self.plot_above)
buttons_row.addWidget(self.plot_above_button)
self.plot_below_button = QPushButton(self, text='Plot Data Below')
self.plot_below_button.clicked.connect(self.plot_below)
buttons_row.addWidget(self.plot_below_button)
main_layout.addLayout(buttons_row)
# Plot (right column)
right_column = QVBoxLayout()
self.fig = Figure(facecolor='white')
self.ax1 = self.fig.add_axes([0.1, 0.3, 0.8, 0.7])
self.ax2 = self.fig.add_axes([0.1, 0.1, 0.8, 0.1])
self.canvas = Canvas(self.fig)
self.canvas.setParent(self)
right_column.addWidget(self.canvas)
right_column.addWidget(Navbar(self.canvas, self.frame))
main_layout.addLayout(right_column)
def plot_above(self):
self.ax1.plot([1, 2], [3, 4])
self.canvas.draw()
def plot_below(self):
self.ax2.plot([1, 2], [3, 4])
self.canvas.draw()
if __name__ == '__main__':
app = QApplication()
gui = MyApp()
gui.show()
sys.exit(app.exec_())
这是我在执行上面的代码时看到的 GUI:
如您所见,canvas 占据了整个可用的 space,垂直和水平。除了缺少垂直滚动条,这正是我想要的。
现在,如果我尝试将 self.canvas
放在 QScrollArea
中,方法是将这些行添加到我的代码中:
from PySide2.QtWidgets import QScrollArea # additional import
# ...
self.canvas.setParent(self) # this is old code
# new code starts here
scroll_area = QScrollArea()
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll_area.horizontalScrollBar().setEnabled(False)
scroll_area.setWidget(self.canvas)
right_column.addWidget(scroll_area)
right_column.addWidget(Navbar(self.canvas, self.frame)) # this is old code
# ...
我现在得到:
不是自动调整其大小,而是 "sits" 在 QScrollArea
中,用默认的 figsize 值初始化。
我也尝试手动调整图形的大小,例如:
self.fig = Figure(facecolor='white', figsize=(11, 12))
这给了我:
这非常接近我想要的,但也很老套:我凭经验根据 canvas 在屏幕上的样子选择了 figsize (11, 12)
,但这些值可能不同在另一个屏幕中,或者如果我在左侧添加额外的小部件。 canvas 也不会随主 window.
一起调整大小
有没有办法实现我想要的?
又试了一天,终于弄明白了。我在这里写下答案,以防将来有人遇到这个问题。
我们可以重新实现 GUI 的 resizeEvent
以将 canvas 宽度调整为 QScrollArea
当前宽度:
def resizeEvent(self, e):
self.canvas.resize(self.scroll_area.width(), self.canvas.height())
super().resizeEvent(e)
如果我在 init 方法末尾调用 self.showMaximized()
,调整大小将自动触发。这意味着当 window 首次显示时 canvas 会正确调整大小,并且当我更改 window 大小时它会适应。
这是结果:
以及用于生成 GUI 的完整代码:
import sys
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (QApplication, QHBoxLayout, QMainWindow,
QPushButton, QScrollArea, QVBoxLayout, QWidget)
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as Canvas,
NavigationToolbar2QT as Navbar)
from matplotlib.pyplot import Figure
class MyApp(QMainWindow):
def __init__(self) -> None:
super().__init__()
# Main Window setup
self.frame = QWidget(self)
self.setCentralWidget(self.frame)
main_layout = QHBoxLayout()
self.frame.setLayout(main_layout)
# Buttons (left column)
buttons_row = QHBoxLayout()
self.plot_above_button = QPushButton(self, text='Plot Data Above')
self.plot_above_button.clicked.connect(self.plot_above)
buttons_row.addWidget(self.plot_above_button)
self.plot_below_button = QPushButton(self, text='Plot Data Below')
self.plot_below_button.clicked.connect(self.plot_below)
buttons_row.addWidget(self.plot_below_button)
main_layout.addLayout(buttons_row)
# Plot (right column)
right_column = QVBoxLayout()
self.fig = Figure(facecolor='white', figsize=(12, 12))
self.ax1 = self.fig.add_axes([0.1, 0.3, 0.8, 0.7])
self.ax2 = self.fig.add_axes([0.1, 0.1, 0.8, 0.1])
self.canvas = Canvas(self.fig)
self.canvas.setParent(self)
self.scroll_area = QScrollArea()
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scroll_area.horizontalScrollBar().setEnabled(False)
self.scroll_area.setWidget(self.canvas)
right_column.addWidget(self.scroll_area)
right_column.addWidget(Navbar(self.canvas, self.frame))
main_layout.addLayout(right_column)
self.showMaximized()
def plot_above(self):
self.ax1.plot([1, 2], [3, 4])
self.canvas.draw()
def plot_below(self):
self.ax2.plot([1, 2], [3, 4])
self.canvas.draw()
def resizeEvent(self, e):
self.canvas.resize(self.scroll_area.width(), self.canvas.height())
super().resizeEvent(e)
if __name__ == '__main__':
app = QApplication()
gui = MyApp()
gui.show()
sys.exit(app.exec_())
我在 pyside2
中创建了一个带有嵌入式 matplotlib
canvas 的 GUI。我打算在这个 canvas 上显示很多信息,所以我需要更多的垂直 space。我想我可以将 canvas 放在 QScrollArea
中并创建一个 "tall" 图,其中一些子图彼此下方。
QScrollArea
内可用的水平 space , 同时仍然只允许垂直滚动图形。
以下代码是没有任何滚动条的 MRE:
import sys
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (QApplication, QHBoxLayout, QMainWindow,
QPushButton, QVBoxLayout, QWidget)
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as Canvas,
NavigationToolbar2QT as Navbar)
from matplotlib.pyplot import Figure
class MyApp(QMainWindow):
def __init__(self) -> None:
super().__init__()
# Main Window setup
self.showMaximized()
self.frame = QWidget(self)
self.setCentralWidget(self.frame)
main_layout = QHBoxLayout()
self.frame.setLayout(main_layout)
# Buttons (left column)
buttons_row = QHBoxLayout()
self.plot_above_button = QPushButton(self, text='Plot Data Above')
self.plot_above_button.clicked.connect(self.plot_above)
buttons_row.addWidget(self.plot_above_button)
self.plot_below_button = QPushButton(self, text='Plot Data Below')
self.plot_below_button.clicked.connect(self.plot_below)
buttons_row.addWidget(self.plot_below_button)
main_layout.addLayout(buttons_row)
# Plot (right column)
right_column = QVBoxLayout()
self.fig = Figure(facecolor='white')
self.ax1 = self.fig.add_axes([0.1, 0.3, 0.8, 0.7])
self.ax2 = self.fig.add_axes([0.1, 0.1, 0.8, 0.1])
self.canvas = Canvas(self.fig)
self.canvas.setParent(self)
right_column.addWidget(self.canvas)
right_column.addWidget(Navbar(self.canvas, self.frame))
main_layout.addLayout(right_column)
def plot_above(self):
self.ax1.plot([1, 2], [3, 4])
self.canvas.draw()
def plot_below(self):
self.ax2.plot([1, 2], [3, 4])
self.canvas.draw()
if __name__ == '__main__':
app = QApplication()
gui = MyApp()
gui.show()
sys.exit(app.exec_())
这是我在执行上面的代码时看到的 GUI:
如您所见,canvas 占据了整个可用的 space,垂直和水平。除了缺少垂直滚动条,这正是我想要的。
现在,如果我尝试将 self.canvas
放在 QScrollArea
中,方法是将这些行添加到我的代码中:
from PySide2.QtWidgets import QScrollArea # additional import
# ...
self.canvas.setParent(self) # this is old code
# new code starts here
scroll_area = QScrollArea()
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll_area.horizontalScrollBar().setEnabled(False)
scroll_area.setWidget(self.canvas)
right_column.addWidget(scroll_area)
right_column.addWidget(Navbar(self.canvas, self.frame)) # this is old code
# ...
我现在得到:
不是自动调整其大小,而是 "sits" 在 QScrollArea
中,用默认的 figsize 值初始化。
我也尝试手动调整图形的大小,例如:
self.fig = Figure(facecolor='white', figsize=(11, 12))
这给了我:
这非常接近我想要的,但也很老套:我凭经验根据 canvas 在屏幕上的样子选择了 figsize (11, 12)
,但这些值可能不同在另一个屏幕中,或者如果我在左侧添加额外的小部件。 canvas 也不会随主 window.
有没有办法实现我想要的?
又试了一天,终于弄明白了。我在这里写下答案,以防将来有人遇到这个问题。
我们可以重新实现 GUI 的 resizeEvent
以将 canvas 宽度调整为 QScrollArea
当前宽度:
def resizeEvent(self, e):
self.canvas.resize(self.scroll_area.width(), self.canvas.height())
super().resizeEvent(e)
如果我在 init 方法末尾调用 self.showMaximized()
,调整大小将自动触发。这意味着当 window 首次显示时 canvas 会正确调整大小,并且当我更改 window 大小时它会适应。
这是结果:
以及用于生成 GUI 的完整代码:
import sys
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (QApplication, QHBoxLayout, QMainWindow,
QPushButton, QScrollArea, QVBoxLayout, QWidget)
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as Canvas,
NavigationToolbar2QT as Navbar)
from matplotlib.pyplot import Figure
class MyApp(QMainWindow):
def __init__(self) -> None:
super().__init__()
# Main Window setup
self.frame = QWidget(self)
self.setCentralWidget(self.frame)
main_layout = QHBoxLayout()
self.frame.setLayout(main_layout)
# Buttons (left column)
buttons_row = QHBoxLayout()
self.plot_above_button = QPushButton(self, text='Plot Data Above')
self.plot_above_button.clicked.connect(self.plot_above)
buttons_row.addWidget(self.plot_above_button)
self.plot_below_button = QPushButton(self, text='Plot Data Below')
self.plot_below_button.clicked.connect(self.plot_below)
buttons_row.addWidget(self.plot_below_button)
main_layout.addLayout(buttons_row)
# Plot (right column)
right_column = QVBoxLayout()
self.fig = Figure(facecolor='white', figsize=(12, 12))
self.ax1 = self.fig.add_axes([0.1, 0.3, 0.8, 0.7])
self.ax2 = self.fig.add_axes([0.1, 0.1, 0.8, 0.1])
self.canvas = Canvas(self.fig)
self.canvas.setParent(self)
self.scroll_area = QScrollArea()
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scroll_area.horizontalScrollBar().setEnabled(False)
self.scroll_area.setWidget(self.canvas)
right_column.addWidget(self.scroll_area)
right_column.addWidget(Navbar(self.canvas, self.frame))
main_layout.addLayout(right_column)
self.showMaximized()
def plot_above(self):
self.ax1.plot([1, 2], [3, 4])
self.canvas.draw()
def plot_below(self):
self.ax2.plot([1, 2], [3, 4])
self.canvas.draw()
def resizeEvent(self, e):
self.canvas.resize(self.scroll_area.width(), self.canvas.height())
super().resizeEvent(e)
if __name__ == '__main__':
app = QApplication()
gui = MyApp()
gui.show()
sys.exit(app.exec_())