<gh_stars>0
import sys
import numpy as np

from PyQt5 import uic
from PyQt5.QtWidgets import QMainWindow, QSizePolicy, QMessageBox
from PyQt5.QtWidgets import QApplication

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.animation as animation

class Pendulum():
    def __init__(self):
        # default data
        self.g = 9.8
        self.pos = [0,0]  # pendulum position (x, y)
        self.time = 0

    def setData(self, data):
        self.L = data[0]
        self.M = data[1]
        self.theta0 = data[2]
        self.F = data[3]
        self.w = np.sqrt(self.g / (self.L))

        self.A = self.theta0 + self.F / (self.M * self.g)
        self.B = self.theta0

    def step(self, dt):
        self.updateTime(dt)
        theta = self.A * np.cos(self.w * self.time * np.pi / 180)\
                + self.B * np.sin(self.w * self.time * np.pi / 180)\
                - self.F/(self.M * self.g)  # degree
        while theta > 360:
            theta -= 360
        while theta < -360:
            theta += 360
        if theta > 180:
            theta = theta - 360
        if theta < -180:
            theta = 360 + theta
        theta = theta * np.pi / 180  # radian
        self.updatePos(theta)
        return ([0, self.pos[0]], [0,self.pos[1]])

    def updateTime(self, dt):
        self.time += dt

    def updatePos(self, theta):
        self.pos = (self.L * np.sin(theta),
                    -self.L * np.cos(theta))
        # 편의를 위해 아래 방향을 x, 우측을 y축으로 설정. 때문에 (L sin@, -L cos@)로 저장

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui = uic.loadUi('pendulum.ui', self)
        self.setWindowTitle("Pendulum simulation")

        self.ui.btn_start.clicked.connect(lambda: self.animation_start())
        self.ui.btn_stop.clicked.connect(lambda: self.animation_stop())
        self.ui.btn_exit.clicked.connect(lambda: QApplication.QCoreApplication.quit())
        self.ui.btn_save.clicked.connect(lambda: self.save_data())
        self.ui.btn_start.setEnabled(False)
        self.ui.btn_stop.setEnabled(False)

        self.ui_l = [self.ui.l_L, self.l_M, self.ui.l_T0, self.ui.l_F]
        self.data = [1,1,1,1]
        self.flag = False
        self.plot()

    def plot(self):
        self.graph = PlotCanvas(self, self.data)  # Pendulum을 그리는 클래스
        self.graph_data = PlotData(self)  # 진자의 좌표의 궤적을 그리는 클래스

    def save_data(self):
        try:
            self.data = [self.ui.l_L.text(), self.ui.l_M.text(), self.ui.l_T0.text(), self.ui.l_F.text()]
            self.data = list(map(float, self.data))  # 실수으로 변환.
            if False in [self.data[idx] > 0 for idx in range(2)]:
                raise ValueError()
        except:
            self.ui.btn_start.setEnabled(False)
            QMessageBox.about(self, "에러", "양의 실수를 입력하세요.")
        else:
            self.ui.btn_start.setEnabled(True)
            self.ui.btn_stop.setEnabled(False)
            self.ui.btn_stop.setText("정지")

    def animation_start(self):
        self.flag = True
        self.ui.btn_start.setEnabled(False)
        self.ui.btn_stop.setEnabled(True)
        self.ui.btn_save.setEnabled(False)
        self.graph.start_animation(self.data)
        self.graph_data.start_animation()

    def animation_stop(self):
        if self.flag == False:  # 재시작
            self.flag = True
            self.restart_wrapper()
            self.ui.btn_stop.setText("정지")

        elif self.flag == True:  # 정지
            self.flag = False
            self.stop_wrapper()
            self.ui.btn_save.setEnabled(True)
            self.ui.btn_stop.setText("재시작")

    def stop_wrapper(self):
        self.graph.ani.event_source.stop()
        self.graph_data.ani.event_source.stop()

    def restart_wrapper(self):
        self.graph.ani.event_source.start()
        self.graph_data.ani.event_source.start()

