見出し画像

MATLAB で矢印ってどう描くの?


まえがき


さて、MATLAB のグラフに矢印を描き入れたいときってありますよね?

「矢印なら ”Arrow" とかだろ!」と思って探してもそんな関数はありません。(File Exchange には "arrow” があります!)


しかしいくつか描く方法はあるので、今回はそれらをご紹介します。


annotation


注釈を作成する関数です。

テキスト付き矢印も簡単に描けます。

annotation
rng(0,"twister");
p = plot(rand(10,3)*10);
ax = gca;
ax.FontName = 'Sans-serif';
ax.FontSize = 10;
ax.YTickLabelRotation = 45;
xlim([1 10])

% annotation
xA = p(2).XData(5);  yA = p(2).YData(5);
[xFig,yFig] = axesPosition2figurePosition([xA, yA], ax);
x = [0.55 xFig];
y = [0.85 yFig];
annotation('textarrow',x,y,'String','annotation ')

xlabel('X-axis');
ylabel('Y-axis');
title('Annotation');


矢尻のスタイルや長さ・幅も色々指定できます。

Arrow のプロパティ


ただし figure 上の正規化座標で指定する必要があるため、座標軸のデータに合わせるには座標変換が必要になります。

座標変換は @michio_MWJ さんが axesPosition2figurePosition() を公開してくださっているので、それを使えば簡単にできます。(上記スクリプトでもそれを使っています)

逆に、以下のような複数座標軸にまたがった描画もできます。

複数座標軸にまたがった annotation


また、figure では挿入メニューから手動で annotation を作成することもできます。(今のところ uifigure は未対応)

挿入メニュー


移動や変形を含めた各種編集もできますし、「コードの表示」でスクリプトへの変換もできます。

後から描き足す用途ではこの方法が一番楽かと思います。

編集メニュー(右クリックメニュー)
% textarrow を作成
annotation(figure1,'textarrow',[0.55 0.474444444444444],...
    [0.85 0.762228582144372],'String','annotation');

自動生成されたスクリプト


annotation
 は汎用的に使えて便利なのですが、座標変換が必要なのと、表示拡大・縮小・移動に追従しないことにも注意が必要です。

拡大&移動
座標データとズレる


quiver

ベクトル(場)をプロットする関数です。

quiver プロット
load('wind','x','y','u','v')
X = x(11:22,11:22,1);
Y = y(11:22,11:22,1);
U = u(11:22,11:22,1);
V = v(11:22,11:22,1);
q = quiver(X,Y,U,V);
axis equal
hold on
xp = X + q.ScaleFactor*U;
yp = Y + q.ScaleFactor*V;
scatter(xp,yp,"*r")


もちろん単独座標を設定すれば単独の矢印も描けます。

しかしデフォルトがベクトル場プロット用になっているので、単純な矢印を描くにはいくつかのパラメータを設定する必要があります。

デフォルト
x0 = 3;  y0 = 1;
x1 = 3;  y1 = 9;
x2 = 5;  y2 = 1;
x3 = 9;  y3 = 1;
x4 = 9;  y4 = 9;

u1 = x1 - x0;  v1 = y1 - y0;
u2 = x2 - x0;  v2 = y2 - y0;
u3 = x3 - x2;  v3 = y3 - y2;
u4 = x4 - x0;  v4 = y4 - y0;

w = 2;


quiver(x0, y0, u1, v1, LineWidth = w)
hold on
quiver(x0, y0, u2, v2, LineWidth = w)
quiver(x2, y2, u3, v3, LineWidth = w)
quiver(x0, y0, u4, v4, LineWidth = w)
hold off
grid on
xlim([1 10])
ylim([0 10])

l1 = sprintf("mag1 = %d",norm([u1, v1]));
l2 = sprintf("mag2 = %d",norm([u2, v2]));
l3 = sprintf("mag3 = %d",norm([u3, v3]));
l4 = sprintf("mag3 = %d",norm([u4, v4]));
l = legend({l1,l2,l3,l4}, Location="best");
l.FontSize = 14;


デフォルトでは線の長さが足りていません。これは、ベクトル同士がぶつからないように自動調整されているからです。

これを停止するには、scale パラメータを "0" または "off" に設定します。

scale パラメータ "0"
quiver(x0, y0, u1, v1, 0, LineWidth = w)
hold on
quiver(x0, y0, u2, v2, 0, LineWidth = w)
quiver(x2, y2, u3, v3, 0, LineWidth = w)
quiver(x0, y0, u4, v4, 0, LineWidth = w)
hold off
grid on
xlim([1 10])
ylim([0 10])


