Match legend to axes objects - matlab

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)

Related

Displaying more than 50 legend entries

I would like to plot 66 datasets and show their legends. Unfortunately, according to the MathWorks Support Team, MATLAB legends are limited by default to 50 entries.
I tried the workaround they suggested that involves making another axes in the plot, copying the previous data, and then hiding the new axes, but I couldn't get it to work (the new axes only shows 1 additional variable from the 16 that are left), and so I'm stuck.
Are there any other ways to display more than 50 legend entries?
As implied by Cris's comment, it's likely that your plot is going to be very unclear - if you need 50+ legend entries then you've got 50+ different line styles, which is pretty crazy from a usability perspective.
That aside, you can achieve an unrestricted legend using the gridLegend FileExchange submission.
% Plot some dummy data, 60 series with various markers / lines
ms = {'*','+','.','d','s','o'};
ls = {'--','-',':','-.'};
x = linspace( 0, 10, 100 ).';
figure(); hold on;
for ii = 1:60;
y = sin(x+ii) + ii + rand(100,1)/2;
p(ii) = plot( x, y, ms{randi(6)}, 'linestyle', ls{randi(4)} );
end
% Call the legend
gridLegend( p );
Output:
I ran into this problem myself and found an undocumented feature that can help—the 'LimitMaxLegendEntries' property of Legend ('matlab.graphics.illustration.Legend') objects. Here's an example:
hF = figure();
hAx = axes(hF);
plot(hAx, magic(100));
hL = legend(hAx, '-DynamicLegend');
set(hL, 'LimitMaxLegendEntries', false, 'NumColumns', 3);
Which results in:
Tested on R2020a.
P.S.
While I agree that these likely way too many legend entries to be useful, I believe one should have the freedom to shoot themselves in the foot.
A solution suggested by Eric Sargent (TMW Staff) is to pass the plot handles to the legend command:
p = plot(magic(100));
legend(p);
Note that in this case, the axes are not determined by gca, but instead using ancestor(p, 'axes') (so there's no need to specify the axes handle when calling legend). Moreover, specifying an axes handle makes this solution stop working!

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);

MATLAB: misaligned boxes in plotyy after saving as fig

I use plotyy to put two plots in one graph:
f = figure('Color','white');
[ax,p1,p2] = plotyy(xx, yy1, xx, yy2);
ylabel(ax(1),'Phase','FontSize',18);
ylabel(ax(2),'Spectrum','FontSize',18);
set(ax,{'ycolor'},{'k';'k'});
set(p1,'LineWidth',2,'Color',[0.4940,0.1840,0.5560]);
set(p2,'LineWidth',2,'Color','red');
xlabel(ax(1),['Frequency [THz]'],'FontSize',18);
set(ax,'FontSize',14)
Figure is displayed perfectly, but when I try to save it as fig something like misaligned boxes appears.
I tried to use linkaxes, but with no result.
plotyy has been one of my favorite MATLAB functions to love to hate. It's a really useful function that I always seem to run into bugs with, to the point where I've completely stopped using it in favor of just stacking two (or more) axes objects and plotting to them separately. You can then set the Position property of the 'sub' axes to the same as your primary axes and they will stack nicely.
A functional example:
xx = linspace(-15,15,100);
yy1 = sin(xx);
yy2 = cos(xx);
f = figure('Color','white');
ax(1) = axes('Parent', f);
ax(2) = axes('Parent', f, 'Color', 'none', 'XTick', [], 'YAxisLocation', 'right');
p1 = plot(ax(1), xx, yy1);
hold(ax(2), 'on'); % Hold to preserve our axes properties set above
p2 = plot(ax(2), xx, yy2);
hold(ax(2), 'off');
ylabel(ax(1),'Phase','FontSize',18);
ylabel(ax(2),'Spectrum','FontSize',18);
set(ax,{'ycolor'},{'k';'k'});
set(p1,'LineWidth',2,'Color',[0.4940,0.1840,0.5560]);
set(p2,'LineWidth',2,'Color','red');
xlabel(ax(1),'Frequency [THz]','FontSize',18);
set(ax,'FontSize',14)
set(ax, 'ActivePositionProperty', 'position'); % Resize based on position rather than outerposition
set(ax(2), 'Position', get(ax(1), 'Position')); % Set last to account for any annotation changes
Along with stacking the axes you will also note that I have set the ActivePositionProperty to position (rather than outerposition). MATLAB resizes axes automatically when the Units property is set to Normalized, and it seems like this is the main spot where the issue is arising. On resizing, MATLAB also modifies the OuterPosition value for the second axes, causing it to resize the plot portion. The difference is small, [0 0 1 1] vs. [0 0.0371 1.0000 0.9599] in my case, but the effect is obviously very pronounced. You can use get and set to fix this, but you'll have to do it on every resize which is fairly annoying. The alternative is to resize based on the Position, which seems to alleviate the issue and is a tweak present in the R2015b implementation of plotyy. This also fixes plotyy except for cases where the window is very small, so I have left my answer with the more generic approach.

