【问题标题】:PyQT5 with matplotlib figure, the event loop is already runningPyQT5 与 matplotlib 图,事件循环已经在运行
【发布时间】:2020-05-21 12:05:27
【问题描述】:

我有一个程序,通过单击 PyQT 应用程序上的按钮在另一个窗口中显示 matplotlib 图。 当我单击按钮时,该窗口会显示,但在图上什么也没有发生,在控制台中我得到了这个:

QCoreApplication::exec: 事件循环已经在运行

我有 3 个类和 PyQT5 Gui 文件。 3 类从 TextEdits 获取值,调用第二类,它计算并返回带有 x、y 坐标的列表以在 matplotlib 图上显示(第一类)。我找到了不同的方法来解决这个问题,但是有一些简单案例的例子,我不明白如何将它们应用到我的代码中

from PyQt5 import QtCore, QtGui, QtWidgets
from pythongui4 import Ui_MainWindow
from scipy import integrate
from random import randint
from math import sin, cos, pi
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import sys
import time


# GLOBAL CONSTANTS:

g = 9.80665 # (Gravity acceleration on the Earth), m/s^2


class GraphTrajectoryAnimation(animation.FuncAnimation):
    def __init__(self, x, y, color, xlim, ylim, parent=None, m=50, lw=3, width=6, height=4):
        """
         Where x, y - NumPy lists with coordinates, color - color of line (default - choose by Random)
                        height, width, dpi - hw, dpi of the figure from PyLab.
        """
        self.figure, self.ax = plt.subplots(figsize=(width, height))
        self.ax.set_xlim([0, xlim])
        self.ax.set_ylim([0, ylim])
        self.graphfunction, = self.ax.plot([], [], color='blue', lw=2)
        self.dot, = self.ax.plot([], [], 'o', color='red')
        self.x = x
        self.y = y
        self.m = m
        self.color = color
        self.lw = lw

    def animation(self, i, x, y, m, color):
        """ Definition of a matplotlib animation. """
        if i * m >= len(x) or i * m >= len(y):
            self.graphfunction.set_data(x[:i*m], y[:i*m])
        else:
            self.graphfunction.set_data(x[:i*m], y[:i*m])
            self.dot.set_data(x[i*m], y[i*m])

    def graph(self, interval=50):
        """ Definition of cycle FuncAnimation, which call animation. """
        self.graph_id = animation.FuncAnimation(self.figure, self.animation,
                                            fargs=(self.x, self.y, self.m, self.color),
                                            repeat=False, interval=interval, frames=400)

    def showfigure(self):
        """ Call the graph function and displays it on the figure. """
        self.graph()
        plt.show()


class SolveSystemOfADifferentialEquations:
    def __init__(self, k, angle, v0, m, ws, tlimit=20):
        self.k = k
        self.angle = angle
        self.m = m
        self.ws = ws
        self.v0 = v0
        self.v0_x = self.v0 * cos(self.angle)
        self.v0_y = self.v0 * sin(self.angle)
        self.kdivm = self.k / self.m
        self.time = np.arange(0, tlimit, 0.0005)

    def xmodel(self, X, t):
        x = X[0]
        dx = X[1]
        zdot = [ [], [] ]
        zdot[0] = dx
        zdot[1] = -self.kdivm * dx + self.ws
        return zdot

    def ymodel(self, Y, t):
        y = Y[0]
        dy = Y[1]
        zdot = [ [], [] ]
        zdot[0] = dy
        zdot[1] = -g - self.kdivm * dy
        return zdot

    def solveX(self):
        x = integrate.odeint(self.xmodel, [0, self.v0_x], self.time)
        return x

    def solveY(self):
        y = integrate.odeint(self.ymodel, [0, self.v0_y], self.time)
        return y


