LoginSignup
13
14

More than 5 years have passed since last update.

MATLABからベクトルグラフィックスとして図を書き出す方法

Last updated at Posted at 2017-01-12

MATLABで図を作った後、それを何らかの画像フォーマットに書き出して、Illustratorなどの画像処理ソフトで仕上げをして、論文に投稿、という作業の流れは非常に一般的だと思います。一旦ちゃんと図さえ作ってしまえば、後の工程は遊びみたいなものだから心配いらないと思うでしょう。私もそうでした。しかしこれが案外、厄介です。

一つの問題は図の中に透明度を指定したものが混じっているときに、それが出力ファイルに維持されているかどうかです。従来よく使われていたEPSへの書き出しは透明度を維持できません。問題はこうした細かいことについて公式のドキュメンテーションには記載がないことです。

実験

透明度(FaceAlpha, EdgeAlphaなどで指定)を含む単純な図を作ってそれを書き出します。それをIllustratorとInkSpaceで開いてみてどのように表現されているか確認しました。


f = figure

f.PaperType

f.PaperUnits = 'centimeters';
f.PaperPosition = [2 2 18 24];

f.Units = 'centimeters';
f.Position = [2 2 18 24];

f.Color = 'w';

axh = axes('Units','centimeters','Position',[2 2 10 10])

v1 = [2 4; 2 8; 8 4];
f1 = [1 2 3];
patch('Faces',f1,'Vertices',v1,'FaceColor','red','FaceAlpha',.3);

v2 = [2 4; 2 8; 8 8];
f2 = [1 2 3];
patch('Faces',f2,'Vertices',v2,'FaceColor','blue','FaceAlpha',.5);

title('Transparent patches')

axh.FontName = 'Arial'; 
% Arial font can be maintained. While default Helvetica will 
% be replaced with something else in Windows.

print(f,'-painters','test_print_behavior1','-dpdf')  % PDF
print(f,'-painters','test_print_behavior1','-depsc') % EPS
print(f,'-painters','test_print_behavior1','-dsvg')  % SVG

download.png

結果

PDF in Illustrator CS5.5

  • 高さと幅は正確に10 cm
  • 透明度は Transparency PanelOpacity として維持されている
  • 色はRGBで保持されている

PDF in InkSpace 0.91

  • 高さと幅は正確に10 cm
  • 透明度は RGB color のA値(Alpha)として維持されている。 Opacity は 100%のまま。
  • 色はRGBで保持されている

SVG in Illustrator CS5.5

  • 高さと幅は 13.335 cm (本来10 cm)
  • 透明度は Appearance PanelOpacity として維持されている
  • 色はRGBで保持されている

SVG in InkSpace 0.91

  • 高さと幅は 10.6680 cm (本来10 cm)
  • 透明度は RGB color のA値(Alpha)として維持されている。 Opacity は 100%のまま。
  • 色はRGBで保持されている

EPS in Illustrator CS5.5

  • 高さと幅は正確に10 cm
  • 透明度は失われて薄い色のベタ塗りで代用されている
  • 色はRGBで保持されている (読み込み時に選択画面が出た)

EPS in InkSpace 0.91

  • ファイルを開くことができなかった

Canvas X 16

  • SVGを開くことが出来ない
  • PDFの透明度を再現することが出来ない

考察

以上から、編集可能なベクトルグラフィックスとして透明度を含む図を書き出す手段は、いろいろ選択肢がありそうに見えて、ほぼこれひとつしかありません。


print(f,'-painters',filename,'-dpdf')  % PDF

  • fは、figureのハンドル(matlab.ui.Figure class)

  • '-painters' は描画エンジンを指定して、ベクトルグラフィックスとしての書き出しを指定する。もう一つの選択肢の'-opengl'を選ぶとビットマップ画像として書き出され、その後の工程で部品を個別に修正することが出来ないので注意。

  • filenameはファイル名の文字列

  • '-dpdf'はPDF書き出しを指定。

未解決問題

FontName問題

また、補足ですが、'FontName'プロパティは私の環境のデフォルトではHelveticaになっていますが、このフォントがWindowsに内蔵されていないため、Windowsで書き出す場合はこれをあらかじめArialなどに変えておく必要があります。そうしないと、後の工程で勝手に別のフォントで代用されてしまいます。

これですべてを網羅することは出来ませんが、大半のオブジェクトの文字については以下のコードでデフォルトの設定を変えてしまうことも出来ます。


