从 Matplotlib 图中提取信息并将其显示在 PyQt5 GUI 中

Posted

技术标签:

【中文标题】从 Matplotlib 图中提取信息并将其显示在 PyQt5 GUI 中【英文标题】:Extracting information from a Matplotlib plot and displaying it in a PyQt5 GUI 【发布时间】:2020-07-15 19:39:18 【问题描述】:

我想知道 PyQt5 GUI 和嵌入式 matplotlib 绘图如何在它们的属性之间交互/传递信息。具体来说,我了解内置 Qt 小部件的信号和插槽是如何工作的,但我不知道它如何扩展到自定义小部件或非原生对象,例如 matplotlib 图。

示例:

考虑下面的例子 - 我创建了一个 Qt GUI,它在左侧显示一个 matplotlib (mpl) 图,在右侧有 4 个 QLineEdit 对象。您可以在 mpl 图内单击并拖动以绘制矩形。 我想知道如何将 QLineEdit 框连接到矩形的角坐标。单击并拖动矩形时,我希望顶部两行编辑显示左下角矩形角的 X 和 Y 数据,底部两行编辑实时显示右上角矩形角的 X 和 Y 数据.相反,我还希望 QLineEdit 框中坐标的编辑反映在 mpl 图中。这是一张供参考的图片:

相关代码如下。非常感谢任何建议!

代码

from PyQt5 import QtCore, QtGui, QtWidgets

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar)

import numpy as np

class topLevelWindow(QtWidgets.QMainWindow):

    def __init__(self):

        super().__init__()
        # Create central widget and set layout
        self.centralwidget = QtWidgets.QWidget(self)
        self.setCentralWidget(self.centralwidget)
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)

        # Create display frame, assign to parent layout, and create its own layout
        self.displayFrame = QtWidgets.QFrame(self.centralwidget)
        self.gridLayout.addWidget(self.displayFrame, 1, 0, 1, 1)
        self.verticalLayout_1 = QtWidgets.QVBoxLayout(self.displayFrame)

        # Add MplPlot object to display frame and assign to parent layout
        self.plotWidget = MplPlot()
        self.verticalLayout_1.addWidget(self.plotWidget)

        # Create numbers gram, assign to parent layout, and create its own layout
        self.numFrame = QtWidgets.QFrame(self.centralwidget)
        self.gridLayout.addWidget(self.numFrame, 1, 1, 1, 1)
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.numFrame)

        # Add QLineEdits to  numbers frame and assign to parent layout
        self.bottomLeftCornerX = QtWidgets.QLineEdit(self.numFrame)
        self.bottomLeftCornerY = QtWidgets.QLineEdit(self.numFrame)
        self.topRightCornerX = QtWidgets.QLineEdit(self.numFrame)
        self.topRightCornery = QtWidgets.QLineEdit(self.numFrame)
        self.verticalLayout_2.addWidget(self.bottomLeftCornerX)
        self.verticalLayout_2.addWidget(self.bottomLeftCornerY)
        self.verticalLayout_2.addWidget(self.topRightCornerX)
        self.verticalLayout_2.addWidget(self.topRightCornery)

        QtCore.QMetaObject.connectSlotsByName(self)

        self.show()



