MATLABでLaTeX論文用の図を整形する

# はじめに
MATLABで図を作って論文などに載せたいときがあると思います。
しかし，MATLABで作ったそのままの図では非常に見にくくなってしまいます。
そのため，文章に載せる前に図を編集する必要があります[^changeDefault]。

[こちらの記事](https://qiita.com/kouichi-c-nakamura/items/468fe09874461911dfab)でも論文用に図を作るコツが紹介されていますが，若干やりたいことが違いました。
そこで，自分なりに手軽な図の作り方を考えたので，紹介したいと思います。
[^changeDefault]: デフォルトの設定自体を変えてしまいたい方は，[こちらの記事](https://qiita.com/mizukirc/items/12fc1a736f0f1b5d8efe)や[こちらの記事](https://qiita.com/hogemaccho/items/c46a81518d2589f36a72)が役立ちます。

# やりたいこと
* 前提： LaTeXを用いて文章を作成する
    * 図はPDFをincludegraphicsで挿入する
    * PDFにはフォントを埋め込む（IEEEなどへの投稿を想定）

* 目標： 無編集でLaTeXに挿入可能な図を作る
    * Illustratorなどは利用しない
    * 共通化できる処理は関数にまとめて自動化
    * その他の部分は必要になるごとにスクリプトを書く

## 共通処理の効果
共通処理は関数にまとめることで，以下のように見やすい図を簡単に作成できます[^howtomakepng]。

| Before | After |
|:-:|:-:|
| ![fig1_org.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248335/b0d8e582-3601-4129-1323-f0de5ce39001.png) | ![fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248335/38dec83f-9f15-0efa-df30-8e48ab08b003.png) |
[^howtomakepng]: 両方ともPDFをImageMagickを用いてPNGへ変換したものです。

# 動作確認環境
* MATLAB R2017b
* Add-on
    * [export_fig](https://jp.mathworks.com/matlabcentral/fileexchange/23629-export_fig) (ver. 2.00)
        * Ghostscript (ver. 9.27)

# 具体的な作成手順
実際に図を作成する手順は大きく3つに分けられます。
三角関数のプロットを例にして，順番に説明していきます。

1. 共通化できない編集
1. 共通化した編集(tuneFigure関数)
1. PDFへ書き出し

## 共通化できない編集
ここに当てはまるのは，以下のような作業です。

* ラインのプロット
* 軸の設定(ラベル，目盛，表示範囲など)
* 凡例の追加

これらの作業は，それぞれの図ごとに適宜設定をします。
今回の例であれば，以下のようなコードになります。

```matlab:makeSinCosFigure.m
fig = figure('name', '三角関数をプロット');
x = 0:0.001:4*pi;
ys = sin(x);
yc = cos(x);
plot(x, ys, x, yc);

% 描画範囲の指定
xlim([0, 2*pi]);
ylim([-1.2, 1.2]);

% 軸ラベルの設定
xlabel('$x$', 'Interpreter', 'latex');
ylabel('$y(x)$', 'Interpreter', 'latex');

% 凡例の設定
legend({'$\sin(x)$', '$\cos(x)$'}, 'Interpreter', 'latex');

% グリッド線の表示
grid on;
```

ここで，注目してほしいのは文字列を設定するときに**Interpreter**を**latex**に指定していることです。
このようにすることで，凡例などにおいてもLaTeXの構文を利用することができます。

## 共通の編集
次に共通の処理ですが，ここに当てはまるのは以下の作業です。

* 色関係
    * 線の太さ(LineWidth)の変更
    * 線や文字の色(XColor, XColor)の変更
    * グリッドの色(GridColor)の変更
    * 背景色(Color)の変更
* フォント関係
    * フォント(FontName)を埋め込み可能なものに変更
    * フォントサイズ(FontSize)を大きくする

これらの作業は，グラフの内容に関わらず同じように行います。
ただし，論文用とプレゼン用のようにいくつか書式を使い分けることはあるでしょう。
そこで，プリセットを指定すれば自動で書式を設定する関数を用意しました。
具体的には以下のように使います。

```matlab:makeSinCosFigure.m
% tunefig('qiita', fig);         % 一枚だけ編集
tunefig('qiita', [fig, fig2]);   % 複数の図を一度に編集
```
<details>
<summary>tunefig関数の実装（コード）</summary><div>

コードの詳細[^moreinfo]の説明は省略しますが，[Figure](https://jp.mathworks.com/help/matlab/ref/matlab.ui.figure-properties.html), [Axes](https://jp.mathworks.com/help/matlab/ref/matlab.graphics.axis.axes-properties.html), [Line](https://jp.mathworks.com/help/matlab/ref/matlab.graphics.primitive.line-properties.html)オブジェクトのプロパティを書き換えています。
つまり，[こちらの記事](https://qiita.com/mizukirc/items/12fc1a736f0f1b5d8efe)とやっていること自体は基本同じです。
ただし，findobj関数ですべての対象オブジェクトを拾ってくるので，subplotを使った場合でもすべての軸やラインを自動調整可能です。
つまり，Childrenが100個あっても関数が自動的にすべてを変更してくれます。

プリセットは，自由に追加可能です。（style_listに名前を追加し，対応する添え字のfig_st, ax_st, ln_stを追記するだけ）

実用上の注意として，この関数は**Lineオブジェクトのみを対象**としています。
そのため，stairなどの[他のオブジェクト](https://jp.mathworks.com/help/matlab/graphics-object-properties.html)は変更されない点は注意が必要です。

```matlab:tunefig.m
function [] = tunefig(style, figs, custom_style)
%RESHAPE_FIGURE figureの見た目を整える
%
%   tunefig(style, figs, custom_style)
%       style:          書式指定('document', 'ppt', %/'custom'/%)
%       figs:           figureハンドル(figure配列)
%       custom_style:   書式設定がcustomの場合のみ手本を指定(cell配列)
%                       custom_style = {fig_st, ax_st, ln_st}
%
%   See also FIGURE, AXES, LINE

% Copyright (c) 2019 larking95(https://qiita.com/larking95)
% Released under the MIT Licence 
% https://opensource.org/licenses/mit-license.php

%% 初期化
style_list = {'document', 'ppt', 'qiita', 'custom'};
style_num = 1;
fig_st = struct([]);
ax_st  = struct([]);
ln_st  = struct([]);

%% デフォルト設定の作成
% ======= document =======
% Type: figure
fig_st(1).Color = 'w';  % 背景色Color = 白'w'

% Type: axis
ax_st(1).Color = 'w';
ax_st(1).Box = 'on';
ax_st(1).GridLineStyle = '-';
ax_st(1).GridColor = [0.15, 0.15,0.15].*1.05;
ax_st(1).MinorGridLineStyle = '-';
ax_st(1).MinorGridAlpha = 0.1;
ax_st(1).LineWidth = 1.5;
ax_st(1).XColor = 'k';
ax_st(1).YColor = 'k';
ax_st(1).ZColor = 'k';
% ax_st(1).GridAlpha = 0.0;
ax_st(1).FontSize= 14;
ax_st(1).FontName = 'arial';
ax_st(1).XLabel.Interpreter = 'latex';
ax_st(1).YLabel.Interpreter = 'latex';
ax_st(1).ZLabel.Interpreter = 'latex';

% Type: line
ln_st(1).LineWidth = 1.5;

% ======= ppt =======
% Type: figure
fig_st(2).Color = 'w';%'none';  % 背景色Color = 透明'none'
% Type: axes
ax_st(2) = ax_st(1);    % axisの設定はdocumentから一旦コピー
ax_st(2).LineWidth = 1.5;   % ライン幅だけ1.5に変更
% Type: line
ln_st(2) = ln_st(1);    % lineの設定はdocumentから一旦コピー
ln_st(2).LineWidth = 1.5;   % ライン幅だけ1.5に変更

% ======= qiita =======
% Type: figure
fig_st(3).Color = 'w';%'none';  % 背景色Color = 透明'none'/ 白 'w'
% Type: axes
ax_st(3) = ax_st(1);    % axisの設定はdocumentから一旦コピー
ax_st(3).LineWidth = 2;   % ライン幅だけに変更
ax_st(3).FontSize= 15;
% Type: line
ln_st(3) = ln_st(1);    % lineの設定はdocumentから一旦コピー
ln_st(3).LineWidth = 2;   % ライン幅だけに変更

%% 引数の確認
style_name = validatestring(style, style_list, 1);
style_num = find(strcmpi(style_name, style_list));
if nargin <= 2 && style_num > length(style_list)
    error('custom_style is not defined');
end
switch nargin
    case 1
        figs = gcf;
        custom_style = gobjects;
    case 2
        custom_style = gobjects;
    case 3
        fig_st(end+1) = custom_style{1};
        ax_st(end+1)  = custom_style{2};
        ln_st(end+1)  = custom_style{3};
end
validateattributes(figs, {'matlab.ui.Figure'}, {'vector'});

%% 整形
ff = fieldnames(fig_st(style_num));
af = fieldnames(ax_st(style_num));
lf = fieldnames(ln_st(style_num));

for f = 1:length(figs)
    for ffidx = 1:length(ff)
        figs(f).(cell2mat(ff(ffidx))) = fig_st(style_num).(cell2mat(ff(ffidx)));
    end
    ax = findobj(figs(f),'Type','axes');
    for a = 1:length(ax)
        for afidx = 1:length(af)
            if isempty(ax_st(style_num).(cell2mat(af(afidx))))
                continue;
            end
            if isstruct(ax_st(style_num).(cell2mat(af(afidx))))
                field1 = cell2mat(af(afidx));
                field2 = cell2mat(fieldnames(ax_st(style_num).(cell2mat(af(afidx)))));
            else
                ax(a).(cell2mat(af(afidx))) = ax_st(style_num).(cell2mat(af(afidx)));
            end
        end
        ln = findobj(ax(a),'Type','Line');
        for l = 1:length(ln)
            for lfidx = 1:length(lf)
                ln(l).(cell2mat(lf(lfidx))) = ln_st(style_num).(cell2mat(lf(lfidx)));
            end
        end
    end
end
```

</div>
</details>

これで，手軽に良い感じに図を編集することができました。
[^moreinfo]: 気になる方は，[グラフィックスオブジェクトの取り扱い](https://jp.mathworks.com/help/matlab/learn_matlab/understanding-handle-graphics-objects.html)，[構造体](https://jp.mathworks.com/help/matlab/structures.html)を見ていただくと，参考になると思います。

## PDFへの書き出し
最後にPDFに書き出しを行います。
ただし，書き出す方法はいくつかあり手間が違うため検討が必要です。

## 組み込み関数 VS export_fig
MATLABのfigureをPDFで保存するには，組み込み関数である[print関数](https://jp.mathworks.com/help/matlab/ref/print.html)や[saveas関数](https://jp.mathworks.com/help/matlab/ref/saveas.html)を使う方法があります。
しかし，print関数は名前の通り印刷を想定した関数なので，PDFのサイズがA4とかになってしまいます[^supplement1]。(saveasも基本同様の動作)
もちろん，[余白を最小限にする方法](https://jp.mathworks.com/help/matlab/creating_plots/save-figure-with-minimal-white-space.html)もあるのですが，subplotを利用しているときにはサイズ調整は難しいです。
さらに，フォントの埋め込みには対応していないようです。
そこで，公開されているアドオン[export_fig](https://jp.mathworks.com/matlabcentral/fileexchange/23629-export_fig)を利用することにします。


|   | 組込み関数 | export_fig |
|:-:|:-:|:-:|
|見た目[^howtomakepng]| ![fig1_print.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248335/bf0dd1d6-afee-c7ff-98eb-cc6a7708cd77.png) <br> 透明だがA4サイズ| ![fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248335/561e58f3-2ae6-30a7-2989-1d392cc697d6.png) <br> 見た目通りのサイズ|
|フォントの埋め込み[^fontembedded]| ![font_by_print.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248335/01a771c5-02a6-9d27-94ad-792888728d96.png) <br> 埋め込みの表示なし| ![font_by_export_fig.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248335/fd974013-7eda-0568-9c04-351da758ed7b.png) <br> 埋め込みサブセットと表示|

[^supplement1]: print関数でのPDF保存の問題については[こちらの記事](https://qiita.com/MusicScience37/items/326559c4de903ab35f04)も参考になります
[^fontembedded]: Adobe Acrobat Reader DC のプロパティより確認

## export_figを利用した方法
export_figはアドオンですので，あらかじめアドオンエクスプローラからインストールしておいてください。(参考：[アドオンの入手](https://jp.mathworks.com/help/matlab/matlab_env/get-add-ons.html))

export_figの詳しい使い方や組み込み関数との違いは[githubのREADME.md](https://github.com/altmany/export_fig)に記載があります。
ですので，export_figの機能そのものには触れず，使っていきます。

PDFを書き出すのは簡単で，以下のようにファイル名とfigureハンドルを指定するだけです。

```matlab
export_fig('三角関数.pdf', fig);
```

これだけで，A4サイズでなく，figureの見た目通りにPDFに書き出されます。
ただし，上書きの確認などはされないため，ラッパー関数を用意しました。
これを用いると以下のように複数ファイルを一行で保存できます。

```matlab:makeSinCosFigure.m
saveFigAsPdf([fig, fig2], {'三角関数(重ねた)', '三角関数(分けた)'});
```
<details>
<summary>saveFigAsPdf関数の実装（コード）</summary><div>
複数ファイル同時処理と上書きの確認処理を追加したexport_figのラッパー関数となっています。

```matlab:saveFigAsPdf
function [flag] = saveFigAsPdf( figs, filenames )
%saveFigAsPdf 指定したfigureハンドルをPDFで保存する
%             export_figのツールボックス（アドオン）が必要
%   flag = saveFigAsPdf( fig, filename )
%
%   figs:       figureハンドル(figure配列)
%   filenames:  ファイル名を拡張子抜きで指定(セル配列)
%   flag:       セーブが正常に完了したかどうか

% Copyright (c) 2019 larking95(https://qiita.com/larking95)
% Released under the MIT Licence 
% https://opensource.org/licenses/mit-license.php

% 初期化
flag = false;
% 引数の検証
validateattributes(figs, {'matlab.ui.Figure'}, {'vector'});
validateattributes(filenames, {'cell'}, {'vector'});
if length(figs) ~= length(filenames)
    error('figureの要素数と名前の要素数が異なります');
end

% PDFファイルの保存
for i = 1:length(figs)
    distFile = [filenames{i}, '.pdf'];
    
    % 上書きの確認
    if isfile(distFile)
        str = input('Do you want to overwrite the exsist file? Y/N [N]', 's');
        if isempty(str) || ~strcmpi(str, 'Y')
            warning('Stop saving the pdf file.');
            return;
        end
        
    end
    export_fig(distFile, figs(i));
end
flag = ~flag;
```

</div>
</details>

# 全体のコード
最後に，紹介してきたコードの全体を示します。
関数はパスの通った場所に配置しなければならないことに注意してください。
<details><summary>makeSinCosfigure.m　の全体</summary><div>

```matlab:makeSinCosFigure.m

%% 共通化できない編集
clearvars
close all;

% 一つ目の図の作成
fig = figure('name', '三角関数をプロット');
x = 0:0.001:4*pi;
ys = sin(x);
yc = cos(x);
plot(x, ys, x, yc);

% 描画範囲の指定
xlim([0, 2*pi]);
ylim([-1.2, 1.2]);

% 軸ラベルの設定
xlabel('$x$', 'Interpreter', 'latex');
ylabel('$y(x)$', 'Interpreter', 'latex');

% 凡例の設定
legend({'$y(x) = \sin(x)$', '$y(x) = \cos(x)$'}, 'Interpreter', 'latex');

% グリッド線の表示
grid on;

saveFigAsPdf(fig, {'三角関数(オリジナル)'});

% 二つ目の図の作成
fig2 = figure('name', 'subplotで三角関数を並べる');
ax = gobjects([2, 1]);
xRange = [0,2*pi];
yRange = [-1.2, 1.2];
ax(1) = subplot(2,1,1);
plot(x, ys);
xlim(xRange); ylim(yRange);
xlabel('$x$', 'Interpreter', 'latex');
ylabel('$\sin(x)$', 'Interpreter', 'latex');
grid on;
ax(2) = subplot(2,1,2);
plot(x, yc, 'r');
xlim(xRange); ylim(yRange);
xlabel('$x$', 'Interpreter', 'latex');
ylabel('$\cos(x)$', 'Interpreter', 'latex');
grid on;

%% 共通化可能な編集
% reshape_figure('qiita', fig);         % 一枚だけ編集
reshape_figure('qiita', [fig, fig2]);   % 複数の図を一度に編集

%% PDFへの書き出し
print(fig, '三角関数(組込み関数).pdf', '-dpdf');
saveFigAsPdf([fig, fig2], {'三角関数', '三角関数(分けた)'});

%% Qiitaへ乗せるためにImageMagickでPNGへ変換(コメントアウト)
% !magick -density 200 三角関数(オリジナル).pdf fig1_org.png
% !magick -density 200 三角関数(組込み関数).pdf fig1_print.png
% !magick -density 200 三角関数.pdf fig1.png
% !magick -density 200 三角関数(分けた).pdf fig2.png
```

</div>
</details>

# おわりに
説明は省略しつつですが，自分流の図の作成方法をまとめることができました。
コードは一応MITライセンスです。
見て頂いた方のお役に立てば幸いです。

何か気になる点などあればお気軽にコメントください。

# 参考ページ
1. Qiita: [MATLABで論文用の図を作るコツ](https://qiita.com/kouichi-c-nakamura/items/468fe09874461911dfab)
2. MATLAB公式ドキュメント: [テキスト関連プロパティ](https://jp.mathworks.com/help/matlab/ref/matlab.graphics.primitive.text-properties.html)
3. MATLAB Central: [export_fig](https://jp.mathworks.com/matlabcentral/fileexchange/23629-export_fig)
4. MATLAB公式ドキュメント: [アドオンの入手](https://jp.mathworks.com/help/matlab/matlab_env/get-add-ons.html)