Loading [MathJax]/extensions/tex2jax.js

[matplotlib animation] 77. ランダムな位置に出現するemojiが拡大し消えていくアニメーション

matplotlib

はじめに

ランダムな位置に出現する絵文字が拡大し消えていくアニメーションをmatplotlib, FuncAnimationで表示する。

下記記事の雨のアニメーションを参考にして作成した。

Rain simulation — Matplotlib 3.2.0 documentation
[matplotlib animation] 22. 雨(Rain simulation)
matplotlib FuncAnimationによる雨のアニメーション

コード

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
#create ini data
n_drops = 20
emoji_drops = np.array(np.zeros(n_drops),
dtype=[('position', 'f8',2),('size','f8'),('growth','f8'),('alpha', 'f8')])
emoji_drops['position'] = np.random.uniform(0, 1, (n_drops, 2))
emoji_drops['growth'] = np.random.uniform(10, 20, n_drops)
emoji_drops['size'] = np.random.rand(n_drops)*20
emoji_drops['alpha'] = np.ones(n_drops)
emoji_drops
'''
array([([0.525481 , 0.9525733 ], 11.40640517, 16.97890011, 1.),
([0.47591703, 0.20430895], 13.11647633, 18.40863668, 1.),
([0.87154477, 0.2623594 ], 2.18445179, 16.54755482, 1.),
([0.23675945, 0.47664944], 14.52640135, 18.06419404, 1.),
([0.07954715, 0.56022691], 13.42285269, 14.20729541, 1.),
([0.81544536, 0.04151431], 10.04656556, 13.20391036, 1.),
([0.42159024, 0.07903694], 6.6884128 , 19.7719662 , 1.),
([0.05697779, 0.48979293], 4.57498104, 16.99453915, 1.),
([0.70958286, 0.15696977], 18.00021145, 10.67566331, 1.),
([0.26698077, 0.1733919 ], 19.82952921, 13.36839084, 1.),
([0.81591394, 0.53246656], 15.47280466, 19.68430789, 1.),
([0.11243768, 0.52536727], 9.05800302, 16.94833053, 1.),
([0.993191 , 0.66019158], 3.50660599, 12.5812297 , 1.),
([0.37264631, 0.66956094], 16.4602031 , 14.66848912, 1.),
([0.77868742, 0.13085961], 2.25006547, 16.72627871, 1.),
([0.29315259, 0.1148292 ], 16.2123757 , 18.38888197, 1.),
([0.224187 , 0.98843321], 3.48138286, 19.11202544, 1.),
([0.85439632, 0.55499377], 10.88810717, 15.0667364 , 1.),
([0.09051999, 0.49917992], 6.10093697, 18.37969202, 1.),
([0.21668941, 0.05439581], 17.59723974, 10.87886474, 1.)],
dtype=[('position', '<f8', (2,)), ('size', '<f8'), ('growth', '<f8'), ('alpha', '<f8')])
'''
#animation 1
fig = plt.figure(figsize=(6, 6))
ax = fig.add_axes([0, 0, 1, 1], frameon=False)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
ax.scatter(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],alpha=0)
marker1 = '😃'
for x,y,s,a in zip(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],emoji_drops['size'],emoji_drops['alpha']):
scat = ax.annotate(marker1, (x,y),fontsize=s, ha='center',va='center',alpha=a,
xytext=(0,0), textcoords='offset pixels')
def update(frame_number):
# Get an index which we can use to re-spawn the oldest emoji.
current_index = frame_number % n_drops
# Make all emojies more transparent as time progresses.
emoji_drops['alpha'] -= 1.0/len(emoji_drops)
emoji_drops['alpha'] = np.clip(emoji_drops['alpha'], 0, 1)
# Make all emojies bigger.
emoji_drops['size'] += emoji_drops['growth']
# Pick a new position for oldest emoji, resetting its size,
# alpha and growth factor.
emoji_drops['position'][current_index] = np.random.uniform(0, 1, 2)
emoji_drops['size'][current_index] = np.random.rand(1)*20
emoji_drops['alpha'][current_index] = 1
emoji_drops['growth'][current_index] = np.random.uniform(10, 20)
# Update the scatter collection, with the new colors, sizes and positions.
ax.cla()
for x,y,s,a in zip(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],emoji_drops['size'],emoji_drops['alpha']):
scat = ax.annotate(marker1, (x,y),fontsize=s, ha='center',va='center',
alpha=a,xytext=(0,0), textcoords='offset pixels')
ani = FuncAnimation(fig, update, interval=50)
ani.save('emoji_expand1.mp4', writer="ffmpeg",dpi=100)
HTML(ani.to_html5_video())

解説

モジュールのインポートなど

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML

バージョン

