Control x-range of multiple subplots in matlab with UIControl - matlab

I'm trying to create a sliding window (with a slider) to view multiple subplots each of which is a long time series.
S=['set(gca,''xlim'',get(gcbo,''value'')+[0 ' num2str(chunkDuration) '])'];
h=uicontrol('style','slider','units','normalized','position',Newpos,...
'callback',S,'min',0,'max',xmax-chunkDuration);
As written, this only causes the bottom plot to move. I understand that's because I set gca. However, changing gcf to gca won't help because that would try to set the xlim of figure instead of its children.
When I try
kids = get(gcf,'Children')
S=['set(kids,''xlim'',get(gcbo,''value'')+[0 ' num2str(chunkDuration) '])'];
I get the error:
??? Undefined function or variable 'kids'.
??? Error while evaluating uicontrol Callback
So, why doesn't the above work?
Even after a substantial change in approach, the problems remain.

Somewhere in your code you try to use a variable named subplot_handles. The error arises because this variable is undefined at the time you try to use it.
Update:
Is there a reason why you are saving your set commands as Strings? I suspect that its completely un-needed.
When you create your subplots try storing the handles to the axes created by the subplot objects.
ax(1) = subplot(311);
ax(2) = subplot(312);
ax(3) = subplot(313);
Later on you can set the limits for all subplots using:
set(ax, 'XLim', get(gcbo,'value') + [0 num2str(chunkDuration)] );

Related

UIAxes' YLim property cannot be listened to

