您自己的 GUI 中的 Matplotlib 动画 [英] Matplotlib animation inside your own GUI

查看:24
本文介绍了您自己的 GUI 中的 Matplotlib 动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm writing software in Python. I need to embed a Matplotlib time-animation into a self-made GUI. Here are some more details about them:

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

The GUI is written in Python as well, using the PyQt4 library. My GUI is not very different from the common GUIs you can find on the net. I just subclass QtGui.QMainWindow and add some buttons, a layout, ...

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

The Matplotlib animation is based on the animation.TimedAnimation class. Here is the code for the animation:

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

这段代码产生了一个简单的动画:

This code produces a simple animation:

动画本身运行良好.运行代码,动画在一个小窗口中弹出并开始运行.但是如何将动画嵌入我自己制作的 GUI 中?

The animation itself works fine. Run the code, the animation pops up in a small window and it starts running. But how do I embed the animation in my own self-made GUI?

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

I have done some research to find out. Here are some things I tried. I have added the following code to the python file. Note that this added code is actually an extra class definition:

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 中.

What I try to do here is embedding the CustomGraph() object - which is essentially my animation - into a FigureCanvas.

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

I wrote my GUI in another python file (but still in the same folder). Normally I can add Widgets to my GUI. I believe that an object from the class CustomFigCanvas(..) is a Widget through inheritance. So this is what I try in my GUI:

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

它在某种程度上有效.我的 GUI 中确实显示了一个数字.但是这个数字是空的.动画不运行:

It works to some extent. I get indeed a figure displayed in my GUI. But the figure is empty. The animation doesn't run:

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

And there is even another strange phenomenon going on. My GUI displays this empty figure, but I get simultaneously a regular Matplotlib popup window with my animation figure in it. Also this animation is not running.

显然我在这里遗漏了一些东西.请帮我解决这个问题.我非常感谢所有帮助.

There is clearly something that I'm missing here. Please help me to figure out this problem. I appreciate very much all help.

推荐答案

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

I think I found the solution. All credit goes to Mr. Harrison who made the Python tutorial website https://pythonprogramming.net. He helped me out.

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

So here is what I did. Two major changes:

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

I previously had two classes: CustomGraph(TimedAnimation) and CustomFigCanvas(FigureCanvas). Now I got only one left, but he inherits from both TimedAnimation and FigureCanvas: CustomFigCanvas(TimedAnimation, FigureCanvas)

这就是我之前制作图形的方式:

This is how I made the figure previously:

self.fig = plt.figure()

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

With 'plt' coming from the import statement 'import matplotlib.pyplot as plt'. This way of making the figure apparently causes troubles when you want to embed it into your own GUI. So there is a better way to do it:

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 中:

That's the code to make the animation in matplotlib. Now you can easily embed it into your own Qt GUI:

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

它似乎工作得很好.谢谢哈里森先生!

It seems to work pretty fine. Thank you Mr. Harrison!


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

EDIT :
I came back to this question after many months. Here is the complete code. Just copy-paste it into a fresh .py file, and run it:

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

''''''

这篇关于您自己的 GUI 中的 Matplotlib 动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