MATLAB のグラフをクリックでデータ表示 ~ マウスクリックキャプチャーとデータヒント機能を使う
まえがき
MATLAB でグラフをクリックして、そのデータをスクリプト内で使いたいこととかがあるかと思います。
MATLAB でクリック座標を所得する方法は複数あり、目的によって適切な方法を取る必要があります。
今回はその方法を整理してみます。
データヒント機能と datatip 関数
データヒント機能
MATLAB のグラフには、データヒント機能があります。
データ点にカーソルを近づけるとデータが表示され、クリックすると表示が固定します。
複数固定、個別または全削除もできますし、データ上をドラッグすればデータヒントも移動します。
便利ですね。
この座標情報は右クリックメニューでワークスペースにエクスポートはできますが、このままではスクリプトから使うのにはちょっと不便です。
しかし実は R2019b でこのデータヒントを作成する datatip 関数が追加され、スクリプト中から使ったり、表示をカスタマイズすることができるようになっています。
データヒントカスタマイズ
R2019a 以降であれば、データヒントの表示フォーマットをカスタマイズすることができます。
>> p = plot(rand(10,3));
>> dt = p.DataTipTemplate
dt =
DataTipTemplate のプロパティ:
DataTipRows: [2×1 matlab.graphics.datatip.DataTipTextRow]
Interpreter: 'tex'
FontSize: 10
FontAngle: 'normal'
FontName: 'Helvetica'
>> dt.DataTipRows(1)
ans =
DataTipTextRow のプロパティ:
Label: 'X'
Value: 'XData'
Format: 'auto'
>> dtt.DataTipRows(2)
ans =
DataTipTextRow のプロパティ:
Label: 'Y'
Value: 'YData'
Format: 'auto'
デフォルトではこのようになっています。
これらを書き換えるか、行を追加・削除することができます。
row1 = dataTipTextRow("X + Y", @(x,y)(x+y));
p(1).DataTipTemplate.DataTipRows(end+1) = row1; % 行追加
p(2).DataTipTemplate.DataTipRows(2) = []; % 行削除
p(3).DataTipTemplate.DataTipRows(1).Label = 'x'; % ラベル変更
p(3).DataTipTemplate.DataTipRows(2).Label = 'y';
このように、datatip 関数を使うとマウスクリックから、どのライン、どのデータ点が近い等の処理なしで、直接データを取得・表示、表示のカスタマイズをすることができます。
具体的なデータの取得方法についてはのちほど解説します。
マウスクリックキャプチャー
datatip をスクリプト中から使うには座標を入れる必要があります。
そのような場合も含め、マウスクリック座標のキャプチャー方法を見ていきましょう。
MATLAB でのマウスクリックは、Figure、Axes、各 plot それぞれでキャプチャーを行うことができます。
専用のコールバックが定義されているので、そこに処理用の関数もしくは処理そのものを定義して使います。
Figure
全ての座標が取れるユニバーサルな方法です。
例えばマウスのクリック座標を取るには
f = gcf;
f.WindowButtonDownFcn = @getaxPos;
でコールバック関数を指定し、コールバック関数内で
function pos = getaxPos(src,~)
figC = get (gcf, 'CurrentPoint')
axC = get (gca, 'CurrentPoint')
end
などとしてクリック座標を取得します。
コールバックを解除するには
f.WindowButtonDownFcn = '';
とします。
figC は figure 全体の中での位置なので、データ座標を求めるには変換が必要です。axC はデータ座標です。
プロットデータが欲しい場合は、どのラインの近くをクリックしたか等の判定が必要になります。
座標変換は例えば
ax.Units = "normalized";
axWidth = f.Position(3) * ax.Position(3);
axHeight = f.Position(4) * ax.Position(4);
axSX = f.Position(3) * ax.Position(1);
axSY = f.Position(4) * ax.Position(2);
axXmin = ax.XLim(1);
axXmax = ax.XLim(2);
axYmin = ax.YLim(1);
axYmax = ax.YLim(2);
とすれば、
x = (C(1) - axSX) / axWidth * (axXmax - axXmin) + axXmin;
y = (C(2) - axSY) / axHeight * (axYmax - axYmin) + axYmin;
pos = [x, y];
で求まります。
クリックボタンの判別も、コールバック関数内で
last_seltype = src.SelectionType;
とすればできます。
normal:左クリック
alt:右クリック
です。
f.ButtonDownFcn もありますが、これは axes 内は反応しないので、データを取る場合に使うことはないかと思います。
以下に、テスト用のスクリプト click_test_figure.m を示します。
function click_test_figure
f = figure;
f.WindowButtonDownFcn = @getfigWinPos;
f.ButtonDownFcn = @getfigPos;
p = plot(rand(10,3),'ButtonDownFcn',@lineCallback);
ax = gca;
ax.ButtonDownFcn = @getaxPos;
function getfigWinPos(src,event)
disp("*** fig WindowButtonDownFcn")
last_seltype = src.SelectionType
event
figC = get (gcf, 'CurrentPoint')
axC = get (gca, 'CurrentPoint')
end
function getfigPos(src,event)
disp("*** fig ButtonDownFcn")
last_seltype = src.SelectionType
event
figC = get (gcf, 'CurrentPoint')
axC = get (gca, 'CurrentPoint')
end
function getaxPos(src,event)
disp("*** ax ButtonDownFcn")
event
figC = get (gcf, 'CurrentPoint')
axC = get (gca, 'CurrentPoint')
end
function lineCallback(src,event)
if event.Button == 1
src.LineWidth = 2;
d = get(gca,'CurrentPoint')
dt = datatip(src,d(1,1),d(1,2))
pause(0.2)
src.LineWidth = 0.5;
end
end
end
データ点以外の axes 内をクリックした場合
*** fig WindowButtonDownFcn
last_seltype =
'normal'
event =
WindowMouseData のプロパティ:
Source: [1×1 Figure]
EventName: 'WindowMousePress'
figC =
144 202
axC =
2.4620 0.4534 1.0000
2.4620 0.4534 -1.0000
axes 外をクリックした場合
*** fig WindowButtonDownFcn
last_seltype =
'normal'
event =
WindowMouseData のプロパティ:
Source: [1×1 Figure]
EventName: 'WindowMousePress'
figC =
34 308
axC =
0.1809 0.7624 1.0000
0.1809 0.7624 -1.0000
*** fig ButtonDownFcn
last_seltype =
'normal'
event =
MouseData のプロパティ:
Source: [1×1 Figure]
EventName: 'ButtonDown'
figC =
34 308
axC =
0.1809 0.7624 1.0000
0.1809 0.7624 -1.0000
lineCallback に関しては、plot の項で説明します。
figure には他のシステムコールバックも用意されており、用途に応じて柔軟な制御が可能です。
WindowButtonDownFcn
WindowButtonMotionFcn
WindowButtonUpFcn
WindowKeyPressFcn
WindowKeyReleaseFcn
WindowScrollWheelFcn
WindowButtonDownFcn、WindowButtonMotionFcn を使い、フィルター周波数・バンド幅をマウスで操作するアプリ例をこの記事に書いています。
さらに WindowButtonUpFcn も使ったお絵かきアプリの例はこちらに。
Axes
ax = gca;
p = plot(ax,rand(10,3));
ax.ButtonDownFcn = "disp('axButtonDown')";
これは、axes 内、データ点以外で呼び出されます。
したがって、データを取ることはできません。
Plot
データの近くでクリックしたときだけコールバックが呼び出されます。
その座標を datatip 関数に入れることにより、実際のデータを得ることができます。
プロットデータを取りたい場合に最も適した方法です。
クリックの種類は、コールバック内で引数のイベントの種類で判別できます。
左クリックは1、右クリックは3です。
以下は、データ近くをクリックすると選択したラインの太さを一時的に変え、近くのデータをデータヒントとして表示する例です。
p = plot(rand(10,3),'ButtonDownFcn',@lineCallback);
function lineCallback(src,event)
if event.Button == 1
src.LineWidth = 2;
d = get(gca,'CurrentPoint');
d(1,1)
d(1,2)
fprintf("Clicked\t:X=%f Y=%f\n",d(1,1),d(1,2))
dt = datatip(src,d(1,1),d(1,2));
fprintf("data\t:X=%f Y=%f\n",dt.X,dt.Y)
pause(0.2)
src.LineWidth = 0.5;
end
end
Clicked :X=2.017544 Y=0.902000
data :X=2.000000 Y=0.923380
また、PickableParts、 HitTest プロパティに設定によって、制御を親に渡したりすることもできます。
詳しくはこちらをご参照ください。
関数プロット(fplot / fimplicit)
これらの関数は、データ点を指定することなく、適宜関数のグラフを描画してくれます。
データの密度は MeshDensity プロパティで指定できますが、ズーム操作によっても適宜変更されます。
MeshDensity とズーム率でデータ点は自動的に決まってしまう制限はありますが、datatip 関数を使うと、自動的にクリック点に近い関数の値を取ることができて便利です。
クリック可能な点データは
fp.XData / YData に入っています。
クリックすると、fp.Children に DataTip が追加されていきます。
プロットオブジェクトにコールバックを設定することにより、プロットが複数あっても、src 引数でどのプロットが選択されたかを識別できます。
表示形式のカスタマイズは、上のデータヒントカスタマイズと同じです。
行を追加したい場合は dataTipTextRow を使って
raw = dataTipTextRow(label,value) の形式で指定します。
fp.DataTipTemplate.DataTipRows(end+1) = row;
無名関数を使うことも可能です。
row = dataTipTextRow("X + Y", @(x,y)(x+y));
fp.DataTipTemplate.DataTipRows(end+1) = row;
この場合、x,y はそれぞれオブジェクトの XData / YData を示すことに注意してください。
他に x,y を変数として使っていても、それとは異なります。
表示フォーマットも指定できます。
f = @(x,y) x.^5 + y.^6 - 1;
fp = fimplicit(f);
axis equal
y2 = cos(fp.XData);
row = dataTipTextRow('Cosine',y2,'%+4.4g');
fp.DataTipTemplate.DataTipRows(end+1) = row;
ただなぜか、関数プロットの場合、各ラインの初回クリック時にデータヒントが表示されないようです。
(どこか1回クリックすると表示される)
対策としては、
dt.Visible = "off";
~
dt.Visible = "on";
を入れておくと、一回目から表示されます。
間に何か処理か pause を入れないとダメなようですが。
データ点取得にのみ datatip を使い、表示は text を使うという手もあります。
figure(Visible="on");
r_values = 1:3;
axis equal
hold on;
fp = arrayfun(@(r) fimplicit(@(x,y) x.^2 + y.^2 - r.^2, [-r r -r r]), r_values);
hold off;
for Ia=r_values
set(fp(Ia), 'ButtonDownFcn',@(src, event) lineCallback(src,event))
% append new data tip if necessary
row = dataTipTextRow("X + Y", @(x,y)(x+y));
fp(Ia).DataTipTemplate.DataTipRows(end+1) = row;
end
function lineCallback(src,event)
if event.Button == 1 % left click
C = get (gca, 'CurrentPoint');
x = C(1, 1); y = C(1, 2);
dt = datatip(src,x,y);
dataX = dt.X; dataY = dt.Y;
z = dataX + dataY
% if no need datatip display
dt.Visible = "off";
hold on
plot(dataX,dataY,'x',MarkerSize=4, MarkerEdgeColor="black" )
hold off
text(dataX+0.1, dataY, "z = " + num2str(z));
end
end
あとがき
MATLAB でクリック座標を所得する方法を、データヒント機能と一緒にまとめてみました。
色々あってちょっと複雑かもしれませんが、データ点取得以外のマウスインタラクティブ処理もしたいのであれば figure の各種コールバック、グラフデータを取るなら plot の @lineCallback が便利です。datatip を併用することにより、実際のデータ座標も簡単に取ることができます。
こういうことができるということを覚えておくと、何かの時に役に立つかもしれません。(u_u)
タイトル画像モデル:結貴
The title image was created using Adobe Generative Fill based on this picture.