QGraphicsView PyQt5 中子图 Matplotlib 图形中的交互式复选框(或按钮)

问题描述 投票:0回答:1

我正在尝试在一个小项目中优化示例(https://matplotlib.org/stable/gallery/widgets/check_buttons.html)。 我有一个有效的例子:

from PyQt5.QtWidgets import QApplication

import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.widgets import CheckButtons
from ShortCircuitCalc.gui.windows import CustomGraphicView, MainWindow

from PyQt5 import QtWidgets


class Window(CustomGraphicView):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)

        self.canvas = FigureCanvas(Figure(figsize=(5, 3)))

        t = np.arange(0.0, 2.0, 0.01)
        s0 = np.sin(2 * np.pi * t)
        s1 = np.sin(4 * np.pi * t)
        s2 = np.sin(6 * np.pi * t)

        ax = self.canvas.figure.subplots()
        l0, = ax.plot(t, s0, visible=False, lw=2, color="k", label="2 Hz")
        l1, = ax.plot(t, s1, lw=2, color="r", label="4 Hz")
        l2, = ax.plot(t, s2, lw=2, color="g", label="6 Hz")

        self.canvas.figure.subplots_adjust(left=0.2)

        self.lines = [l0, l1, l2]

        rax = self.canvas.figure.add_axes([0.05, 0.4, 0.1, 0.15])

        self.labels = [str(line.get_label()) for line in self.lines]
        visibility = [line.get_visible() for line in self.lines]

        self.check = CheckButtons(rax, self.labels, visibility)
        self.check.on_clicked(self.is_click)

    def is_click(self, label):
        index = self.labels.index(label)
        self.lines[index].set_visible(not self.lines[index].get_visible())
        self.canvas.draw()


def main():
    import sys

    app = QApplication(sys.argv)

    w = MainWindow()
    r = Window()
    w.resultView.scene = QtWidgets.QGraphicsScene()
    w.resultView.scene.addWidget(r.canvas)
    w.resultView.setScene(w.resultView.scene)
    w.show()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

我正确地得到了下一个工作模型:

Correctly one plot model

但是我想为我图中的每个子图获得这样一个活跃的模型。 我尝试修改上面的示例,甚至得到了我需要的布局,但其上的按钮不是交互式的。我如何改进它以获得每个交互式子图。目前我有这段代码和这些结果:

from PyQt5.QtWidgets import QApplication

import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.widgets import CheckButtons
from ShortCircuitCalc.gui.windows import CustomGraphicView, MainWindow

from PyQt5 import QtWidgets


class Window(CustomGraphicView):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)

        ncols = 5
        nrows = 8

        self.canvas = FigureCanvas(Figure(figsize=(ncols * 5, nrows * 3)))

        t = np.arange(0.0, 2.0, 0.01)
        s0 = np.sin(2 * np.pi * t)
        s1 = np.sin(4 * np.pi * t)
        s2 = np.sin(6 * np.pi * t)

        ax = self.canvas.figure.subplots(ncols=ncols, nrows=nrows)
        for col in range(ncols):
            for row in range(nrows):
                (l0,) = ax[row, col].plot(t, s0, visible=False, lw=2, color="k", label="2 Hz")
                (l1,) = ax[row, col].plot(t, s1, lw=2, color="r", label="4 Hz")
                (l2,) = ax[row, col].plot(t, s2, lw=2, color="g", label="6 Hz")
                lines = [l0, l1, l2]
                rax = ax[row, col].inset_axes([0.05, 0.4, 0.1, 0.15])
                labels = [str(line.get_label()) for line in lines]
                visibility = [line.get_visible() for line in lines]
                self.check = CheckButtons(rax, labels, visibility)
                self.check.on_clicked(self.is_click)

        self.canvas.figure.subplots_adjust(left=0.2)

    def is_click(self, label):
        # index = self.labels.index(label)
        # self.lines[index].set_visible(not self.lines[index].get_visible())
        # self.canvas.draw()
        print('Button is pressed')


def main():
    import sys

    app = QApplication(sys.argv)

    w = MainWindow()
    r = Window()
    w.resultView.scene = QtWidgets.QGraphicsScene()
    w.resultView.scene.addWidget(r.canvas)
    w.resultView.setScene(w.resultView.scene)
    w.show()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Uncorrect model with inactive buttons

