Matplotlib 실시간 차트 그리기

이번 시간에는 matplotlib를 이용해 실시간 챠트(FuncAnimation이용)를 구성하는 방법과 해당 챠트를 PyQt5와 연동하는 방법에 대해 소개합니다.

  • sin 파 FuncAnimation Class를 이용해 실시간으로 그리기
[matplotlib + PyQt5 + FuncAnimation]



인터넷에 matplotlib를 이용해 챠트를 그리는 방법은 많이 소개되어 있지만, 실시간 챠트 예제는 자료가 거의 없어 직접 만들어 보았습니다.
  •  Pyinstaller로 제작한 실행파일 : chart

matplotlib 예시

matplotlib는 데이터를 여러 종류 챠트를 구성하고, 보기좋게 그래프로 시각화 시키는 파이
썬 패키지 입니다.



소스코드

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import numpy as np

QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)

class CWidget(QWidget):
    
    def __init__(self):
        super().__init__()
        # for PyQt embedding
        self.fig = plt.Figure()
        self.canvas = FigureCanvasQTAgg(self.fig)

        self.timeInterval = 0.1
        
        self.x = np.arange(0, 2*np.pi, self.timeInterval)
        self.y = np.sin(self.x)       
        
        
        self.initUI()

    def initUI(self):
        vbox = QVBoxLayout()
        vbox.addWidget(self.canvas)

        self.setLayout(vbox)
        self.setGeometry(0,0,800,400)
        
        # 1~1 중 1번째(1,1,1)  서브 챠트 생성
        self.ax = self.fig.add_subplot(1,1,1)           
        # 2D line
        self.line, = self.ax.plot(self.x, self.y)        

        # 애니메이션 챠트 생성
        self.ani = animation.FuncAnimation(self.fig, self.animate, init_func=self.initPlot, interval=100, blit=False, save_count=50)
        self.canvas.draw()

        self.show()   

    def initPlot(self):
        self.line.set_ydata([np.nan]*len(self.x))
        return self.line,    

    def animate(self, i):        
        self.line.set_ydata(np.sin(self.x + i * self.timeInterval))
        return self.line,

    def closeEvent(self, e):
        pass
        

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = CWidget()
    sys.exit(app.exec_())

먼저 1~7번 라인에서 필요한 모듈을 불러옵니다. (matplotlib, pyqt5, numpy 등)

9번 라인은 4k 모니터를 위한 Qt 고해상도 설정 코드입니다.

11번 라인부터 QWidget에서 상속받은 CWidget class 선언이 시작됩니다.

13~22번 라인, CWidget의 생성자 함수로 matplotlib의 Figure 객체를 생성하고 PyQt5에 챠트를 붙이기 위한 FigureCanvasQTAgg 객체도 생성합니다.

좀 더 자세히 설명하자면, Figure 객체는 plot 의 모든 요소 중 최상위 객체이며, FigureCanvasQTAgg는 차후 Qt의 Widget(일종의 윈도우 창)에 붙어 보여(그려)지게 됩니다.

timeInterval은 나중에 챠트를 계속 이어 그려가게 되므로 sin 파의 시간 (x축) 간격을 의미합니다.

이어서 self.x 리스트를 0부터 2파이 까지 0.1(timeInterval) 간격으로 x축 numpy 리스트(배열) 를 생성합니다.



self.y는 y축의 데이터를 리스트한 배열로 sin(self.x, 라디안)의 값이 리스트로 저장됩니다.


27~43번 라인의 initUI() 함수는 생성자에서 호출되는 함수로, 생성자함수(__init__)에서 객체 변수와 관계된 코드를 작성했다면 여기서는 UI와 관계된 초기화를 수행하도록 합니다.

PyQt의 수직 레이아웃 박스를 만들고 addWidget() 함수를 이용해 생성자에서 만든 canvas 객체를 추가합니다.

Qt의 레이아웃 박스는 윈도우의 크기가 변경되면 배치된 컨트롤 위젯의 크기를 자동으로 조절해 리사이즈를 편리하게 도와줍니다.

이어서 CWidget의 setLayout() 함수를 통해 챠트가 추가된 canvas를 메인 윈도우에 배치합니다.

35번 라인 Figure 객체를 이용해 서브 챠트 그래프축(Axes)을 생성하는데, (1, 1, 1) 의 의미는 1부터 1까지 챠트 중 1번째라는 뜻입니다. 즉 하나만 만든다는 의미죠.