set(groot,'defaultAxesFontName',    'Arial');
set(groot,'defaultTextFontName',    'Arial');
set(groot,'defaultLegendFontName',  'Arial');
set(groot,'defaultColorbarFontName','Arial');

最初の引数としてgrootの代わりに、特定のfigureやaxesのオブジェクトを指定すれば、デフォルトの指定範囲を限定することが出来ます。

特殊文字を用いるとPDF中で文字列がベクトルグラフィックスに置換される問題

論文などでは "0–200" というように範囲を示す場合、ハイフンではなくen-dashを用いるのが正式です。よく見るとハイフンよりも少し長いのがわかります。

Windowsでは Altを押しながら0150、MacではOption+-で、en-dashが打てます。

sprintf('0%s200',char(8211))

これをMATLABの図の上でも実現しようとすると、上の例のようにchar(8211)で実現できますが、なんと、このようにしてせっかく図の上に en-dashを用いて文字を書いても、PDFで書き出す時に、en-dashを含む文字列のみベクトルグラフィックスに置換されてしまいます。PDF画面で見ると他の文字よりも若干大きさや形が違って見えるでしょう。

結局、このような特殊文字については、PDF書き出しの後で手動で入力するように対策がないようです。en-dash以外に正確にどの文字がこれに影響されるのかは分かっていません。

三角形の集合体問題

さて、print(f,'-painters',filename,'-dpdf')で一件落着、としたいところですが、R2017aの時点ではまだ問題が残ってます。patchオブジェクトやsurfaceオブジェクトをこの方法でベクトルグラフィックスとして書き出すと、IllustratorやInkSpaceでは、細かい三角形の集合体として扱われます。つまり、ひとつのオブジェクトとしては扱われません。PDFの閲覧ソフトウェアの設定によっては、この小さな三角形の境界線が白い線として目に見えてしまいます。

patchオブジェクト

patchオブジェクトについては、手動である程度対応できることが分かりました。

Illustratorの場合、

  1. 小さな三角形をひとつ選ぶ
  2. Select > Same > Fill & Strokeで同じ色の三角形をすべて選ぶ
  3. Groupでグループ化
  4. Isolate Selected Pathでこのグループだけを分離して表示
  5. 結合するべきpatch オブジェクトの三角形をすべて選択する
  6. Window > Pathfinder > Merge によって結合する
  7. Exist Isolation Mode
  8. Ungroup

InkSpaceの場合、

  1. Ctrl + click で小さな三角形をひとつ選ぶ
  2. Edit > Select Same > Fill Color
  3. Path > Union

surfaceオブジェクト

しかしsurfaceオブジェクトについてはこの方法はうまく動作しないようです。MathWorksに改善要求を出しましたが、応じてくれるかどうかは分かりません。回避法としては、surfaceオブジェクトだけをビットマップ画像として書き出して後で貼り合わせるという手が一応あります。だいたいはうまくいきますが今のところ完全にピタリとはサイズが合わないようです。

imagesc を2D surfaceの代わりに用いる

二次元的に(view(0,90)など)表現されているsurfaceオブジェクトであれば、昔から用いられていた問題回避法は、imagescを代わりに使用することです。imagescは二次元のimageオブジェクトを作りますが、三次元方向の変数の大小をcolormapを用いて表現できるので、surfacesurfを使って作られたプロットの代用になります。

しかし、ひとつ問題があります。surface オブジェクトの場合、描画後に、shading interpを実行するだけで、簡単にinterpolation(補間、内挿)され、ギザギザのデータがなめらかな色表現として示されましたが、imagescでは生データがそのまま表示されてしまいます。interpolationはどうすればよいのでしょうか?interp2を使えば、一手間増えますが、ほぼ同じような結果を得ることが出来ます。

下のsurface2imagescは、既に存在しているsurfaceオブジェクトをimagescを用いてImageオブジェクトに置き換えるための関数です。それほどテストしたわけではないのでまだ問題があるかもしれませんが、試した範囲では非常によく機能します。

Imageオブジェクトに変換された後は、PDFに書き出しても、単一のオブジェクトとして扱われます。