在我的代码中,CustomGraphicView 只是一个带有一些导航调整的 QGraphicsView。目前CustomGraphicView的实现及其用例如下:

import sys

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import (
    FigureCanvasQTAgg as FigCanvas,
    NavigationToolbar2QT as NavToolbar,
)
from PyQt5 import QtWidgets, QtCore, QtGui


class CustomGraphicView(QtWidgets.QGraphicsView):
    def __init__(self,
                 parent: QtWidgets = None,
                 figure: matplotlib.figure.Figure = None,
                 title: str = 'Viewer') -> None:
        super(CustomGraphicView, self).__init__(parent)

        self._title = title
        self._figure = figure
        self._scene = QtWidgets.QGraphicsScene()

        if parent is not None:
            self._canvas = None
        else:
            self._canvas = FigCanvas(self._figure)
            self._scene.addWidget(self._canvas)

        self.setScene(self._scene)
        self.setWindowTitle(self._title)
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        # Start viewing position
        self.horizontalScrollBar().setSliderPosition(1)
        self.verticalScrollBar().setSliderPosition(1)

        self._zoom = 0
        self._mousePressed = False
        self._drag_pos = None

        self.save_model_action = QtWidgets.QAction('Save model as ...', self)
        self.save_model_action.triggered.connect(self.save_model)

        self.save_fragment_action = QtWidgets.QAction('Save fragment as ...', self)
        self.save_fragment_action.triggered.connect(self.save_fragment)

    def set_figure(self, figure):
        self._figure = figure
        self._canvas = FigCanvas(self._figure)
        self._scene.addWidget(self._canvas)
        self.setScene(self._scene)

    def mousePressEvent(self, event: QtCore.Qt.MouseButton.LeftButton) -> None:

        if event.button() == QtCore.Qt.MouseButton.LeftButton:
            self._mousePressed = True
            self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.OpenHandCursor))
            self._drag_pos = event.pos()
            event.accept()

        else:
            super(CustomGraphicView, self).mousePressEvent(event)

    def mouseMoveEvent(self, event: QtCore.Qt.MouseButton.LeftButton) -> None:

        if self._mousePressed:
            new_pos = event.pos()
            diff = new_pos - self._drag_pos
            self._drag_pos = new_pos
            self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - diff.x())
            self.verticalScrollBar().setValue(self.verticalScrollBar().value() - diff.y())
            self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.OpenHandCursor))

        else:
            super(CustomGraphicView, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event: QtCore.Qt.MouseButton.LeftButton) -> None:

        if event.button() == QtCore.Qt.MouseButton.LeftButton:
            self._mousePressed = False
            self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor))

        super(CustomGraphicView, self).mouseReleaseEvent(event)

    def wheelEvent(self, event: QtCore.Qt.KeyboardModifier.ControlModifier) -> None:

        modifiers = QtWidgets.QApplication.keyboardModifiers()

        if modifiers == QtCore.Qt.KeyboardModifier.ControlModifier:

            if event.angleDelta().y() > 0:
                factor = 1.25
                self._zoom += 1
            else:
                factor = 0.8
                self._zoom -= 1

            if self._zoom > -1:
                self.scale(factor, factor)
            else:
                self._zoom = 0

        else:
            super(CustomGraphicView, self).wheelEvent(event)

    def contextMenuEvent(self, event: QtGui.QContextMenuEvent) -> None:

        # Creating context menu
        menu = QtWidgets.QMenu(self)
        menu.addAction(self.save_model_action)

        # Creating / adding separator
        separator = QtWidgets.QAction(self)
        separator.setSeparator(True)
        menu.addAction(separator)

        menu.addAction(self.save_fragment_action)
        menu.exec(event.globalPos())

    def save_model(self):
        NavToolbar.save_figure(self._figure)

    def save_fragment(self):
        rect_region = QtCore.QRect(0, 0,
                                   self.width() - self.verticalScrollBar().width(),
                                   self.height() - self.horizontalScrollBar().height())
        pixmap = self.grab(rect_region)
        fname = QtWidgets.QFileDialog.getSaveFileName(self, 'Save fragment as ...', 'image.png',
                                                      'Portable Network Graphics (*.png);;'
                                                      'Joint Photographic Experts Group (*.jpeg *.jpg)')[0]
        if fname:
            pixmap.save(fname)


