２つ重なったグラフを複数まとめて１つの図にする方法

# はじめに


2 つの Axes をいい感じに配置したグラフが例えば 4 個あり、それらを 2 x 2 のタイル状に並べたい。




MATLAB Answers にそんな問い合わせがありました。確かに簡単じゃないな、、と思いましたので１つの方法を解説します。（もっと簡単な方法あればアイデアください）





参照：[MATLAB Answers: ２つ重なったグラフを複数、１つ図にまとめる。](https://jp.mathworks.com/matlabcentral/answers/550224-?s_eid=PSM_29435)


  
## 環境


MATLAB R2020a


# やったこと


2 つの axes をいい感じに配置したグラフを n*m 個作り、それらを n x m のタイル状に並べた。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149511/97d033b6-0979-770f-5de1-efb501334b24.png" alt="attach:cat" title="attach:cat" width=500px>

こんな感じ。記事のポイントは



   -  axes オブジェクトの位置決め 
   -  'Position' と 'OuterPosition' の違い 
   -  `subplot` の機能 



です。




もともとのグラフが個別に *.fig で保存されている場合の対処方法についてはまた別途。


  
# やりたいことのイメージ図


例えばこんな 2 つセットの図を作ったとします。（２つ重ねる意義のあるデータがあればよかったのですが・・）

<details><summary>コード折りたたみ</summary><div>

```matlab:Code
close all
% Axes の位置調整
ax1 = axes('Position',[0.13, 0.50, 0.775, 0.35]);
ax2 = axes('Position',[0.13, 0.15, 0.775, 0.35]);

% サンプルデータ
N = 200;
t = linspace(0,1,N);
x = randn(1,N) + sin(2*pi*120*t);

% プロット
plot(ax1, x)
plot(ax2, abs(fft(x)))

% 座標軸は消しちゃおう
ax1.XTickLabel = [];
ax1.YTickLabel = [];
ax2.XTickLabel = [];
ax2.YTickLabel = [];
title(ax1,'時系列データ x')
title(ax2,'abs(fft(x))')
```

</div></details>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149511/cc39e4fa-1452-a127-9ca2-3c90da4078e1.png" alt="attach:cat" title="attach:cat" width=500px>



これを 1 セットとして、4 セットを 2 x 2 のタイル状に並べたい。少しヤヤコシイので丁寧に解説してみます。

# subplot じゃダメなの？


1 つのグラフをタイル状に並べるだけなら簡単です。




お馴染みの [`subplot`](https://jp.mathworks.com/help/matlab/ref/subplot.html?s_eid=PSM_29435) や最近だと [`tiledlayout`](https://jp.mathworks.com/help/matlab/ref/tiledlayout.html?s_eid=PSM_29435) というものもあります。`subplot` を使った例としてはこちらも参照：[このプロットどうやって描いたの？：複数プロット、アニメーション編](https://qiita.com/eigs/items/c6efb11c145e4e0e8577)



```matlab:Code
subplot(1,2,1)
plot(rand(10,1),'r')
subplot(1,2,2)
plot(rand(10,1),'b')
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149511/e30d7a92-812e-5e2d-fb31-43da1312b2cd.png" alt="attach:cat" title="attach:cat" width=500px>



簡単ですね。




ただタイル 1つ分（上の赤い線が表示されている部分）に 2 つの axes で構成されるプロットを載せるのは `subplot` でだと直感的にはできません。




なぜなら `subplot` はただ単に Figure 上にいい感じに配置された axes オブジェクトを作る関数なので、複数の axes オブジェクト位置の微調整には向いていません。




残された選択肢は、上で紹介した



```matlab:Code(Display)
ax1 = axes('Position',[0.13, 0.50, 0.775, 0.35]);
ax2 = axes('Position',[0.13, 0.15, 0.775, 0.35]);
```



のように1つ1つの axes 位置をマニュアルで指定する方法だが、、全部に対してこれはやりたくないですね。


  
# じゃぁどうするか？


ここでは `subplot` がタイル状にいい感じに配置してくれた配置情報を利用して、それぞれのタイル内に axes を 2 つを配置する作業を試してみることにする。


## axes オブジェクトの位置確認：OuterPosition


`subplot` が作る axes オブジェクトの位置情報としては 'OuterPosition' プロパティが良いと思われます。




'Position' プロパティの方が登場すると思いますが、以下の図の通りこれは座標軸自体（データの表示範囲）の大きさなので axes オブジェクトそのものの大きさより一回り小さい点に注意が必要です。




2 x 2 の配置で 'OuterPosition' と 'Position' の位置を可視化してみました。


<details><summary>コード折りたたみ</summary><div>

```matlab:Code
h_fig = figure(1);
h_axes1 = subplot(2,2,1);
outer1 = h_axes1.OuterPosition;
annotation(h_fig, 'rectangle', outer1,'LineWidth',2,'Color','blue');
inner1 = h_axes1.Position;
annotation(h_fig, 'rectangle', inner1,'LineWidth',2,'Color','red');

h_axes4 = subplot(2,2,4);
outer4 = h_axes4.OuterPosition;
annotation(h_fig, 'rectangle', outer4,'LineWidth',2,'Color','blue');
inner4 = h_axes4.Position;
annotation(h_fig, 'rectangle', inner4,'LineWidth',2,'Color','red');

annotation('textarrow',[0.58 0.48],[0.84 0.84],'String','OuterPosition','Color','blue')
annotation('textarrow',[0.18 0.14],[0.82 0.82],'String','Position','Color','red')
```

</div></details>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149511/c1a83107-3715-ebae-1568-9b04dd8adcdc.png" alt="attach:cat" title="attach:cat" width=500px>



これは 2 つだけ表示していますが、同じように



```matlab:Code(Display)
subplot(2,2,2)
subplot(2,2,3)
```



と加えれば 2 x 2 の配置で axes が 4 つ作られます。


  
## OuterPosition 内での位置決め


次は青枠の中（'OuterPosition' 内）に冒頭で表示した 2 つの axes を組み合わせた図を入れてみます。冒頭で示したように 'Position' を 4 つの数値で定義します。




例：



```matlab:Code(Display)
ax1 = axes('Position',[0.13, 0.50, 0.775, 0.35]);
ax2 = axes('Position',[0.13, 0.15, 0.775, 0.35]);
```



これは Figure の縦横の長さをそれぞれ 1 と考えて、[x,y,width,hight] の順に並んでいます。



   -  x: 左下端の x 座標 
   -  y: 左下端の y 座標 
   -  width: 横幅 
   -  height: 縦幅 



です。




**ややこしいところとしては、この数値はあくまでFigure 全体における位置情報で指定する必要があること。**




Figure 上と同じ相対位置関係で OuterPosition 内に配置しようとすると、**「OuterPosition 内での相対位置」**から**「Figure 全体での絶対位置」**に変換する処理が必要です。


  


例えば 'OuterPosition' の縦横の長さをそれぞれ 1 と考えて



```matlab:Code
r1 = [0.13, 0.50, 0.775, 0.35];
r2 = [0.13, 0.15, 0.775, 0.35];
```



という配置で 2 つの axes を、1 つの subplot 枠内に設置したいとします。


<details><summary>コード折りたたみ</summary><div>

```matlab:Code
h_fig = figure(2);
h_axes1 = subplot(2,1,1);
h_axes2 = subplot(2,1,2);

% 1 つ目subplot の位置情報
pos = h_axes1.OuterPosition;
% 青色で可視化
annotation(h_fig, 'rectangle', pos,'LineWidth',2,'Color','blue');
annotation(h_fig, 'rectangle', h_axes2.OuterPosition,'LineWidth',2,'Color','blue');

% subplot が作った axes は削除
delete(h_axes1)
delete(h_axes2)

% OuterPosition から Figure 上での配置に変換
width = pos(3);
height = pos(4);

% r1 = [0.13, 0.50, 0.775, 0.35];
xInit1 = pos(1) + width*r1(1);
xWidth1 = width*r1(3);
yInit1 = pos(2) + height*r1(2);
yHeight1 = height*r1(4);

% r2 = [0.13, 0.15, 0.775, 0.35];
xInit2 = pos(1) + width*r2(1);
xWidth2 = width*r2(3);
yInit2 = pos(2) + height*r2(2);
yHeight2 = height*r2(4);

% 2 つ axes を作成して
ax1 = axes('Position',[xInit1, yInit1, xWidth1, yHeight1]);
ax2 = axes('Position',[xInit2, yInit2, xWidth2, yHeight2]);

% プロット
N = 100;
t = linspace(0,1,N);
x = randn(1,N) + sin(2*pi*120*t);
plot(ax1, x)
plot(ax2, abs(fft(x)))

% 座標軸は消しちゃおう
ax1.XTickLabel = [];
ax1.YTickLabel = [];
ax2.XTickLabel = [];
ax2.YTickLabel = [];
title(ax1,'時系列データ x')
title(ax2,'abs(fft(x))')
```

</div></details>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149511/66b9d2b6-b821-8877-9a08-d482e8f994b9.png" alt="attach:cat" title="attach:cat" width=500px>


こんな具合。参考までに 2 つの 'OuterPosition' の位置を青枠で表示しています。


# 使ってみた


あとはどう使うか次第ですが‥例えば以下のように `subplot` の拡張版風に axes オブジェクトを返す関数（`drawTwoAxesPlot.m`）にしても良いかもしれない。




以下は 2 x 3 の配置ですが以下のやり方任意の数設置できるはず。



```matlab:Code
figure
for ii=1:6
    
    % subplot 内に 2 つ axes 作成
    [ax1,ax2] = drawTwoAxesPlot(2,3,ii);
    
    % 各 axes 内にプロット
    N = 100;
    t = linspace(0,1,N);
    x = randn(1,N) + sin(2*pi*120*t);
    plot(ax1, x)
    plot(ax2, abs(fft(x)))
    
    % 座標軸は消しちゃおう
    ax1.XTickLabel = [];
    ax1.YTickLabel = [];
    ax2.XTickLabel = [];
    ax2.YTickLabel = [];
    title(ax1,'時系列データ x')
    title(ax2,'abs(fft(x))')
end
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149511/2e3476d4-1ebb-e1ad-823a-f62b3a3a40ec.png" alt="attach:cat" title="attach:cat" width=500px>


# まとめ

結局のところ全 axes の 'Position' を明示的に指定しているんですが、`subplot` 関数も使って工数は削減できているかなと思います。

ここで紹介したやり方を使って、もともとのグラフが個別に *.fig で保存されている場合の対処方法についてはまた別途纏めます。

コメント・改善点・もっと良い方法などありましたらコメントください。


# Appendix: `drawTwoAxesPlot` 関数

```matlab:Code
function [ax1, ax2] = drawTwoAxesPlot(n,m,p)
    h = subplot(n,m,p);
    
    % この例だと 'OuterPosition' の枠内での相対位置は
    % 固定としています。
    r1 = [0.13, 0.50, 0.775, 0.35];
    r2 = [0.13, 0.15, 0.775, 0.35];
    
    pos = h.OuterPosition;
    width = pos(3);
    height = pos(4);
    
    % r1 = [0.13, 0.50, 0.775, 0.35];
    xInit1 = pos(1) + width*r1(1);
    xWidth1 = width*r1(3);
    yInit1 = pos(2) + height*r1(2);
    yHeight1 = height*r1(4);
    
    % r2 = [0.13, 0.15, 0.775, 0.35];
    xInit2 = pos(1) + width*r2(1);
    xWidth2 = width*r2(3);
    yInit2 = pos(2) + height*r2(2);
    yHeight2 = height*r2(4);
    
    % axes 作成
    ax1 = axes('Position',[xInit1, yInit1, xWidth1, yHeight1]);
    ax2 = axes('Position',[xInit2, yInit2, xWidth2, yHeight2]);
    
    % subplot が作った axes は削除
    delete(h)
end
```