タイトル画像
琴葉葵

前回、「Matplotlib」のちょっと違った使い方を紹介って言ってたけど、どんな使い方なの?

琴葉茜

今まで紹介していたMatplotlibって、基本的には既に用意されたデータを扱ってグラフを表示する使い方だったでしょ?

琴葉茜

今回は、マイクの音をリアルタイムで取得してその情報を表示したいから、「グラフのアニメーション」を表示する方法になるんだ

琴葉葵

「グラフのアニメーション」...?

琴葉茜

まぁ簡単にいえば、常にグラフの情報を書き換え続けるって感じだね
「Pyxel」でホッケーを作った時みたいに、「値の更新」と「描画」を1秒間に何回も行う時の感覚に近いかな?

琴葉葵

なるほど、そんなことがMatplotlibでもできるんだね!

琴葉茜

それで、この更新を続けるために使うのが「matplotlib.animation」

琴葉葵

ちゃんとリアルタイムで更新するための機能もあるんだね!

琴葉茜

じゃあまずは試しに、「sin波」がずっと流れ続けるっていうアニメーションを作って解説していこう
下にあるサンプルのプログラムですぐに動くと思うから、コピー&ペーストで試してみてね!

琴葉葵

オッケー!

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import japanize_matplotlib


class SinWaveAnimator:
    """sin波を少しずつずらしながら描き直すクラス"""

    def __init__(self, ax):
        self.x = np.linspace(0, 4 * np.pi, 200)
        self.phase = 0  # 波をずらすための値。selfに持たせることで状態を保持する

        # 最初に1回だけ線を描いておき、以降はこの self.line を描き直していく
        self.line, = ax.plot(self.x, np.sin(self.x))

    def update(self, frame):
        """
        matplotlibが一定間隔ごとに自動で呼び出すメソッド。
        frame: 何回目の呼び出しかを表す番号(今回は使わない)
        """
        self.phase += 0.1  # 呼ばれるたびに波を少しずつずらす
        self.line.set_ydata(np.sin(self.x + self.phase))
        return self.line,


"""1.描画するグラフの土台を準備"""
fig, ax = plt.subplots()
ax.set_ylim(-1.5, 1.5)
ax.set_title("sin波が流れるアニメーション")

"""2.sin波を描画するためのクラスを扱うための準備"""
animator = SinWaveAnimator(ax)

"""4.アニメーションを適用"""
# animator.update を渡すことで、更新する度に同じ animator の self.phase が更新され続ける
ani = animation.FuncAnimation(fig, animator.update, interval=50)

"""5.グラフを表示"""
plt.show()
sin波が流れるアニメーション
琴葉葵

おおー!グラフが波打って動いてる...!!

琴葉茜

このプログラムのポイントは、37行目にある「animation.FuncAnimation()」
1つ目の引数は、事前に用意したグラフの土台部分(fig)
2つ目の引数は、更新のたびに動かしたい関数(animator.update)
3つ目の引数は、どれくらいの頻度(ミリ秒)で更新するか(interval=50)
を指定することで、自分でループを書かなくてもウィンドウを閉じない限りはずっと動き続けてくれるよ

ani = animation.FuncAnimation(fig, animator.update, interval=50)
琴葉葵

あれ?2つ目の引数で関数を指定してるのに、後ろに「()」が要らないんだね

琴葉茜

あくまで、更新の度に動かしたい関数を「指定」してるだけだからね
逆に関数に「()」が付くと「その場で実行する」って意味になる、と認識しておくと良いと思うよ

琴葉葵

そうなんだ!りょーかい!!

琴葉茜

「SinWaveAnimator」については、sin波を更新の度に「0.1」ずつズラせるように変数「self.phase」(12, 22行目)を用意してるって感じ
「self.x」はNumPyの配列になってるから、「self.phase」の値を足し算すると、各要素にその数値がまとめて足し合わされるよ

class SinWaveAnimator:
    def __init__(self, ax):
        self.x = np.linspace(0, 4 * np.pi, 200)
        self.phase = 0  # 波をずらすための値

        self.line, = ax.plot(self.x, np.sin(self.x))

    def update(self, frame):
        self.phase += 0.1
        self.line.set_ydata(np.sin(self.x + self.phase))
        return self.line,
琴葉葵

NumPyの配列に四則演算をすると、全ての要素にまとめて適用されるんだったね!

琴葉茜

あとは、「self.line」(15行目)がグラフの描画を管理している変数になっているから、その中を「.set_ydata()」(23行目)で更新しているって流れだね

# 最初に1回だけ線を描いておき、以降はこの self.line を描き直していく
self.line, = ax.plot(self.x, np.sin(self.x))

# 更新のたびに y の値だけを差し替える
self.line.set_ydata(np.sin(self.x + self.phase))
琴葉葵

ほぅほぅ

琴葉茜

まぁ今回はあくまで「グラフのアニメーション」のデモだから、「self.line」の処理部分の流れをしっかりと覚えてくれたらOK

琴葉葵

重要なのは、グラフを描画する部分を変数で管理(15行目)して、それを基準に更新できるようにする(23行目)ってことなんだね!

琴葉茜

そういうこと

琴葉茜

つまり、Matplotlib側でのアニメーションの手順は
1. グラフの土台を用意する(28行目)
2. アニメーションを描画するオブジェクトを用意する(33行目)
3. 更新する度に処理をする関数を定義する(17行目〜24行目)
4. アニメーションを適用する(37行目)
5. グラフを表示する(40行目)
って流れになるよ

fig, ax = plt.subplots()
ax.set_ylim(-1.5, 1.5)
ax.set_title("sin波が流れるアニメーション")

animator = SinWaveAnimator(ax)

ani = animation.FuncAnimation(fig, animator.update, interval=50)

plt.show()
琴葉葵

更新の度に動かす関数さえちゃんと用意できれば、あとはほぼテンプレって感じなんだね!

琴葉茜

そしたら次回は、「sounddevice」の処理について説明していくよ!