class PlotData(FigureCanvas):
    def __init__(self, window):
        self.x = []
        self.y = []
        self.t = []
        self.max = 100
        self.min = 0

        self.fig = Figure(figsize=(4, 4), dpi=100)
        FigureCanvas.__init__(self, self.fig)
        self.setParent(window.ui.c_data)
        self.plot_data()

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def plot_data(self):
        self.ax1 = self.fig.add_subplot(211)
        self.ax2 = self.fig.add_subplot(212)
        self.draw()

        self.data_x, = self.ax1.plot([], [], 'ro-', markersize = 1)
        self.data_y, = self.ax2.plot([], [], 'ro-', markersize = 1)
        self.ax1.set_xlim(self.min, self.max)
        self.ax2.set_xlim(self.min, self.max)
        self.ax1.set_xticklabels([])
        self.ax2.set_xticklabels([])
        self.ax1.set_ylabel("X")
        self.ax1.set_xlabel("time")
        self.ax2.set_ylabel("Y")
        self.ax2.set_xlabel("time")

    def start_animation(self):
        self.t = []
        self.x = []
        self.y = []
        self.ax1.set_ylim(-1.5 * pendulum.L, 1.5 * pendulum.L)
        self.ax2.set_ylim(-1.5 * pendulum.L, 1.5 * pendulum.L)
        self.ani = animation.FuncAnimation(self.fig, self.animate, frames=30, interval=10, blit=True)

    def animate(self, i):
        if pendulum.time >= self.max:
            self.min = self.min + pendulum.time - self.max + 1
            self.max = pendulum.time + 1
            self.ax1.set_xlim(self.min, self.max)
            self.ax2.set_xlim(self.min, self.max)
        self.x.append(pendulum.pos[0])
        self.y.append(pendulum.pos[1])
        self.t.append(pendulum.time)
        self.data_x.set_data(self.t, self.x)
        self.data_y.set_data(self.t, self.y)

        return self.data_x, self.data_y,
        # Return values tell framework what should be animated.

class PlotCanvas(FigureCanvas):
    def __init__(self, window, data):
        self.data = data

        self.fig1 = Figure(figsize=(4, 4), dpi=100)
        FigureCanvas.__init__(self, self.fig1)
        self.setParent(window.ui.c_graph)
        self.plot_graph()

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def plot_graph(self):
        self.ax = self.fig1.add_subplot(111, aspect="equal", xlim=(-1,1), ylim=(-1,1))
        self.ax.grid()

        # line, = ax.plot([0], [0], 'sr-', lw=1)
        self.line, = self.ax.plot([0, 0], [0, 0], 'ro-', lw=2)
        self.time_text = self.ax.text(0.02, 0.95, '', transform=self.ax.transAxes)
        self.axis_x = self.ax.text(0.02, 0.90, '', transform=self.ax.transAxes)
        self.axis_y = self.ax.text(0.02, 0.85, '', transform=self.ax.transAxes)

    def init(self):
        """initialize animation"""
        self.line.set_data([], [])
        self.time_text.set_text('time = 0.0')
        self.axis_x.set_text('X : 0.0')
        self.axis_y.set_text('Y : 0.0')
        return self.line, self.time_text,self.axis_x, self.axis_y,

    def animate(self, i):
        """perform animation step"""
        self.line.set_data(pendulum.step(self.dt))
        self.time_text.set_text('time = %.1f' % pendulum.time)
        self.axis_x.set_text('X : %.1f' % pendulum.pos[0])
        self.axis_y.set_text('Y : %.1f' % pendulum.pos[1])

        return self.line, self.time_text,self.axis_x, self.axis_y,
        # Return values tell framework what should be animated.


    def start_animation(self, d):
        self.data = d
        while self.data[2] > 360:
            self.data[2] -= 360

        if self.data[2] > 180:
            self.data[2] = self.data[2] - 360

        pendulum.setData(self.data)
        pendulum.pos=[0,0]
        pendulum.time=0
        self.ax.set_xlim(-1.5 * self.data[0], 1.5 * self.data[0])
        self.ax.set_ylim(-1.5 * self.data[0], 1.5 * self.data[0])

        # choose the interval based on dt and the time to animate one step
        self.dt = 1.0 / 30 # 30fps
        interval = 100 * self.dt
        self.ani = animation.FuncAnimation(self.fig1,
                                           self.animate,  # this called sequentially
                                           frames=30,
                                           interval=interval,  # ms
                                           blit=True,
                                           # this tells the animation to only re-draw the plot which has changed.
                                           init_func=self.init)

pendulum = Pendulum()
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
import sys
import numpy as np

from PyQt5 import uic
from PyQt5.QtWidgets import QMainWindow, QSizePolicy, QMessageBox
from PyQt5.QtWidgets import QApplication

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.animation as animation