class MainCommunicationWithGui:
    def startbutton(self):
        self.getvalues()

    def getvalues(self):
        ws = float(ui.textEdit_1.toPlainText())
        m = float(ui.textEdit_2.toPlainText())
        if m == 0:
            return False
        k = float(ui.textEdit_3.toPlainText())
        angle = float(ui.textEdit_4.toPlainText()) * (pi / 180)
        v0 = float(ui.textEdit_5.toPlainText())
        xlim = float(ui.textEdit_7.toPlainText())
        ylim = float(ui.textEdit_8.toPlainText())
        x, y = self.tosystem(k, angle, v0, m, ws)
        self.figureinit(x, y, xlim, ylim)

    def tosystem(self, k, angle, v0, m, ws):
        system = SolveSystemOfADifferentialEquations(k, angle, v0, m, ws)
        Xi = np.array(system.solveY())
        Yi = np.array(system.solveY())
        x = []
        y = []
        for j in range(1, len(Yi)):
            if Yi[j][0] > 0:
                x.append(Yi[j][0])
                y.append(Xi[j][0])
        return x, y

    def figureinit(self, x, y, xlim, ylim):
        colors = ['blue', 'green', 'cyan', 'magenta', 'black']
        figure = GraphTrajectoryAnimation(x, y, colors[randint(0, 4)] , xlim, ylim)
        figure.showfigure()

    def exitbutton(self):
        sys.exit(1)

    def initbuttons(self):
        ui.pushButton_3.clicked.connect(self.startbutton)
        ui.pushButton_1.clicked.connect(self.exitbutton)

def main():
    global ui, app
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    communicate = MainCommunicationWithGui()
    communicate.initbuttons()
    MainWindow.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

