Related
Quite a simple question but just couldn't find the answer online... I want to visualise a point cloud gathered from a lidar. I can plot the individual frames but wanted to loop them to create a "animation". I know how to do it for normal plots with drawnow but can't get it working with a scatter3. If I simply call scatter3 again like I have done in the commented code then the frame that I am viewing in the scatter plot jumps around with every update (Very uncomfortable). How do i get the scatter3 plot to update to the new points without changing the UI of the scatter ie. Still be able to pan and zoom around the visualised point cloud while it loops through.
EDIT: The file is a rosbag file, I cannot attach it because it is 170MB. The problem doesn't happen when using scatter3 in a loop with a normal array seems to be something with using scatter3 to call a PointCloud2 type file using frame = readMessages(rawBag, i).
EDIT: The problem does not seem to be with the axis limits but rather with the view of the axis within the figure window. When the scatter is initialised it is viewed with the positive x to the right side, positive y out of the screen and positive z upwards, as shown in view 1. Then after a short while it jumps to the second view, where the axis have changed, positive x is now out of the screen, positive y to the right and positive z upwards (both views shown in figures). This makes it not possible to view in a loop as it is constantly switching. So basically how to update the plot without calling scatter3(pointCloudData)?
rawBag = rosbag('jackwalking.bag');
frame = readMessages(rawBag, 1);
scatter3(frame{1});
hold on
for i = 1:length(readMessages(rawBag))
disp(i)
frame = readMessages(rawBag, i);
% UPDATE the 3D Scatter %
% drawnow does not work?
% Currently using:
scatter3(frame{1})
pause(.01)
end
The trick is to not use functions such as scatter or plot in an animation, but instead modify the data in the plot that is already there. These functions always reset axes properties, which is why you see the view reset. When modifying the existing plot, the axes are not affected.
The function scatter3 (as do all plotting functions) returns a handle to the graphics object that renders the plot. In the case of scatter3, this handle has three properties of interest here: XData, YData, and ZData. You can update these properties to change the location of the points:
N = 100;
data = randn(N,3) * 40;
h = scatter3(data(:,1),data(:,2),data(:,3));
for ii = 1:500
data = data + randn(N,3);
set(h,'XData',data(:,1),'YData',data(:,2),'ZData',data(:,3));
drawnow
pause(1/5)
end
The new data can be totally different too, it doesn't even need to contain the same number of points.
But when modifying these three properties, you will see the XLim, YLim and ZLim properties of the axes change. That is, the axes will rescale to accommodate all the data. If you need to prevent this, set the axes' XLimMode, YLimMode and ZLimMode to 'manual':
set(gca,'XLimMode','manual','YLimMode','manual','ZLimMode','manual')
When manually setting the limits, the limit mode is always set to manual.
As far as I understood what you describe as "plots jumpying around", the reason for this are the automatically adjusted x,y,z limits of the scatter3 plot. You can change the XLimMode, YLimMode, ZLimMode behaviour to manual to force the axis to stay fixed. You have to provide initial axes limits, though.
% Mock data, since you haven't provided a data sample
x = randn(200,50);
y = randn(200,50);
z = randn(200,50);
% Plot first frame before loop
HS = scatter3(x(:,1), y(:,1), z(:,1));
hold on
% Provide initial axes limits (adjust to your data)
xlim([-5,5])
ylim([-5,5])
zlim([-5,5])
% Set 'LimModes' to 'manual' to prevent auto resaling of the plot
set(gca, 'XLimMode', 'manual', 'YLimMode', 'manual', 'ZLimMode', 'manual')
for i=2:len(x,2)
scatter3(x(:,i), y(:,i), z(:,i))
pause(1)
end
This yields an "animation" of plots, where you can pan and zoom into the data while continuous points are added in the loop
I am trying to bold the right side y axis for a Pareto plot in Matlab, but I can not get it to work. Does anyone have any suggestions? When I try to change the second dimension of ax, I get an error:
"Index exceeds matrix dimensions.
Error in pcaCluster (line 66)
set(ax(2),'Linewidth',2.0);"
figure()
ax=gca();
h1=pareto(ax,explained,X);
xlabel('Principal Component','fontweight','b','fontsize',20)
ylabel('Variance Explained (%)','fontweight','b','fontsize',20)
set(ax(1),'Linewidth',2.0);
set(ax(1),'fontsize',18,'fontweight','b');
%set(ax(2),'Linewidth',2.0);
%set(ax(2),'fontsize',18,'fontweight','b');
set(h1,'LineWidth',2)
Actually you need to add an output argument during the call to pareto and you will then get 2 handles (the line and the bar series) as well as 2 axes. You want to get the YTickLabel property of the 2nd axes obtained. So I suspect that in your call to pareto above you do not need to supply the ax argument.
Example:
[handlesPareto, axesPareto] = pareto(explained,X);
Now if you use this command:
RightYLabels = get(axesPareto(2),'YTickLabel')
you get the following (or something similar):
RightYLabels =
'0%'
'14%'
'29%'
'43%'
'58%'
'72%'
'87%'
'100%'
What you can do is actually to erase them altogether and replace them with text annotations, which you can customize as you like. See here for a nice demonstration.
Applied to your problem (with dummy values from the function docs), here is what you can do:
clear
clc
close all
y = [90,75,30,60,5,40,40,5];
figure
[hPareto, axesPareto] = pareto(y);
%// Get the poisition of YTicks and the YTickLabels of the right y-axis.
yticks = get(axesPareto(2),'YTick')
RightYLabels = cellstr(get(axesPareto(2),'YTickLabel'))
%// You need the xlim, i.e. the x limits of the axes. YTicklabels are displayed at the end of the axis.
xl = xlim;
%// Remove current YTickLabels to replace them.
set(axesPareto(2),'YTickLabel',[])
%// Add new labels, in bold font.
for k = 1:numel(RightYLabels)
BoldLabels(k) = text(xl(2)+.1,yticks(k),RightYLabels(k),'FontWeight','bold','FontSize',18);
end
xlabel('Principal Component','fontweight','b','fontsize',20)
ylabel('Variance Explained (%)','fontweight','b','fontsize',20)
which gives this:
You can of course customize everything you want like this.
That is because ax is a handle to the (first/left) axes object. It is a single value and with ax(1) you got lucky, its ax again, but ax(2) is simply not valid.
I suggest to read the docs about how to get the second axis. Another good idea always is to open the plot in the plot browser, click whatever object you want so it is selected and then get its handle by typing gco (get current object) in the command window. You can then use it with set(gco, ...).
Context:
I have a (programmatic) GUI which contains several axes objects inside some uipanel parenting structure. Some of these axes have legend objects associated, some don't.
I want to include a button in my GUI which copies the currently visible plot into a new figure including its legend if it has one.
I know how to get the handles to the currently visible uipanel and all axes objects inside it. I also know how to tell the axes apart from the legends.
Question:
How can I match the legends to the axes?
For example, in one case my GUI shows 2 axes with some plots, each of which has its own legend. When I click the 'export' button, I want 2 new figures to be created, each containing one axes with its corresponding legend.
What I'm currently able to do is
put everything in one figure (they overlap in that case because their positions in the original uipanels are the same),
put each axes and each legend into their own respective figures,
put all axes in one and all legends in another figure and
put all axes in their own figure with all legends within the same panel.
split it up by panel, that is, put all subplots into the same figure and each group of plots in their own figure.
Problem:
The problem is, I don't have the handles to either of those objects. I only have the handles to the uipanel objects. The graphics inside the panels are built by another function which contains all sorts of tricky stuff, but doesn't return handles. Also the parenting structure of said panels makes it rather hard to do this with tricks like get(handles.panels{1},'Children') because it will work in some, but not all cases.
I thought about simply exporting the panels (and have actually a working version which does this), but this has several problems, mainly related to figure tools and resizing. I want to get rid of the panels when I use the "Export" button.
Code Snippet / Example:
The following code snippet will create an example GUI with access to all handles I have access to in my complete GUI. Clicking the buttons will show the different versions I got to "work". What I want is one figure for each axes including its legend, if it has one. the 4th version (same parent) comes close, but breaks if it encounters subplots, the 5th version (by panel) simply puts entire subplot groups into one window (in which case, at least, they don't overlap). Copy the code into a new .mfile to try it.
function test
figure(1)
clf
t=(0:0.1:10)'; %'// dummy comment
p2 = uipanel('Visible','off','Position',[0 0 1 1]);
p1 = uipanel('position',[0 0 1 1]);
p11 = uipanel('Parent',p1,'Position',[0 0 0.5 0.9]);
p12 = uipanel('Parent',p1,'Position',[0.5 0 0.5 0.9]);
uicontrol('Style','push','String','all in one','Units','norm',...
'Position',[0.05 0.91 0.14 0.06],'Callback',#export1);
uicontrol('Style','push','String','all in own','Units','norm',...
'Position',[0.24 0.91 0.14 0.06],'Callback',#export2);
uicontrol('Style','push','String','by type','Units','norm',...
'Position',[0.43 0.91 0.14 0.06],'Callback',#export3);
uicontrol('Style','push','String','same parent','Units','norm',...
'Position',[0.62 0.91 0.14 0.06],'Callback',#export4);
uicontrol('Style','push','String','same panel','Units','norm',...
'Position',[0.81 0.91 0.14 0.06],'Callback',#export5);
subplot(1,1,1,'Parent',p11)
plot(t,[sin(t) cos(t)])
legend('Sine','Cosine')
subplot(2,1,1,'Parent',p12)
plot(t,[polyval([0.05 -1 2],t) exp(-t) abs(t-3)])
subplot(2,1,2,'Parent',p12)
plot(t,erf(t))
legend('Error function')
function export1(~,~)
current = findobj('Type','uipanel','Parent',1,'Visible','on');
visible_axes = findobj(current,'Type','axes');
copyobj(visible_axes,figure);
end
function export2(~,~)
current = findobj('Type','uipanel','Parent',1,'Visible','on');
visible_axes = findobj(current,'Type','axes');
for i=1:length(visible_axes)
copyobj(visible_axes(i),figure);
end
end
function export3(~,~)
current = findobj('Type','uipanel','Parent',1,'Visible','on');
visible_axes = findobj(current,'Type','axes','Tag','');
visible_legends = findobj(current,'Tag','legend');
copyobj(visible_axes,figure);
copyobj(visible_legends,figure);
end
function export4(~,~)
current = findobj('Type','uipanel','Parent',1,'Visible','on');
visible_axes = findobj(current,'Type','axes','Tag','');
visible_legends = findobj(current,'Tag','legend');
for i=1:length(visible_axes)
par = get(visible_axes(i),'Parent');
same = findobj(visible_legends,'Parent',par);
h=figure;
copyobj(visible_axes(i),h)
copyobj(same,h)
end
end
function export5(~,~)
current = findobj('Type','uipanel','Parent',1,'Visible','on');
visible_axes = findobj(current,'Type','axes');
parents = cell2mat(get(visible_axes,'Parent'));
uparents = unique(parents);
for i=1:length(uparents)
copyobj(visible_axes(parents==uparents(i)),figure)
end
end
end
In a figure, graphical objects are organized hierarchically and can all be handled individually. For instance, axes is a child of a figure, plot is a child of an axes, and legends are build as axes.
The following example plots 2 lines (red and blue, with legends), then mixes plots and legends using copyobj.
figure;
subplot(1,2,1)
hp1 = plot(1:10,'r')
hl1 = legend('red')
subplot(1,2,2)
hp2 = plot(1:10,'b')
hl2 = legend('blue')
hf = figure;
hax = axes;
copyobj(hp1, hax); %copy plot to axes
copyobj(hl2, hf); %copy legend to figure
Not tested with a GUI though.
I think the simpler solution is to save the axes of the figure you're about to save as a fig file.
h = figure(1);
x = linspace(1,100);
y = 2*x;
ax = findall(h,'type','axes');
plot(x,y);
save('youraxes', 'ax');
hgsave(h, 'yourfig.fig');
I'm using Matlab R2012a, alternatively in R2013a or b the function to save the fig is now savefig.
Once you have obtained the axes handle you can find the corresponding legend handle using
legend_handle = legend(axes_handle)
I have formed a 2D matrix of 180X360. In fact, it is a LatXLong grid of 1°X1°. Each grid point has a value calculated according to my algorithm.
If I want to plot this LatXLong grid using any contour function, it is easy to do.
Now, what I need to do is to make this grid a clickable/ interactive contour plot in a way that when the user clicks anywhere on my grid plot, he gets an onscreen information or a further plot to be displayed specifically related to that grid point.
In short, I want to make a grid/contour plot in which all grid points are hyperlinks and linked to further background information.
check this answer:
if you don't want to have the variable as title of the plot, you can modify the code as:
function mouseExample()
h = plot(rand(10,1), 'o-');
set(h, 'ButtonDownFcn',#buttonDownCallback)
function out = buttonDownCallback(o,e)
p = get(gca,'CurrentPoint');
out = p(1,1:2);
% title( sprintf('(%g,%g)',p) ) % --> no need this line anymore
end
end
the information is saved in the P variable that you can use later.
To get started, look into ginput and text. ginput will let you click on points in your plot and return the coordinates to some function that can generate information to be displayed in the current plot using text of by opening another figure.
You can use ginput in a loop to display multiple data points as you go:
for t = 1:10
[x,y] = ginput(1);
text(x,y,'some info');
end
I don't know of a way to remove the gird lines. NKN's solution might do that for you.
In Matlab 2011b, I have a multidimensional matrix which is to be initially presented as a 2D plot of 2 of its dimensions. I wish to make the markers clickable with the left mouse button. Clicking on a marker draws a new figure of other dimensions sliced by the clicked value.
This question is related to Matlab: Plot points and make them clickable to display informations about it but I want to run a script, not just pop up data about the clicked point.
Googling hinted that ButtonDownFcn could be used, but examples I found require manually plotting each point and attaching a handler, like so:
hp = plot(x(1), y(1), 'o');
set(hp, 'buttondownfcn', 'disp(1)');
As there are many markers in the main graph, is it possible to just attach a handler to the entire curve and call the subgraph-plotting function with the index (preferable) or coordinates of the marker clicked?
this is an idea of what you need, and should help get you started if I understand your requirements.
In this case, when you select a curve, it will draw it in the bottom subplot preserving the color.
function main
subplot(211)
h = plot (peaks);
set (h,'buttondownfcn', #hitme)
end
function hitme(gcbo,evendata)
subplot (212)
hold on;
col = get (gcbo,'Color');
h2 = plot (get (gcbo,'XData'),get (gcbo,'YData'));
set (h2,'Color', col)
pt = get (gca, 'CurrentPoint');
disp (pt);
end
You can explore your options for get by simply writing get(gcbo) in the hitme function.