MATLAB Answers

0

Get position when using uicontextmenu

Asked by Luca Amerio on 15 Oct 2019 at 14:35
Latest activity Edited by Adam Danz
on 16 Oct 2019 at 11:45
I'm trying to code an interface that alows the user to perform some actions on a set of points visualized as a scatter plot.
To do so, I thought I could use a contextmenu, where the user could right-click on a point and decide what to do among a list of actions.
However when using uicontextmenu, I could not get nor the handle of the parent, nor the position (or index) of the point the user is clicking.
Consider this example
function test()
figure
x = randn(1,20);
y = randn(1,20);
menu = uicontextmenu();
hs = scatter(x,y,'UIContextMenu',menu);
uimenu(menu,'Label','Do something with this point','Callback',@something)
end
function something(src,evnt)
fprintf('x = %f, y = %f',x,y)
end
I know a workaround is to pass the parent to the function, is to call
uimenu(menu,'Label','Do something with this point','Callback',{@something, hs})
and define
function something(src,evnt,parent)
fprintf('%s\n',class(parent))
end
but this doesn't help me understanding on which point the user has clicked, as the input to "something" is defined during creation ans src,evnt do not contain the information I'm looking for.
Is there a workaround?

  0 Comments

Sign in to comment.

2 Answers

Answer by Adam Danz
on 15 Oct 2019 at 15:01
Edited by Adam Danz
on 16 Oct 2019 at 11:11
 Accepted Answer

Instead of a context menu, use a ButtonDownFcn that responds to a mouse click on the object. It's lighter weight than a context menu and requires only 1 click rather than a right-click + selection.
Here's a demo using your idea: It outputs the (x,y) coordinates to the command window and marks the most recently selected coordinate with a red 'x'. hObj is the handle to the line object you're clicking. eventM is a structure that contains the coordinates of the point you selected and the button used to select it.
figure
x = randn(1,20);
y = randn(1,20);
% menu = uicontextmenu();
hs = scatter(x,y);
hs.ButtonDownFcn = @clickPointFcn;
hold on
function clickPointFcn(hObj,event)
% Do whatever you want with that....
fprintf('x = %f, y = %f\n',event.IntersectionPoint(1:2))
% Search and remove previous 'recent selection'
delete(findall(hObj.Parent, 'Tag','recentSelection'))
% Add 'x' to mark selection
plot(event.IntersectionPoint(1),event.IntersectionPoint(2), 'rx', 'tag','recentSelection')
end

  2 Comments

Luca Amerio on 16 Oct 2019 at 11:09
The "print x and y" was just an example. I want to be able to unserstand which data point I'm clicking on.
For example, each data point corresponds to an ID and I want to get the ID of the point I'm clicking on.
Also, just to make things more complicated, I'm working in 3D, so even "looking for the point given it's coordinates" is messy, since ButtonDownFcn would return the projected point the user is clicking and not the real point. I'd need to get the view direction, cast a ray, etc etc.
Finally, i find getting a point based on where the user clicked is somehow "dangerous" as there could be multiple points overlapping or aligned, and you don't know which one will be selected before executing the function
Adam Danz
on 16 Oct 2019 at 11:43
"each data point corresponds to an ID and I want to get the ID of the point I'm clicking on."
That's fairly easy. Below is the 3D version of my suggestion and the ButtonDownFcn returns the 3D coordinates and the index value. The hObj input is the handle to the line object and contains all of the x,y,z coordinates. The event input contains the x0,y0,z0 coordinates of the dot your clicked. To find its index you just look for those values in the object handle.
"Also, just to make things more complicated, I'm working in 3D, so even "looking for the point given it's coordinates" is messy since ButtonDownFcn would return the projected point the user is clicking and not the real point."
No, it returns the coordinates of the selected point, not the coordinates of the cursor. To convince yourself of that, plot a single coordinate with a very large markersize and click anywhere within the marker. It will return the same value. So no need to casy any arrays or to consider view direction.
hs = plot3(1,2,3,'o','MarkerSize',80,'ButtonDownFcn',@clickPointFcn);
"i find getting a point based on where the user clicked is somehow "dangerous" as there could be multiple points overlapping or aligned, "
This is a valid concern but you'd have the same problem with your context menu idea. If two points are exactly overlapping, there is no way to select just one of them. If they are slighly overlapping you can zoom or rotate the plot to maximize their separation. This is why I gave an example of adding a red x to the selected marker so that the user gets feedback on his selection. In the case of exactly overlapping points, you could make a small modification to the example code below to return all of the points selected rather than just one.
3D example with index returned
figure
x = randn(1,20);
y = randn(1,20);
z = randn(1,20);
% menu = uicontextmenu();
hs = scatter3(x,y,z);
grid on
hs.ButtonDownFcn = @clickPointFcn;
hold on
function clickPointFcn(hObj,event)
% Find the index of the chosen coordinate
allCoord = [hObj.XData(:), hObj.YData(:), hObj.ZData(:)];
[~, idx] = min(sum(abs(allCoord - event.IntersectionPoint),2));
% Do whatever you want with that....
fprintf('x = %f, y = %f, z = %f, idx = %.0f\n',[event.IntersectionPoint,idx])
% Search and remove previous 'recent selection'
delete(findall(hObj.Parent, 'Tag','recentSelection'))
% Add 'x' to mark selection
plot3(event.IntersectionPoint(1),event.IntersectionPoint(2), event.IntersectionPoint(3), 'rx', 'tag','recentSelection')

Sign in to comment.


Answer by Adam
on 15 Oct 2019 at 15:11

I use code like this to position my context menu under the cursor on an axes, though if you already have the figure handle at the ready all you need is the second line to give you your current location. In my case I just set this value as the 'Position' property of the context menu, but you can use it for whatever purpose you wish.
hFig = ancestor( hAxes, 'figure' );
hFig.CurrentPoint;
This will give the figure position, but the axes also has a 'CurrentPoint' property which I would assume will give you the current point in axes coordinates. I can't remember off the top of my head as my example does not do this and uses the figure CurrentPoint instead.

  0 Comments

Sign in to comment.