これからの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()

教訓

マニュアルを先にちゃんと読みましょう。

bannerAds