if __name__ == '__main__':

    # Some figure
    fig, ax = plt.subplots()

    t = np.arange(0.0, 2.0, 0.01)
    s0 = np.sin(2 * np.pi * t)
    s1 = np.sin(4 * np.pi * t)
    s2 = np.sin(6 * np.pi * t)

    ax.plot(t, s0, lw=2, color="k", label="2 Hz")
    ax.plot(t, s1, lw=2, color="r", label="4 Hz")
    ax.plot(t, s2, lw=2, color="g", label="6 Hz")

    app = QtWidgets.QApplication(sys.argv)
    window = CustomGraphicView(parent=None, figure=fig)
    window.show()
    sys.exit(app.exec_())

    # Or i may
    # app = QtWidgets.QApplication(sys.argv)
    # window = CustomGraphicView(parent=None, figure=None)
    # window.set_figure(fig)
    # window.show()
    # sys.exit(app.exec_())
python matplotlib pyqt5 subplot
1个回答
0
投票

使用字典和以下方法解决了这个问题,现在我得到了我期望的模型的行为。

from collections import namedtuple

import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.widgets import CheckButtons
from ShortCircuitCalc.gui.windows import CustomGraphicView, MainWindow

from PyQt5.QtWidgets import QApplication
from PyQt5 import QtWidgets


class Window(CustomGraphicView):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)

        ncols = 2
        nrows = 3

        self.canvas = FigureCanvas(Figure(figsize=(ncols * 5, nrows * 3)))

        t = np.arange(0.0, 2.0, 0.01)
        s0 = np.sin(2 * np.pi * t)
        s1 = np.sin(4 * np.pi * t)
        s2 = np.sin(6 * np.pi * t)

        self.ax = self.canvas.figure.subplots(ncols=ncols, nrows=nrows)
        self.checks = dict()
        for row in range(nrows):
            for col in range(ncols):
                ax = self.ax[row, col]
                # Check buttons work correctly!
                rax = self.canvas.figure.add_axes([ax.get_position().x0, ax.get_position().y0,
                                                   ax.get_position().width / 2, ax.get_position().height / 2],
                                                  frameon=False)
                # Check buttons in inserted axes is not interactive!!!
                # rax = ax.inset_axes([0.05, 0.4, 0.1, 0.15])
                (l0,) = ax.plot(t, s0, visible=False, lw=2, color="k", label="2 Hz")
                (l1,) = ax.plot(t, s1, lw=2, color="r", label="4 Hz")
                (l2,) = ax.plot(t, s2, lw=2, color="g", label="6 Hz")
                lines = [l0, l1, l2]
                labels = [str(line.get_label()) for line in lines]
                visibility = [line.get_visible() for line in lines]
                check = CheckButtons(rax, labels, visibility)
                check.on_clicked(lambda label, i=row, j=col: self.is_click(label, i, j))
                DictButton = namedtuple("DictButton", ["check", "lines", "labels", "visibility"])
                self.checks[row, col] = DictButton(check, lines, labels, visibility)

    def is_click(self, label, i, j):
        index = self.checks[i, j].labels.index(label)
        self.checks[i, j].lines[index].set_visible(
            not self.checks[i, j].lines[index].get_visible()
        )
        self.canvas.draw()
        print('Button is pressed')


def main():
    import sys

    app = QApplication(sys.argv)

    w = MainWindow()
    r = Window()
    w.resultView.scene = QtWidgets.QGraphicsScene()
    w.resultView.scene.addWidget(r.canvas)
    w.resultView.setScene(w.resultView.scene)
    w.show()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

重要提示!!! 不要在插入轴中使用复选按钮。

现在我得到了我需要的东西: Correct result

我是 PyQt 和 matplotlib 的新手,这段代码只是一个测试版本,我自己测试了新功能,如果这些想法对其他人有帮助,我将很高兴。

© www.soinside.com 2019 - 2024. All rights reserved.