function im = surface2imagesc(sf,varargin)
%
% Converts a 2D (i.e, view(0,90) ) surface object to image with builtin
% imagesc. It is a known and long standing problem of MATLAB that surface
% plot with the height represented by color is beautiful, but quite
% difficult to be exported and handled afterwards. Exporting 2D surface
% plot as imagesc is a workaround for this.
%
% im = surface2imagesc(sf)
% im = surface2imagesc(____'Parameter', value)
% 
% INPUT ARGUMENTS
% sf          Surface object or array of surface objects
%
% OPTIONAL PARAMETER/VALUE PAIRS
% 'InterpMethod'  
%             'linear' (default)| 'nearest' | 'cubic' | 'spline'
%             The method of 2d interpolation (see
%             interp2 for details) The second and third element defined the
%             factor by which interpolation is carried out in X and Y
%             dimensions. {'linear',5,5} specifies 'linear' method with 5
%             times more resolution in X and Y.
%
% "InterpFactor'
%             [5,5] (default)
%             Two element vector. Defines the factor by which interpolation
%             is carried out in X and Y dimensions. [5,5] gives 5 times
%             more resolution in X and Y.
%
% 'KeepSf'    true (default) | false | 1 | 0
%             Whether to keep the original surf object after convertion.
%
% OUTPUT ARGUMENTS
% im          Image object 
%             DisplayName, Tag, YDir and CLim equals to those of
%             sf.DisplayName 
%
% Limitations 
% It is yet unknown if this funciton works any surface object that is
% presented in 2D.
%
% See also
% surface, imagesc, interp2
%
%
% Written by Kouichi C. Nakamura Ph.D.
% MRC Brain Network Dynamics Unit
% University of Oxford
% kouichi.c.nakamura@gmail.com
% 25-Apr-2017 14:25:12

p = inputParser;
p.addRequired('sf',@(x) all(all(isgraphics(x,'surface'))));
p.addParameter('InterpMethod','linear',@(x) iscell(x) && isrow(x) && ...
    numel(x) == 3 && ...
    ismember(x{1},{'linear' | 'nearest' | 'cubic' | 'spline'}) ...
    && (isscalar(x{2}) && x{2} > 0) && (isscalar(x{3}) && x{3} > 0));
p.addParameter('InterpFactor',[5,5],@(x) isreal(x) && isrow(x) && ...
    numel(x) == 2 && all(x > 0));
p.addParameter('KeepSf',true,@(x) isscalar(x) && x == 1 || x== 0);

p.parse(sf,varargin{:});


interpmethod = p.Results.InterpMethod;
interpfactor = p.Results.InterpFactor;
keepSf = p.Results.KeepSf;

im = gobjects(size(sf));
for i = 1:size(sf,1)
    for j = 1:size(sf,2)
        %NOTE it was hard to determine the right orientation. X and Y can
        % be flipped over or interp2 and imagesc issues an error.
        % Below was just by trial and error approach

        if all(sf(i,j).XData(1,1) == sf(i,j).XData(:,1))
            swap = true;
            xdata = sf(i,j).XData(1,:); 
        else
            swap = false; % WORKING
            ydata = sf(i,j).XData(:,1); 
        end

        if all(sf(i,j).YData(1,1) == sf(i,j).YData(:,1))
            assert(swap == false)
            xdata = sf(i,j).YData(1,:); 
        else
            assert(swap == true)
            ydata = sf(i,j).YData(:,1); 
        end

        zdata = sf(i,j).ZData;
        % cdata = sf(i,j).CData;

        axh = local_findparentaxh(sf(i,j));


        Xq = (xdata(1):diff(xdata(1:2))/interpfactor(1):xdata(end))';
        Yq = (ydata(1):diff(ydata(1:2))/interpfactor(2):ydata(end));


        if swap
            V = interp2(xdata,ydata,zdata,Xq,Yq,interpmethod);
        else
            V = interp2(xdata,ydata,zdata,Xq,Yq,interpmethod)';

        end


        ydir = axh.YDir;
        clim = axh.CLim;
        xlimit = axh.XLim;
        ylimit = axh.YLim;


        hold(axh,'on');
        if swap
            im(i,j) = imagesc(axh,Xq,Yq,V);
        else
            im(i,j) = imagesc(axh,Yq,Xq,V);
        end

        axh.YDir = ydir;
        axh.CLim = clim;

        im(i,j).UserData = sf(i,j).DisplayName;
        im(i,j).Tag = sf(i,j).Tag;

        axh.XLim = xlimit;
        axh.YLim = ylimit;

        if ~keepSf
            delete(sf(i,j));
            fprintf('A surface object has been deleted.\n')
        end

    end
end

end

%--------------------------------------------------------------------------

function axh = local_findparentaxh(sf)

if isgraphics(sf.Parent,'axes')

    axh = sf.Parent;

else
    %TODO need to be tested
    axh = local_findparentaxh(sf.Parent);

end

end

13
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
14