简体   繁体   English

PyQT5 和 matplotlib 图,事件循环已经在运行

[英]PyQT5 with matplotlib figure, the event loop is already running

I have a program, which by clicking button on PyQT app shows matplotlib figure in another window.我有一个程序,通过单击 PyQT 应用程序上的按钮,在另一个 window 中显示 matplotlib 图。 When i click button, that windows shows, but on Figure nothing happends and in console i get this:当我单击按钮时,windows 显示,但在图上没有任何反应,在控制台中我得到这个:

QCoreApplication::exec: The event loop is already running QCoreApplication::exec: 事件循环已经在运行

I have a 3 classes and file with PyQT5 Gui.我有 3 个班级和 PyQT5 Gui 文件。 3 class get values from TextEdits, call second class, which calculating and return lists with x, y coords to show it on the matplotlib figure (first class). 3 class 从 TextEdits 获取值,调用第二个 class,它计算并返回带有 x、y 坐标的列表以在 matplotlib 图上显示它(第一类)。 I find different ways to solve this, but there is examples for simple cases and i don't understand how i can apply them solutions to my code我找到了不同的方法来解决这个问题,但是有一些简单案例的例子,我不明白如何将它们应用到我的代码中

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()

If you are going to use a GUI then you should not use pyplot but the canvas of the respective backend so that there is no conflict of event loops.如果要使用 GUI,则不应使用 pyplot,而应使用相应后端的 canvas,以免事件循环发生冲突。

Considering the above, the solution is:考虑到上述情况,解决方案是:

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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM