본문 바로가기

PyQt GUI

PyQt GUI (14) 그래프 라이브러리 Matplotlib을 GUI에 포함하기(embeding)

728x90

Matplotlib은 Python을 이용하여 그래프를 그릴 때 매우 빈번하게 사용하는 라이브러리 입니다. 

matplotlib을 이용하면 단순한 2차원 그래프부터, 3차원 그래프 뿐 아니라, 막대그래프, 파이그래프 등 거의 원하는 모든 형태의 그래프를 그릴 수 있습니다. Python에는 다양한 그래프 라이브러리가 있지만, matplotlib은 그 중에서도 아주 많이 사용되는 라이브러리이니, 만일 matplotlib을 처음 접하시는 분이라면 아래 포스팅을 읽는 것 보다는 GUI와는 상관 없이 우선 일반적인 python 출력을 통해 matplotlib에서 그래프를 그려 보는 것을 추천합니다. 꼭 GUI 뿐만 아니라 다양한 곳에서 활용도가 매우 높은 라이브러리 이기 때문입니다. (제 블로그의 모든 그래프는 이 matplotlib을 이용하여 그리고 있습니다)

 

이번 포스팅에서는 matplotlib을 PyQt GUI에 넣는(embeding)하는 방법을 알아 보도록 하겠습니다. 

 

우선, 매우 간단한 그래프를 그려주는 코드의 예시를 보겠습니다. 

import sys
import numpy as np
from PyQt5.QtWidgets import *

import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

class MplCanvas(FigureCanvasQTAgg):
    def __init__(self):
        fig = Figure()
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class Main(QDialog):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.sc = MplCanvas()
        self.draw_graph()
        toolbar = NavigationToolbar(self.sc, self)

        layout = QVBoxLayout()
        layout.addWidget(toolbar)
        layout.addWidget(self.sc)

        self.setLayout(layout)
        self.show()

    def draw_graph(self):
        xs = np.linspace(0, 1, 101)
        ys = xs**2.0
        self.sc.axes.cla()
        self.sc.axes.plot(xs, ys, color = 'blue', lw = 1)
        self.sc.axes.set_xlabel("x")
        self.sc.axes.set_ylabel("y")
        self.sc.axes.grid()
        self.sc.draw()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = Main()
    sys.exit(app.exec_())

전체 코드는 위와 같습니다. 위 코드를 실행하면, 아래와 같은 QDialog 창을 얻을 수 있습니다. 

위 예시 프로그램의 이미지 처럼, QDialog의 메인에 matplotlib 그래프가 추가되었습니다. 

 

코드를 위에서 부터 하나 하나 설명하면 다음과 같습니다. 물론, 이전 포스팅에서 설명한 내용은 생략하고, matplotlib을 추가하는것과 관련된 부분만을 설명하도록 하겠습니다. 

import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

우선, matplotlib과 과련된 라이브러리를 불러 와야 합니다. 평소에 matplotlib을 자주 사용하는 분이라면 위의 라이브러리를 불러 오는것이 익숙하실 텐데요, 만일 matplotlib을 처음 사용하시는 분이라면 그냥 위 네 줄을 복-붙 하시면 됩니다. 위 라이브러리들을 설명하는 것은, PyQt에 대해서 설명하는 것이라기 보다는 말 그대로 matplotlib에 대해서 설명하는 것이니, matplotlib에 대해서 다루는 다른 사이트를 통해 이해하는 편이 더 좋을 것 같습니다. 

class MplCanvas(FigureCanvasQTAgg):
    def __init__(self):
        fig = Figure()
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)

MplCanvas 클래스를 생성하였습니다. 실제로 이 클래스가 QDialog의 main Layout에 추가되는 Widget이 됩니다. matplotlib의 그래프 출력을 단순히 화면에 팝업의 형태로 출력되거나, 파일의 형태로 저장하는 것이 아닌, Qt의 형태로 출력하는 클래스를 만든다고 생각하면 됩니다. 

self.axes = fig.add_subplot(111)

은 matplotlib을 이용하여 subplot을 생성하는 매우 기본적인 명령어 라고 할 수 있습니다. 이것에 익숙하지 않으신 분은, matplotlib을 이용하여 다양한 형태의 그래프를 먼저 그려 보는 것을 추천합니다. 

self.sc = MplCanvas()
self.draw_graph()
toolbar = NavigationToolbar(self.sc, self)