class Pendulum():
    def __init__(self):
        # default data
        self.g = 9.8
        self.pos = [0,0]  # pendulum position (x, y)
        self.time = 0

    def setData(self, data):
        self.L = data[0]
        self.M = data[1]
        self.theta0 = data[2]
        self.F = data[3]
        self.w = np.sqrt(self.g / (self.L))

        self.A = self.theta0 + self.F / (self.M * self.g)
        self.B = self.theta0

    def step(self, dt):
        self.updateTime(dt)
        theta = self.A * np.cos(self.w * self.time * np.pi / 180)\
                + self.B * np.sin(self.w * self.time * np.pi / 180)\
                - self.F/(self.M * self.g)  # degree
        while theta > 360:
            theta -= 360
        while theta < -360:
            theta += 360
        if theta > 180:
            theta = theta - 360
        if theta < -180:
            theta = 360 + theta
        theta = theta * np.pi / 180  # radian
        self.updatePos(theta)
        return ([0, self.pos[0]], [0,self.pos[1]])

    def updateTime(self, dt):
        self.time += dt

    def updatePos(self, theta):
        self.pos = (self.L * np.sin(theta),
                    -self.L * np.cos(theta))
        # 편의를 위해 아래 방향을 x, 우측을 y축으로 설정. 때문에 (L sin@, -L cos@)로 저장

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui = uic.loadUi('pendulum.ui', self)
        self.setWindowTitle("Pendulum simulation")

        self.ui.btn_start.clicked.connect(lambda: self.animation_start())
        self.ui.btn_stop.clicked.connect(lambda: self.animation_stop())
        self.ui.btn_exit.clicked.connect(lambda: QApplication.QCoreApplication.quit())
        self.ui.btn_save.clicked.connect(lambda: self.save_data())
        self.ui.btn_start.setEnabled(False)
        self.ui.btn_stop.setEnabled(False)

        self.ui_l = [self.ui.l_L, self.l_M, self.ui.l_T0, self.ui.l_F]
        self.data = [1,1,1,1]
        self.flag = False
        self.plot()

    def plot(self):
        self.graph = PlotCanvas(self, self.data)  # Pendulum을 그리는 클래스
        self.graph_data = PlotData(self)  # 진자의 좌표의 궤적을 그리는 클래스

    def save_data(self):
        try:
            self.data = [self.ui.l_L.text(), self.ui.l_M.text(), self.ui.l_T0.text(), self.ui.l_F.text()]
            self.data = list(map(float, self.data))  # 실수으로 변환.
            if False in [self.data[idx] > 0 for idx in range(2)]:
                raise ValueError()
        except:
            self.ui.btn_start.setEnabled(False)
            QMessageBox.about(self, "에러", "양의 실수를 입력하세요.")
        else:
            self.ui.btn_start.setEnabled(True)
            self.ui.btn_stop.setEnabled(False)
            self.ui.btn_stop.setText("정지")

    def animation_start(self):
        self.flag = True
        self.ui.btn_start.setEnabled(False)
        self.ui.btn_stop.setEnabled(True)
        self.ui.btn_save.setEnabled(False)
        self.graph.start_animation(self.data)
        self.graph_data.start_animation()

    def animation_stop(self):
        if self.flag == False:  # 재시작
            self.flag = True
            self.restart_wrapper()
            self.ui.btn_stop.setText("정지")

        elif self.flag == True:  # 정지
            self.flag = False
            self.stop_wrapper()
            self.ui.btn_save.setEnabled(True)
            self.ui.btn_stop.setText("재시작")

    def stop_wrapper(self):
        self.graph.ani.event_source.stop()
        self.graph_data.ani.event_source.stop()

    def restart_wrapper(self):
        self.graph.ani.event_source.start()
        self.graph_data.ani.event_source.start()

