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!
Related
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)
In a m-file, I am plotting 16 different types of plots (not in a loop). Is it possible to set the following properties of all the plots by writing only once:
set(gca,'linewidth',1,'fontsize',12);
set([xh, yh, th],'fontsize',12);
It turns out that the answer is yes, which can be easily checked e.g. using this code
figure
xh = gca;
figure
yh = gca;
set([xh, yh], 'fontsize', 12, 'linewidth', 1);
Note that the 'linewidth' property of an axes applies to the lines that make up the axes, not to any lines plotted into it. If you want to change their properties, you have to either collect the handles of all the line objects, or search for line objects later using
lh = findobj(0, 'Type', 'line');
set(lh, 'linewidth', 1)
Here '0' refers to the root object, of which all figures are children.
You can change default plotting parameters:
set(0,'DefaultLineLineWidth',1);
set(0,'DefaultAxesFontSize', 12);
set(0,'DefaultTextFontSize', 12);
Yes, it is meant to be "LineLine", it's to distinguish it from DefaultPatchLineWidth. Once you change these, it will only affect new figure windows; if you have already created the plots, use the answer given by A. Donda.
I used the regress function to find the slope for some data I plotted. I have managed to plot the data and the fitted line both on the same plot. I know how to make it clear that the fitted line is the slope, but I would also like to add a box in corner of the graph (dont care where) that shows the actual value of the slope (basically shows the value that the regress function returns), and I'm trying to find a way to do this automatically (like if there's a function for that or something). Can anybody help (I hope I explained my question well enough...)?
I didn't try to recreate your slope line but have you considered using an annotation?
Example:
x = [-1:.2:1];
plot(x,x.^2,'-bo');
annotation('textbox', [.4 .4 .1 .1], 'String', ...
['slope at x = 0.6 is: ',num2str(2*.6)]);
Which shows:
Of course you can control how the box is positioned, formatted, and so forth.
Check the help files for more detailed info. In some cases you might also consider using a legend().
The function text adds text to a figure. It requires a position and a string to display. In addition, you can highly customize the appearance of the text. For example:
x = 1:100;
y = randn(size(x)) + 0.3*x;
plot(x,y,'.');
p = polyfit(x,y,1);
hold on;
plot(x, polyval(p,x),'k-');
h = text(min(xlim(gca)), max(ylim(gca)), ...
sprintf('%fx + %f', p(1), p(2)),...
'verticalalignment','top',...
'horizontalalignment','left');
Then, to see the various settinsg you can change, look at:
get(h)
Those properties can almost all be changes at creation (like verticalalignment above) or after creation (e.g. set(h, verticalalignment, 'top')).
Suppose that I have 2 figures in MATLAB both of which plot data of size (512x512), however one figure is being plotted by an external program which is sets the axis parameters. The other is being plotted by me (using imagesc). Currently the figures, or rather, the axes are different sizes and my question is, how do I make them equal?.
The reason for my question, is that I would like to export them to pdf format for inclusion in a latex document, and I would like to have them be the same size without further processing.
Thanks in Advance, N
Edit: link to figures
figure 1: (big)
link to smaller figure (i.e. the one whose properties I would like to copy and apply to figure 1)
For this purpose use linkaxes():
% Load some data included with MATLAB
load clown
% Plot a histogram in the first subplot
figure
ax(1) = subplot(211);
hist(X(:),100)
% Create second subplot
ax(2) = subplot(212);
Now link the axes of the two subplots:
linkaxes(ax)
By plotting on the second subplot, the first one will adapt
imagesc(X)
First, you have the following:
Then:
Extending the example to images only:
load clown
figure
imagesc(X)
h(1) = gca;
I = imread('eight.tif');
figure
imagesc(I)
h(2) = gca;
Note that the configurations of the the first handle prevail:
linkaxes(h)
1.Get the handle of your figure and the axes, like this:
%perhaps the easiest way, if you have just this one figure:
myFigHandle=gcf;
myAxHandle=gca;
%if not possible, you have to search for the handles:
myFigHandle=findobj('PropertyName',PropertyValue,...)
%you have to know some property to identify it of course...
%same for the axes!
2.Set the properties, like this:
%set units to pixels (or whatever you prefer to make it easier to compare to the other plot)
set(myFigHandle, 'Units','pixels')
set(myAxHandle, 'Units','pixels')
%set the size:
set(myFigHandle,'Position',[x_0 y_0 width height]) %coordinates on screen!
%set the size of the axes:
set(myAxHandle,'Position',[x_0 y_0 width height]) %coordinates within the figure!
Ok, based on the answer of #Lucius Domitius Ahenoba here is what I came up with:
hgload('fig1.fig'); % figure whose axis properties I would like to copy
hgload('fig2.fig');
figHandles = get(0,'Children');
figHandles = sort(figHandles,1);
ax(1) = findobj(figHandles(1),'type','axes','-not','Tag','legend','-not','Tag','Colorbar');
ax(2) = findobj(figHandles(2),'type','axes','-not','Tag','legend','-not','Tag','Colorbar');
screen_pos1 = get(figHandles(1),'Position');
axis_pos1 = get(ax(1),'Position');
set(figHandles(2),'Position',screen_pos1);
set(ax(2),'Position',axis_pos1);
This is the 'before' result:
and this is the 'after' result:
Almost correct, except that the aspect ratios are still off. Does anybody know how to equalize everything related to the axes? (I realize that I'm not supposed to ask questions when posting answers, however adding the above as a comment was proving a little unwieldy!)
Consider the following example code:
load sumsin;
s = sumsin+10; % example data series
time = linspace(0,5*24,1000);
figure(1);
subplot(311);
plot(time,s,'k');
subplot(312);
plot(time,s,'k');
hold on;
[s_denoised,~, ~] = wden(s,'minimaxi','s','sln',1,'db4');
plot(time,s_denoised,'r');
subplot(313);
plot(time,s,'k');
hold on;
plot(time,s_denoised,'r');
xlim([20 40]);
Resulting in
I would like to alter this plot by inserting lines between subplot 2 and 3 to show that subplot 3 is a portion of subplot2. For example:
How can this be achieved in matlab?
Edit:
I was thinking of something along the lines of generating a invisible axes over the entire figure, obtain the position of each subplot, the location of 20 and 40 will be a certain percentage of the subplot width so I could use the annotation command from here to start a line and then apply the same method to the third subplot to connect the lines with the desired location. I have trying this, but no solution so far.
Just for the sake of the answer, you could use annotation objects to get the effect that you're looking for, as correctly suggested in a comment. Note that their coordinates have to be normalized to the [0, 1] range with respect to the figure window, so it might be quite tedious to adjust them.
This does get the job done, but it's horrible. Don't do it this way.
Example
Since I don't have your original data, I'll draw something of my own (but similar to yours):
t = linspace(0, 120, 1000);
s_denoised = sin(t / 2);
s = s_denoised + 0.2 * randn(size(s_denoised));
subplot(3, 1, 1), plot(t, s, 'k')
subplot(3, 1, 2), plot(t, s, 'k', t, s_denoised, 'r')
subplot(3, 1, 3), plot(t, s, 'k', t, s_denoised, 'r'), xlim([20 40])
Now let's add "annotation" lines like you want:
annotation('doublearrow', [.26 .39], [.38 .38]); %// Top double-arrow line
annotation('doublearrow', [.13 .9], [.34 .34]); %// Bottom double-arrow line
annotation('line', [.325 .325], [.38 .37]); %// Top little connector
annotation('line', [.515 .515], [.35 .34]); %// Bottom little connector
annotation('line', [.325 .515], [.37 .35]); %// Line
Result:
A bit late in the game, but still it can be beneficial to know of these optional tools that are available at the file exchange (FEX):
inset2DAbsolute - creates an axes inset, defined using the larger axes, and corresponding annotations.
On-figure magnifier - is a zooming tool for 2D graphics of images on the same plot. It is composed of two blocks (the secondary axes and the magnifier). The secondary axes, which can be arbitrarily allocated within the limits of the figure, displays the area marked by the magnifier.
Interesting question.
However, from my experience, beautification of graphs and plots can be done more efficiently using graphics software.
I usually use excel + powerpoint for this purpose.
Therefore, my advice (which is not exactly a good answer for your question) is:
export your data to excel, using xlswrite
use excel to create the desired plots.
copy-paste the plots to power point for "hand-crafted" finishing...