まだ、矢尻部分の大きさが小さすぎるベクトルがあります
これも、ベクトルの大きさで自動調整されているためです。

これを止めることはできないようなので、その大きさをベクトル長で正規化した値で設定することにより、自動調整を無効化します。

正規化を無効化
arMag = 1.4;
m1 = norm([u1, v1]);
m2 = norm([u2, v2]);
m3 = norm([u3, v3]);
m4 = norm([u4, v4]);

ar1 = quiver(x0, y0, u1, v1, 0, LineWidth = w);
ar1.MaxHeadSize = arMag / m1;
hold on
ar2 = quiver(x0, y0, u2, v2, 0, LineWidth = w);
ar2.MaxHeadSize =  arMag / m2;
ar3 = quiver(x2, y2, u3, v3, 0, LineWidth = w);
ar3.MaxHeadSize =  arMag / m3;
ar4 = quiver(x0, y0, u4, v4, 0, LineWidth = w);
ar4.MaxHeadSize =  arMag / m4;
hold off
grid on
xlim([1 10])
ylim([0 10])


もう少し複雑な例で見てみましょう。

(Marker と矢印両方を使う場合は、ShowArrowHead = 'on'; が必要になります。)

quiver を使った矢印の例
rng(0,"twister");
p = plot(rand(10,3)*10);
ax = gca;
ax.FontName = 'Sans-serif';
ax.FontSize = 10;
ax.YTickLabelRotation = 45;

hold on

x0 = p(1).XData(3);
y0 = p(1).YData(3);
x1 = p(1).XData(6);
y1 = p(1).YData(6);
x2 = p(2).XData(3);
y2 = p(2).YData(3);
x3 = p(3).XData(10);
y3 = p(3).YData(10);

u1 = x1 - x0;
v1 = y1 - y0;
m1 = norm([u1, v1]);
u2 = x2 - x0;
v2 = y2 - y0;
m2 = norm([u2, v2]);
u3 = x3 - x1;
v3 = y3 - y1;
m3 = norm([u3, v3]);

arMag = 1.4;

ar1 = quiver(x0, y0, u1, v1, 0, 'o-', LineWidth=2, ...
    MarkerSize = 10, MarkerEdgeColor='r', MarkerFaceColor='g');
% ar1.Annotation.LegendInformation.IconDisplayStyle = "off";  % レジェンドから除外する他の方法
ar1.ShowArrowHead = 'on';
ar1.MaxHeadSize = arMag / m1;

ar2 = quiver(x0, y0, u2, v2, 0, '*:', LineWidth=2.5, DisplayName='Difference');
ar2.ShowArrowHead = 'on';
ar2.MaxHeadSize =  arMag / m2;

ar3 = quiver(x1, y1, u3, v3, 0, 'pentagram-.', LineWidth=0.5, DisplayName='Difference');
ar3.ShowArrowHead = 'on';
ar3.MaxHeadSize =  arMag / m3;
ar3.MarkerSize = 12;

xp = x3;  yp = y3;
% R2024a 以降では、スケーリングプロパティの値を参照することもできます
% xp = x1 + ar3.ScaleFactor*u3;
% yp = y1 + ar3.ScaleFactor*v3;
scatter(xp, yp, 100, "*r")

% annotation
xA = p(2).XData(5);  yA = p(2).YData(5);
[xFig,yFig] = axesPosition2figurePosition([xA, yA], ax);
x = [0.55 xFig];
y = [0.85 yFig];
annotation('textarrow',x,y,'String','annotation ')

xlabel('X-axis');
ylabel('Y-axis');
title('Quiver Plot');
l = legend([p; ar2]);
l.BackgroundAlpha = 0.75;
hold off


当然ですが annotation と違い、グラフの拡大・縮小・移動をしてもズレることはありません。

拡大&移動
quiver で描画した矢印はズレない

スクリプト中に最初から入れ込むのであれば、quiver が適しているかと思います。

極座標で使う場合は、pol2cart で直交座標に変換して指定します。


compassplot


原点から発生する矢印を描く関数です。

極座標で用います。

元々 compass という関数もありましたが、R2024b 以降、より高機能な compassplot への移行が推奨されています。