class PlotData(FigureCanvas):
    def __init__(self, window):
        self.x = []
        self.y = []
        self.t = []
        self.max = 100
        self.min = 0

        self.fig = Figure(figsize=(4, 4), dpi=100)
        FigureCanvas.__init__(self, self.fig)
        self.setParent(window.ui.c_data)
        self.plot_data()

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def plot_data(self):
        self.ax1 = self.fig.add_subplot(211)
        self.ax2 = self.fig.add_subplot(212)
        self.draw()

        self.data_x, = self.ax1.plot([], [], 'ro-', markersize = 1)
        self.data_y, = self.ax2.plot([], [], 'ro-', markersize = 1)
        self.ax1.set_xlim(self.min, self.max)
        self.ax2.set_xlim(self.min, self.max)
        self.ax1.set_xticklabels([])
        self.ax2.set_xticklabels([])
        self.ax1.set_ylabel("X")
        self.ax1.set_xlabel("time")
        self.ax2.set_ylabel("Y")
        self.ax2.set_xlabel("time")

    def start_animation(self):
        self.t = []
        self.x = []
        self.y = []
        self.ax1.set_ylim(-1.5 * pendulum.L, 1.5 * pendulum.L)
        self.ax2.set_ylim(-1.5 * pendulum.L, 1.5 * pendulum.L)
        self.ani = animation.FuncAnimation(self.fig, self.animate, frames=30, interval=10, blit=True)

    def animate(self, i):
        if pendulum.time >= self.max:
            self.min = self.min + pendulum.time - self.max + 1
            self.max = pendulum.time + 1
            self.ax1.set_xlim(self.min, self.max)
            self.ax2.set_xlim(self.min, self.max)
        self.x.append(pendulum.pos[0])
        self.y.append(pendulum.pos[1])
        self.t.append(pendulum.time)
        self.data_x.set_data(self.t, self.x)
        self.data_y.set_data(self.t, self.y)

        return self.data_x, self.data_y,
        # Return values tell framework what should be animated.

class PlotCanvas(FigureCanvas):
    def __init__(self, window, data):
        self.data = data

        self.fig1 = Figure(figsize=(4, 4), dpi=100)
        FigureCanvas.__init__(self, self.fig1)
        self.setParent(window.ui.c_graph)
        self.plot_graph()

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def plot_graph(self):
        self.ax = self.fig1.add_subplot(111, aspect="equal", xlim=(-1,1), ylim=(-1,1))
        self.ax.grid()

        # line, = ax.plot([0], [0], 'sr-', lw=1)
        self.line, = self.ax.plot([0, 0], [0, 0], 'ro-', lw=2)
        self.time_text = self.ax.text(0.02, 0.95, '', transform=self.ax.transAxes)
        self.axis_x = self.ax.text(0.02, 0.90, '', transform=self.ax.transAxes)
        self.axis_y = self.ax.text(0.02, 0.85, '', transform=self.ax.transAxes)

    def init(self):
        """initialize animation"""
        self.line.set_data([], [])
        self.time_text.set_text('time = 0.0')
        self.axis_x.set_text('X : 0.0')
        self.axis_y.set_text('Y : 0.0')
        return self.line, self.time_text,self.axis_x, self.axis_y,

    def animate(self, i):
        """perform animation step"""
        self.line.set_data(pendulum.step(self.dt))
        self.time_text.set_text('time = %.1f' % pendulum.time)
        self.axis_x.set_text('X : %.1f' % pendulum.pos[0])
        self.axis_y.set_text('Y : %.1f' % pendulum.pos[1])

        return self.line, self.time_text,self.axis_x, self.axis_y,
        # Return values tell framework what should be animated.


    def start_animation(self, d):
        self.data = d
        while self.data[2] > 360:
            self.data[2] -= 360

        if self.data[2] > 180:
            self.data[2] = self.data[2] - 360

        pendulum.setData(self.data)
        pendulum.pos=[0,0]
        pendulum.time=0
        self.ax.set_xlim(-1.5 * self.data[0], 1.5 * self.data[0])
        self.ax.set_ylim(-1.5 * self.data[0], 1.5 * self.data[0])

        # choose the interval based on dt and the time to animate one step
        self.dt = 1.0 / 30 # 30fps
        interval = 100 * self.dt
        self.ani = animation.FuncAnimation(self.fig1,
                                           self.animate,  # this called sequentially
                                           frames=30,
                                           interval=interval,  # ms
                                           blit=True,
                                           # this tells the animation to only re-draw the plot which has changed.
                                           init_func=self.init)

pendulum = Pendulum()
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
# default data # pendulum position (x, y) # degree # radian # 편의를 위해 아래 방향을 x, 우측을 y축으로 설정. 때문에 (L sin@, -L cos@)로 저장 # Pendulum을 그리는 클래스 # 진자의 좌표의 궤적을 그리는 클래스 # 실수으로 변환. # 재시작 # 정지 # Return values tell framework what should be animated. # line, = ax.plot([0], [0], 'sr-', lw=1) initialize animation perform animation step # Return values tell framework what should be animated. # choose the interval based on dt and the time to animate one step # 30fps # this called sequentially # ms # this tells the animation to only re-draw the plot which has changed.