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

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

新規登録して質問してみよう
ただいま回答率
85.50%
Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Q&A

解決済

1回答

3255閲覧

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

H.K2

総合スコア88

Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

0グッド

0クリップ

投稿2019/02/02 16:35

編集2019/02/10 14:54

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

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

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

python

1 2class MplCanvas: 3 def __init__(self, master=None, st_x=5.0, st_y=1.0, string="MPL_visualize_No.X"): 4 """ 5 :param master: 親widgetの実体(root, or flame) 6 :param st_x: mplのfigsize_x(単位pix, dpi=100) 7 :param st_y: mplのfigsize_y(単位pix, dpi=100) 8 :param string: mplの表示タイトル名 9 """ 10 # 描画関連(pane,fig,mpl,canvas,ax) 11 self.pane = tk.Frame(master, padx=15, pady=10) 12 self.fig = plt.Figure(figsize=(st_x, st_y)) 13 self.ax = self.fig.add_subplot(111) 14     self.fig.subplots_adjust(bottom=0.4) 15 self.mpl = tk.Label(self.pane, width=10, text=string) 16 self.mpl.grid(column=0, row=0) 17 18 # canvas割り当て 19 self.canvas = FigureCanvasTkAgg(self.fig, master=self.pane) 20 self.canvas.get_tk_widget() 21 self.canvas.get_tk_widget().grid(column=1, row=0) 22 23 self.x = np.arange(0, 2 * np.pi, 0.01) # x軸 24 self.s_df = pd.DataFrame(index=[], columns=[]) 25 self.stop_mpl = True 26 self.line, self.line2, self.line3 = None, None, None 27 self.t = 0 # mpl表示用データのインデックス(経過時間t) 28 self.t_max = 5000 # mpl表示用データのインデックス(最大) 29 30 xax = self.ax.xaxis 31 yax = self.ax.yaxis 32 self.ax.set_xlabel("x軸") 33 self.ax.set_ylabel("y軸") 34 print('xax.get_major_formatter()', xax.get_major_formatter()) 35 print('yax.get_major_formatter()', yax.get_major_formatter()) 36 print('xax.get_major_locator():', xax.get_major_locator()) 37 print('yax.get_major_locator():', yax.get_major_locator()) 38 39 # canvasに書くものを決定。(プロット初期化) 40 def setting_plot(self, disp_df=None): 41 #  ためしにサイン波を書いてみる。 42 self.line, = self.ax.plot(self.x, np.sin(self.x)) 43 self.line2, = self.ax.plot(self.x, np.sin(self.x)) 44 45 def pack(self): 46 self.pane.pack() 47 48 def place(self, x, y): 49 self.pane.place(x=x, y=y) 50 51 # animation自体の更新関数。トリガはstart_animate()で実施。 52 def animate2(self, i): 53 self.line.set_data(self.x, np.sin(self.x + i / 10.0)) 54 self.line2.set_data(self.x, np.sin(self.x + i / 5.0)) 55 self.ax.grid(b=True, color='k', linestyle='--', linewidth=0.5) 56 return self.line, self.line2, 57 58 # pandas_dfを使う形の場合 59 def animate3(self, i): 60 self.line = self.ax.plot(self.s_df.iloc[:, 2], self.s_df.iloc[:, 3]) 61 self.line2 = self.ax.plot(self.s_df.iloc[:, 2], self.s_df.iloc[:, 4]) 62 self.line3 = self.ax.plot(self.s_df.iloc[:, 2], self.s_df.iloc[:, 5]) 63 # self.line.set_ydata(np.sin(self.x + i / 10.0)) # update the data 64 return self.line, self.line2, self.line3, 65 66# x(t?)を進める関数(グラフのx軸値(時間t?)を進める) 67 def increment(self): 68 if not self.stop_mpl: # mplが停止中じゃないとき 69 if self.t < self.t_max: # 範囲内のとき 70 self.t += 1 71 yield self.t 72 73# アニメーション開始 74 def start_animate(self, flg_stop=False): 75 self.stop_mpl = flg_stop 76# self.ani2 = animation.FuncAnimation(self.fig, self.animate2, np.arange(1, 500), interval=50, blit=False, 77# repeat=True) 78 self.ani2 = animation.FuncAnimation(self.fig, self.animate2, self.increment, interval=50, blit=False, 79 repeat=True)

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KSwordOfHaste

2019/02/02 22:28

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

回答1

0

ベストアンサー

実際にアニメーションを開始する論理がどうなっているかわからないので問題の原因をはっきり指摘できません。ただ自分も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に次のようなメソッドを追加し、ボタンを押したときなどにこれらのメソッドを呼び出せばよさそうです。

Python

1class MplCanvas: 2 def __init__(self, ...): 3 ... 4 self.ani2 = None # 初期化しておく 5 6 ... 7 8 def pause(self): 9 if self.ani2 is not None: 10 self.ani2.event_source.stop() 11 12 def resume(self): 13 if self.ani2 is not None: 14 self.ani2.event_source.start()

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

Python

1# setting_plotは必ずstart_animateと組で使うので個々のメソッドにしないほうがよい 2def _setting_plot(self, disp_df): # disp_dfは今は使っていない 3 self.ax.clear() # Axesをクリア 4 self.line, = self.ax.plot(self.x, np.sin(self.x)) 5 self.line2, = self.ax.plot(self.x, np.sin(self.x)) 6 7def start_animate(self, disp_df=None): 8 self.pause() # 以前アニメーションを表示していたらそれを止める 9 self._setting_plot(disp_df) # グラフを初期描画 10 self.ani2 = animation.FuncAnimation(self.fig, self.animate2, frames=self.t_max, 11 interval=50, blit=False, repeat=True) 12 self.canvas.draw() # おそらく現在の実装ではMplCanvasの親で呼び出しているのだろうと 13 # 思いますがここで呼び出しておいた方がよいのでは?

投稿2019/02/03 05:13

編集2019/02/03 11:06
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

H.K2

2019/02/03 07:33

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

2019/02/03 11:06 編集

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

2019/02/03 12:34

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問