質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.36%

tkinter+matplotlibで、アニメーションしたplotを停止できない。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,983

H.K2

score 42

「解決したいこと」:TkinterでGUIを作成し、その中でmatplotlibのFuncAnimationを使って、アニメーション描画をしています。

FuncAnimationの第三引数にnp.arange(1, 500)を入れた場合、アニメーションをすることを確認したため、この代わりに、Tkinterで、ボタンを押したら停止する処理を追加したいと考えました。
そのため、np.arangeの代わりに自前のメソッド(self.increment())を作って、ボタンを押したときに変更される「self.stop_mpl」の値がfalseのときは、更新しないように処理を記載しました。
しかしながら、一瞬アニメーションが停止する(ようにみえる?)のですが、
なぜかすぐにアニメーション(テストで書いているサイン波)が再開します。
いろいろ、調べたのですが、解決策が分かりませんでした。
もしよろしければ、解決策についてご教示いただければと思います。

以下に、matplotlibのfigureから、axesを取得し、アニメーションしているクラスを記載します。

class MplCanvas:
    def __init__(self, master=None, st_x=5.0, st_y=1.0,  string="MPL_visualize_No.X"):
       """
        :param master: 親widgetの実体(root, or flame)
        :param st_x: mplのfigsize_x(単位pix, dpi=100)
        :param st_y: mplのfigsize_y(単位pix, dpi=100)
        :param string: mplの表示タイトル名
        """
        # 描画関連(pane,fig,mpl,canvas,ax)
        self.pane = tk.Frame(master, padx=15, pady=10)
        self.fig = plt.Figure(figsize=(st_x, st_y))
        self.ax = self.fig.add_subplot(111)
     self.fig.subplots_adjust(bottom=0.4)
        self.mpl = tk.Label(self.pane, width=10, text=string)
        self.mpl.grid(column=0, row=0)

        # canvas割り当て
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.pane)
        self.canvas.get_tk_widget()
        self.canvas.get_tk_widget().grid(column=1, row=0)

        self.x = np.arange(0, 2 * np.pi, 0.01)  # x軸
        self.s_df = pd.DataFrame(index=[], columns=[])
        self.stop_mpl = True
        self.line, self.line2, self.line3 = None, None, None
        self.t = 0          # mpl表示用データのインデックス(経過時間t)
        self.t_max = 5000   # mpl表示用データのインデックス(最大)

        xax = self.ax.xaxis
        yax = self.ax.yaxis
        self.ax.set_xlabel("x軸")
        self.ax.set_ylabel("y軸")
        print('xax.get_major_formatter()', xax.get_major_formatter())
        print('yax.get_major_formatter()', yax.get_major_formatter())
        print('xax.get_major_locator():', xax.get_major_locator())
        print('yax.get_major_locator():', yax.get_major_locator())

    # canvasに書くものを決定。(プロット初期化)
    def setting_plot(self, disp_df=None):
        #  ためしにサイン波を書いてみる。
        self.line, = self.ax.plot(self.x, np.sin(self.x))
        self.line2, = self.ax.plot(self.x, np.sin(self.x))

    def pack(self):
        self.pane.pack()

    def place(self, x, y):
        self.pane.place(x=x, y=y)

    # animation自体の更新関数。トリガはstart_animate()で実施。
    def animate2(self, i):
        self.line.set_data(self.x, np.sin(self.x + i / 10.0))
        self.line2.set_data(self.x, np.sin(self.x + i / 5.0))
        self.ax.grid(b=True, color='k', linestyle='--', linewidth=0.5)
        return self.line, self.line2,

    # pandas_dfを使う形の場合
    def animate3(self, i):
        self.line = self.ax.plot(self.s_df.iloc[:, 2], self.s_df.iloc[:, 3])
        self.line2 = self.ax.plot(self.s_df.iloc[:, 2], self.s_df.iloc[:, 4])
        self.line3 = self.ax.plot(self.s_df.iloc[:, 2], self.s_df.iloc[:, 5])
        # self.line.set_ydata(np.sin(self.x + i / 10.0))  # update the data
        return self.line, self.line2, self.line3,

# x(t?)を進める関数(グラフのx軸値(時間t?)を進める)
    def increment(self):
        if not self.stop_mpl:   # mplが停止中じゃないとき
            if self.t < self.t_max:   # 範囲内のとき
                self.t += 1
            yield self.t

# アニメーション開始
    def start_animate(self, flg_stop=False):
        self.stop_mpl = flg_stop
