これからのGUIは、Jupyterだろ!ということで、いろいろ調べているのだけど、ハマったのでメモ。下のコードは動く。animate
という関数がコールバックされてプロット内のline
をアップデートしている。直接コールバック関数のなかでplot
してもいいらしいが、ここでの本題ではないので、とりあえずこれで。
%matplotlib nbagg
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
line, = ax.plot([], [], lw=2)
def animate(i):
x = np.linspace(0, 1, 1000)
y = np.sin((x + (0.01 * i)) * 2 * np.pi) * 0.5 + 0.5
line.set_data(x, y)
return (line,)
animation.FuncAnimation(fig, animate, frames=100,
interval=20, blit=True)
おどろくべきことに、このコードを関数の中に移しただけの次のコードは動かない。
def createGraph():
fig, ax = plt.subplots()
line, = ax.plot([], [], lw=2)
def animate(i):
x = np.linspace(0, 1, 1000)
y = np.sin((x + (0.01 * i)) * 2 * np.pi) * 0.5 + 0.5
line.set_data(x, y)
return (line,)
animation.FuncAnimation(fig, animate, frames=100,
interval=20, blit=True)
createGraph()
エラーがでるわけではないのだけど、プロット領域が表示されるだけで、全くグラフが描画されない。いろいろ試してみるとanimate
関数が全く呼ばれていないことがわかった。
これを動くようにするには、createGraph
関数の末尾で作成したAnimation
オブジェクトをreturn
すればいい。
return animation.FuncAnimation(fig, animate, frames=100,
interval=20, blit=True)
なぜなのか
マニュアルを見たら、冒頭にちゃんと書いてあった。。
In both cases it is critical to keep a reference to the instance object. The animation is advanced by a timer (typically from the host GUI framework) which the Animation object holds the only reference to. If you do not hold a reference to the Animation object, it (and hence the timers), will be garbage collected which will stop the animation.
Animation
オブジェクトの参照が作られると同時に開放されるので、animate
を一度も呼び出す暇もなく、即回収されていた、ということらしい。1番最初の書き方でも返り値を変数に代入していないので即死しそうなものだけど、Jupyterでは各入力セッションの最後の文の値が自動的に_
に代入されるので、参照が失われないので死ななかったようだ。だから、末尾に
a = 'test'
とかダミーの文を入れてやると、_
にバインドされるのがこのダミーの文の結果になるので動かなくなる。
でも、一度動き出すと代入した変数を書き潰しても止まらないんだよなあ。どこかに別途参照が保持されるのだろうか。
ちなみに、アニメーションを停止させるには下記のようにするればよい。
_.event_source.stop()
教訓
マニュアルを先にちゃんと読みましょう。