본문 바로가기
Python/PySide PyQt

[PySide6] matplotlib 이용하기: FigureCanvasQTAgg, NavigationToolbar2QT

by ds31x 2024. 4. 29.

matplotlib 이용하기

PyQt, PySide에서는 PyQtGraph를 통해서도 graph등을 그릴 수 있으나,

대중적으로 사용되는 matplotlib를 이용할 수도 있다.

PyQtGraph
Qt vector 기반의 QGraphicsScene를 통해
상호작용이 가능한 고성능의 plotting기능을 제공함.

 

 

더욱이, matplotlib에 기반하는 seabornpandas의 plotting도

matplotlib를 사용하는 방법과 같은 방식(=같은 backend를 사용)으로 적용이 가능하기 때문에

익혀둘 필요가 있다.

matplotlib의 사용할 때의 주의할 점은
만들어진 graph에서의 mouse 좌표들의 처리는 
matplotlib를 통해서 수행해야 한다.


FigureCanvasQTAgg 클래스

PyQt, PySide는 matplotlib을 위한 canvas로 FigureCanvasATAgg를 제공한다.

  • matplotlib.backends.backend_qtagg 모듈에서 제공하는 클래스임.
  • QWidget의 subclass이기도 함: 즉, Qt widget 에 포함될 수 있음.
  • matplotlibFigure인스턴스를 생성자의 argument로 입력받음.
  • FigureCanvasQTAgg는 생성자에서 넘겨진 해당 figure가 그려지는 matplotlib의 canvas를 PySide개발자에게 제공해줌.

일반적으로 FigureCanvasQTAgg를 상속하는 custom class를 통해, matplotlib의 graph를 Qt Application에 추가한다.

 

FigureCanvasQTAgg
Agg backend에 대한 일종의 wrapper class 임.

 

2024.04.29 - [Python/matplotlib] - [Etc] Anti-Grain Geometry (AGG)

 

[Etc] Anti-Grain Geometry (AGG)

Anti-Grain Geometry (AGG) Anti-Grain Geometry(AGG)는 다음과 같은 특징을 가지는Open-source 고성능 2D 벡터 그래픽 라이브러리임.C++로 구현됨.anti-alising과 sub-pixel정확도에 중점을 두고고품질의 이미지를 생성

ds31x.tistory.com

2023.07.20 - [Python/matplotlib] - [Python] matplotlib : backend란

 

[Python] matplotlib : backend란

matplotlib의 backend 관련자료를 정리한 문서임. Matplotlib Architecture Matplotlib 아키텍트는 다음과 같이 크게 3가지 레이어로 구성된다. Backend Layer : 상위 layer에서 graph를 생성하는데 초점을 두는 것과 달

ds31x.tistory.com


좀 더 자세한 사용법은 다음 code snippet을 참고하라.

import sys

from PySide6.QtWidgets import (
    QMainWindow,
    QApplication,
)

import matplotlib
import matplotlib.pyplot as plt

from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg

matplotlib.use('QtAgg')

class MyCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, figsize =(5,5), dpi=100):

        self.fig, self.axes = plt.subplots(
            1,2,
            figsize=figsize, 
            dpi=dpi
        )
        super(MyCanvas, self).__init__(self.fig)

class MW(QMainWindow):

    def __init__(self):

        super().__init__()

        plt_canvas = MyCanvas(self, (5,10), 100)
        plt_canvas.axes[0].plot([0,1,2,3,4], [10,13,20,30,15], label='line')
        plt_canvas.axes[1].scatter([0,1,2,3,4], [10,13,20,30,15], label='scatter')
        for a in plt_canvas.axes:
            a.legend()
            a.grid()

        self.setCentralWidget(plt_canvas)
        self.show()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    wnd = MW()
    sys.exit(app.exec())

 


NavigationToolbar2QT 클래스

PyQt와 PySide에서 matplotlib를 사용하여 그린 graph에 대해
zoom, panning 기능과 마우스 클릭된 위치의 데이터 등에 접근을 제공해주는 클래스.

 

NavigationToolbar2Qt 클래스는
matplotlibQT backend에서 제공하는
navigation toolbar임.
이 toolbar는 현재 backend가 그린 chart 등을 제어하는 데 필요한 기능을 제공함.
그래프를 확대하고 축소하거나,
플롯을 저장하거나,
플롯을 초기화 등이
이 클래스의 인스턴스를 통해 가능함.


가장 대표적인 사용예가 OpenCV 라이브러리의 imshow 로 보여지는 window임.

https://dsaint31.tistory.com/480

 

[OpenCV] imshow 창설정 및 종료 처리 (x버튼 처리).

