I am confused on the best way to implement plotting 3D points live in Matlab. One idea I had is to keep updating an array of x,y,z values and then plot that, but I want it to plot live, as I'm using a LiDAR scanner for a project and want to show a 3D map being created. This is the example I've been trying to work off of:
x = rand(10,1) ; y = rand(10,1) ;
h = scatter(x,y,'Or') ;
for i = 1:100
x = rand(10,1)
y = rand(10,1) ;
set(h,'XData',x,'YData',y) ;
drawnow
pause(0.1)
end
I have one problem with this, and it persists when I move to adding a Z value as well. The plotting works, even in 3D, but then every iteration of the loop removes all of the previous points and then replaces them with the new points. As I'm making a map, I need the points to be maintained with maximum efficiency.
So, I'm wondering if there is a way to plot in real time without having to plot the entire thing over and over again, instead just "appending" the points onto the 3D plane. If there isn't, I would want to know how to fix my code so that points will not be replaced.
Instead of set(h,'XData',x,'YData',y) ;, hold on with new plot could enable you append something onto the plane, so the code following might work:
x = rand(10,1) ; y = rand(10,1) ;
h = scatter(x,y,'Or') ;
hold on % allow appending
for i = 1:100
x = rand(10,1)
y = rand(10,1) ;
h = scatter(x,y,'Or') ; %draw new points
% set(h,'XData',x,'YData',y) ;
drawnow
pause(0.1)
end
But keeping all datas, such as saving new statistics into a new column which is just behind the old ones, is highly recommended:
[x,y] = deal(zeros(10,100)); %pre-allocate
figure(1); hold on
for ii = 1:100
x(:,ii)=rand(10,1); %save new x
y(:,ii)=rand(10,1); %save new y
scatter(x(:,ii),y(:,ii),'Or') ; %plot them
drawnow
pause(0.1)
end
Moreover, hold on has downside sometimes when we need further editing the figure by dealing with the handle, as every ten points would have unique handle. To keep them all in one, I think a minor change to your code might be a better solution if we don't have too much data to plot(It demands re-allowcate vectors in RAM, 'clean' the figure, and replot all the points, including those old one, again and again).
x = rand(10,1) ; y = rand(10,1) ;
h = scatter(x,y,'Or') ;
for i = 1:100
x(end+1:end+10) = rand(10,1) ; % append x
y(end+1:end+10) = rand(10,1) ; % append y
set(h,'XData',x,'YData',y) ; % draw new figure
drawnow
pause(0.1)
end
Related
I'm trying to plot a simple graph using for loop as shown below
x=linspace(0,2*pi,100);
for i=1:numel(x)
y=sin(x(i));
plot(x(i),y)
hold on
end
However, nothing appear on my figure. Why is that?
Why this happens...
With plot(x(i),y) you are plotting 100 single points (one in each iteration) and they are not shown by default. Therefore the plot looks empty.
Solution 1: Vectorized calculation and direct plot
I assume you meant to draw a continuous line. In that case no for-loop is needed because you can calculate and plot vectors directly in MATLAB. So the following code does probably what you want:
x = linspace(0,2*pi,100);
y = sin(x);
plot(x,y);
Note that y is a vector as well as x and that y(n) equals to sin(x(n)) for all n. If you want to plot the points itself, use LineSpec-syntax when calling plot like this1:
plot(x,y,'*');
1) Other types of points are possible as well, see the above linked documentation.
Solution 2: Calculate values within for-loop and plot afterwards
If you want to calculate the values within a for-loop and plot it afterwards: Pre-allocate the needed variable (in this case y), calculate the values within the for-loop and finally plot it with one single command after the calculation.
x = linspace(0,2*pi,100);
y = zeros(size(x));
for i = 1:numel(x)
y(i) = sin(x(i));
end
plot(x,y);
Solution 3: Dynamically update plot while calculating
In case you insist on plotting within each iteration, the previous code from Solution 2 can be expanded as follows: Create a figure, add an 'empty' plot to it and store its handle. Within the for-loop calculate the values and add them to the y-vector as shown above. As a last step you can update the plot by changing its XData and YData properties and calling drawnow. Note that calling plot every time within the for-loop is unnecessarily expensive and I don't recommend it.
% create figure and plot
figure;
ph = plot(0,0);
ax = gca;
set(ax,'XLim',[0,2*pi]);
set(ax,'YLim',[-1,1]);
% calculate and update plot
x = linspace(0,2*pi,100);
y = zeros(size(x));
for i = 1:numel(x)
y(i) = sin(x(i));
set(ph,'XData',x(1:i));
set(ph,'YData',y(1:i));
drawnow;
end
Simple approach
If you want to draw a curve as you add data, try the following:
x = linspace(0,2 * pi, 100);
y = zeros(size(x));
for i=1:numel(x)
y(i) = sin(x(i));
plot(x(1:i), y(1:i), 'color', 'r')
drawnow();
end
Be aware that the plot automatically tries to set x and y limits (curve is scaled to the plot window), to prevent that you have to manually set the x- and y-limits with xlimand ylim.
As Matt wrote in his answer, calling plot in each iteration is quite expensive (i.e. time consuming). Therefore I suggest using datasources:
Update graph using data sources
% Create a panel and axes object
h_panel = uipanel;
h_axes = axes( 'Parent', h_panel);
% Create data sources
x = linspace(0,2 * pi, 100);
y = zeros(size(x));
% Create graph object, in this case stairs
% and bind the variables x and y as its data sources
h_stairs = stairs(h_axes, x, y, 'XDataSource', 'x', 'YDataSource', 'y');
for i=1:size(x)
y(i) = sin(x(i));
% Update the data of the stairs graph
refreshdata(h_stairs);
drawnow();
end
The call to drawnow isn't neccessary in each iteration, it is only used to update the visuals, so you can see the changes directly.
I want to use a for loop to plot a function. The code is
y = 0;
for k = 0:0.1:2
y = y + k;
plot(k, y);
hold on;
end
However, by running the code, the plot window is empty! How can I fix that in order to see a line on a 2D area?
Matlab automatically plots the linear interpolation between the points that are given to the plot function. Thus as you only give a single point, no interpolation can happen.
What you can do is to save the old y value and the old x value to ask Matlab to plot a linear interpolation between these two points. e.g.
yold = 0; %Init
kold=0; %Init
for k = 0:0.1:2
y = yold + k; %New y value
plot([kold,k], [yold,y]); %Plot a linear interpolation
kold = k; %Save the new values as old
yold = y; %Same
hold on;
end
EDIT/NOTE:
When adding new plots to the same window, MATLAB automatically changes color, thus the above will give you a rainbow, which is nice, but in case you want to keep it professional, you can add a color to the plot command, e.g.
plot([kold,k], [yold,y],'blue');
I'm trying to create an animated plot but my code is very slow, perhaps the method I'm using is too naive. In the below example, I have 4 subplots each with 3 lines, which I update in a 'time' loop.
clc;clear;close all;
state = {'$x-Position$','$x-Velocity$','$y-Position$','$y-Velocity$'};
ylabels = {'$x$','$\dot{x}$','$y$','$\dot{y}$'};
options1 = {'interpreter','latex'};
options2 = {'interpreter','latex','fontsize',20};
maxT = 300;
for pp = 1:4
hh1(pp)=subplot(2,2,pp);
xlabel('$t$',options2{:});
ylabel(ylabels{pp},options2{:});
title(state{pp},options1{:})
xlim([0 maxT])
hold on
end
x = randn(4,300);
z = randn(4,300);
x_est = randn(4,300);
for k = 2:maxT
for p = 1:4
plot(hh1(p),k-1:k,x(p,k-1:k),'b','linewidth',2)
plot(hh1(p),k-1:k,z(p,k-1:k),'m')
plot(hh1(p),k-1:k,x_est(p,k-1:k),':k','linewidth',2)
end
drawnow;
end
As can be seen from the profiler output, the drawnow is killing the time. Is there any way I can be more efficient in creating this animation?
Because you want an animation, there is no alternative to using drawnow to update the frame. However, it's not drawnow in particular which is slowing you down - the profiler can be misleading... drawnow simply updates all of the graphics changes since the last re-draw, which in your case is a dozen new plots!
You'll find that hold is pretty slowing. For instance if you're wiser about your holding, remove the existing hold on and only hold when actually plotting
% ... above code the same but without 'hold on'
for p = 1:4
hold(hh1(p), 'on');
% plots
hold(hh1(p), 'off');
end
This saves ~10% time on my PC (12.3sec down to 11.3sec).
The real speed up comes from removing hold entirely, along with all of the individual plot calls! This method also doesn't touch the line formatting which will help with speed. See a previous question about updating plot data here.
Simply update the plot data instead of adding plots. This gives me a speedup of ~68% (12.3sec down to 4.0sec).
% ... your same setup
% Initialise plot data
x = randn(4,300);
z = randn(4,300);
x_est = randn(4,300);
plts = cell(4,3);
hh1 = cell(4,1);
% Loop over subplots and initialise plot lines
for p = 1:4
hh1{p}=subplot(2,2,p);
xlabel('$t$',options2{:});
ylabel(ylabels{p},options2{:});
title(state{p},options1{:})
xlim([0 maxT])
% Hold on to make 3 plots. Create initial points and set line styles.
% Store the plots in a cell array for later reference.
hold on
plts{p,1} = plot(hh1{p},1:2,x(p,1:2),'b','linewidth',2);
plts{p,2} = plot(hh1{p},1:2,z(p,1:2),'m');
plts{p,3} = plot(hh1{p},1:2,x_est(p,1:2),':k','linewidth',2);
hold off
end
% March through time. No replotting required, just update XData and YData
for k = 2:maxT
for p = 1:4
set(plts{p,1}, 'XData', 1:k, 'YData', x(p,1:k) );
set(plts{p,2}, 'XData', 1:k, 'YData', z(p,1:k) );
set(plts{p,3}, 'XData', 1:k, 'YData', x_est(p,1:k) );
end
drawnow;
end
Now the plotting is pretty optimised. If you want the animation to be even quicker then just plot every 2nd, 3rd, ..., nth timestep instead of every timestep by using for k = 2:n:maxT.
I'm trying to plot a simple graph using for loop as shown below
x=linspace(0,2*pi,100);
for i=1:numel(x)
y=sin(x(i));
plot(x(i),y)
hold on
end
However, nothing appear on my figure. Why is that?
Why this happens...
With plot(x(i),y) you are plotting 100 single points (one in each iteration) and they are not shown by default. Therefore the plot looks empty.
Solution 1: Vectorized calculation and direct plot
I assume you meant to draw a continuous line. In that case no for-loop is needed because you can calculate and plot vectors directly in MATLAB. So the following code does probably what you want:
x = linspace(0,2*pi,100);
y = sin(x);
plot(x,y);
Note that y is a vector as well as x and that y(n) equals to sin(x(n)) for all n. If you want to plot the points itself, use LineSpec-syntax when calling plot like this1:
plot(x,y,'*');
1) Other types of points are possible as well, see the above linked documentation.
Solution 2: Calculate values within for-loop and plot afterwards
If you want to calculate the values within a for-loop and plot it afterwards: Pre-allocate the needed variable (in this case y), calculate the values within the for-loop and finally plot it with one single command after the calculation.
x = linspace(0,2*pi,100);
y = zeros(size(x));
for i = 1:numel(x)
y(i) = sin(x(i));
end
plot(x,y);
Solution 3: Dynamically update plot while calculating
In case you insist on plotting within each iteration, the previous code from Solution 2 can be expanded as follows: Create a figure, add an 'empty' plot to it and store its handle. Within the for-loop calculate the values and add them to the y-vector as shown above. As a last step you can update the plot by changing its XData and YData properties and calling drawnow. Note that calling plot every time within the for-loop is unnecessarily expensive and I don't recommend it.
% create figure and plot
figure;
ph = plot(0,0);
ax = gca;
set(ax,'XLim',[0,2*pi]);
set(ax,'YLim',[-1,1]);
% calculate and update plot
x = linspace(0,2*pi,100);
y = zeros(size(x));
for i = 1:numel(x)
y(i) = sin(x(i));
set(ph,'XData',x(1:i));
set(ph,'YData',y(1:i));
drawnow;
end
Simple approach
If you want to draw a curve as you add data, try the following:
x = linspace(0,2 * pi, 100);
y = zeros(size(x));
for i=1:numel(x)
y(i) = sin(x(i));
plot(x(1:i), y(1:i), 'color', 'r')
drawnow();
end
Be aware that the plot automatically tries to set x and y limits (curve is scaled to the plot window), to prevent that you have to manually set the x- and y-limits with xlimand ylim.
As Matt wrote in his answer, calling plot in each iteration is quite expensive (i.e. time consuming). Therefore I suggest using datasources:
Update graph using data sources
% Create a panel and axes object
h_panel = uipanel;
h_axes = axes( 'Parent', h_panel);
% Create data sources
x = linspace(0,2 * pi, 100);
y = zeros(size(x));
% Create graph object, in this case stairs
% and bind the variables x and y as its data sources
h_stairs = stairs(h_axes, x, y, 'XDataSource', 'x', 'YDataSource', 'y');
for i=1:size(x)
y(i) = sin(x(i));
% Update the data of the stairs graph
refreshdata(h_stairs);
drawnow();
end
The call to drawnow isn't neccessary in each iteration, it is only used to update the visuals, so you can see the changes directly.
I have to create some draggable points on an axes. However, this seems to be a very slow process, on my machine taking a bit more than a second when done like so:
x = rand(100,1);
y = rand(100,1);
tic;
for i = 1:100
h(i) = impoint(gca, x(i), y(i));
end
toc;
Any ideas on speed up would be highly appreciated.
The idea is simply to provide the user with the possibility to correct positions in a figure that have been previously calculated by Matlab, here exemplified by the random numbers.
You can use the the ginput cursor within a while loop to mark all points you want to edit. Afterwards just click outside the axes to leave the loop, move the points and accept with any key.
f = figure(1);
scatter(x,y);
ax = gca;
i = 1;
while 1
[u,v] = ginput(1);
if ~inpolygon(u,v,ax.XLim,ax.YLim); break; end;
[~, ind] = min(hypot(x-u,y-v));
h(i).handle = impoint(gca, x(ind), y(ind));
h(i).index = ind;
i = i + 1;
end
Depending on how you're updating your plot you can gain a general speedup by using functions like clf (clear figure) and cla (clear axes) instead of always opening a new figure window as explained in this answer are may useful.
Alternatively the following is a very rough idea of what I meant in the comments. It throws various errors and I don't have the time to debug it right now. But maybe it helps as a starting point.
1) Conventional plotting of data and activating of datacursormode
x = rand(100,1);
y = rand(100,1);
xlim([0 1]); ylim([0 1])
f = figure(1)
scatter(x,y)
datacursormode on
dcm = datacursormode(f);
set(dcm,'DisplayStyle','datatip','Enable','on','UpdateFcn',#customUpdateFunction)
2) Custom update function evaluating the chosen datatip and creating an impoint
function txt = customUpdateFunction(empt,event_obj)
pos = get(event_obj,'Position');
ax = get(event_obj.Target,'parent');
sc = get(ax,'children');
x = sc.XData;
y = sc.YData;
mask = x == pos(1) & y == pos(2);
x(mask) = NaN;
y(mask) = NaN;
set(sc, 'XData', x, 'YData', y);
set(datacursormode(gcf),'Enable','off')
impoint(ax, pos(1),pos(2));
delete(findall(ax,'Type','hggroup','HandleVisibility','off'));
txt = {};
It works for the, if you'd just want to move one point. Reactivating the datacursormode and setting a second point fails:
Maybe you can find the error.