このプロットどうやって描いたの?:複数プロット、アニメーション編


こんな感じのプロット描きたい

@motorcontrolman さんとの話の中で挙がったのがこちら。

引用:「モデル予測制御(MPC)による軌道追従制御」 by @taka_horibe さん

引用元では GIF 動画になっているのですが、簡潔+綺麗な可視化ですね。動いている小さな長方形はどうやって描いているんだろう・・。

ということで必要な要素の解説を入れながらちょっとづつやってみましょう。@motorcontrolman さんならサクッとできてしまいそうですが(笑

まずは subplot1

大枠から攻めていきます。Figure 上にプロットを複数作る定番は subplot (公式 doc: subplot) ですね。

例えば subplot(3,1,2) とすると Figure 画面上を 3 x 1 に分割して 2 個目(左から右、上から下に数えます)の位置に axes (座標軸) を作ります(下図左側)。2 つ以上の枠にまたがることも OK なので、例えば subplot(3,1,[1,2])(下図右側)として大きめの図を作ります。

こんな感じ。組み合わせればとりあえずの形ができそう。

subplot(5,1,1:3) % subplot(5,1,[1,2,3]) と同じ 
subplot(5,1,4)
subplot(5,1,5)

データは適当なもので描いてみました。

実際のコードはこちら

% ダミーデータ
Ndata = 200;
t = linspace(0,4*pi, Ndata);
x = sin(3*t);
y = cos(t);

% プロット
subplot(5,1,1:3)
plot(x,y), title('x vs y')
subplot(5,1,4)
plot(t,x), title('x')
subplot(5,1,5)
plot(t,y), title('y')

動きを加えてみる:アニメーション作成

あと欠かせない要素は動く部分。MATLAB のドキュメンテーション:アニメーション手法 によると

  1. 関数 animatedline を使用してストリーミング データのライン アニメーションを作成する。
  2. 新規のグラフィックス オブジェクトを作成する代わりに、既存のオブジェクトのプロパティを更新する。

という 2 択のようです。公式 doc :アニメーション、プロットのアニメーション化 には他にも情報がまとまっています。ちなみに 1 を使った方法は Qiita: グラフ背景色をデータの追加とともに変化させるアニメーション作成 でも紹介しました。

ラインに沿ったマーカーのトレース

ここではとりあえず 2 つ目の「既存のオブジェクトのプロパティを更新する」方法を使ってみます。

このサンプル使ってみましょう。先ほど描いた線上をマーカーを動かします。ポイントは drawnow一番重要なことなのでもう一度言います。ポイントは drawnow です。

コードの実行中はこのコマンドを実行しないと描画を更新しません(処理速度を下げないため)ので、ループの内で drawnow を実行して、逐次描画が更新されるようにします。

実際のコードはこちら

Ndata = 200;
t = linspace(0,4*pi, Ndata);
x = sin(3*t);
y = cos(t);

subplot(5,1,1:3)
plot(x,y), title('x vs y')
hold on
p1 = plot(x(1),y(1),'o','MarkerFaceColor','red'); % 逐次更新するオブジェクト1
hold off

subplot(5,1,4)
plot(t,x), title('x')
hold on
p2 = plot(t(1),x(1),'o','MarkerFaceColor','red'); % 逐次更新するオブジェクト2
hold off

subplot(5,1,5)
plot(t,y), title('y')
hold on
p3 = plot(t(1),y(1),'o','MarkerFaceColor','red'); % 逐次更新するオブジェクト3
hold off

filename = 'animation_sample.gif'; % Specify the output file name
frame = getframe(gcf); % Figure 画面をムービーフレーム(構造体)としてキャプチャ
tmp = frame2im(frame); % 画像に変更
[A,map] = rgb2ind(tmp,256); % RGB -> インデックス画像に
imwrite(A,map,filename,'gif','LoopCount',Inf,'DelayTime',0.1);

for k = 2:length(x)
    % オブジェクト1のデータ更新
    p1.XData = x(k);
    p1.YData = y(k);
    % オブジェクト2のデータ更新
    p2.XData = t(k);
    p2.YData = x(k);
    % オブジェクト3のデータ更新
    p3.XData = t(k);
    p3.YData = y(k);

    drawnow % 描画更新

    frame = getframe(gcf); % Figure 画面をムービーフレーム(構造体)としてキャプチャ
    tmp = frame2im(frame); % 画像に変更
    [A,map] = rgb2ind(tmp,256); % RGB -> インデックス画像に
    imwrite(A,map,filename,'gif','WriteMode','append','DelayTime',0.1);% 画像をアペンド

end

imwrite を使って gif ファイルに出力するコードも入れています。結果は

こんな感じ。

ひとまずまとめ

基本的なところを実装してみました。
他にも追加したい要素あればやってみますのでなんなりとコメントください。


  1. 実は R2019b からより柔軟に使える tiledlayout (公式 doc) というものも入っているんですが、これまはたの機会に。Figure の大きさ変更すると流動的にサブプロットの位置が変わります。