将垂直滚动条添加到嵌入式 matplotlib 画布,同时保持其水平大小占据整个 QScrollArea

Posted

技术标签:

【中文标题】将垂直滚动条添加到嵌入式 matplotlib 画布,同时保持其水平大小占据整个 QScrollArea【英文标题】:Add vertical scroll bar to embedded matplotlib canvas while keeping its horizontal size occupying the entire QScrollArea 【发布时间】:2020-04-16 23:17:33 【问题描述】:

我在 pyside2 中创建了一个 GUI,其中嵌入了 matplotlib 画布。我打算在这个画布上显示很多信息,所以我需要更多的垂直空间。我想我可以将画布放在 QScrollArea 中,并创建一个“高”的图形,其中一些子图彼此下方。

This answer 接近我想要的,但我根本不想要水平滚动。相反,我希望我的画布根据 GUI 扩展/缩小,完全占据 QScrollArea 内的可用水平空间,同时仍然只允许图形的垂直滚动。

以下代码是没有任何滚动条的 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:

如您所见,画布在垂直和水平方向上占据了整个可用空间。除了缺少垂直滚动条,这正是我想要的。

现在,如果我尝试将 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
        # ...

我现在结束了:

图形不会自动调整其大小,而是“坐在”QScrollArea 内,并使用默认的 figsize 值进行初始化。

我还尝试手动调整图形大小,例如:

        self.fig = Figure(facecolor='white', figsize=(11, 12))

这给了我:

这非常接近我想要的,但也非常 hacky:我根据画布在屏幕上的外观凭经验选择了 figsize (11, 12),但是这些值在另一个屏幕上可能会有所不同,或者如果我添加额外的左侧的小部件。画布也不会随主窗口一起调整大小。

有没有办法实现我想要的?

【问题讨论】:

试试scroll_area.setWidgetResizable(True) @alec 感谢您的评论,我尝试了您的建议,但不幸的是结果与我的第一张图片相同(即没有滚动条)。 【参考方案1】:

在尝试另一天解决这个问题后,我终于想通了。我在这里写下答案,以防将来有人偶然发现这个问题。

我们可以重新实现 GUI 的 resizeEvent 以将画布宽度调整为 QScrollArea 当前宽度:

    def resizeEvent(self, e):
        self.canvas.resize(self.scroll_area.width(), self.canvas.height())
        super().resizeEvent(e)

如果我在 init 方法结束时调用self.showMaximized(),调整大小将自动触发。这意味着当窗口首次显示时画布会正确调整大小,并且会在我更改窗口大小时进行调整。

这是结果:

以及用于生成 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_())

【讨论】:

以上是关于将垂直滚动条添加到嵌入式 matplotlib 画布,同时保持其水平大小占据整个 QScrollArea的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5实现-将Matplotlib图像嵌入到Scoll Area中(显示滚动条)

将垂直滚动条添加到面板

将垂直滚动条添加到 shinyjs 下拉按钮 - 闪亮

如何将鼠标滚轮滚动添加到垂直滚动条或滚动区域?

如何以编程方式将垂直滚动条添加到组合框

当网格达到一定高度时,如何将垂直滚动条添加到 ext 网格面板?