layout = QVBoxLayout()
layout.addWidget(toolbar)
layout.addWidget(self.sc)

init_ui 메서드 부분입니다. 앞서 설명드린대로 MplCanvas는 실제로 그래프가 그려지는 위젯이니 이 클래스를 하나 만들어 줍니다. 

 

draw_graph() 메서드를 이용하여 실제로 그리려고 하는 그래프를 matplotlib의 기본 코딩을 이용하여 그려주면 됩니다. 이 부분은 나중에 설명하도록 하겠습니다. NavigationToolbar()는 꼭 있어야 하는 부분은 아니지만, 있으면 활용도가 높아지기 때문에 추가하였습니다. GUI가 아닌 일반적인 프로그램에서 matplotlib을 이용해서 그래프를 띄우면, 그래프 상단에 나오는 다양한 기능에 해당하는 부분입니다. 

 

마지막 세 줄은 GUI에서 나오는 일반적인 구문인데, 앞에서 생성한 toolbar와 self.sc를 main Layout에 추가하는 부분입니다. 

    def draw_graph(self):
        xs = np.linspace(0, 1, 101)
        ys = xs**2.0
        self.sc.axes.cla()
        self.sc.axes.plot(xs, ys, color = 'blue', lw = 1)
        self.sc.axes.set_xlabel("x")
        self.sc.axes.set_ylabel("y")
        self.sc.axes.grid()
        self.sc.draw()

실제로 원하는 그래프를 그리는 draw_graph() 메서드 부분입니다. 이 부분은 GUI와는 상관 없이 matplotlib을 이용해서 그래프를 그리는 부분입니다. 원하는 형태의 그래프를 자유롭게 그리면 됩니다. 

 

self.sc.axes.cla()는 그려져 있는 그래프를 모두 지우는 부분이며, self.sc.draw()는 그래프를 GUI에 반영하도록(그리도록) 하는 부분입니다. 위 예시에서는 numpy 라이브러리를 이용하여 구간 [0, 1]을 101조각으로 나누고, y = x^2를 계산하여, (x,y)의 그래프를 그리도록 한 것 입니다. 위 실행 예시 이미지에서 볼 수 있는 것과 같이 원하는 형태의 그래프가 GUI 상에 반영 되었습니다. 

 

순수하게 GUI와 관련하여 추가된 부분은 MplCanvas 클래스를 생성하고, 이를 main Layout에 추가하는 부분 밖에 없습니다. GUI 상에 그래프를 추가하는 매우 활용도 높은 코딩인데, 실제로는 몇 줄 정도면 쉽게 구현할 수 있습니다. 

 

여기서 포스팅을 마칠 수 있지만, 그래프 활용 예시를 하나 만들어 보도록 하겠습니다. 사용자가 정의하는 함수를 그래프로 그려주는 프로그램을 만들어 보도록 하겠습니다. 위 프로그램에서 그리게 되는 함수의 형태는 y=x^2으로 하드코딩 되어 있습니다. 따라서

 

(1) 사용자가 수식을 입력 할 수 있는 부분과,

(2) 입력된 수식에 따라서 그래프를 그려주는 부분

 

을 추가적으로 구현해 보도록 하겠습니다.

 

(1) 사용자 수식 입력 부분 추가

 

수식은 text형태의 변수 이므로 QLineEdit()을 이용하면 됩니다. 그리고 수식 입력이 끝나고 나서 그래프를 그리라는 명령을 전달해 주는 QPushButton()을 또 추가하면 됩니다. 두 Widget을 하나의 QHBoxLayout()을 만들어서 묶어 주도록 하겠습니다. 이전 포스팅을 모두 봐 오신 분이라면 매우 쉽게 할 수 있을거라 생각합니다. 예시는 아래와 같습니다. 

    def init_ui(self):
        self.sc = MplCanvas()
        toolbar = NavigationToolbar(self.sc, self)

        self.equation = QLineEdit()
        button = QPushButton("Draw")
        button.clicked.connect(self.draw_graph)
        layout_equation_button = QHBoxLayout()
        layout_equation_button.addWidget(self.equation)
        layout_equation_button.addWidget(button)

        layout = QVBoxLayout()
        layout.addLayout(layout_equation_button)
        layout.addWidget(toolbar)
        layout.addWidget(self.sc)

        self.setLayout(layout)
        self.show()