MATLAB provides the addlistener function.
Listeners allow us to keep track of changes of an object's properties and act upon them. For example, we can create a very simple listener that will display a message in the command window when the 'YLim' property of an axes object is changed:
% Example using axes
ax = axes();
addlistener(ax, 'YLim', 'PostSet', #(src, evnt)disp('YLim changed'));
Try panning the axes or zooming in/out and see what happens. This works fine.
I need to do the same but using an uiaxes instead.
Unfortunately, it looks like we are not allowed to do so. Try running the following example:
% Example using uiaxes
ax = uiaxes();
addlistener(ax, 'YLim', 'PostSet', #(src, evnt)disp('YLim changed'));
It throws this error:
Error using matlab.ui.control.UIAxes/addlistener While adding a
PostSet listener, property 'YLim' in class 'matlab.ui.control.UIAxes'
is not defined to be SetObservable.
I've read the articles Listen for Changes to Property Values and Observe Changes to Property Values and I learned that a property must be declared as SetObservable to allow being listened:
classdef PropLis < handle
properties (SetObservable)
ObservedProp = 1 % <-- Observable Property
end
end
I've tried taking a look at the UIAxes class definition via edit matlab.ui.control.UIAxes but it's not possible because it's a P-file.
If 'YLim' is not observable then how can I keep track of changes in this property?
I'm using App Designer in MATLAB R2018b.
We should attach the listener to the internal Axes object, and not the UIAxes itself. Try this:
hFig = uifigure();
hAx = uiaxes(hFig);
addlistener(struct(hAx).Axes, 'YLim', 'PostSet', #(src, evnt)disp("YLim changed"));
hAx.YLim = [0 2];
In case anybody is wondering, I found this via trial and error.
Tested on R2018a & R2018b.
Thank you so much for this solution! I was having a real problem with zooming in on 3D data on a UIAxes. The 3D axes contained a .png background raster map at z=0 (plotted as a surface) and the 3D position of a UAV flight in x-y-x. When I would zoom in, the z would zoom as well and the new z limits would exclude the map I wanted always displayed. What was odd, is that setting
app.UIAxes2.Interactions = [zoomInteraction('Dimensions','xy')];
would correct the problem when zooming with the scroll wheel on my mouse, but if I selected the zoom toolbar button (clicking to zoom), it would still zoom in z. Really frustrating.
To get around this, I used your example, but added the listener to the 'ZLim' and made a callback function that would look at all the elements of the plot, and reset ZLim to include all the data whenever the ZLim changed.
warning('off','MATLAB:structOnObject');
addlistener(struct(app.UIAxes2).Axes, 'ZLim', 'PostSet', #(src,evnt)mapholdaltlims(app,app.UIAxes2));
function [inclusivezlim] = mapholdaltlims(app,ax)
objwithz = findobj(app.UIAxes2.Children,'-property','ZData');
currmin_z = 0;
currmax_z = 0;
for i = 1:length(objwithz)
currmin_z = min([min(min(objwithz(i).ZData)), currmin_z]);%Need double mins because data could be 2d surface
currmax_z = max([max(max(objwithz(i).ZData)), currmax_z]);
end
inclusivezlim = [currmin_z currmax_z];
ax.ZLim = inclusivezlim;
%disp('Updated the limits')
end
Man, what a pain this was. I am glad it now works. Thanks again.

GCA function doen't work to change axis-labels

I try to present 8 (name) labels on my x-axis. Instead, I get number 1 to 8.
Problem: In my previous asked question, I used gca function, that allows me to change axis labels. However, the same gca function doesn't work here.
This is my MatLab output:
Instead of 1,...8, I want to see Firm1...Firm8!
This is my code:
figure(2);
%four variables:
%pi --> 8x1 vector
%E_R_BL_Idzorek --> 8x1 vector
%pi_star1 --> 8x1 vector
%ER_100_TF1 --> 8x1 vector
ALL_DATA=[pi(1,1) E_R_BL_Idzorek(1,1) pi_star1(1,1) ER_100_TF1(1,1);pi(2,1) E_R_BL_Idzorek(2,1) pi_star1(2,1) ER_100_TF1(2,1);pi(3,1) E_R_BL_Idzorek(3,1) pi_star1(3,1) ER_100_TF1(3,1);pi(4,1) E_R_BL_Idzorek(4,1) pi_star1(4,1) ER_100_TF1(4,1);pi(5,1) E_R_BL_Idzorek(5,1) pi_star1(5,1) ER_100_TF1(5,1);pi(6,1) E_R_BL_Idzorek(6,1) pi_star1(6,1) ER_100_TF1(6,1);pi(7,1) E_R_BL_Idzorek(7,1) pi_star1(7,1) ER_100_TF1(7,1);pi(8,1) E_R_BL_Idzorek(8,1) pi_star1(8,1),ER_100_TF1(8,1)];
%plotting it with a bar function
bar(ALL_DATA);
%This is where I have problem with gca function
set(gca,'xticklabel',{'Firm1','Firm2','Firm3','Firm4','Firm5','Firm6','Firm7','Firm8'});
%this is the grid part:
grid on
ll = cell(1,4);
ll{1}='pi'; ll{2}='ERidz'; ll{3}='piTF'; ll{4}='ERTF';
legend(bar(ALL_DATA),ll);
You are using a newer version of MATLAB so you should use the newer graphics system. The newer system is based on objects. This makes setting properties of things like axes easier. For example:
fh = figure; % creates the figure window save the figure handle to set it's properties
ax = axes(fh); % creates the axes in the figure, again save the object
x = rand(8,100);
h = bar(ax, x); % create the bar graph in your axes object
% now use the saved object to access the exact feature you want. This way you always have the thing you want. No searching.
ax.XTickLabel = {'Firm1','Firm2','Firm3','Firm4','Firm5','Firm6','Firm7','Firm8'};
Saving the objects is also handy for tracking legends and other things. For example: legend(ax,... You know exactly which legend you're dealing with.
What appears to be happening is that you are correctly changing the XTicks as you want but then you overwrite your graph with legend(bar(.... That creates a new bar graph. Try changing that line to just legend(ll). I would still suggest using the object system.
It seems the problem is that you redraw the bar when you run
legend(bar(ALL_DATA),ll);
You should simply do
legend(ll);

Slider disappears from GUI after being used

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

How to make an axes current which is inside a figure created by GUIDE?

I created a bank figure using GUIDE and put an axes object inside it and saved the figure. Now I want to load the figure and set its axes as current axes object. This is my code:
close all; clear all; clc;
fh = openfig('test.fig');
ah = findobj(fh, 'tag', 'axes1');
figure(fh);
axes(ah);
plot(rand(10, 1));
But plot creates a new figure and plots in it! Am I missing something?
I know that I can solve it with plot(ah, ...), but I want to make gca to return this new axes. I have a lot of plotting codes that I want to be drawn in this new axes.
By default, HandleVisibility of GUIDE figures is set such that they aren't automatically detected. For example if you load the figure and then call gcf, you'll also create a new figure.
To have the plot be placed within the axes, you can specify the axes explicitly as the parent of the plot command.
plot(rand(10, 1), 'Parent', ah)
Alternately, you could specify that the HandleVisibility of the figure is 'on'. And then plot will be able to find it. This could be done by either setting the value of HandleVisibility using the property editor in GUIDE or calling the set function:
set(fh, 'HandleVisibility', 'on')
I recommend the first option as explicitly specifying the parent axes is always better than implicit.

In Matlab, own function which dynamically load/display images into Axes from a Gui in a loop?

With Guide I made a Matlab Gui that have 10 Axes in which i want to display images, in all of them at once, after i press a Button.
I made a separate .m file with function Load_Write_Img_Results(img_index) that i call from the Button Callback which have the following code:
for i = 1 : 10
handles_imgOut = findobj('Tag', ['imgOut' num2str(i)]);
set(handles_imgOut, 'HandleVisibility', 'ON');
axes(handles_imgOut);
image(imgs_data{img_index(i)});
...
end
Every time when i run the main Gui and press the button for the first time, images are displayed in all axes, so everything works ok.
The problem appear when i press the button for the second time and i get this error:
Error using axes
Invalid object handle
on this line:
axes(handles_imgOut);
When debugging, i saw that after handles_imgOut = findobj('Tag', ['imgOut' num2str(i)]); the handles_imgOut is not getting any value and is empty, so it's obvious that error.
Is there any chance i can't get handles for axes at the second press of the button?
Also, i want to know how can i solve this warning from Matlab:
Calling AXES(h) in a loop can be slow. Consider moving the call to AXES outside the loop.
Thanks in advance, any suggestions are welcome!
[SOLUTION]:
for i = 1 : 10
handles_imgOut = findobj('Tag', ['imgOut' num2str(i)]);
set(handles_imgOut, 'HandleVisibility', 'ON');
axes(handles_imgOut);
image(imgs_data{img_index(i)});
set(gca, 'Tag', ['imgOut' num2str(i)]); //! renew the tag
...
end
I'm fairly new to GUIDE and I've encountered similar problems, with the graph not being updated more than once / axes not found.
In the end, I used the following approach, which I hope can be useful to you too:
% I get my axes directly from the handles object, instead of using findObj:
graph1 = handles.axes1;
graph2 = handles.axes2;
% clear axes
cla(graph1);
cla(graph2);
% showTrial is my function that draws the graphs -
%notice that I used handles to store other variables as well (.data, .trials)
showTrial(handles.data, handles.trials(1), graph1, graph2)
To sum it up:
don't use findObj, get your axes from handles (it should automatically contain them as imgOut1,imgOut2, etc.)
pass the axes to your drawing function or pass directly the handles variable e.g. Load_Write_Img_Results(img_index, handles)
The fact that it works on the first button press, but not thereafter hints that
image(imgs_data{img_index(i)});
opens new axes instead of drawing into the existing ones. Since the new axes are not initialized with your tags, findobj will not find them. Either
(1) make sure to hold the original axes by placing a hold on command right after their creation,
or
(2) renew the tag right after the image command by
set(gca, 'Tag', ['imgOut' num2str(i)]);