#version
import IPython
import matplotlib
print(matplotlib.__version__)
print(IPython.__version__)
print(np.__version__)
#3.2.0
#7.13.0
#1.18.1

データの作成

#create ini data
n_drops = 20
emoji_drops = np.array(np.zeros(n_drops),
dtype=[('position', 'f8',2),('size','f8'),('growth','f8'),('alpha', 'f8')])
emoji_drops['position'] = np.random.uniform(0, 1, (n_drops, 2))
emoji_drops['growth'] = np.random.uniform(10, 20, n_drops)
emoji_drops['size'] = np.random.rand(n_drops)*20
emoji_drops['alpha'] = np.ones(n_drops)
emoji_drops
'''
array([([0.525481 , 0.9525733 ], 11.40640517, 16.97890011, 1.),
([0.47591703, 0.20430895], 13.11647633, 18.40863668, 1.),
([0.87154477, 0.2623594 ], 2.18445179, 16.54755482, 1.),
([0.23675945, 0.47664944], 14.52640135, 18.06419404, 1.),
([0.07954715, 0.56022691], 13.42285269, 14.20729541, 1.),
([0.81544536, 0.04151431], 10.04656556, 13.20391036, 1.),
([0.42159024, 0.07903694], 6.6884128 , 19.7719662 , 1.),
([0.05697779, 0.48979293], 4.57498104, 16.99453915, 1.),
([0.70958286, 0.15696977], 18.00021145, 10.67566331, 1.),
([0.26698077, 0.1733919 ], 19.82952921, 13.36839084, 1.),
([0.81591394, 0.53246656], 15.47280466, 19.68430789, 1.),
([0.11243768, 0.52536727], 9.05800302, 16.94833053, 1.),
([0.993191 , 0.66019158], 3.50660599, 12.5812297 , 1.),
([0.37264631, 0.66956094], 16.4602031 , 14.66848912, 1.),
([0.77868742, 0.13085961], 2.25006547, 16.72627871, 1.),
([0.29315259, 0.1148292 ], 16.2123757 , 18.38888197, 1.),
([0.224187 , 0.98843321], 3.48138286, 19.11202544, 1.),
([0.85439632, 0.55499377], 10.88810717, 15.0667364 , 1.),
([0.09051999, 0.49917992], 6.10093697, 18.37969202, 1.),
([0.21668941, 0.05439581], 17.59723974, 10.87886474, 1.)],
dtype=[('position', '<f8', (2,)), ('size', '<f8'), ('growth', '<f8'), ('alpha', '<f8')])
'''

numpyの構造化配列で作成する。pandasのDataFrameのような感じで配列の要素を変更することができる。

np.random.uniform(10, 20, n_drops)で10〜20の値を持つ要素数n_dropsの配列を作成する。
np.random.rand(n_drops)*20では0〜20の要素をもつ要素数n_dropsの配列を作成する。

初期データで図を表示

#show ini data
fig = plt.figure(figsize=(4, 4))
ax = fig.add_axes([0, 0, 1, 1], frameon=False)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
ax.scatter(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],alpha=0)
marker1 = '😃'
for x,y,s,a in zip(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],emoji_drops['size'],emoji_drops['alpha']):
scat = ax.annotate(marker1, (x,y),fontsize=s, ha='center',va='center',alpha=a,
xytext=(0,0), textcoords='offset pixels')
plt.savefig('emoji_expand.png',dpi=100)
plt.show()

図中への絵文字の表示は下記記事と同様にannotateで行う。

[matplotlib] 78. emojiをマーカーとしてプロットする
matplotlibで表示する図のマーカーとして、絵文字を用いる方法について説明する。annotateを用いて表示する。 具体的な方法は下記記事の画像をマーカーとして用いる方法と同じとなっている。

絵文字は文字列扱いなのでfontsizeでサイズを変更する。

図示すると以下のようになる。

アニメーション関数の設定

def update(frame_number):
# Get an index which we can use to re-spawn the oldest emoji.
current_index = frame_number % n_drops
# Make all emojies more transparent as time progresses.
emoji_drops['alpha'] -= 1.0/len(emoji_drops)
emoji_drops['alpha'] = np.clip(emoji_drops['alpha'], 0, 1)
# Make all emojies bigger.
emoji_drops['size'] += emoji_drops['growth']
# Pick a new position for oldest emoji, resetting its size,
# alpha and growth factor.
emoji_drops['position'][current_index] = np.random.uniform(0, 1, 2)
emoji_drops['size'][current_index] = np.random.rand(1)*20
emoji_drops['alpha'][current_index] = 1
emoji_drops['growth'][current_index] = np.random.uniform(10, 20)
# Update the scatter collection, with the new colors, sizes and positions.
ax.cla()
for x,y,s,a in zip(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],emoji_drops['size'],emoji_drops['alpha']):
scat = ax.annotate(marker1, (x,y),fontsize=s, ha='center',va='center',
alpha=a,xytext=(0,0), textcoords='offset pixels')