self.equation, button, layout_equation_button을 추가로 생성하였고, layout_equation_button을 메인 Layout의 맨 윗부분에 추가하였습니다. 그리고 사용자가 수식을 입력하기 전까지는 아무런 그래프가 그려지지 않아야 하기 때문에 self.draw_graph()는 생략하였습니다. 이 상태에서 프로그램을 실행하면 생성되는 QDialog는 아래와 같습니다. 

우리가 원하는 대로 수식을 입력할 수 있는 QLineEdit()과 수식 입력 후, 그래프를 그리는 명령을 하는 "Draw" 이름의 QPushButton()이 생성되었습니다. 

 

(2) 입력된 수식에 따라서 그래프를 그려주는 부분

 

사용자가 입력한 text 형태의 수식을 실제 숫자로 계산하기 위해서는 python의 기본 내장 함수인 eval()을 사용하면 됩니다. eval()은 text 형태의 수식을 입력으로 받아서, 이를 계산하고 숫자 형태의 출력을 하는 함수 입니다. eval()에 대해서 익숙하지 않은 분이라면 역시나 eval()에 대해서 익숙해진 다음에 다음 부분을 읽는 것이 좋습니다. 

 

draw_graph() 메서드를 아래와 같이 변경하였습니다. 

from math import sin, cos, tan, log    
    
    def draw_graph(self):
        formula = self.equation.text()
        xs = np.linspace(0, 1, 101)
        
        try:
            ys = [eval(formula) for x in xs]
            self.sc.axes.cla()
            self.sc.axes.plot(xs, ys, color = 'blue', lw = 1)
            self.sc.axes.set_xlabel("x")
            self.sc.axes.set_ylabel("y")
            self.sc.axes.grid()
            self.sc.draw()
        except Exception as e:
            QMessageBox.critical(self, "Error", "Wrong equation : {}".format(e))

formula 에는 사용자가 입력한 수식이 저장됩니다. 

 

그래프의 정의역은 [0, 1] 구간이 됩니다. numpy의 linspace를 사용한 것인데, numpy 역시 매우 많이 사용되는 라이브러리 입니다. matplotlib을 아시는 분이라면 numpy 역시 잘 알 거라고 생각합니다. 

 

try 안의 구문이 사용자가 입력한 함수값을 계산하는 부분입니다. eval(formular)를 이용하여 사용자가 입력한 수식에 대해서 xs의 값을 하나 하나 입력해 가면서 ys를 얻게 됩니다. 즉, xs는 함수의 정의역을, ys는 xs에 대한 사용자 입력 수식의 함수값이 list의 형태로 저장됩니다. 삼각함수(sin, cos, tan), 로그함수(log)와 같이 복잡한 형태의 함수를 입력할 때에는 반드시 해당 함수를 사용할 수 있도록, 라이브러리를 추가해야 하는데요, "from math import sin, cos, tan, log" 와 같이 간단하게 추가할 수 있습니다. 

 

이제 xs, ys 를 matplotlib을 통해서 그려주면 됩니다. 

 

아래 5줄은 matplotlib을 이용하여 그래프를 그려주는 부분과 생성된 그래프를 widget을 통해서 그려주는 부분입니다. 앞에서 이미 설명하였습니다. 

 

excetp를 이용하여 예외 처리를 했는데요, 사용자가 입력한 수식이 부정확 하거나, eval() 함수에서 처리하지 못하는 수식이 입력된 경우에는 에러가 발생했다는 문구를 팝업으로 띄워 주는 역할을 합니다. Exception 을 이용하여 에러를 e 라는 변수에 저장하고, 만일 exception이 발생한 경우에는 이 내용을 팝업으로 알려줍니다. QMessage.critical()은 PyQt GUI 포스팅에서 처음 등장하는 위젯인데, 팝업을 띄워주는 위젯이라고 생각하면 됩니다. 차후에 팝업 위젯에 대해서 한꺼번에 설명하도록 하겠습니다. 

 

실제로 한 번 사용해 보도록 하겠습니다. y = x^2 + 5*cos(x) 의 그래프를 그려 보도록 하겠습니다. 

위 수식 입력창에 수식을 입력하고 Draw를 클릭하면, 원하는 대로 그래프가 그려졌습니다. 

 

위에서는 수식을 입력하는 기능을 추가하였지만, x정의역의 범위를 지정한다든가 혹은 그래프를 그릴때 색이나 라인의 굵기를 변경한다든가 등 원하는 옵션을 입력할 수 있게 Combobox나 Spinbox등을 추가할 수도 있습니다. 

728x90