class MplPlot(FigureCanvasQTAgg):
    def onclick(self, event):
        self.bottomLeftX = event.xdata
        self.bottomLeftY = event.ydata
        self.topRightX = event.xdata
        self.topRightY = event.ydata

        self.x = np.array(
            [
                self.bottomLeftX,
                self.bottomLeftX,
                self.topRightX,
                self.topRightX,
                self.bottomLeftX
            ]
        )
        self.y = np.array(
            [
                self.bottomLeftY,
                self.topRightY,
                self.topRightY,
                self.bottomLeftY,
                self.bottomLeftY
            ]
        )

        # Update the data
        self.ax.lines[0].set_xdata(self.x)
        self.ax.lines[0].set_ydata(self.y)

        self.draw()

        self.moving = True

    def onrelease(self, event):
        self.topRightX = event.xdata
        self.topRightY = event.ydata

        self.x = np.array(
            [
                self.bottomLeftX,
                self.bottomLeftX,
                self.topRightX,
                self.topRightX,
                self.bottomLeftX
            ]
        )
        self.y = np.array(
            [
                self.bottomLeftY,
                self.topRightY,
                self.topRightY,
                self.bottomLeftY,
                self.bottomLeftY
            ]
        )

        # Update the data
        self.ax.lines[0].set_xdata(self.x)
        self.ax.lines[0].set_ydata(self.y)

        self.draw()

        self.moving = False

    def onmotion(self, event):
        if not self.moving:
            return

        self.topRightX = event.xdata
        self.topRightY = event.ydata

        self.x = np.array(
            [
                self.bottomLeftX,
                self.bottomLeftX,
                self.topRightX,
                self.topRightX,
                self.bottomLeftX
            ]
        )
        self.y = np.array(
            [
                self.bottomLeftY,
                self.topRightY,
                self.topRightY,
                self.bottomLeftY,
                self.bottomLeftY
            ]
        )

        self.ax.lines[0].set_xdata(self.x)
        self.ax.lines[0].set_ydata(self.y)

        self.draw()


    def __init__(self, parent=None):
        fig = Figure()
        super(MplPlot, self).__init__(fig)
        self.setParent(parent)
        self.regionUpdated = QtCore.pyqtSignal()

        # Create a figure with axes
        self.ax = self.figure.add_subplot(111)
        self.ax.set_xlim((-100, 100))
        self.ax.set_ylim((-100, 100))
        self.ax.plot([0],[0])

        # Initialize junk values
        self.bottomLeftX = 0
        self.bottomLeftY = 0
        self.topRightX = 0
        self.topRightY = 0
        self.x = 0
        self.y = 0

        # Set moving flag false (determines if mouse is being clicked and dragged inside plot). Set graph snap
        self.moving = False

        # Set up connectivity
        self.cid = self.mpl_connect("button_press_event", self.onclick)
        self.cid = self.mpl_connect("button_release_event", self.onrelease)
        self.cid = self.mpl_connect("motion_notify_event", self.onmotion)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = topLevelWindow()
    sys.exit(app.exec_())

【问题讨论】:

【参考方案1】:

逻辑是创建一个发出该信息的信号,我也看到了重复的代码,所以我在一个函数中减少了它。

from PyQt5 import QtCore, QtGui, QtWidgets

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import (
    FigureCanvasQTAgg,
    NavigationToolbar2QT as NavigationToolbar,
)

import numpy as np


class topLevelWindow(QtWidgets.QMainWindow):
    def __init__(self):

        super().__init__()
        # Create central widget and set layout
        self.centralwidget = QtWidgets.QWidget(self)
        self.setCentralWidget(self.centralwidget)
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)

        # Create display frame, assign to parent layout, and create its own layout
        self.displayFrame = QtWidgets.QFrame(self.centralwidget)
        self.gridLayout.addWidget(self.displayFrame, 1, 0, 1, 1)
        self.verticalLayout_1 = QtWidgets.QVBoxLayout(self.displayFrame)

        # Add MplPlot object to display frame and assign to parent layout
        self.plotWidget = MplPlot()
        self.verticalLayout_1.addWidget(self.plotWidget)

        # Create numbers gram, assign to parent layout, and create its own layout
        self.numFrame = QtWidgets.QFrame(self.centralwidget)
        self.gridLayout.addWidget(self.numFrame, 1, 1, 1, 1)
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.numFrame)

        # Add QLineEdits to  numbers frame and assign to parent layout
        self.bottomLeftCornerX = QtWidgets.QLineEdit(self.numFrame)
        self.bottomLeftCornerY = QtWidgets.QLineEdit(self.numFrame)
        self.topRightCornerX = QtWidgets.QLineEdit(self.numFrame)
        self.topRightCornery = QtWidgets.QLineEdit(self.numFrame)
        self.verticalLayout_2.addWidget(self.bottomLeftCornerX)
        self.verticalLayout_2.addWidget(self.bottomLeftCornerY)
        self.verticalLayout_2.addWidget(self.topRightCornerX)
        self.verticalLayout_2.addWidget(self.topRightCornery)

        self.plotWidget.regionUpdated.connect(self.update_le)

    @QtCore.pyqtSlot(QtCore.QRectF)
    def update_le(self, region):
        self.bottomLeftCornerX.setText(str(region.left()))
        self.bottomLeftCornerY.setText(str(region.top()))
        self.topRightCornerX.setText(str(region.right()))
        self.topRightCornery.setText(str(region.bottom()))