current_index = frame_number % n_dropsでリセットするインデックスを決めておく。

emoji_drops[‘alpha’] -= 1.0/len(emoji_drops)でalpha値を下げていき、np.clip(emoji_drops[‘alpha’], 0, 1)で0を下回ったときに0とし、1を上回ったときに1とする。

emoji_drops[‘size’] += emoji_drops[‘growth’]でsizeにgrowthを加えていくことでsizeを大きくしていく。

78-81行ではcurrent_indexのデータをリセットしている。

ax.cla()で表示されている図をけして、すべてのデータを再びannotateで表示することでアニメーションとする。

アニメーションの表示

ani = FuncAnimation(fig, update, interval=50)
ani.save('emoji_expand1.mp4', writer="ffmpeg",dpi=100)
HTML(ani.to_html5_video())

FuncAnimationでアニメーションを表示する。frame数はデフォルトで100なのでintervalが50msの場合、5秒のアニメーションとなる。
HTML(ani.to_html5_video())により、jupyter notebook またはjupyter lab上にアニメーションを表示できる。

ani.save(‘ファイル名’, writer=”ffmpeg”,dpi=100)でアニメーションをMP4形式で保存することができる。

マーカーを😎とした場合

#animation 2
fig = plt.figure(figsize=(6, 6))
ax = fig.add_axes([0, 0, 1, 1], frameon=False)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
ax.scatter(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],alpha=0)
marker1 = '😎'
for x,y,s,a in zip(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],emoji_drops['size'],emoji_drops['alpha']):
scat = ax.annotate(marker1, (x,y),fontsize=s, ha='center',va='center',alpha=a,
xytext=(0,0), textcoords='offset pixels')
def update(frame_number):
current_index = frame_number % n_drops
emoji_drops['alpha'] -= 1.0/len(emoji_drops)
emoji_drops['alpha'] = np.clip(emoji_drops['alpha'], 0, 1)
# Make all circles bigger.
emoji_drops['size'] += emoji_drops['growth']
emoji_drops['position'][current_index] = np.random.uniform(0, 1, 2)
emoji_drops['size'][current_index] = np.random.rand(1)*20
emoji_drops['alpha'][current_index] = 1
emoji_drops['growth'][current_index] = np.random.uniform(10, 20)
ax.cla()
for x,y,s,a in zip(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],emoji_drops['size'],emoji_drops['alpha']):
scat = ax.annotate(marker1, (x,y),fontsize=s, ha='center',va='center',
alpha=a,xytext=(0,0), textcoords='offset pixels')
ani = FuncAnimation(fig, update, interval=50)
ani.save('emoji_expand2.mp4', writer="ffmpeg",dpi=100)
HTML(ani.to_html5_video())

マーカーを😴とした場合

#animation 3
fig = plt.figure(figsize=(6, 6))
ax = fig.add_axes([0, 0, 1, 1], frameon=False)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
ax.scatter(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],alpha=0)
marker1 = '😴'
for x,y,s,a in zip(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],emoji_drops['size'],emoji_drops['alpha']):
scat = ax.annotate(marker1, (x,y),fontsize=s, ha='center',va='center',alpha=a,
xytext=(0,0), textcoords='offset pixels')
def update(frame_number):
current_index = frame_number % n_drops
emoji_drops['alpha'] -= 1.0/len(emoji_drops)
emoji_drops['alpha'] = np.clip(emoji_drops['alpha'], 0, 1)
emoji_drops['size'] += emoji_drops['growth']
emoji_drops['position'][current_index] = np.random.uniform(0, 1, 2)
emoji_drops['size'][current_index] = np.random.rand(1)*20
emoji_drops['alpha'][current_index] = 1
emoji_drops['growth'][current_index] = np.random.uniform(10, 20)
ax.cla()
for x,y,s,a in zip(emoji_drops['position'][:, 0], emoji_drops['position'][:, 1],emoji_drops['size'],emoji_drops['alpha']):
scat = ax.annotate(marker1, (x,y),fontsize=s, ha='center',va='center',
alpha=a,xytext=(0,0), textcoords='offset pixels')
ani = FuncAnimation(fig, update, interval=50)
ani.save('emoji_expand3.mp4', writer="ffmpeg",dpi=100)
HTML(ani.to_html5_video())
コードをダウンロード(.pyファイル)

コードをダウンロード(.ipynbファイル)

参考

Rain simulation — Matplotlib 3.2.0 documentation
Structured arrays — NumPy v2.2 Manual

コメント