【问题讨论】:

    标签: python pyqt pyqt5


    【解决方案1】:

    如果你打算使用 GUI,那么你不应该使用 pyplot,而是使用相应后端的画布,这样就不会发生事件循环冲突。

    综合以上情况,解决办法是:

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    from scipy import integrate
    import numpy as np
    
    import matplotlib.animation as animation
    from matplotlib.backends.backend_qt5agg import (
        FigureCanvas,
        NavigationToolbar2QT as NavigationToolbar,
    )
    from matplotlib.figure import Figure
    
    from pythongui4 import Ui_MainWindow
    
    
    g = 9.80665  # (Gravity acceleration on the Earth), m/s^2
    
    
    class Canvas(QtWidgets.QMainWindow):
        def __init__(
            self, color, xlim=1, ylim=1, m=50, lw=3, width=6, height=4, parent=None
        ):
            super().__init__(parent)
    
            self.figure = Figure(figsize=(width, height))
            self.canvas = FigureCanvas(self.figure)
            self.setCentralWidget(self.canvas)
    
            self.addToolBar(QtCore.Qt.TopToolBarArea, NavigationToolbar(self.canvas, self))
    
            self.ax = self.figure.subplots()
            self.ax.set_xlim([0, xlim])
            self.ax.set_ylim([0, ylim])
            (self.graphfunction,) = self.ax.plot([], [], color="blue", lw=2)
            (self.dot,) = self.ax.plot([], [], "o", color="red")
    
            self._color = color
            self._xlim = xlim
            self._ylim = ylim
            self._m = m
            self._lw = lw
    
        @QtCore.pyqtSlot(list, list)
        def update_values(self, x, y):
            self.x = x[:]
            self.y = y[:]
            self.graph()
    
        @property
        def color(self):
            return self._color
    
        @color.setter
        def color(self, color):
            self._color = color
    
        @property
        def xlim(self):
            return self._xlim
    
        @xlim.setter
        def xlim(self, xlim):
            self._xlim = xlim
    
        @property
        def ylim(self):
            return self._ylim
    
        @color.setter
        def ylim(self, ylim):
            self._ylim = ylim
    
        @property
        def m(self):
            return self._m
    
        @m.setter
        def m(self, m):
            self._m = m
    
        @property
        def lw(self):
            return self._lw
    
        @lw.setter
        def lw(self, lw):
            self._lw = lw
    
        def update_function(self, i, x, y, m, color):
            """ Definition of a matplotlib animation. """
            if i * m >= len(x) or i * m >= len(y):
                self.graphfunction.set_data(x[: i * m], y[: i * m])
            else:
                self.graphfunction.set_data(x[: i * m], y[: i * m])
                self.dot.set_data(x[i * m], y[i * m])
    
        def graph(self, interval=50):
            """ Definition of cycle FuncAnimation, which call animation. """
            self.graph_id = animation.FuncAnimation(
                self.figure,
                self.update_function,
                fargs=(self.x, self.y, self.m, self.color),
                repeat=False,
                interval=interval,
                frames=400,
            )
    
    
    class SolveSystemOfADifferentialEquations:
        def __init__(self, k, angle, v0, m, ws, tlimit=20):
            self.k = k
            self.angle = angle
            self.m = m
            self.ws = ws
            self.v0 = v0
            self.v0_x = self.v0 * np.cos(self.angle)
            self.v0_y = self.v0 * np.sin(self.angle)
            self.kdivm = self.k / self.m
            self.time = np.arange(0, tlimit, 0.0005)
    
        def xmodel(self, X, t):
            x = X[0]
            dx = X[1]
            zdot = [[], []]
            zdot[0] = dx
            zdot[1] = -self.kdivm * dx + self.ws
            return zdot
    
        def ymodel(self, Y, t):
            y = Y[0]
            dy = Y[1]
            zdot = [[], []]
            zdot[0] = dy
            zdot[1] = -g - self.kdivm * dy
            return zdot
    
        def solveX(self):
            x = np.integrate.odeint(self.xmodel, [0, self.v0_x], self.time)
            return x
    
        def solveY(self):
            y = np.integrate.odeint(self.ymodel, [0, self.v0_y], self.time)
            return y
    
    
    class MainCommunicationWithGui(QtCore.QObject):
        dataChanged = QtCore.pyqtSignal(list, list)
    
        @QtCore.pyqtSlot(float, float, float, float, float)
        def update_values(self, ws, m, k, angle, v0):
            x, y = self.tosystem(k, angle, v0, m, ws)
            self.dataChanged.emit(x, y)
    
        def tosystem(self, k, angle, v0, m, ws):
            system = SolveSystemOfADifferentialEquations(k, angle, v0, m, ws)
            Xi = np.array(system.solveY())
            Yi = np.array(system.solveY())
            x = []
            y = []
            for j in range(1, len(Yi)):
                if Yi[j][0] > 0:
                    x.append(Yi[j][0])
                    y.append(Xi[j][0])
            return x, y
    
    
    class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setupUi(self)
    
            self.communicate = MainCommunicationWithGui()
            self.pushButton_3.clicked.connect(self.start)
            self.pushButton_1.clicked.connect(QtCore.QCoreApplication.quit)
    
        @QtCore.pyqtSlot()
        def start(self):
            try:
                ws = float(self.textEdit_1.toPlainText())
                m = float(self.textEdit_2.toPlainText())
                if m == 0:
                    return
                k = float(self.textEdit_3.toPlainText())
                angle = float(self.textEdit_4.toPlainText()) * (np.pi / 180)
                v0 = float(self.textEdit_5.toPlainText())
                xlim = float(self.textEdit_7.toPlainText())
                ylim = float(self.textEdit_8.toPlainText())
            except ValueError as e:
                print("error", e)
            else:
                self.communicate.xlim = xlim
                self.communicate.ylim = ylim
                self.communicate.update_values(ws, m, k, angle, v0)
    
    
    def main():
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
    
        w = MainWindow()
        w.show()
    
        canvas = Canvas(color=["blue", "green", "cyan", "magenta", "black"])
        w.communicate.dataChanged.connect(canvas.update_values)
        canvas.show()
    
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()
    

    【讨论】:

    • 感谢您的详细帮助,但是当我单击按钮时,画布不显示图形,对此没有反应
    • 我解决了i.imgur.com/Pan42kA.png这样的问题,但也谢谢你,
    猜你喜欢
    • 2015-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多