compassplot
path = userpath + "\Examples\R2024b\matlab\PlottingInPolarCoordinatesExample\"; 
load(path + "antennaData.mat")
polarplot(theta,rho,DisplayName='Radiation Pattern')
title('antennaData')

pax = gca;
hold on
[~,midx] = max(abs(rho));
compassplot(pax, theta(midx), rho(midx), DisplayName='Maximum Intensity')
hold off
legend


feather


x 軸からの矢印を描く関数です。

n 番目の矢印は、 x 軸の n に起点があります。

feather
t = (0:pi/8:2*pi) + pi/16;
u = ones(size(t)) * 0.5;
v = sin(t);
fp = feather(u,v);
grid on
ax = gca;

fp には、各矢印の Line オブジェクトが格納されます。

最後の要素は x 軸に沿った水平線になるので、(矢印の本数+1)の Line オブジェクトベクトルが返されます

>> x = fp(5).XData

x = 1×6
    5.0000    5.5000       NaN    5.3215    5.5000    5.4785

>> y = fp(5).YData

y = 1×6
         0    0.9808       NaN    0.8246    0.9808    0.7446


それらは個別に操作することができます。

5番目の YData を1番目と入替え
tmp = fp(5).YData;
fp(5).YData = fp(1).YData;
fp(1).YData = tmp;


Line データ自体も変更できるので、矢印の形を変えたりもできます。

5番目の Line プロパティ変更
fp(5).XData(end+1) = fp(5).XData(end-2);  % 矢尻部分を閉じた形に変更
fp(5).YData(end+1) = fp(5).YData(end-2);
fp(5).LineWidth = 2;  % 太さも変更


そのままプロットすることもできます。

plot 関数による描画
figure
plot(fp(5).XData, fp(5).YData)
grid on


最後に NaN を追加すれば、patch でも描けます。

patch 関数による描画
figure
patch([fp(5).XData NaN], [fp(5).YData NaN], '', EdgeColor = 'r', EdgeAlpha = 0.35, LineWidth = 5)
grid on


おまけに、feather を使ったお遊びを。
(参考:リサージュ図形

f = figure;
f.Color = [1 1 1];
% f.Visible = 'on';
colororder(f,"glow12")

ths = -pi:pi/100:pi;
a1 = 60;  a2 = 20;  a3 = 25;
b1 = 60;  b2 = 30;  b3 = 30;
wx1 = 1;  wx2 = 4;  wx3 = 10;
wy1 = 1;  wy2 = 3;  wy3 = 9;

% 最初に全て描画
fp = gobjects(1,length(ths)+1);
hold on
for k = 1:length(ths)
    th = ths(k);
    u = a1 * sin(wx1 * th) + a2 * sin(wx2 * th) + a3 * sin(wx3 * th);
    v = b1 * sin(wy1 * th + pi/2) + b2 * sin(wy2 * th + pi/2) + b3 * sin(wy3 * th + pi/2);
    fp([k end]) = feather(u,-v);  % (end) は水平線
    fp(k).Color = [fp(k).Color 0];  % いったん透明化(隠しパラメータ)
end
hold off
ax = gca;
xlim(ax, [-150 150])
ylim(ax, [-150 150])
axis(ax,'off')
axis equal
drawnow

% 一部のみ不透明度を上げる
hold on  % for scatter
N2 = 5;  % 一度に可視化する数
for j=1:length(ths)
    for k=0:N2
        idx = mod(j+k, length(ths));
        idx = max(idx,1);
        alpha = k/N2;  % 徐々に濃く
        if (j+k)>length(ths); alpha = 0; end
        fp(idx).Color = [fp(idx).Color alpha];  % 透明度変更(隠しパラメータ)
    end
    x = fp(j).XData(2);  y = fp(j).YData(2);
    s = scatter(x,y,100,"filled");  % 矢印の先に ● を追加していく
    distfromzero = norm([x, y]);
    s.AlphaData = distfromzero;
    s.MarkerFaceAlpha = 'flat';  % 原点からの距離によって透明度変更
    drawnow
end
hold off

% カラーマップ変更ループ
C = orderedcolors("glow12");
C2 = C;
for k = 1:30
    C2 = circshift(C2, 1);
    colororder(f,C2)
    drawnow
end
colororder(f,"glow12")


あとがき


しかしなぜ、需要が高いと思われる矢印描画専用関数がないんでしょうね?

MathWorks さん結構、「~使えばできるからいいだろ!」ってとこがあるような・・。🤔


この記事が気に入ったらサポートをしてみませんか?