37번 라인은 해당 축에 x, y 데이터 리스트를 전달해 생성하고 Line2D(self.line) 객체를 생성합니다. 특이한 점은 Line2D 객체를 리턴 받을때 'self.line, = ' 쉼표가 들어가 있는데 이는 여러개의 Line2D 객체를 전달받을 수 있도록 리스트를 리턴하기 때문입니다.

42번 라인이 이 코드의 핵심인 FuncAnimation 클래스 객체를 생성하는 부분입니다. 바로 이 클래스가 실시간(애니메이션) 챠트를 구성하도록 합니다.

이 클래스의 생성자의 전달인자는 아주 복잡해서 matplotlib의 문서를 인용해 설명해 보겠습니다.

먼저 FuncAnimation 클래스 생성자의 모습입니다.

class matplotlib.animation.FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, *, cache_frame_data=True, **kwargs)


다음은 생성자 함수의 전달인자에 대한 설명입니다.
  • fig : matplotlib.figure.Figure (Figure 객체)
The figure object that is used to get draw, resize, and any other needed events.
  • func : callable (매 프레임마다 호출되는 콜백 함수)
The function to call at each frame. The first argument will be the next value in frames. Any additional positional arguments can be supplied via the fargs parameter.
  • frames : iterable, int, generator function, or None, optional (데이터 소스)
Source of data to pass func and each frame of the animation
  • init_func : callable, optional (첫 프레임 전 호출되는 초기화 함수)
A function used to draw a clear frame. If not given, the results of drawing from the first item in the frames sequence will be used. This function will be called once before the first frame.
  • fargs : tuple or None, optional (콜백함수의 추가 전달 인자)
Additional arguments to pass to each call to func.
  • save_count : int, optional (프레임의 캐시수)
The number of values from frames to cache.
  • interval : number, optional (새로 그려질 매 프레임의 시간 간격)
Delay between frames in milliseconds. Defaults to 200.
  • repeat_delay : number, optional (그리기가 반복될때 추가 지연)
If the animation in repeated, adds a delay in milliseconds before repeating the animation. Defaults to None.
  • repeat : bool, optional (애니메이션 반복 여부)
Controls whether the animation should repeat when the sequence of frames is completed. Defaults to True.
  • blit : bool, optional (화면의 최적화와 관련, 변경된 부분만 그리는 것으로 추정)
Controls whether blitting is used to optimize drawing. Note: when using blitting any animated artists will be drawn according to their zorder. However, they will be drawn on top of any previous artists, regardless of their zorder. Defaults to False.
  • cache_frame_data : bool, optional (프레임 데이터를 캐쉬해 대용량데이터에 도움)
Controls whether frame data is cached. Defaults to True. Disabling cache might be helpful when frames contain large objects.

이상이 FuncAnimation Class의 설명인데, 저는 초기화, 콜백 함수를 설정하고 간격은 약 100ms로 설정하였습니다.

이제 설정을 마치고 canvas.draw() 함수를 이용해 챠트를 그리고, self.show() 함수를 이용해 Qt Widget을 띄웁니다.

이제 45번 라인 initPlot() 함수가 호출되고 생성자에서 만들어둔 numpy 배열 self.y를 이용해 plot의 Y축을 초기화 합니다.

이 함수는 FuncAnimation 클래스 생성 시 inut_func로 등록해 둔 함수이며, 애니메이션 프레임을 갱신전 한번만 호출됩니다.

마지막으로 49번 라인 animate() 함수는 매 100ms 마다 호출되며, 챠트 Y축 값에 timeInterval (0.1) 을 더한 sin 그래프를 그려가게 됩니다.

이상으로 코드 분석을 마칩니다.

감사합니다.

  • 개발 환경 : Windows 10 Pro, VS2017, Python3.7.2(64bit), PyQt5.13.2, matplotlib 3.1.1

댓글

  1. 안녕하세요~ matplotlib으로 실시간 그래프 그리는 방법을 깔끔하게 정리해주셔서 많은 도움이 되었습니다.
    본 글을 참고하여 조작 창에서 실시간 그래프를 띄우는 프로그램 코드를 작성했습니다.
    https://stella47.tistory.com/445
    너무 도움 많이 되었습니다. 감사합니다~

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

Qt Designer 설치하기

PyQt5 기반 동영상 플레이어앱 만들기