python - 您自己的 GUI 中的 Matplotlib 动画

标签 python animation matplotlib pyqt pyqt4

我正在用 Python 编写软件。我需要将 Matplotlib 时间动画嵌入到自制的 GUI 中。以下是关于它们的更多详细信息:

1. 图形界面

GUI 也是用 Python 编写的,使用 PyQt4 库。我的 GUI 与您可以在网上找到的常见 GUI 没有太大区别。我只是子类化 QtGui.QMainWindow 并添加一些按钮、布局、...

2.动画

Matplotlib 动画基于animation.TimedAnimation 类。这是动画的代码:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
import matplotlib.animation as animation

        class CustomGraph(animation.TimedAnimation):

        def __init__(self):

            self.n = np.linspace(0, 1000, 1001)
            self.y = 1.5 + np.sin(self.n/20)
            #self.y = np.zeros(self.n.size)


            # The window
            self.fig = plt.figure()
            ax1 = self.fig.add_subplot(1, 2, 1)
            self.mngr = plt.get_current_fig_manager() 
            self.mngr.window.setGeometry(50,100,2000, 800)

            # ax1 settings
            ax1.set_xlabel('time')
            ax1.set_ylabel('raw data')
            self.line1 = Line2D([], [], color='blue')
            ax1.add_line(self.line1)
            ax1.set_xlim(0, 1000)
            ax1.set_ylim(0, 4)


            animation.TimedAnimation.__init__(self, self.fig, interval=20, blit=True)


        def _draw_frame(self, framedata):
            i = framedata
            print(i)


            self.line1.set_data(self.n[ 0 : i ], self.y[ 0 : i ])

            self._drawn_artists = [self.line1]

        def new_frame_seq(self):
            return iter(range(self.n.size))

        def _init_draw(self):
            lines = [self.line1]
            for l in lines:
                l.set_data([], [])

        def showMyAnimation(self):
            plt.show()


    ''' End Class '''


    if __name__== '__main__':
        print("Define myGraph")
        myGraph = CustomGraph()
        myGraph.showMyAnimation()

此代码生成一个简单的动画:

Sine wave animation

动画本身工作正常。运行代码,动画会在一个小窗口中弹出并开始运行。但是如何将动画嵌入到我自己制作的 GUI 中呢?

3.在自制的GUI中嵌入动画

我做了一些研究来找出答案。这是我尝试过的一些事情。我已将以下代码添加到 python 文件中。请注意,这段添加的代码实际上是一个额外的类定义:

from PyQt4 import QtGui
from PyQt4 import QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas

class CustomFigCanvas(FigureCanvas):

    def __init__(self):

        self.myGraph = CustomGraph()
        FigureCanvas.__init__(self, self.myGraph.fig)

我在这里尝试做的是将 CustomGraph() 对象(本质上是我的动画)嵌入到 FigureCanvas 中。

我在另一个 python 文件中编写了我的 GUI(但仍在同一文件夹中)。通常我可以将小部件添加到我的 GUI 中。我相信 CustomFigCanvas(..) 类中的对象是通过继承的 Widget。所以这就是我在我的 GUI 中尝试的:

    ..
    myFigCanvas = CustomFigCanvas()
    self.myLayout.addWidget(myFigCanvas)
    ..

它在一定程度上起作用。我的 GUI 中确实显示了一个图形。但是这个数字是空的。动画不运行:

my GUI with dysfunctional animation

还有一个奇怪的现象正在发生。我的 GUI 显示这个空图形,但我同时得到一个常规的 Matplotlib 弹出窗口,其中包含我的动画图形。此动画也没有运行。

这里显然缺少一些东西。请帮我弄清楚这个问题。我非常感谢所有帮助。

最佳答案

我想我找到了解决方案。所有功劳都归功于创建 Python 教程网站的哈里森先生 https://pythonprogramming.net .他帮助了我。

这就是我所做的。两个主要变化:

1.结构变化

我以前有两个类:CustomGraph(TimedAnimation)CustomFigCanvas(FigureCanvas)。现在我只剩一个了,但是他同时继承了TimedAnimation和FigureCanvas:CustomFigCanvas(TimedAnimation, FigureCanvas)

