FuncAnimationを使って待ち行列の長さを人の表情でアニメーションしてみた

2024年3月8日

前回の記事ではトラック待機台数のシミュレーション結果を、右に流れていくグラフでアニメーション化しました。

 

今回はもっと直感的に、人の表情で待ち行列の逼迫度合を表すようなアニメーションを作ってみました。

 

◆仕事や勉強の息抜きに。。。

物流センターの周りに40台のトラックが待機しているイラストを描く

Matplotlibpatchesモジュールで図形を描けるので、まずはこれをインポートします。

from matplotlib import patches

次に、普通にグラフを描く時と同じように、figureaxesオブジェクトを作成します。

fig, ax = plt.subplots()

 

今回は物流センターを10✕8の長方形で表し、その周りに半径0.5の円で表すトラックを40台配置することにします。

そこで座標軸はx軸、y軸ともに0-20に設定します。

ax.set_xlim(0, 20)
ax.set_ylim(0, 20)

長方形は先程インポートしたpatchesモジュールの中のRectangleクラスを使ってオブジェクトを作れます。

引数として左下の座標、幅、高さ、角度を指定できるので、次のように指定してオブジェクトを作ります。

r = patches.Rectangle(xy=(5, 5), width=10, height=8, angle=0)

そして、このオブジェクトをaxesに追加します。

ax.add_patch(r)

 

次に40台のトラックを半径0.5の円として加えていきますが、4区間に分けて次のように円中心の座標を指定します。

これを円を描くCircleクラスでコーディングすると次のように書けます。

for i in range(40):
    if i < 10:
        c1 = patches.Circle(xy=(5.5+i, 13.5), radius=0.5, fc='lightgrey', ec='black')
        ax.add_patch(c1)
    elif i < 20:
        c2 = patches.Circle(xy=(15.5, 13.5-i+10), radius=0.5, fc='lightgrey', ec='black')
        ax.add_patch(c2)
    elif i <30:
        c3 = patches.Circle(xy=(14.5-i+20, 4.5), radius=0.5, fc='lightgrey', ec='black')
        ax.add_patch(c3)
    else:
        c4 = patches.Circle(xy=(4.5, 4.5+i-30), radius=0.5, fc='lightgrey', ec='black')
        ax.add_patch(c4)

以上を実行すると次のような図形が描画されました。

 

待機しているトラックを増減させるアニメーション

次に円の数を待機トラックの台数に連動させてみましょう。

先程は40個の円を描きましたが、1台目のトラックが到着した時に待たずに荷卸しができたなら0個の円を、2台目のトラックは待ったけれども待っていたのが自分だけだったら1個の円を、3台目のトラックは待たされたうえに前のトラックも待っていたなら2個の円を、、、と描くようにします。

【作業分析3】トラックドックでの待機時間や待機台数をSimpyでシミュレーション

の結果ファイルとして、待機台数はlen_in_queueのリストに入っています。

これをXにリストとして代入して、それぞれの要素iを先程の円を描いた時の40の代わりに使えば、待機台数に連動した円の数を表示できます。

fig, ax = plt.subplots()

X = len_in_queue # 各トラックの待機台数の結果をリストXに代入

def plot(i):
    plt.cla() # 前のグラフを消去

    ax.set_xlim(0, 20) # 新たにグラフの座標軸を設定
    ax.set_ylim(0, 20)

    r = patches.Rectangle(xy=(5, 5), width=10, height=8, angle=0) # 長方形を設定
    ax.add_patch(r)

    for i in range(X[i]): # 待機台数を1台ずつiとして取り出す
        for i in range(i): # 以下、i台分の円を表示
            if i < 10:
                c1 = patches.Circle(xy=(5.5+i, 13.5), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c1)
            elif i < 20:
                c2 = patches.Circle(xy=(15.5, 13.5-i+10), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c2)
            elif i <30:
                c3 = patches.Circle(xy=(14.5-i+20, 4.5), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c3)
            else:
                c4 = patches.Circle(xy=(4.5, 4.5+i-30), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c4)

ani = animation.FuncAnimation(fig, plot) # アニメーションを表示

実行すると次のようになりました。

 

待機台数に応じて色を変えるアニメーション

次に待機台数に応じて、切迫度を色で表すようなアニメーションを作ってみましょう。

待機台数が少ない時には肌色で、多い時には段々赤色に近づくように長方形の色を変化させます。

また経過時間も表示させます。

fig, ax = plt.subplots()

X = len_in_queue # 各トラックの待機台数の結果をリストXに代入
Y = tm_in_queue # 各トラックが行列に並び始めた時間の結果をリストYに代入

