I am confused about how resizing works for plots with two axes in matlab. I am finding inconsistent behaviour in the result depending on whether I step through the graph generation code in a debugger (works properly) or if I run it all at once.
For example, in the following function, I am linking the Position property of my two axes:
if I put a breakpoint on the last line, and step through it, the position of the first axis is changed automatically as it should be
if I run it all at once, it does not change and gets misaligned with the second axis
What is the reason for this?
I am using 2015B if this matters.
function graph_test
% set up a horizontal bar plot with a scattre plot on a secondary x-axis
% at the top
barh(1:10)
ax1 = gca;
set(ax1,'Box','off');
ax2 = axes('XAxisLocation','top','Color','none','Position', ax1.Position);
linkaxes([ax1 ax2],'y');
linkprop([ax1 ax2],'Position');
set(ax2,'Ytick',[]);
hold on;
scatter(ax2,[1:-.1:.1],[1:10]);
xlabel(ax1,'bottom axis');
xlabel(ax2,'top axis');
% set title
% we need to set this on second axis so that it does
% not overlap with axis legend
% the secondary axis is not auto resized as per matlab docs
title(ax2,'My graph');
% resize ax2 manually
ax2.OuterPosition(4) = 1-ax2.OuterPosition(1);
end
The issue is that you are listening to changes in Position yet you are explicitly updating OuterPosition. The changes made in OuterPosition ultimately make their way back to changes in Position but if the processor or renderer is busy, it is unable to propagate the change back (and notify listeners) until the processor is idle.
To fix this, you can explicitly tell MATLAB to flush all queued events immediately after changing the OuterPosition using drawnow. This will cause Position to update and notify all potential listeners, which in your case will lead to the Position of ax1 being updated.
ax2.OuterPosition(4) = 1 - ax2.OuterPosition(1);
drawnow nocallbacks
The reason that you don't see this behavior when stepping through it with the debugger is that, at that point in time, the MATLAB interpreter is idle and able to process all graphics events in real-time.
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
Version: Matlab R2014b
XY problem disclaimer: my objective is to make MATLAB format ticks in my graphic with a certain custom function that takes as argument the tick position and outputs a string. Default tick placement is ok but I want to control the labels. Failure to execute the callback function as ticks change results in mislabelled ticks.
The MWE below shows dummy versions of the callback; the real callback sets tick labels to the output of
tick_formatter = #(tv)([sprintf('%1.2f',tv/10^N) 'e' num2str(N)])
(where tv is the tick value in X units). (This is a crude replacement for ax.XAxis.Exponent of versions R2015b and following, see https://fr.mathworks.com/matlabcentral/answers/275054-change-multiple-exponent-on-x-axis)
The actual problem:
I want to be able to do "something" (in my case recompute tick labels) every time ticks change (i.e. their number or their y-value changes). To do so I looked at addlistener but I cannot manage to make it work as I wish.
Ideally, I would put a listener on the XTick property but it does not seem to work correctly. When XTickMode is on auto, zooming in and out (programatically by xlim() or by hand) or resizing the graphic window will cause change in the number / position of ticks, and consequently change the value returned by get(ax,'XTick'), but it will not trigger such an event listener.
For zooming in/out, a workaround is to listen to XLim instead, which does trigger events; but I found no such workaround for figure window resizing (at least using listeners at the axes object level).
Any suggestions welcome.
MWE:
% generate figure
x = 0:100;
y = x.^2;
figure(1)
clf() % ensure we cleared out previous tests
plot(x,y)
ax = gca();
% This event listener does nothing as you zoom in/out or resize the graphic
% window, even though XTick does change; it will only respond to explicit
% commands e.g. set(ax, 'XTick', ...).
listen_to_XTick = addlistener(ax, 'XTick', 'PostSet',#(src,evn) disp('XTick changed'));
% This event listener responds to zooming in/out, but not to window
% resizing
listen_to_XLim = addlistener(ax, 'XLim', 'PostSet',#(src,evn) disp('XLim changed'));
% Play a bit with the figure, zoom in/out, resize the graphics window etc.
% -> only listen_to_XLim ever gets triggered, never listen_to_XTick
% Other properties that do not seem to ever trigger as window is resized:
% 'XTick', 'XTickLabel', 'Position'
If you would like to change your tick labels when your figure window is resized, you could make use of the SizeChangedFcn callback of the figure window itself. Try:
f = figure('SizeChangedFcn',#(src,evn) disp('Window resized'))
I'm working on a custom progress monitor with some graphs. I've noticed that Matlab's waitbar creates a figure with some special properties so that if you do
plot(rand(100,1));
wb = waitbar(0);
plot(rand(100,1));
the second plot ends up replacing the first plot and not in wb. Is there a property I can set so that when I create my progress monitor and then plot something afterwards, the graph doesn't end up in my figure?
To be clear, I'm trying to have
plot(rand(100,1));
temp = MyProgressBar();
plot(rand(100,1));
create a figure for the first plot, create a different figure in the second line, then plot a new graph in the third line.
To protect your progress bar figure against subsequent plotting operations, I would set the 'HandleVisibility' property of its axes to 'off'. That should prevent it ever becoming the current axes, thus keeping subsequent plotting commands from modifying or adding to it. It's a good practice for stand-alone figures/GUIs in general that you turn off the handle visibility of all objects (figure, uicontrols, etc.) in this way to insulate them against being modified by outside code. This is almost certainly what is done in the code for waitbar.
As an additional aside, it's good practice to target your plots to a given axes by passing the axes handle as the first argument. You also have to make sure that, if you want new plots to be added to existing plots, you use things like the hold command first. Here's how I'd rework your example, assuming you want the two plots to appear on the same axes:
plot(rand(100,1)); % Creates new figure and axes
hAxes = gca; % Get the axes handle
hold on; % Allow subsequent plots to be added
temp = MyProgressBar();
plot(hAxes, rand(100,1)); % Will be added to the first plot axes
I try to make a MATLAB GUI programmatically and face the problem that my slider disappears after using it. I isolated the problem to keep the code short. In this GUI I want to refresh the plotmatrix each time the slider is used (ignore the fact that the value of the slider is completely irrelevant to my program, as mentioned before I really wanted to keep the code clean that's why I also removed this functionality). Here's the code (you have to run it as function):
function StackOverflowQuestion_GUI()
% clear memory
close all; clc;
% initialize figure
f = figure;
% create main axes
AX_main = axes('Parent',f,...
'Units','normalized','Position',[.1 .2 .8 .7]);
% create slider
uicontrol('Parent',f,...
'Style','slider','Callback',{#sliderCallback,AX_main},...
'Units','normalized','Position',[0.05 0.05 0.9 0.05]);
plotmatrix(AX_main,randn(500,3));
title('Random Plotmatrix');
end
function sliderCallback(~,~,AX_main) % callback for slider
plotmatrix(AX_main,randn(500,3));
title('Random Plotmatrix NEW');
end
Any help is appreciated! I think I misunderstood the concept of AXES. When I plot to the AXES-handle I created, why are other parts of the figure affected as well? If someone could explain to me how this graphic-handle system basically works that would be very nice too!
While daren shan's answer is correct, it's bizarre enough behavior that I was curious to see what is behind it.
Stepping through the source of plotmatrix we can find the line that deletes our slider object:
% Create/find BigAx and make it invisible
BigAx = newplot(cax);
Nothing obvious here, what does newplot do?
Use newplot at the beginning of high-level graphics code to determine
which figure and axes to target for graphics output. Calling newplot
can change the current figure and current axes. Basically, there are
three options when you are drawing graphics in existing figures and
axes:
Add the new graphics without changing any properties or deleting any objects.
Delete all existing objects whose handles are not hidden before drawing the new objects.
Delete all existing objects regardless of whether or not their handles are hidden, and reset most properties to their defaults before
drawing the new objects (refer to the following table for specific
information).
Oh...
So newplot is deleting the slider object.
So why does hold prevent the slider from being deleted, despite it being an axis method and not a figure method? To start, take a look at the "Algorithms" topic in the documentation:
The hold function sets the NextPlot property of the Axes or PolarAxes
object to either 'add' or 'replace'.
So hold on sets this to 'add' for the current axes. However, for a reason I can't currently figure out, this also sets the NextPlot of the figure to add as well.
We can see this with a short snippet:
f = figure('NextPlot', 'replacechildren');
ax = axes;
fprintf('NextPlot Status, base:\nFig: %s, Ax(1): %s\n\n', f.NextPlot, ax.NextPlot)
hold on
fprintf('NextPlot Status, hold on:\nFig: %s, Ax(1): %s\n\n', f.NextPlot, ax.NextPlot)
Which prints:
NextPlot Status, base:
Fig: replacechildren, Ax(1): replace
NextPlot Status, hold on:
Fig: add, Ax(1): add
Weird behavior, but I won't dwell on that.
Why does this matter? Go back to the newplot documentation. First, newplot reads the figure's NextPlot property to determine what to do. By default, a figure's NextPlot property is set to 'add', so it would retain all of the present graphics objects but plotmatrix explicitly changes this:
if ~hold_state
set(fig,'NextPlot','replacechildren')
end
So newplot goes from:
Draw to the current figure without clearing any graphics objects already present.
To:
Remove all child objects whose HandleVisibility property is set to on
and reset figure NextPlot property to add.
This clears the current figure and is equivalent to issuing the clf
command.
Which explains why the slider disappears and why hold on fixes the problem.
Per the documentation for newplot we can also set the HandleVisibility of the slider UIcontrol to save it from being destroyed:
% create slider
uicontrol('Parent',f,...
'Style','slider','Callback',{#sliderCallback,AX_main},...
'Units','normalized','Position',[0.05 0.05 0.9 0.05], ...
'HandleVisibility', 'off');
When you call plotmatrix, the function completely redraws the figure,
to conserve other elements you should use hold on; hold off; statements:
function StackOverflowQuestion_GUI()
% clear memory
clear; close all; clc;
% initialize figure
f = figure;
% create main axes
AX_main = axes('Parent',f,...
'Units','normalized','Position',[.1 .2 .8 .7]);
% create slider
uicontrol('Parent',f,...
'Style','slider','Callback',{#sliderCallback,AX_main},...
'Units','normalized','Position',[0.05 0.05 0.9 0.05]);
plotmatrix(AX_main,randn(500,3));
title('Random Plotmatrix');
end
function sliderCallback(~,~,AX_main) % callback for slider
hold on;
plotmatrix(AX_main,randn(500,3));
hold off;
title('Random Plotmatrix NEW');
end
I am trying to make a program that displays live chart data from a broker once in a period. The period could be for example 5 seconds or 15 minutes.
I have made a GUI and a Timer. When the program starts, the first plot goes to the axes in the GUI. However, all the updated plots (from the timer) come to a new figure (only one), but not into the figure in the GUI.
Attached is some code:
This is in the openingFcn of the GUI .m-file
handles.timer = timer(...
'ExecutionMode', 'fixedRate', ... % Run timer repeatedly
'Period', 5, ... % Initial period is 5 sec.
'TimerFcn', {#updateChart,hObject}); % Specify callback
guidata(hObject,handles)
axes(handles.axes1);
candle(highBid, lowBid, closeBid, openBid);
start(handles.timer);
And the function updateChart:
function updateChart(hObject,eventdata,hfigure)
% Get new data, one candle at a time
...
% How many times the chart has already updated
handles = guidata(hfigure);
k = handles.timer.TasksExecuted;
...
% Draw (update) the chart
hold on;
axes(handles.axes1);
candle(highBid, lowBid, closeBid, openBid); % this will be plotted in a new figure !
Any suggestions on how to update the chart at the GUI window?
I found the way to solve it. Indeed the same thing happens with any kind of high level plotting function. I had to reproduce your problem with the plot function and the behaviour was as you described.
Short answer:
you have to set the HandleVisibility of the figure to on (instead of the default setting callback). If you are working with GUIDE you have to set that directly in the figure property inspector from GUIDE (for some obscure reason it does not work if this is set later in the initialisation code):
This settings will let the timer callback have visibility of the figure children object so the plot command will not decide to create a new set of axes & figure when faced with invisible ones.
Note 1:
The plot was always refreshed in the right axes when the plot command was specified with the right target handle. For graphic functions which support passing the target axes in parameter, the syntax :
% infallible syntax (when allowed)
plot( data2plot , 'Parent',targetAxesHandle)
is always preferable to the one you were using (setting an axes active then plotting in the current active axes)
% this syntax may fail to plot in the right "axes" somtimes, as you observed
axes(targetAxesHandle);
plot( data2plot )
Now reading the documentation for your specific plotting function candle, I did not find clues that you can pass an axes handle to it, so for this type of function, you have to resort to the solution given on top of this post. However, if you ever give some feedback to the writers of the toolbox, I would strongly suggest to tell them about this important missing feature.
Note 2:
You do not have to call hold on before each plot. If you know you will always "add" to the plot, you can set it once and for all in the initialisation code:
set(handles.axes1,'Nextplot','add') % set "Hold on" permanently for "axes1"
And if you ever want to remove the locked hold, just set :
set(handles.axes1,'Nextplot','replace') % set "Hold off" permanently for "axes1"