창 설정 cv2.namedWindow를 통해 미리 창에 대한 title을 지정하여 놓을 수 있음. cv2.namedWindow('image', cv2.WINDOW_NORMAL) # Using resizeWindow() : Below code does not works with cv2.WINDOW_AUTOSIZE # cv2.resizeWindow("image", 300, 700)

dsaint31.tistory.com

 

NavigationToolbar2QT의 생성자는 다음과 같음.

NavigationToolbar2QT(canvas, parent, coordinates=True)
  • canvas:
    • toolbar 와 연결될 matplotlib의 canvas임.
    • 일반적으로 FigureCanvasQTAgg를 상속한 클래스임.
  • parent:
    • toolbar 의 부모 widget.
    • 일반적으로 QMainWindowQWidget 의 객체임.
  • coordinates:
    • optional parameter
    • 기본적으로 True로 설정됨.
    • True 인 경우, toolbar에 현재 마우스 위치 좌표가 graph의 좌표축을 기준으로 표시됨.
    • 만약 False로 설정하면 좌표 표시가 비활성화됨.

NavigationToolbar2QT가 제공하는 주요 기능은 다음과 같음:

  1. Home, Back/Forward:
    • Home: figure가 맨 처음 그려진 상태로 초기화.
    • Back/Forward: zoom이나 translation 이 이루어진 경우, 이전 상태나 이후 상태의 figure로 이동하는데 사용됨.
  2. Pan/Zoom:
    • 그래프를 panning을 통해 다양한 부분을 살펴보게 해 줌.
    • 마우스 드래그를 통한 panning (그림이 보여지는 view를 상하좌우로 이동시켜 보여지는 영역을 바꿈)
    • 주로 확대된 상태에서 많이 이용되는 기능임.
  3. Zoom-to-Rectangle: 그래프의 영역을 확대하거나 축소.
  4. Configure Subplots: boader와 spacing등을 조정할 수 있는 다이알로그를 띄어줌.
  5. Edit axis, curve and image parameters:
    • Matplotlib의 그래프 축, 곡선 및 이미지 매개변수를 편집하는 기능을 제공
  6. Save: figure를 이미지 파일로 저장하는 기능을 제공.

보다 자세한 건 matplotlib의 Interactive naviation 문서 참고


이에 대한 code snippet은 다음과 같음.

import sys

from PySide6.QtWidgets import (
    QMainWindow,
    QApplication,
    QWidget,
    QVBoxLayout,
)

import matplotlib
import matplotlib.pyplot as plt

from matplotlib.backends.backend_qtagg import (
    FigureCanvasQTAgg,
    NavigationToolbar2QT,
)

matplotlib.use('QtAgg')

class MyCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, figsize =(5,5), dpi=100):

        self.fig, self.axes = plt.subplots(
            1,2,
            figsize=figsize, 
            dpi=dpi
        )
        super(MyCanvas, self).__init__(self.fig)

class MW(QMainWindow):

    def __init__(self):

        super().__init__()
        self.setWindowTitle('ex: matplotlib')

        plt_canvas = MyCanvas(self, (5,10), 100)
        plt_canvas.axes[0].plot([0,1,2,3,4], [10,13,20,30,15], label='line')
        plt_canvas.axes[1].scatter([0,1,2,3,4], [10,13,20,30,15], label='scatter')
        for a in plt_canvas.axes:
            a.legend()
            a.grid()

        toolbar = NavigationToolbar2QT(plt_canvas, self)

        lm = QVBoxLayout()
        lm.addWidget(toolbar)
        lm.addWidget(plt_canvas)

        tmp = QWidget()
        tmp.setLayout(lm)    


        self.setCentralWidget(tmp)
        self.show()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    wnd = MW()
    sys.exit(app.exec())

Dynamic Plot

실시간으로 matplotlib graph를 그려야 하는 경우를 위해 다음의 2가지 방법이 지원됨.

  1. Clear and Redraw
    • 표시한 Canvas객체를 동적으로 새로 생성하여
    • 이전 canvas객체의 모든 axes들을 지우고
    • 새로 canvas내의 모든 axes들을 다시 그려주는 방법
    • 간단하지만 느림.
  2. In-place Update
    • 그려진 차트에 대한 reference를 유지하면서
    • data를 업데이트 하고 redraw 처리하는 방법.
    • 조금 복잡하지만 빠름.

이 코드는 PySide6를 사용하여 matplotlib를 이용한 dynamic plot을 수행하는 예제임

import sys
import random

from PySide6.QtWidgets import (
    QMainWindow,
    QApplication,
)

from PySide6.QtCore import QTimer

import matplotlib
import matplotlib.pyplot as plt

from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg

matplotlib.use('QtAgg')

class MyCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, figsize =(5,5), dpi=100):

        self.fig, self.axes = plt.subplots(
            1,2,
            figsize=figsize, 
            dpi=dpi
        )
        super(MyCanvas, self).__init__(self.fig)