#        self.ani2 = animation.FuncAnimation(self.fig, self.animate2, np.arange(1, 500), interval=50, blit=False,
#                                            repeat=True)
        self.ani2 = animation.FuncAnimation(self.fig, self.animate2, self.increment, interval=50, blit=False,
                                            repeat=True)
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • KSwordOfHaste

    2019/02/03 07:28

    ご質問のMplCanvasを制御する親ウィジェットのコードがないため具体的な制御方法が正確にわかりません。おそらく
    mcv = MplCanvas(frame, string='senser_0').pack()
    mcv.setting_plot()
    としたうえで
    mcv.start_animate()
    mcv.canvas.draw()
    でアニメーションを開始しておられるのだろうと想像しますが、停止時にどのようにしているか正確にわからないのです。

    キャンセル

回答 1

checkベストアンサー

+1

実際にアニメーションを開始する論理がどうなっているかわからないので問題の原因をはっきり指摘できません。ただ自分もmatplotlibのアニメーション&tkinterに埋め込みというところに興味があり方法を知らなかったので「matplotlib animation how to stop」でgoogle検索してみたところ、以下のQ&Aのfredさんの回答に注目しました。

https://stackoverflow.com/questions/16732379/stop-start-pause-in-python-matplotlib-animation

ただ残念なことに上記のFuncAnimation.event_sourceはドキュメントに書かれてません。おそらくfredさんはmatplotlibをハックしたんだろうと思います。自分もPyCharmでFuncAnimationの基底クラスのTimedAnimationの定義をControl+Bで表示させたところなんとなく仕組みが見えてきました。

TimedAnimationはGUIのbackendに応じたタイマーイベントを発するオブジェクト(=event_source)を生成しそのオブジェクトが発するタイマーイベントをとらえてそれによってグラフの更新をしているらしいです。このオブジェクトにはstart(), stop()というメソッドがありタイマーイベントを停止したり再開できたりするみたいですね。それがfredさんが用いた方法になります。

具体的な変更点

  • FuncAnimationのframes引数
    元のコードではincrementメソッドを渡してますが上記イベントソースを用いるならこメソッドは不要で、単に
    self.ani2 = animation.FuncAnimation(self.fig, self.animate2, frames=self.t_max, interval=50, blit=False, repeat=True)
    などとすればよいと思います。

  • pauseメソッド・resumeメソッドを追加
    MplCanvasに次のようなメソッドを追加し、ボタンを押したときなどにこれらのメソッドを呼び出せばよさそうです。

class MplCanvas:
    def __init__(self, ...):
        ...
        self.ani2 = None  # 初期化しておく

    ...

    def pause(self):
        if self.ani2 is not None:
            self.ani2.event_source.stop()

    def resume(self):
        if self.ani2 is not None:
            self.ani2.event_source.start()

なお、MplCanvasを用いたアニメーションを実験していて気付いた点があります。このクラスでstart_animationを複数回呼び出すと困ったことが起きます。呼び出すたびにアニメーションのスピードが2倍, 3倍, ...と早くなっていってしまうのです。
推測ですが原因は以前表示していたアニメーションのevent_soruceが新たなアニメーション生成後にも動き続けていてstart_animationを呼び出すごとに発生するイベントの密度が2倍、3倍と増えていくからであろうと思います。対処するにはstart_animationをとりあえず次のようにすればよいと思いました。

# setting_plotは必ずstart_animateと組で使うので個々のメソッドにしないほうがよい
def _setting_plot(self, disp_df):  # disp_dfは今は使っていない
    self.ax.clear()  # Axesをクリア
    self.line, = self.ax.plot(self.x, np.sin(self.x))
    self.line2, = self.ax.plot(self.x, np.sin(self.x))

def start_animate(self, disp_df=None):
    self.pause()  # 以前アニメーションを表示していたらそれを止める
    self._setting_plot(disp_df)  # グラフを初期描画
    self.ani2 = animation.FuncAnimation(self.fig, self.animate2, frames=self.t_max,
                                        interval=50, blit=False, repeat=True)
    self.canvas.draw()  # おそらく現在の実装ではMplCanvasの親で呼び出しているのだろうと
                        # 思いますがここで呼び出しておいた方がよいのでは?

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/03 16:33

    迅速なご対応ありがとうございます!
    帰宅したら試してみます!なるほど、Funcanimationのevent_sourceで動作を止めたりできるんですね…。
    ちなみに、「self.canvas.ani2.」とありますが、self.ani2のことでよろしかったでしょうか。

    キャンセル

  • 2019/02/03 20:03 編集

    > self.ani2のこと・・・
    これはしたり。おっしゃるとおりです。
    ---
    コードを訂正しました。

    キャンセル

  • 2019/02/03 21:34

    動いたの確認しました!ありがとうございます!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.36%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る