2.制作图形对象的变化

我之前是这样画的:

self.fig = plt.figure()

'plt' 来自 import 语句 'import matplotlib.pyplot as plt'。当您想将图形嵌入到您自己的 GUI 中时,这种制作图形的方法显然会造成麻烦。 所以有一个更好的方法来做到这一点:

self.fig = Figure(figsize=(5,5), dpi=100)

现在可以了!

完整代码如下:

import numpy as np
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D

from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas


    class CustomFigCanvas(FigureCanvas, TimedAnimation):

        def __init__(self):

            # The data
            self.n = np.linspace(0, 1000, 1001)
            self.y = 1.5 + np.sin(self.n/20)

            # The window
            self.fig = Figure(figsize=(5,5), dpi=100)
            ax1 = self.fig.add_subplot(111)

            # ax1 settings
            ax1.set_xlabel('time')
            ax1.set_ylabel('raw data')
            self.line1 = Line2D([], [], color='blue')
            ax1.add_line(self.line1)
            ax1.set_xlim(0, 1000)
            ax1.set_ylim(0, 4)

            FigureCanvas.__init__(self, self.fig)
            TimedAnimation.__init__(self, self.fig, interval = 20, blit = True)


        def _draw_frame(self, framedata):
            i = framedata
            print(i)


            self.line1.set_data(self.n[ 0 : i ], self.y[ 0 : i ])
            self._drawn_artists = [self.line1]

        def new_frame_seq(self):
            return iter(range(self.n.size))

        def _init_draw(self):
            lines = [self.line1]
            for l in lines:
                l.set_data([], [])


    ''' End Class '''

这是在 matplotlib 中制作动画的代码。现在您可以轻松地将它嵌入到您自己的 Qt GUI 中:

    ..
    myFigCanvas = CustomFigCanvas()
    self.myLayout.addWidget(myFigCanvas)
    ..

它看起来工作得很好。 谢谢哈里森先生!


编辑:
几个月后我回到了这个问题。这是完整的代码。只需将其复制粘贴到一个新的 .py 文件中,然后运行它:

###################################################################
#                                                                 #
#                     PLOTTING A LIVE GRAPH                       #
#                  ----------------------------                   #
#            EMBED A MATPLOTLIB ANIMATION INSIDE YOUR             #
#            OWN GUI!                                             #
#                                                                 #
###################################################################


import sys
import os
from PyQt4 import QtGui
from PyQt4 import QtCore
import functools
import numpy as np
import random as rd
import matplotlib
matplotlib.use("Qt4Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import time
import threading



def setCustomSize(x, width, height):
    sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
    sizePolicy.setHorizontalStretch(0)
    sizePolicy.setVerticalStretch(0)
    sizePolicy.setHeightForWidth(x.sizePolicy().hasHeightForWidth())
    x.setSizePolicy(sizePolicy)
    x.setMinimumSize(QtCore.QSize(width, height))
    x.setMaximumSize(QtCore.QSize(width, height))

''''''

class CustomMainWindow(QtGui.QMainWindow):

    def __init__(self):

        super(CustomMainWindow, self).__init__()

        # Define the geometry of the main window
        self.setGeometry(300, 300, 800, 400)
        self.setWindowTitle("my first window")

        # Create FRAME_A
        self.FRAME_A = QtGui.QFrame(self)
        self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QtGui.QColor(210,210,235,255).name())
        self.LAYOUT_A = QtGui.QGridLayout()
        self.FRAME_A.setLayout(self.LAYOUT_A)
        self.setCentralWidget(self.FRAME_A)

        # Place the zoom button
        self.zoomBtn = QtGui.QPushButton(text = 'zoom')
        setCustomSize(self.zoomBtn, 100, 50)
        self.zoomBtn.clicked.connect(self.zoomBtnAction)
        self.LAYOUT_A.addWidget(self.zoomBtn, *(0,0))

        # Place the matplotlib figure
        self.myFig = CustomFigCanvas()
        self.LAYOUT_A.addWidget(self.myFig, *(0,1))

        # Add the callbackfunc to ..
        myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, daemon = True, args = (self.addData_callbackFunc,))
        myDataLoop.start()

        self.show()

    ''''''


    def zoomBtnAction(self):
        print("zoom in")
        self.myFig.zoomIn(5)

    ''''''

    def addData_callbackFunc(self, value):
        # print("Add data: " + str(value))
        self.myFig.addData(value)