class MplPlot(FigureCanvasQTAgg):
    regionUpdated = QtCore.pyqtSignal(QtCore.QRectF)

    def __init__(self, parent=None):
        fig = Figure()
        super(MplPlot, self).__init__(fig)
        self.setParent(parent)
        # Create a figure with axes
        self.ax = self.figure.add_subplot(111)
        self.ax.set_xlim((-100, 100))
        self.ax.set_ylim((-100, 100))
        self.ax.plot([0], [0])

        # Initialize junk values
        self.bottomLeftX = 0
        self.bottomLeftY = 0
        self.topRightX = 0
        self.topRightY = 0
        self.x = 0
        self.y = 0

        # Set moving flag false (determines if mouse is being clicked and dragged inside plot). Set graph snap
        self.moving = False

        # Set up connectivity
        self.cid = self.mpl_connect("button_press_event", self.onclick)
        self.cid = self.mpl_connect("button_release_event", self.onrelease)
        self.cid = self.mpl_connect("motion_notify_event", self.onmotion)

    def onclick(self, event):
        self.bottomLeftX = event.xdata
        self.bottomLeftY = event.ydata
        self.topRightX = event.xdata
        self.topRightY = event.ydata

        self.update_rect()

        self.moving = True

    def onrelease(self, event):
        self.topRightX = event.xdata
        self.topRightY = event.ydata

        self.update_rect()

        self.moving = False

    def onmotion(self, event):
        if not self.moving:
            return

        self.topRightX = event.xdata
        self.topRightY = event.ydata
        self.update_rect()

    def update_rect(self):
        x = np.array(
            [
                self.bottomLeftX,
                self.bottomLeftX,
                self.topRightX,
                self.topRightX,
                self.bottomLeftX,
            ]
        )
        y = np.array(
            [
                self.bottomLeftY,
                self.topRightY,
                self.topRightY,
                self.bottomLeftY,
                self.bottomLeftY,
            ]
        )

        self.ax.lines[0].set_xdata(x)
        self.ax.lines[0].set_ydata(y)

        if any(
            e is None
            for e in [
                self.topRightX,
                self.topRightY,
                self.bottomLeftX,
                self.bottomLeftY,
            ]
        ):
            return

        rect = QtCore.QRectF(
            self.topRightX, self.topRightY, self.bottomLeftX, self.bottomLeftY
        )
        self.regionUpdated.emit(rect)
        self.draw()


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = topLevelWindow()
    w.show()
    sys.exit(app.exec_())

【讨论】:

以上是关于从 Matplotlib 图中提取信息并将其显示在 PyQt5 GUI 中的主要内容,如果未能解决你的问题,请参考以下文章

从数据库中提取 pdf 并将其显示在我的 cshtml 上

如何使用条形码从 api 中提取信息并将其解析到用户详细信息小部件屏幕 - 在颤振中

突出显示 matplotlib 散点图中的特定点

使用公共 API 从站点中提取 JSON 数据并将其异步显示在页面上

如何从 2 张 excel 中提取数据并将其显示在 VB 表单字段中

如何使用 GeoIP2() django 模型从 IP 地址调用中提取保存的信息以显示在我的 html 中