最近はPythonも人気があるようですが、依然として科学研究者の中では(とくに私のいる神経科学分野では)MATLABで計算やデータ解析をするというのはかなり一般的だと思います。そしてMATLABでデータ解析を一旦始めると、誰しも「MATLABでどうやって論文用の図を作るか」という問題に直面するはずです。
そして、ほとんど全員が、MATLAB単独で論文用の図の作成を完遂することはあまり考えず、Illustratorなどの自由度の高い描画ソフトで何らかの手を加えて、図を完成させるのではないかと思います。
MATLAB単独で完遂できればそれはそれでよいのですが、やはりグラフィックソフトではないので自由度に制限があります。顕微鏡写真などと組み合わせようと思うと、写真のイメージデータのサイズやコントラスト調整などMATLABでやると煩わしいので、やはりグラフィックソフトとの併用を考えるのではないでしょうか。
そして、MATLABと、Illustratorなどのグラフィックソフトとの併用を前提にした場合、方針としては、大きく分けて以下の二通りのどちらかを選ぶことになるでしょう。
- MATLABでの工程を最小限にする: MATLABではほとんどデフォルトの条件で単純なプロットをひとつずつ描いてPDFまたはEPS書き出しを行い、Illustratorなどの描画ソフトでそれを編集して組み合わせる際に、デザインのほとんど(サイズ、線の太さ、文字の配置、マーカーの大きさ、色指定等)を行う。
- MATLABでの工程を最大限にする: MATLABの中で、ほとんどのデザインを行ってしまい(サイズ、線の太さ、文字の配置、マーカーの大きさ、色指定等)、ひとつのfigureの中に複数のaxesを組み合わせて、複雑な図を作り、全体をPDF書き出しして、Illustratorなどの描画ソフトでの編集は必要最終限度に留める。
もし、あなたがすでにIllustratorを使った図作りに習熟していて、そのワークフローの中にMATLABで作った図を嵌め込むということを考えると、自然と1を選ぶことになるでしょう。
このどちらを選ぶ人が世間では多いのかという実際の数字にも興味がありますが(もしこの記事を読んだ人がいればどちらの方針かコメント欄で教えていただければ幸いです。)、私の予想では1のほうが多いものと思います。少なくとも私の身の回りでは1の方が多いようです。
MATLABでの工程を最小限にする場合の長所短所
MATLABでの工程を最小限にする場合(上の選択肢1)、長所として考えられるのは、デザインの部分をグラフィックソフトのGUIに依存することになるので、操作に習熟している限り、直感的に作業が進められることが挙げられるでしょう。そして当然、図を描く部分のMATLABコードは最小限になるので、プログラミングの負担も小さくなります。
この方針の短所としては、図作りの重要な部分をグラフィックソフトのGUI操作に頼るので、繰り返しが効かないことでしょう。
もし、MATLABから出力するのが、一回切りで、やり直しを想定しなくて良い場合、こちらの方が断然作業が早いでしょう。しかし、私の経験では、一度自信満々で出力したプロットも後々になって計算ミスやバグが見つかって、後で少しだけ手直ししなければならないというのは日常茶飯事ですので、ほとんどの場合、繰り返しを想定する必要があると考えています。
幾度も繰り返しMATLABからの出力をグラフィックソフトのGUIで編集して図に組み上げるのは、とってもクリエイティブな作業である一回目とは打って変わって、奴隷労働のような作業です。それでも場合によっては、辛ささえ我慢すればこの方針のほうが結果的に早いということがあり得ると思います。私の場合は、辛さに耐えられず、方針2の方を結局選びました。
MATLABでの工程を最大限にする場合の長所短所
MATLABでの工程を最大限にする場合(上の選択肢2)の長所短所は、ちょうど逆になります。手直しの回数が増えれば増えるほどこちらの方針のほうが有利になりますが、プログラム的に論文用の図をデザインするのはGUI操作よりも面倒な場合が多いでしょう。このプログラミングの労力コストと、繰り返し時の気楽さのバランスで決めるのが最も合理的なのでしょう。個人的には、こちらの方針のほうが長期手には効率が良いと思っていますが、本当のところは分かりません。
MATLABで精密に図をデザインする場合のコツ
方針2に従って、MATLAB側で精密に図を作るにはどうすればよいでしょうか。
反応や実行速度は普通のスクリプトや関数よりも遅くなりますが、それでも私はLive Scriptで図を作るようにしています。以前述べたように、コードと出力の図が一緒に保存できるメリットはとても大きいと考えています。
1. figureを作る
最初に figureオブジェクトを作ります。
fig = figure;
このときに、figのプロパティを論文の図用に設定しましょう。
fig.PaperType = 'a4';
fig.PaperUnits = 'centimeters';
fig.PaperPosition = [2,2,17.6,24];
fig.Units = 'centimeters';
fig.Position = [2,2,17.6,24];
fig.Color = 'w';
fig.InvertHardcopy = 'off';
PaperUnits
とUnits
を共に'centimeters'
に設定します。その上で、PaperPosition
と Position
を同じ値に設定し、MATLAB画面上の表示サイズと、PDF書き出し時にサイズとを一致させています。
このPaperPosition
(PDFのサイズ)と Position
(画面所のサイズ)の値はどちらも、[左、下、幅, 高さ]
をセンチメートルで指定しています。
次に、以下ではこのfig
に対して、デフォルトのプロパティ値を変更しています。
set(fig,'defaultAxesFontSize',axesFontSize);
set(fig,'defaultAxesXColor','k'); % factory is [0.15,0.15,0.15]
set(fig,'defaultAxesYColor','k');
set(fig,'defaultAxesZColor','k');
set(fig,'defaultAxesUnits','centimeters');
set(fig,'defaultAxesTickDir','out');
set(fig,'defaultAxesBox','off');
set(fig,'defaultAxesFontName',fontName);
set(fig,'defaultTextFontName',fontName);
set(fig,'defaultTextFontSize',axesFontSize);
set(fig,'defaultLegendFontName',fontName);
set(fig,'defaultLegendFontSize',axesFontSize);
set(fig,'defaultAxesLabelFontSizeMultiplier',fontSizeMP);
例えば、defaultAxesXColor
を'k'
にしているのは、MATLAB工場出荷状態のデフォルト値が [0.15 0.15 0.15]
であり、純粋な黒ではなく、「濃い灰色」に設定されているからです。あまり気づかないかもしれませんが、このことは普通想定していませんから、後々の工程でグラフの軸の色が黒でないことに気づいて困るという事態を避けるためにデフォルト値を変更しています。
defaultAxesLabelFontSizeMultiplier
も特殊ですが、デフォルトではaxes
オブジェクトのLabelFontSizeMultiplier
は1.1となっており、x軸ラベルやy軸ラベル(XLabel
やYLabel
)の文字は他の部分の文字よりも10%大きくなっています。これもこちらの予期せぬ結果をもたらす要因ですので、自分で値を設定しています。
2. axesを配置する
次に、座標軸axes
オブジェクトをfig
に配置していきます。
ax = gobjects(2,1);
ax(1) = axes('Tag','Great data');
ax(2) = axes('Tag','Super data');
ax(1).Position = [2, 15, 10, 5];
ax(2).Position = [2, 7, 5, 3];
axes('Tag','Great data')
と入れてあるのは、それぞれの座標軸に名前をつけて、後々どのax
がどのグラフに対応するのかをひと目で分かるようにするための工夫です。disp(ax)
とすると、Tag
プロパティの値がコマンドウィンドウに表示されるのが分かります。
上で、defaultAxesUnits
をcentimeters
にしてあるので、ax(1).Position
の値は、いきなりセンチメートルで絶対値指定しています。
ここでは数字をそのまま指定していますが、実際にはこれらの値も変数で置き換えて、共通する値は変数に違う値を代入するだけで変更できるようにしたほうが賢いでしょう。
いきなり、ax('Position',[2, 15, 5, 5])
とすることも可能ですが、わざと二段階に分けてPosition
は後で指定しています。この理由は、二段階に分けておけば、ax(1).Position = [2, 15, 5, 5];
の設定値を変えてコマンドウィンドウで次々に試すことで、反応を見ながらふさわしい値を探すことができるからです。
しかし、Live Editorで編集している時は、図はLive Editorのスクリーンショットしか見えませんね。いちいち、新しい設定値の効果を試すためにセクションをまるごと実行しなおすのは面倒です。どうすればよいでしょうか。
これには裏技があり、Live Editorの背後に隠れている fig
そのものを、以下のコマンドで可視化させることができるのです。
fig.Visible = 'on';
これで、fig
をにらみながら、Position
の値を次々に試して行くことができます。とても快適ですね。
3. 座標軸目盛線の長さを指定する
さて、論文用の図ということで考えると、とても細かいことですが、グラフの座標軸の目盛線(ticks)の長さを厳密に指定したいところです。
MATLABの仕様はどうなっているかというと、axes
オブジェクトのTickLength
プロパティで指定できることになっていますが、これは長い方の軸の長さに対する相対値ですので、グラフの座標軸の大きさが一定でないと、実際の目盛線の長さは変わってしまうことになります。
今の例で言うと、ax(1)
の方は長いほうが10 cm、ax(2)
の方は長いほうがが 5 cmです。デフォルトではTickLength
プロパティは[0.01 0.025]
で、この一つ目が二次元プロットのための設定値ですから、目盛線の長さは、ax(1)
が、1 mm、ax(2)
のほうが 0.5 mm、と二倍もの開きが出てしまいます。これは実際にはとても不格好に見えます。
以下のticklengthcm
は、この問題を解決して、目盛線の長さをセンチメートル単位の絶対値で指定します。
ticklengthcm(ax,0.1);
これも、もし自動化してなければ、グラフィックソフト側で手動で長さを直すことになり、大変な手間が発生します。
function ticklengthcm(axh,cm)
% ticklengthcm allows you to specify the length of Axes ticks in
% centimeters. In MATLAB, TickLength of Axes object is specified in
% relaiton to the longest axes. When you have mutliple axes of different
% sizes in one figure, then TickLenght for each Axes objects will be
% different. ticklengthcm allows you to have tick marks with an uniform
% length throughout the figure.
%
% ticklengthcm(axh,cm)
%
% INPUT ARGUMENTS
% axh an Axes, ColorBar or a graphics array containg Axes handles
%
% cm Legnth of ticks in centimeters (scalar)
%
%
% LIMITATION
% only supports 2D plots
% How to get the length of the longest axes in 3D plot?
%
% DOCUMENTATION on TickLength propertis of Axes
%
% [0.01 0.025] (default) | two element vector
%
% Tick mark length, specified as a two-element vector of the form
% [2Dlength 3Dlength]. The first element is the tick mark length in 2-D
% views and the second element is the tick mark length in 3-D views.
% Specify the values in units normalized relative to the longest of the
% visible x-axis, y-axis, or z-axis lines.
%
% See also
% doc Axes properties
% setupFigure, putpanelID, savefigplus
%
% Written by Kouichi C. Nakamura Ph.D.
% MRC Brain Network Dynamics Unit
% University of Oxford
% kouichi.c.nakamura@gmail.com
% 11-Jan-2017 15:45:14
p = inputParser;
p.addRequired('axh',@(x) (isscalar(x) && ...
all(isgraphics(x,'axes') | isgraphics(x,'colorbar'))) ...
|| isa(x,'matlab.graphics.Graphics'));
p.addRequired('cm', @(x) isscalar(x) && x >= 0);
p.parse(axh,cm);
assert(~verLessThan('matlab','8.4.0'),...
'ticklength only works with MATLAB R2014b or later.');
for i = 1:numel(axh)
if isgraphics(axh(i),'axes') || isgraphics(axh(i),'colorbar')
oriignalunits = axh(i).Units;
axh(i).Units = 'centimeters';
pos = axh(i).Position;
if pos(3) > pos(4)
longest = pos(3);
else
longest = pos(4);
end
newlength = cm/longest; % longest may be wrong for some cases
if isgraphics(axh(i),'axes')
axh(i).TickLength = [newlength,axh(i).TickLength(2)];
elseif isgraphics(axh(i),'colorbar')
% must be scalar
axh(i).TickLength = newlength;
end
axh(i).Units = oriignalunits;
end
end
end
4. パネル参照文字を配置する
論文用の図の場合、それぞれのパネルに対して、それらを参照するために「A, B, C,..」と言った文字を配置するのが一般的です。もちろんグラフィックソフトで簡単にできますが、これも自動化しましょう。この場合、文字列を、座標軸の範囲を超えて配置することになるので、annotation
関数を用います。
もし、文字を回転させたりすることが必要であれば、裏技として、fig
全体に、大きなaxes
を配置して、それを不可視化して(ax(3).Visible = 'off'
)、その上に、text
関数で文字列を描き、回転させて、可視化させるということが可能です。
5. セクション毎にパネルをひとつずつ描画する
ここでパネルと読んでいるのは、一個の図の部分を構成する部品のことですが、ax(1)
やax(2)
が相当します。
原則として、Live Scriptのセクション毎に、ひとつずつax
を描画していきます。
i = 1;
axes(ax(i))
plot(ax(i),X,Y);
xlim(ax(i),[0 1])
ylim(ax(i),[0 1])
xlabel(ax(i),'Time (s)')
ylabel(ax(i),'Voltage (mV)')
ここでは、ax(1)
と直接指定せずに、変数i
を用いています。この理由は、後々になって、図の配置を入れ替える必要が生じた時に、iへの代入値を変えるだけで済むからです。
描画の時の細かい設定は、このコマンドで
fig.Visible = 'on';
fig
を可視化させながらの作業のほうが簡単でしょう。
さて、描画のためのコマンドの部分ですが、いちいち関数毎にax(i)
を指定せずとも、最初にaxes(ax(i))
で、ax(i)
がアクティブになっているので必要ないのではないかと疑問に思われた方もおられるでしょう。つまり、次のようにシンプルに書けばよいではないかと。
plot(X,Y);
xlim([0 1])
ylim([0 1])
xlabel('Time (s)')
ylabel('Voltage (mV)')
わざわざ、plot(ax(i),X,Y);
のように、その都度 axes
を指定しているのは理由があります。
fig.Visible = 'on'
で、Live Scriptで作られ、元々隠されていた fig
を可視化させながらコマンドウィンドウで作業をする際の特殊事情として、plot(ax(i),X,Y);
のように具体的に対象を指定しない限り、fig
にあるax(i)
にはplot
が働かず、新しい別の figure が作られてしまうので、これを回避するためです。
6. 図を保存する
必要に応じてannotation('textbox',[L B W H])
を用いて、この後の工程でグラフィックソフト側で行うべき作業を箇条書きにでもして、図の中に書き込んでしまうことは有用だと思います。
出来上がった図はsavefig
関数でMATLAB *.fig
形式で保存できます。
しかし、以前詳しくご紹介した通り、グラフィックソフトで満足に編集できるのは唯一PDF形式のみです。
print(fig,'-painters','-r450',filename,'-dpdf')
前の記事でも触れましたがこの時の確認事項として、以下が挙げられます。
- 図の中で使用しているフォントの
FontName
が デフォルトのHelveticaの場合、Windows環境にはこのフォントが内蔵されていないため、PDFでのフォントが勝手に別のフォントに置き換えられてしまいます。そこで、あらかじめ Arial などのシステム内蔵フォント(listfonts
で調べられる)に指定しておく。 - 文字サイズが希望した通りのものだけになっているかも確認すべき。
LabelFontMultiplier
,TitleFontMultiplier
などによって、想定外のフォントサイズが紛れ込む可能性があります。annotation
による textboxや、colorbar
など、一部の関数は、FontSize
のデフォルト値のカスタム設定ができないことが分かっており、個別に対応する必要があります。 - ndash – のような特殊文字を図中で使用すると、書き出したPDFにおいて、この特殊文字を含む文字列のみ、フォントとしてではなく、ベクトルデータとして保存されてしまいます。現時点では、これらの特殊文字についてはグラフィックソフト側で編集するしか無いでしょう。
- 図中で使用する線の太さは、希望した通りにものだけになっているか。
これらの目的のための関数も作っていますが、個別の話になるのでまたの時にご紹介しましょう。
結語
いかがでしたでしょうか。以上が、2012年以来の5年間の悪戦苦闘の末に私が辿り着いた、論文用の図作りのための雛形になります。
実際には、同じLive Scriptのこれよりさらに前の部分に、フォルダ設定・パラメータ設定、データの更新・同期、データ解析・計算、統計解析結果の表示の各部分があり、最終的にはこのLive Scriptひとつで、データの更新から図の出力まで一気に全部出来るような仕掛けになっています。
当然ながら、前の方のステップでは、時間のかかる計算処理結果を保存しておき、次回からは保存データを読み込むか再計算するか尋ねるようなワークフローを実現する関数を利用して、結果を保存しておいて、次回からは保存した結果を読み出して済ませるか、再計算をするかを対話的に決めるようにしています。
このデザイン全体をカノニカル・ワークフロー canonical workflow と一人で名付けて乙に入っていますが、読者の方が役に立ったとか、立たなかったとか、もっとこうしたほうが良いんじゃないかというご意見があれば、よろしくお願いします。現在の仕事に関してはもうゴール間近なのでこれで行くしかないですが、次をどうするかも考えていきたいところですので。