''' End Class '''


class CustomFigCanvas(FigureCanvas, TimedAnimation):

    def __init__(self):

        self.addedData = []
        print(matplotlib.__version__)

        # The data
        self.xlim = 200
        self.n = np.linspace(0, self.xlim - 1, self.xlim)
        a = []
        b = []
        a.append(2.0)
        a.append(4.0)
        a.append(2.0)
        b.append(4.0)
        b.append(3.0)
        b.append(4.0)
        self.y = (self.n * 0.0) + 50

        # The window
        self.fig = Figure(figsize=(5,5), dpi=100)
        self.ax1 = self.fig.add_subplot(111)


        # self.ax1 settings
        self.ax1.set_xlabel('time')
        self.ax1.set_ylabel('raw data')
        self.line1 = Line2D([], [], color='blue')
        self.line1_tail = Line2D([], [], color='red', linewidth=2)
        self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r')
        self.ax1.add_line(self.line1)
        self.ax1.add_line(self.line1_tail)
        self.ax1.add_line(self.line1_head)
        self.ax1.set_xlim(0, self.xlim - 1)
        self.ax1.set_ylim(0, 100)


        FigureCanvas.__init__(self, self.fig)
        TimedAnimation.__init__(self, self.fig, interval = 50, blit = True)

    def new_frame_seq(self):
        return iter(range(self.n.size))

    def _init_draw(self):
        lines = [self.line1, self.line1_tail, self.line1_head]
        for l in lines:
            l.set_data([], [])

    def addData(self, value):
        self.addedData.append(value)

    def zoomIn(self, value):
        bottom = self.ax1.get_ylim()[0]
        top = self.ax1.get_ylim()[1]
        bottom += value
        top -= value
        self.ax1.set_ylim(bottom,top)
        self.draw()


    def _step(self, *args):
        # Extends the _step() method for the TimedAnimation class.
        try:
            TimedAnimation._step(self, *args)
        except Exception as e:
            self.abc += 1
            print(str(self.abc))
            TimedAnimation._stop(self)
            pass

    def _draw_frame(self, framedata):
        margin = 2
        while(len(self.addedData) > 0):
            self.y = np.roll(self.y, -1)
            self.y[-1] = self.addedData[0]
            del(self.addedData[0])


        self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ])
        self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin]))
        self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin])
        self._drawn_artists = [self.line1, self.line1_tail, self.line1_head]



''' End Class '''


# You need to setup a signal slot mechanism, to 
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class Communicate(QtCore.QObject):
    data_signal = QtCore.pyqtSignal(float)

''' End Class '''



def dataSendLoop(addData_callbackFunc):
    # Setup the signal-slot mechanism.
    mySrc = Communicate()
    mySrc.data_signal.connect(addData_callbackFunc)

    # Simulate some data
    n = np.linspace(0, 499, 500)
    y = 50 + 25*(np.sin(n / 8.3)) + 10*(np.sin(n / 7.5)) - 5*(np.sin(n / 1.5))
    i = 0

    while(True):
        if(i > 499):
            i = 0
        time.sleep(0.1)
        mySrc.data_signal.emit(y[i]) # <- Here you emit a signal!
        i += 1
    ###
###




if __name__== '__main__':
    app = QtGui.QApplication(sys.argv)
    QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique'))
    myGUI = CustomMainWindow()


    sys.exit(app.exec_())

''''''

关于python - 您自己的 GUI 中的 Matplotlib 动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36665850/

相关文章:

javascript - jQuery 动画无限循环

python - psycopg2 - 从数组插入表

用于后缀的 Python 过滤器

java - 动画圆以跨线移动

python - 如何为 seaborn 的热图或相关矩阵制作动画?

python - 使用 "subplot2grid"方法将子图添加到特定图形

python - matplotlib 交互模式 : determine if figure window is still displayed

Python re.findall 获取值

Python - 如何在 ArangoDB 中创建边

android - Android 上的双启动 onAnimationEnd