MATLAB で矢印ってどう描くの?
まえがき
さて、MATLAB のグラフに矢印を描き入れたいときってありますよね?
「矢印なら ”Arrow" とかだろ!」と思って探してもそんな関数はありません。(File Exchange には "arrow” があります!)
しかしいくつか描く方法はあるので、今回はそれらをご紹介します。
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');
矢尻のスタイルや長さ・幅も色々指定できます。
ただし figure 上の正規化座標で指定する必要があるため、座標軸のデータに合わせるには座標変換が必要になります。
座標変換は @michio_MWJ さんが axesPosition2figurePosition() を公開してくださっているので、それを使えば簡単にできます。(上記スクリプトでもそれを使っています)
逆に、以下のような複数座標軸にまたがった描画もできます。
また、figure では挿入メニューから手動で annotation を作成することもできます。(今のところ uifigure は未対応)
移動や変形を含めた各種編集もできますし、「コードの表示」でスクリプトへの変換もできます。
後から描き足す用途ではこの方法が一番楽かと思います。
% textarrow を作成
annotation(figure1,'textarrow',[0.55 0.474444444444444],...
[0.85 0.762228582144372],'String','annotation');
自動生成されたスクリプト
annotation は汎用的に使えて便利なのですが、座標変換が必要なのと、表示拡大・縮小・移動に追従しないことにも注意が必要です。
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" に設定します。
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'; が必要になります。)
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 が適しているかと思います。
極座標で使う場合は、pol2cart で直交座標に変換して指定します。
compassplot
原点から発生する矢印を描く関数です。
極座標で用います。
元々 compass という関数もありましたが、R2024b 以降、より高機能な 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 に起点があります。
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
それらは個別に操作することができます。
tmp = fp(5).YData;
fp(5).YData = fp(1).YData;
fp(1).YData = tmp;
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; % 太さも変更
そのままプロットすることもできます。
figure
plot(fp(5).XData, fp(5).YData)
grid on
最後に NaN を追加すれば、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 さん結構、「~使えばできるからいいだろ!」ってとこがあるような・・。🤔
この記事が気に入ったらサポートをしてみませんか?