class MW(QMainWindow):

    def __init__(self):

        super().__init__()
        self.setWindowTitle('ex: dynamic plot')

        self.plt_canvas = MyCanvas(self, (5,10), 100)

        n_data = 10
        interval_ms = 10
        self.x_data0 = list(range(n_data))
        self.x_data1 = list(range(n_data)) 
        self.y_data0 = [0, 20]+[random.randint(0,20) for i in range(n_data-2)]
        self.y_data1 = [0, 20]+[random.randint(0,20) for i in range(n_data-2)]

        self.update_plot0()
        self.old_plot_ref = None
        self.update_plot1()   


        self.setCentralWidget(self.plt_canvas)
        self.show()

        self.timer0 = QTimer()
        self.timer0.setInterval(interval_ms) # milliseconds
        self.timer0.timeout.connect(self.update_plot0)
        self.timer0.start()


        self.timer1 = QTimer()
        self.timer1.setInterval(interval_ms) # milliseconds
        self.timer1.timeout.connect(self.update_plot1)
        self.timer1.start()

    def update_plot0(self):
        self.y_data0 = [ *self.y_data0[1:], random.randint(0,20)]
        self.plt_canvas.axes[0].cla()
        self.plt_canvas.axes[0].plot(self.x_data0, self.y_data0, label='method0')
        self.plt_canvas.axes[0].grid()
        self.plt_canvas.axes[0].legend(loc='upper right')
        self.plt_canvas.draw() # trigger the canvas to update and redraw

    def update_plot1(self):    
        self.y_data1 = [ *self.y_data1[1:], random.randint(0,20)]
        if self.old_plot_ref is not None:
            self.old_plot_ref.set_ydata(self.y_data1)
        else: 
            self.old_plot_ref = self.plt_canvas.axes[1].plot(
                self.x_data1, self.y_data1, 
                label='method1',
            )[0]
            self.plt_canvas.axes[1].grid()
            self.plt_canvas.axes[1].legend(loc='upper right')
        self.plt_canvas.draw()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    wnd = MW()
    sys.exit(app.exec())
  1. MyCanvas 클래스는 Matplotlib의 FigureCanvasQTAgg를 상속하며, matplotlibFigure 객체를 PySide6 애플리케이션에 삽입하는데 사용됨.
    • 위의 예제의 경우 1행 2열의 subplots로 구성됨.
  2. MW 클래스는 QMainWindow를 상속하며, PySide6 애플리케이션의 main window에 해당함.
    • 이 클래스를 통해 matplotlib 그림을 포함하는 MyCanvas 인스턴스를 생성하고,
    • 두 개의 그래프를 dynamic plot하는 데 사용되는 데이터와 타이머를 설정.
  3. update_plot0 메서드는 Clear and Redraw 를 구현함.
    • 이 메서드는 먼저 새로운 데이터를 생성하고,
    • 그 다음에는 첫 번째 서브플롯을 지우고 새 데이터로 다시 그립니다.
  4. update_plot1 메서드는 In-place Update 를 구현함.
    • 이 메서드는 먼저 새로운 데이터를 생성하고,
    • 이전에 생성된 그래프의 reference 를 유지하여 그래프를 업데이트함.
    • 처음 호출될 때는 새로운 선을 그리고, 이후 호출될 때는 기존 선을 업데이트.
  5. 마지막으로, __main__ 블록에서는 QApplication을 생성하고 MW 클래스의 인스턴스를 생성하여 애플리케이션을 실행합니다.

References

https://www.pythonguis.com/tutorials/pyside6-plotting-matplotlib/

 

Matplotlib plots in PySide6, embedding charts in your GUI applications

Integrate Matplotlib plots within your PySide6 applications for dynamic data visualization. This tutorial guides you through embedding interactive Matplotlib charts, enhancing your GUI projects with powerful graphing capabilities including real-time plotti

www.pythonguis.com

2024.05.19 - [Python/PySide PyQt] - [PySide] FigureCanvas.mpl_connect

 

[PySide] FigureCanvas.mpl_connect

FigureCanvas.mpl_connect FigureCanvas 클래스는Matplotlib 이 이용하여 그려진 그래픽 요소와의 interaction(상호작용)을 위한 event handling을 구현하기 위해,mpl_connect method를 제공함.더보기Matplotlib는 wypython, tkint

ds31x.tistory.com

 


 

'Python > PySide PyQt' 카테고리의 다른 글

[PySide6] pyside6-uic 사용하기  (0) 2024.05.07
[PySide6] Qt Designer6  (0) 2024.05.06
[PySide6] QUiLoader 를 Qt Designer의 .ui 사용하기.  (0) 2024.05.06
[PySide6] QProgressBar  (0) 2024.04.29
[PySide6] 2024년 참고할 만한 책들.  (0) 2024.03.04