You are now following this question
- You will see updates in your followed content feed.
- You may receive emails, depending on your communication preferences.
How to enable a figure so that if I click on a point and it will show the value?
128 views (last 30 days)
Show older comments
If I have a three column data of x, y, and z, how do I make a plot of x vs y, so that if I click on a point, the value z will show up on the figure next to the point, or even better can be extracted for other calculations.
Thanks!
Accepted Answer
Adam Danz
on 12 Aug 2019
Edited: Adam Danz
on 16 Aug 2019
Active "data tips" and then you can click on any plotted coordinate to return the (x,y,z) values. From r2018b to present, the toolbar becomes visible when you hover over the axes. Prior to r2018b, the toolbar that contains the data tip icon is at the top of the figure. More info on that (link).
" ...or even better can be extracted for other calculations"
To return the coordinate selected by a mouse click, you can assign a ButtonDownFcn to the plotted object handle. Within the ButtonDownFcn you can determine which of your coordinates were closest to your mouse-click and then return that coordinate. Here's a complete demo that returns the entire (x,y,z) coordinate you selected. If you just want z, run this function and then extract z from the first output.
% Run this independently. A random 3D array of dots will be drawn. Click on
% any marker to invoke the showZValueFcn function. See comments for more detail
clf()
axh = axes();
x = rand(1,20);
y = rand(1,20);
z = rand(1,20);
h = plot3(axh, x, y, z, 'ko');
xlabel('x axis')
ylabel('y axis')
zlabel('z axis')
% view(0,90) % to view as 2D
grid on
h.ButtonDownFcn = @showZValueFcn;
% axh.ButtonDownFcn = {@showZValueFcn, x, y, z}; % old version of answer
function [coordinateSelected, minIdx] = showZValueFcn(hObj, event)
% FIND NEAREST (X,Y,Z) COORDINATE TO MOUSE CLICK
% Inputs:
% hObj (unused) the axes
% event: info about mouse click
% OUTPUT
% coordinateSelected: the (x,y,z) coordinate you selected
% minIDx: The index of your inputs that match coordinateSelected.
x = hObj.XData;
y = hObj.YData;
z = hObj.ZData;
pt = event.IntersectionPoint; % The (x0,y0,z0) coordinate you just selected
coordinates = [x(:),y(:),z(:)]; % matrix of your input coordinates
dist = pdist2(pt,coordinates); %distance between your selection and all points
[~, minIdx] = min(dist); % index of minimum distance to points
coordinateSelected = coordinates(minIdx,:); %the selected coordinate
% from here you can do anything you want with the output. This demo
% just displays it in the command window.
fprintf('[x,y,z] = [%.5f, %.5f, %.5f]\n', coordinateSelected)
end % <--- optional if this is embedded into a function
*An older version of this answer assigned the ButtonDown function to the axes instead of the line object.
29 Comments
Leon
on 12 Aug 2019
Many thanks for the reply!
Is it possible, I can read the z value when the plot is only about x and y?
My z value is really the station number, it's possible to be a string, instead of a numerical value.
Adam Danz
on 12 Aug 2019
Edited: Adam Danz
on 12 Aug 2019
I just updated my answer with a demo. When you run the demo, click anywhere on the axes to see the output. If you 'near' a point it works better than clicking directly 'on' a point since matlab interprets that as selecting the object rather than calling the ButtonDownFcn.
Leon
on 12 Aug 2019
Thank you very much! Would you please help me with the below two questions?
(a) Can we make it a plot of x y and still get the z value?
(b) When I tried the example you shared (I just copy te code into a Matlab editor and execute it), the figure is not clickable.
Adam Danz
on 12 Aug 2019
(a) Can we make it a plot of x y and still get the z value?
Sure! Take a few minutes to read the code and the comments in the code. The x,y,z input to the function are the coordinates of each point whether they are plotted or not. When you click on the axes, it will search for the closest point in 3D space. If you just want to search in 2D space you'll need to alter the callback function a tiny bit so that pdist() only receives x and y coordinates.
(b) When I tried the example you shared (I just copy te code into a Matlab editor and execute it), the figure is not clickable
Where there any errors? Make sure you're clicking near the markers, not exactly on them. If the problem persists, please let me know what version of matlab you're working with.
Leon
on 12 Aug 2019
Hi Adam,
Sure, I'll let you know how it works perhaps sometime tomorrow. Had to put out some fire at the moment, but will definitely look into the details later.
Many thanks for your help!
Leon
Leon
on 13 Aug 2019
Good morning, Adam!
In order to make it a 2-D plot, should I use the below code?
% Run this independently. A random 3D array of dots will be drawn. Click anywhere
% in the axes to invoke the showZValueFcn function. See comments for more detail
clf()
axh = axes();
x = rand(1,20);
y = rand(1,20);
z = rand(1,20);
plot(axh, x, y, 'ko');
grid on
axh.ButtonDownFcn = {@showZValueFcn, x, y, z};
function [coordinateSelected, minIdx] = showZValueFcn(hObj, event, x, y, z)
% FIND NEAREST (X,Y,Z) COORDINATE TO MOUSE CLICK
% Inputs:
% hObj (unused) the axes
% event: info about mouse click
% x,y,z: vectors of coordinates to consider
% OUTPUT
% coordinateSelected: the (x,y,z) coordinate you selected
% minIDx: The index of your inputs that match coordinateSelected.
pt = event.IntersectionPoint; % The (x0,y0) coordinate you just selected
coordinates = [x(:),y(:)]; % matrix of your input coordinates
dist = pdist2(pt,coordinates); %distance between your selection and all points
[~, minIdx] = min(dist); % index of minimum distance to points
coordinateSelected = coordinates(minIdx,:); %the selected coordinate
% from here you can do anything you want with the output. This demo
% just displays it in the command window.
fprintf('[x,y,z] = [%.3f, %.3f, %.3f]\n', coordinateSelected)
end % <--- optional if this is embedded into a function
Unfortunately, I got this error message:
Error using pdist2 (line 149)
X and Y must have the same number of columns.
Error in Untitled>showZValueFcn (line 28)
dist = pdist2(pt,coordinates); %distance
between your selection and all points
Error while evaluating Axes ButtonDownFcn.
How do I alter the below sentence to make it a 2-D function of x and y?
pt = event.IntersectionPoint;
When it was working with the 3-D plot, I was only able to click and get a value into the command window by clicking somewhere in the axles instead of on the points. I have plots with extremely dense points, so I have to be able to click on exactly where the point is.
Many thanks.
Adam Danz
on 13 Aug 2019
Changes to make
dist = pdist2(pt,coordinates); %bad
dist = pdist2(pt(1:2),coordinates); %good
fprintf('[x,y,z] = [%.3f, %.3f, %.3f]\n', coordinateSelected) %bad
fprintf('[x,y,z] = [%.3f, %.3f, %.3f]\n', coordinateSelected,z(minIdx)) %good
Adam Danz
on 13 Aug 2019
Edited: Adam Danz
on 13 Aug 2019
" I have plots with extremely dense points, so I have to be able to click on exactly where the point is."
Here's how to fix this. Assign the buttondown function to the plot object rather than the axes. It's a tiny change.
h = plot(axh, x, y, 'ko');
h.ButtonDownFcn = {@showZValueFcn, x, y, z};
That's the only changes you have to make. Now the funciton will respond when you select a marker rather than the axes.
This should have been the solution from the start (my bad!). I'll update my answer to reflect this improvement.
[update]
After you make this change, you can get the (x,y,z) coordinates from the first input in the ButtonDown function instead of passing them into the function. My updated answer will reflect this.
Leon
on 13 Aug 2019
Hooray, everything is working exactly what I hoped for!
Thank you sooooooooooooooooooo much!
Leon
Leon
on 15 Aug 2019
Hi Adam,
Thanks again for your big help!
I just find one area that can be improved. For example, if my x-axis is pH and has a range of 8.05 to 8.12, but my y-axis is depth with a range of 0-5000 meters. Then the program will become dysfunctional, because the x range and y range vary so much.
Can you write a program that will plot x and y with their range weighted values, so that the range of y divided by the range of x is always close to 1? That way, the program will work flawless in all situations. I believe such a program will benefit a lot of people.
Thanks for your consideration,
Leon
Adam Danz
on 16 Aug 2019
"if my x-axis is pH and has a range of 8.05 to 8.12, but my y-axis is depth with a range of 0-5000 meters. Then the program will become dysfunctional, because the x range and y range vary so much. "
What program? The code from my example works well under any axes ranges. Do you mean something from your code isn't working because of the axis ranges? What is not working?
"Can you write a program that will plot x and y with their range weighted values, so that the range of y divided by the range of x is always close to 1?"
It sounds like you want to normalize the data which is pretty easy to do but the new problem you're facing isn't clear to me.
Leon
on 16 Aug 2019
When I said 'program', I was referring to the example you wrote. I save it as a function, showZValueFcn.m.
It works flawlessly with most plots, except the pH example I mentioned. For example, I click on a point close to 7.6, it would show me an example like 7.9, which is far off. I tried to divide the depth values by 5000, then it works properly again.
Adam Danz
on 16 Aug 2019
Edited: Adam Danz
on 16 Aug 2019
I just tested the code from my answer except I used (x,y,z) values scaled to your description.
x = rand(1,20) * .07 + 8.05; % Values between 8.05 and 8.12
y = rand(1,20) * 5000; % Values between 0 and 5000
z = rand(1,20);
I varified the outputs in two different ways and they are accurate. You can do this, too.
- I clicked on 3 dots and 3 coordinates were returned to the command window. I used those 3 coordinates to add 3 red X's. You can see 2 on the figure below. The 3rd one is covered up by the data tip. If the coordinates were inaccurate, they would not have matched the 3 points I clicked on.
- I turned on data tips and clicked on those same 3 points to see their coordinates within the plot. One of the data tips is shows below. Those numbers matched my outputs perfectly.
What changes were made in your version?
Leon
on 16 Aug 2019
As to the changes to the program (or function), I only modified the output format. You can just assume no change at all.
Glad to know the program works in your example. Below are my arguments :-) If one variable is many orders of magnitude greater than the other, the distance will be skewed towards being determined by one parameter (the greater one) solely, am I right?
First of all, would you mind to try a 2-D plot without Z as well? An additional 3rd parameter could help it to constrain better actually.
Secondly, please try to random 2000 data points instead of 20. In my case, when the data points are just a few, e.g., only showing one station's data, it works without any problem at all, because the depth variable will do a very good job of separating them out, but when I plot 200 stations together and trying to identify outliers, it is messed up, mainly in areas with many measurements at essentially the same depth. I would click on one point with pH values of ~7.61, but the value I get from the program will be 8.01, something like that. I just confirmed it again on my program.
Will it be easy to modify the pdist2 calculation? All we need to do is to apply a factor that is calculated as max(x)-min(x) divided by max(y)-min(y), then it will be perfect.
Adam Danz
on 16 Aug 2019
Edited: Adam Danz
on 16 Aug 2019
"If one variable is many orders of magnitude greater than the other, the distance will be skewed towards being determined by one parameter (the greater one) solely, am I right? "
Sure, but when you click on a single point, it will still compute it's correct distance to all other points correctly. The only thing I can think of that might cause a problem is the case where one axis range is very very small such that the distance calculation may cause roundoff error. But the axis ranges you described and the ones I tested are far from that problem.
I made adjustments to the inputs as you described.
x = rand(1,2000) * .07 + 8.05; % Values between 8.05 and 8.12
y = rand(1,2000) * 5000;
z = zeros(size(x));
Importantly, I increased the precision of the outputs printed to the command window by adding decimal places (important).
fprintf('[x,y,z] = [%.7f, %.7f, %.7f]\n', coordinateSelected)
% ^ ^
Lastly, my z values are all 0 but instead of plotting in 2D I'm plotting in 3D and then changing the view so that I only see the x and y axes as if it were 2D. This won't make a difference in the calculations but I think it's a better implementation than cutting out the Z values.
h = plot3(. . .);
xlabel('x axis')
ylabel('y axis')
zlabel('z axis')
view(0,90)
Testing
After producing the figure (seen below) I clicked on several points and then plotted those outputs using red x's to ensure that they agree with the points I clicked. As you can see, it's functioning perfectly.
% Example
hold on
plot3(8.0574349, 1147.6249844, 0.0000000, 'rx', 'linewidth', 2)
Something is either wrong with your implementation of this code or something is strange about your inputs. Below I have provided a copy of the exact code I used to produce this figure and test it. If the problem persists, please attach your code and the inputs so I can easily run it and reproduce the problem.
Copy of code used in this test
% Run this independently. A random 3D array of dots will be drawn. Click on
% any marker to invoke the showZValueFcn function. See comments for more detail
clf()
axh = axes();
x = rand(1,2000) * .07 + 8.05; % Values between 8.05 and 8.12
y = rand(1,2000) * 5000;
z = zeros(size(x));
h = plot3(axh, x, y, z, 'ko');
xlabel('x axis')
ylabel('y axis')
zlabel('z axis')
view(0,90)
grid on
h.ButtonDownFcn = @showZValueFcn;
% axh.ButtonDownFcn = {@showZValueFcn, x, y, z}; % old version of answer
function [coordinateSelected, minIdx] = showZValueFcn(hObj, event)
% FIND NEAREST (X,Y,Z) COORDINATE TO MOUSE CLICK
% Inputs:
% hObj (unused) the axes
% event: info about mouse click
% OUTPUT
% coordinateSelected: the (x,y,z) coordinate you selected
% minIDx: The index of your inputs that match coordinateSelected.
x = hObj.XData;
y = hObj.YData;
z = hObj.ZData;
pt = event.IntersectionPoint; % The (x0,y0,z0) coordinate you just selected
coordinates = [x(:),y(:),z(:)]; % matrix of your input coordinates
dist = pdist2(pt,coordinates); %distance between your selection and all points
[~, minIdx] = min(dist); % index of minimum distance to points
coordinateSelected = coordinates(minIdx,:); %the selected coordinate
% from here you can do anything you want with the output. This demo
% just displays it in the command window.
fprintf('[x,y,z] = [%.7f, %.7f, %.7f]\n', coordinateSelected)
end % <--- optional if this is embedded into a function
Leon
on 16 Aug 2019
Edited: Leon
on 16 Aug 2019
Hi Adam,
Many thanks for your time in helping me out!
Unfortunately, I have been getting issues consistently for this plot only (just tested it again), because of the suspected magnitude issue I described.
Attached is the data (A.mat, see above):
Column 1: Depth,
Column 2: Variable 1
Column 3: StationNo + DepthLevel/1000
Here is my plotting function (it is within app-designer):
set(app.Plot3, 'Ydir', 'reverse', 'xAxisLocation', 'top', 'tickDir', 'out');
xlim(app.Plot3, [7.5 8.2]);
ylim(app.Plot3, [0 1000]);
xlabel(app.Plot3, 'Variable 1');
ylabel(app.Plot3, 'Depth (m)');
title(app.Plot3, '');
box(app.Plot3, 'on');
grid(app.Plot3, 'on');
cla(app.Plot3);
hold(app.Plot3, 'on');
if isnan(A1) == 0;
h3 = plot(app.Plot3, A1(:,2), A1(:,1) , 'mx', 'markersize',7);
h3.ButtonDownFcn = {@showZValueFcn, app, A1(:,2), A1(:,1) , A1(:,3)};
x1 = min(A1(:,2))-0.1;
x2 = max(A1(:,2))+0.1;
xlim(app.Plot3, [x1, x2]);
x3 = min(A1(:,1))-0.1*(max(A1(:,1)) - min(A1(:,1)));
x4 = max(A1(:,1))+0.1*(max(A1(:,1)) - min(A1(:,1)));
if x4>x3
ylim(app.Plot3, [x3 , x4 ]);
end
end
app.Plot3.Toolbar.Visible = 'off';
Here is the modified function created by you:
function [coordinateSelected, minIdx] = showZValueFcn(hObj, event, app, x, y, z)
% FIND NEAREST (X,Y,Z) COORDINATE TO MOUSE CLICK
% Inputs:
% hObj (unused) the axes
% event: info about mouse click
% x,y,z: vectors of coordinates to consider
% OUTPUT
% coordinateSelected: the (x,y,z) coordinate you selected
% minIDx: The index of your inputs that match coordinateSelected.
pt = event.IntersectionPoint; % The (x0,y0) coordinate you just selected
coordinates = [x(:),y(:)]; % matrix of your input coordinates
dist = pdist2(pt(1:2),coordinates); %distance between your selection and all points
[~, minIdx] = min(dist); % index of minimum distance to points
coordinateSelected = coordinates(minIdx,:); %the selected coordinate
% from here you can do anything you want with the output. This demo
% just displays it in the command window.
%fprintf('[x,y,z] = [%.3f, %.3f, %.3f]\n', coordinateSelected, z(minIdx));
app.Cursor.Value = ['Sta:', num2str(floor(z(minIdx))), '; Level:', ...
num2str((z(minIdx)-floor(z(minIdx)))*1000), '; x:', num2str(coordinateSelected(1)),...
'; y:', num2str(coordinateSelected(2))];
% num2str(coordinateSelected);
end % <--- optional if this is embedded into a function
After the plot, please click on the data points on the top left corner of the figure. Their actual values are between 7.6 and 7.7, but the program is telling me values above 7.9 or even 8.0.
Leon
Leon
on 16 Aug 2019
Weird enough, after I plot it as a separate figure, the issue does not come back.
Anyway, I think moving forward, the easiest way for me to fix the issue is to dividie the depth by 1000 across board, and use a depth (km), instead of depth (m).
Many thanks for your big help!
Leon
on 16 Aug 2019
Sure, attached is a zip file containing a folder with all the needed files to run the app.
Just open the app, click "run" and then click the "plot staiton" button. The values will show up on a text bar to the middle right side of the interface.
Adam Danz
on 16 Aug 2019
I transferred the code I suggested in my answer to your app and it works fine. One problem might be that your axes are so small that when you think you're clicking on a certain coordinate, you might be actually clicking on another coordinate very close by. I recommend making the axes much larger.
Attached is your app file. All necessary functions are now within that file so there's no need to an additional m file.
I made several changes and I included a comment with " *AD " to show the changes. Here are some of the important changes.
- There is a new nested function "returnSelectedCoordinate()" within the button callback function. This replaces the m file you attached.
- The plot is now a 3D plot but it only shows the (x,y) axes so it appears as a 2D plot. This has the advantage of keeping your data together rather than separating the z coordinates. This was also implemented in my answer.
- The returnSelectedCoordinate() function extracts the (x,y,z) coordinates from the axes so you no longer have to send those coordinates into the function. This is much cleaner and safer. This was also implemented in my answer.
- The returnSelectedCoordinate() function not only prints the (x,y,z) coordinates to the command window but, importantly, it also draws a blue circle around the (x,y,z) output coordinates! It uses the output coordinates and the blue circle correctly is drawn around the point selected. This confirms it's working properly. You can comment that out later.
I put way more time into this than I had planned. Please make sure you understand all of the changes and see my comments within the code.
Leon
on 16 Aug 2019
Thank you so much, Adam! I love the new circle feature so much!
Did you try to click on the top left two data points? When I click on them, a circle to the right of it, i.e., in the middle of the figure, very far away from where I click, shows up. That clearly demos where I thought was wrong.
Leon
on 16 Aug 2019
OK! That concludes my questions! I really appreciate your tremendous help very much!
I'm sure the program you helped me create will benefit many future users as well.
Diptangshu Paul
on 17 Oct 2024
Dear Altman, this is a very simple code, yet a beautiful solution to a big problem. However for my purpose, I need to store these [coordinateSelected, minIdx] into two variables. How can it be done?
More Answers (0)
See Also
Categories
Find more on Annotations in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!An Error Occurred
Unable to complete the action because of changes made to the page. Reload the page to see its updated state.
Select a Web Site
Choose a web site to get translated content where available and see local events and offers. Based on your location, we recommend that you select: .
You can also select a web site from the following list
How to Get Best Site Performance
Select the China site (in Chinese or English) for best site performance. Other MathWorks country sites are not optimized for visits from your location.
Americas
- América Latina (Español)
- Canada (English)
- United States (English)
Europe
- Belgium (English)
- Denmark (English)
- Deutschland (Deutsch)
- España (Español)
- Finland (English)
- France (Français)
- Ireland (English)
- Italia (Italiano)
- Luxembourg (English)
- Netherlands (English)
- Norway (English)
- Österreich (Deutsch)
- Portugal (English)
- Sweden (English)
- Switzerland
- United Kingdom(English)
Asia Pacific
- Australia (English)
- India (English)
- New Zealand (English)
- 中国
- 日本Japanese (日本語)
- 한국Korean (한국어)