def plot(i):
    plt.cla() # 前のグラフを消去

    ax.set_xlim(0, 20) # 新たにグラフの座標軸を設定
    ax.set_ylim(0, 20)

    r = patches.Rectangle(xy=(5, 5), width=10, height=8, angle=0, color=(1.0, 1.0, 0.8)) # 長方形を設定、色は肌色
    ax.add_patch(r)

    ax.text(12, 17, round(Y[i]), size=30) # i番目のトラックが行列に並び始めた時間を表示
    ax.text(5, 17, 'ELAPSED TIME :', size=15)

    for i in range(X[i]): # 待機台数を1台ずつiとして取り出す
        k = i/40 # 傾き1/40で待機台数に比例して色が変化するようにkを調整
        r = patches.Rectangle(xy=(5, 5), width=10, height=8, angle=0, color=(1.0, 1.0-k, 0.8-4*k/5)) # kに応じて長方形の色を変化させる
        ax.add_patch(r)

        for i in range(i): # 以下、i台分の円を表示
            if i < 10:
                c1 = patches.Circle(xy=(5.5+i, 13.5), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c1)
            elif i < 20:
                c2 = patches.Circle(xy=(15.5, 13.5-i+10), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c2)
            elif i <30:
                c3 = patches.Circle(xy=(14.5-i+20, 4.5), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c3)
            else:
                c4 = patches.Circle(xy=(4.5, 4.5+i-30), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c4)

ani = animation.FuncAnimation(fig, plot)

実行すると次のようになりました。

 

待機台数に応じて表情を変えるアニメーション

これでだいぶ待ち行列の逼迫度合いがわかるようになりましたが、更に顔も描いて、その表情でも表すようにしてみましょう。

具体的には、待機台数が増えてくるにつれて眉毛が釣り上がり、目と口が大きく空いてくるようにします。

図形の高さや角度を値に応じて変化させるだけです。

fig, ax = plt.subplots()

X = len_in_queue # 各トラックの待機台数の結果をリストXに代入
Y = tm_in_queue # 各トラックが行列に並び始めた時間の結果をリストYに代入

def plot(i):
    plt.cla() # 前のグラフを消去

    ax.set_xlim(0, 20) # 新たにグラフの座標軸を設定
    ax.set_ylim(0, 20)

    r = patches.Rectangle(xy=(5, 5), width=10, height=8, angle=0, color=(1.0, 1.0, 0.8)) # 長方形を設定、色は肌色
    ax.add_patch(r)
    e1 = patches.Ellipse(xy=(7.5, 11), width=2, height=0.5, angle=10) # 左眉毛
    ax.add_patch(e1)
    e2 = patches.Ellipse(xy=(12.5, 11), width=2, height=0.5, angle=-10) # 右眉毛
    ax.add_patch(e2)
    e3 = patches.Ellipse(xy=(10, 7), width=3, height=0.5, angle=0) # 口
    ax.add_patch(e3)
    e4 = patches.Ellipse(xy=(7.5, 10), width=1, height=0.5, angle=0) # 左目
    ax.add_patch(e4)
    e5 = patches.Ellipse(xy=(12.5, 10), width=1, height=0.5, angle=0) # 右目
    ax.add_patch(e5)

    ax.text(12, 17, round(Y[i]), size=30) # i番目のトラックが行列に並び始めた時間を表示
    ax.text(5, 17, 'ELAPSED TIME :', size=15)

    for i in range(X[i]): # 待機台数を1台ずつiとして取り出す
        k = i/40 # 傾き1/40で待機台数に比例して色が変化するようにkを設定する
        r = patches.Rectangle(xy=(5, 5), width=10, height=8, angle=0, color=(1.0, 1.0-k, 0.8-4*k/5)) # kに応じて長方形の色を変化させる
        ax.add_patch(r)

        m = i*1.8 # 眉毛の角度が変わる傾きを調整
        n = i/10 # 口の開き度合いが変わる傾きを調整
        o = i/40 # 目の開き度合いが変わる傾きを調整
        e1 = patches.Ellipse(xy=(7.5, 11), width=2, height=0.5, angle=10-m) # mに応じて左眉毛の角度を変化
        ax.add_patch(e1)
        e2 = patches.Ellipse(xy=(12.5, 11), width=2, height=0.5, angle=-10+m) # mに応じて右眉毛の角度を変化
        ax.add_patch(e2)
        e3 = patches.Ellipse(xy=(10, 7), width=3, height=0.5+n, angle=0) # nに応じて口の開き度合いを変化
        ax.add_patch(e3)
        e4 = patches.Ellipse(xy=(7.5, 10), width=1, height=0.5+o, angle=0) # oに応じて左目の開き度合いを変化
        ax.add_patch(e4)
        e5 = patches.Ellipse(xy=(12.5, 10), width=1, height=0.5+o, angle=0) # oに応じて右目の開き度合いを変化
        ax.add_patch(e5)

        for i in range(i): # 以下、i台分の円を表示
            if i < 10:
                c1 = patches.Circle(xy=(5.5+i, 13.5), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c1)
            elif i < 20:
                c2 = patches.Circle(xy=(15.5, 13.5-i+10), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c2)
            elif i <30:
                c3 = patches.Circle(xy=(14.5-i+20, 4.5), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c3)
            else:
                c4 = patches.Circle(xy=(4.5, 4.5+i-30), radius=0.5, fc='lightgrey', ec='black')
                ax.add_patch(c4)

ani = animation.FuncAnimation(fig, plot)

実行すると次のようになりました。