matplotlib 이용하기
PyQt, PySide에서는 PyQtGraph
를 통해서도 graph등을 그릴 수 있으나,
대중적으로 사용되는 matplotlib
를 이용할 수도 있다.
PyQtGraph
는
Qt vector 기반의QGraphicsScene
를 통해
상호작용이 가능한 고성능의 plotting기능을 제공함.
더욱이, matplotlib
에 기반하는 seaborn
과 pandas
의 plotting도
matplotlib
를 사용하는 방법과 같은 방식(=같은 backend를 사용)으로 적용이 가능하기 때문에
익혀둘 필요가 있다.
matplotlib
의 사용할 때의 주의할 점은
만들어진 graph에서의 mouse 좌표들의 처리는matplotlib
를 통해서 수행해야 한다.
FigureCanvasQTAgg 클래스
PyQt, PySide는 matplotlib
을 위한 canvas로 FigureCanvasATAgg
를 제공한다.
matplotlib.backends.backend_qtagg
모듈에서 제공하는 클래스임.QWidget
의 subclass이기도 함: 즉, Qt widget 에 포함될 수 있음.matplotlib
의Figure
인스턴스를 생성자의 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)
2023.07.20 - [Python/matplotlib] - [Python] matplotlib : backend란
좀 더 자세한 사용법은 다음 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
클래스는matplotlib
의 QT backend에서 제공하는
navigation toolbar임.
이 toolbar는 현재 backend가 그린 chart 등을 제어하는 데 필요한 기능을 제공함.
그래프를 확대하고 축소하거나,
플롯을 저장하거나,
플롯을 초기화 등이
이 클래스의 인스턴스를 통해 가능함.
가장 대표적인 사용예가 OpenCV 라이브러리의 imshow 로 보여지는 window임.
https://dsaint31.tistory.com/480
NavigationToolbar2QT
의 생성자는 다음과 같음.
NavigationToolbar2QT(canvas, parent, coordinates=True)
canvas
:- toolbar 와 연결될
matplotlib
의 canvas임. - 일반적으로
FigureCanvasQTAgg
를 상속한 클래스임.
- toolbar 와 연결될
parent
:- toolbar 의 부모 widget.
- 일반적으로
QMainWindow
나QWidget
의 객체임.
coordinates
:- optional parameter
- 기본적으로
True
로 설정됨. True
인 경우, toolbar에 현재 마우스 위치 좌표가 graph의 좌표축을 기준으로 표시됨.- 만약
False
로 설정하면 좌표 표시가 비활성화됨.
NavigationToolbar2QT
가 제공하는 주요 기능은 다음과 같음:
Home
,Back/Forward
:Home
: figure가 맨 처음 그려진 상태로 초기화.Back/Forward
: zoom이나 translation 이 이루어진 경우, 이전 상태나 이후 상태의 figure로 이동하는데 사용됨.
Pan/Zoom
:- 그래프를 panning을 통해 다양한 부분을 살펴보게 해 줌.
- 마우스 드래그를 통한 panning (그림이 보여지는 view를 상하좌우로 이동시켜 보여지는 영역을 바꿈)
- 주로 확대된 상태에서 많이 이용되는 기능임.
Zoom-to-Rectangle
: 그래프의 영역을 확대하거나 축소.Configure Subplots
: boader와 spacing등을 조정할 수 있는 다이알로그를 띄어줌.Edit axis, curve and image parameters
:- Matplotlib의 그래프 축, 곡선 및 이미지 매개변수를 편집하는 기능을 제공
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가지 방법이 지원됨.
- Clear and Redraw
- 표시한 Canvas객체를 동적으로 새로 생성하여
- 이전 canvas객체의 모든
axes
들을 지우고 - 새로 canvas내의 모든
axes
들을 다시 그려주는 방법 - 간단하지만 느림.
- 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())
MyCanvas
클래스는 Matplotlib의FigureCanvasQTAgg
를 상속하며,matplotlib
의Figure
객체를 PySide6 애플리케이션에 삽입하는데 사용됨.- 위의 예제의 경우 1행 2열의 subplots로 구성됨.
MW
클래스는 QMainWindow를 상속하며, PySide6 애플리케이션의 main window에 해당함.- 이 클래스를 통해
matplotlib
그림을 포함하는MyCanvas
인스턴스를 생성하고, - 두 개의 그래프를 dynamic plot하는 데 사용되는 데이터와 타이머를 설정.
- 이 클래스를 통해
update_plot0
메서드는 Clear and Redraw 를 구현함.- 이 메서드는 먼저 새로운 데이터를 생성하고,
- 그 다음에는 첫 번째 서브플롯을 지우고 새 데이터로 다시 그립니다.
update_plot1
메서드는 In-place Update 를 구현함.- 이 메서드는 먼저 새로운 데이터를 생성하고,
- 이전에 생성된 그래프의 reference 를 유지하여 그래프를 업데이트함.
- 처음 호출될 때는 새로운 선을 그리고, 이후 호출될 때는 기존 선을 업데이트.
- 마지막으로,
__main__
블록에서는 QApplication을 생성하고 MW 클래스의 인스턴스를 생성하여 애플리케이션을 실행합니다.
References
https://www.pythonguis.com/tutorials/pyside6-plotting-matplotlib/
2024.05.19 - [Python/PySide PyQt] - [PySide] FigureCanvas.mpl_connect
'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 |