Creating annotation boxes for subplots in a for-loop in Matlab

I have the following code in Matlab that runs through a for loop, reads data from a file and plots 9 different figures, that correspond to some particular "channels" in my data, so I decided to annotate them in the for loop.
clear
clc
for i=1:9
subplot(3,3,i);
hold on
x = [4 13]; % from your example
y = ([1 1]); % from your example
y2 = ([-0.4 -0.4]);
H=area(x,y,'LineStyle','none',...
'FaceColor',[1 0.949019610881805 0.866666674613953]);
H1=area(x,y2,'LineStyle','none',...
'FaceColor',[1 0.949019610881805 0.866666674613953]);
% Create textbox
annotation('textbox',...
[0.719849840255583 0.603626943005185 0.176316293929713 0.308290155440411],...
'String',{'FABLIGHT04','Channel',i},...
'FontWeight','bold',...
'FontSize',10,...
'FontName','Geneva',...
'FitBoxToText','off',...
'EdgeColor','none');
axis([0 24 -0.4 1])
set(gca,'XTick',[0:1:24])
set(gca,'YTick',[-0.4:0.2:1])
xlabel('Time (s)');
end
Initially it was giving me 9 different figures and the annotation thing worked fine. But I wanted to be able to tile them onto a subplot for easier comparison.
Since I switched over to using subplot, it does not annotate my figure properly. On opening the editing dock and generating the code, I find that matlab is plotting everything first and then just putting the annotation boxes in the same figure, one on top of the other. Looking at the code it generated, it apparently takes this part of the code:
annotation('textbox',...
[0.719849840255583 0.603626943005185 0.176316293929713 0.308290155440411],...
'String',{'FABLIGHT04','Channel',i},...
'FontWeight','bold',...
'FontSize',10,...
'FontName','Geneva',...
'FitBoxToText','off',...
'EdgeColor','none');
and does it as:
annotation(figure1,'textbox'...)
etc etc
So for all 9 text boxes, it puts them onto the same figure. I tried to do S=subplot(3,3,i) then annotation(S,'textbox') etc etc, I have also tried S(i)=subplot(3,3,i) and then annotation(S,'textbox') etc etc but nothing seems to work.
I have also tried to change the location of the box. I can't seem to figure out how to make it smaller either.
Does anyone know how to have annotation boxes in the right subplot in a for loop?
Thanks
I'm afraid annotation objects are properties of figures and NOT axes, as such its harder to customize the position of each annotation objects because no matter how many subplots you have, they are all part of the same figure and you need to specify their position relatively to the figure coordinate system.
Therefore, you can manually set the position of each text box in your code depending on the subplot it belongs to...
Simple example:
clear
clc
close all
figure('Units','normalized'); %// new figure window
for k = 1:2
str = sprintf('Subplot %d',k);
subplot(1,2,k)
plot(rand(1,10));
%// Customize position here
hAnnot(k) = annotation('textbox', [k*.4-.2 .6 .1 .1],...
'String', str,'FontSize',14);
end
Which looks like this:
Its not very elegant but I'm personally not aware of any other option if you do need to use annotations objects. A less cumbersome alternative would be to use a simple text objects, which are properties of axes and therefore much more friendly to position :)
Hope that helps!

Holding multiple axes' plots in Matlab GUI:

I'm currently developing a GUI (programatically. No GUIDE has been used) for a project and I need to place 11 axes on the same GUI. I'm using the axes command to get the handles of the 11 controls:
h.AXES_ALL(1)=axes('parent',h.fig,'position',[L1 T W H]);
h.AXES_ALL(2)=axes('parent',h.fig,'position',[L2 T W H]);
h.AXES_ALL(3)=axes('parent',h.fig,'position',[L3 T W H]);
...
They all have the same dimensions and I'm using the for instruction to plot the data:
for i=1:11
set(h.PLOT(i),'parent',h.AXES_ALL(i),'XData',x_data,'YData',y_data);
end
But the problem is that the last plot (the 11th) is the one that is shown on the axes control (the 11th) and all the other axes are empty. My objective is to plot 11 curves on 11 different axes controls. They aren't located in the same position, just for the record.
THanks in advance!
Charlie
You said in your comment that you start with a single axes handle:
ha = axes;
And you try to create two plots with the same parent axes, but it does not work as you intended:
>> h.PLOT(1:2) = plot(ha,0,0)
h.PLOT =
195.0035 195.0035
That just replicated the same plot series handle. So, when you go to set the plot data and parent axes for each plot, you are just moving the plot from axes to axes, updating the data while you go.
Use the plot command in a loop, using the appropriate axes handle for each plot:
for ip=1:11,
h.PLOT_ALL(ip) = plot(h.AXES_ALL(ip),...);
end
Then when you update the plot's XData and YData as you want to do, you do not have to change the parent axes.