File Exchange

## legendflex.m: a more flexible, customizable legend

version 1.9.0.0 (116 KB) by
Create a legend with more flexible positioning and labeling capabilities

Updated 09 Aug 2017

From GitHub

Editor's Note: This file was selected as MATLAB Central Pick of the Week

This function offers a more flexible version of the legend command. It uses a different method of positioning the legend (placing it relative to any axis, object, or figure), as well as options to:

- organize legend text and symbols in a grid with a specified number of rows and/or columns
- rescale the horizontal space used by each legend symbol
- create multiple legends for the same axis
- add a title to the legend within the legend box

### Cite As

Kelly Kearney (2021). legendflex.m: a more flexible, customizable legend (https://github.com/kakearney/legendflex-pkg), GitHub. Retrieved .

Baptiste Vandecrux

Hi, Thanks for a nice tool that I used over the last few years.
I just switched to Matlab2020 and it does not work any more because (among other) the native "legend" no longer outputs text objects (stored in h.obj(1:n:obj) in legendflex). Nevertheless "legend" in Matlab2020 has a lot more options than in previous Matlab versions (NumColumns f.e.).

yvan becard

Hi, thanks for the function, I am having an issue with orientation: I want 'orientation','horizontal', or equivalently, 'NumColumns',3, but none seems to work, the legend is stacked one item over another. Here is my code, any idea how to sort this out?

legendflex({'Data','Market sentiment shock','All other shocks'},'FontSize',fontsize*mul2,'Location','NorthWest','xscale',0.5,'orientation','horizontal','box','off');

Kelly Kearney

@Mathias, These mismatched-alignment issues seem to be cropping up in recent releases. I'm looking into it but haven't found a robust fix yet.

Benjamin Larin

Mathias

@Kelly, legendflex has been working great since I started using it! Although I am experiencing a problem now. If you run the code below, you will see that there is a misalignment between the line and the text in the legend. Do you know how it is possible to fix this issue?
Thank you,
Mathias
-----------------------------
x = 0:0.1:5;
figure
hold on
plot(x,1.*x);
plot(x,2.*x);
plot(x,3.*x);
plot(x,4.*x);
plot(x,5.*x);
plot(x,6.*x);
hold off
legendflex(gca,{' target condition',' \beta = ± 0.005\Delta{\itT}_{01}',' \beta = ± 0.01\Delta{\itT}_{01}',' \beta = ± 0.02\Delta{\itT}_{01}',' \beta = ± 0.05\Delta{\itT}_{01}',' \beta = ± 0.1\Delta{\itT}_{01}'},'xscale',0.5,'anchor',{'ne','ne'},'buffer',[-5,-5],'ncol',1,'box','off','FontSize',9.5)

Kelly Kearney

@Jon Adsersen, To modify any properties of the objects in the legend, use legendflex with two output arguments. For example: h = plot(rand(10,2), '-o');
[hleg, ho] = legendflex(h, {'one','two'}); set(ho, 'linewidth', 2);. (The exact objects and order returned will depend on your plotted data; for line plots, each label will include a handle for the legend text, line, and marker associated with it). Regarding, the legend moving, can you follow the GitHub link above and start an "Issue" on this, with code to reproduce the problem and your Matlab version included?

Found another issue: Had to reposition the legend due to different axes and plotbox position. This works fine when the plot is visible, but if the plot is not vissible, the legend jumps back to the original position. Do you have any idea what the issue might be?

This an amazing function, great work!
Is it possible to adjust the line width of scatter and line symbols in the legend?

Samuel de Vries

Kelly Kearney

@Conrado Neto, Sorry, TiledChartLayout doesn't seem to play nicely with this function (see Issue #15 on the GitHub repo). On my to-do list, but right now I'm limited by my license (running 2019a, need 2019b for TiledChartLayout). Without the tiled layout, the default position will be northwest; are you seeing problems when you specify something else?

@Kelly Kearney, thanks a lot, exactly what i needed!

Also, WITHOUT using tiled layout as in my question below, the position is always fixed to northwest, why is that?

Is there a workaround for when you are using tiled layout and get the following message:
"There is no units property on the TiledChartLayout class."

Kelly Kearney

@Jon Adsersen, take a look at the 'padding' parameter... I gave up trying to pick a set of values that worked well across all versions (the defaults worked well when I first wrote it, but tend to be a little cramped in newer versions due to updates to the built-in legend function), but you should be able to easily adjust things to your liking.

Amazing function!

Only thing i wondered: Is it possible to get a little more white space between test and right boundary box? Would be nice to have the same white space in both left and right side of the legend box.

PPM

great tool, very useful.

Wolfram

Great tool that helps a lot. Thank you!

Neuropragmatist

Nice function. The FaceAlpha of the legend symbols doesn't match the plot when using patch/area. But this can be fixed manually using the output handles. Not sure why this isn't done automatically though as it looks odd to have solid markers in a legend for a plot with transparent data.

Chris

Great tool. Had to make a small change on line ~575 "hnew.obj(it) = text(..." I had to add explicitly what object to add the text to, ie "hnew.obj(it) = text(hnew.leg, ...". Is only needed a small percent of the time but was 100% repeatable. It may have to do with my graphics card or windows/ML version.

Kristoffer Clasén

Excellent function, however it seems to block formatting of fontsizes of the latest figure that is active. I haven't been able to resolve this without creating an additional empty figure that seems to remove the block.

Wei-Heng Hsu

Kelly Kearney

@Joshua Wilson, Can you post this as an issue on the Github repo (click on the github icon above the download link, top right of this page), along with code to reproduce the problem and the version of Matlab you are using?

Joshua Wilson

This is a great function, but I can't seem to get the legend to match with my "errorbar" plot. I get the error: "Error using matlab.graphics.chart.primitive.ErrorBar/get
There is no units property on the ErrorBar class."

Alexander Rangel

Does anyone know how to change the size of the figure in the legend?
I want the lines to look bigger or whatever else I put on.

Sofia

Gabriele Mosaico

Christoph

Cool function, but for some reason, the symbols have a different FaceAlpha than filled areas in my plot (matlab R2016a). Am I missing something?

savvasgk

Matthias Pospiech

I copied the modified getpos and it works now. However the legend is first created at its original position and then moved to the target position. This means the view is flickering. (I use it in a gui)

Kelly Kearney

@Matthias,
I’m not the author of the original getpos (i.e. the one offered on the FEX as a stand-alone entry), so I cannot make any changes to it. The modified version offered for download here has fixed the issue you mentioned, but you need to make sure it’s higher on your Matlab path than any other versions.

Matthias Pospiech

The code does not work with Matlab 2018a using the latest getpos script. I get this error

Undefined function 'not' for input arguments of type 'matlab.ui.Figure'.

Error in getpos (line 160)
if ~href % HREF is the Root object (no 'Position' property)

Error in legendflex (line 465)
legpospx = getpos(h.leg, 'px');

Øyvind Petersen

I reckon there is a problem with new versions of Matlab. Changed
if hg2flag && strcmp(Lf.ref.Type, 'figure')
to
if hg2flag && strcmp(get(Lf.ref,'Type'), 'figure')

Laurent Quibus

Andrew Schwartz

Does anyone know if you can scale a vector legend to a specific value? I am plotting a wind field and want the vector within the legend to be the same as a 10 m/s vector on the map. Is that possible? Thanks in advance!

Example code -

figure

hquiv = quiver(lat2,lon2,u850_9,v850_9, 'color', 'black');

[hl(4).leg, hl(4).obj, hl(4).hout, hl(4).mout] = ...
legendflex(hquiv, {'10 ms^{-1}'}, ...
'anchor',{'ne','se'}, ...
'buffer',[0, 0.01], ...
'bufferunit', 'normalized');

Massimo Ciacci

Finally something that works! I tried gridLegend, to no avail, then columnlegend which almost made it, but there was no way to create a box above axis and behind legend text.
Finally this, which works as a charm on its own !
Had I tried this first I would have saved 2 hours of attempts hacking around with scribe legend...
Great work!

Kelly Kearney

@Fabian Miglianti,

Hmm, the legend should refresh unless you've explicitly told it not to (via the 'nolisten' = true input parameter). If that's not the case, could you please upload an example of the behavior as an issue on Github? (follow the "Learn More" link above to get to the Github repo).

Miglianti

When I change the color of a plotted line, the legend doesn't refresh. There is a way to do it?

Yan Liu

Thank you for this great function !
But how to change the font?

Felix

Dear Kelly,

Thank you so much for sharing your code.
However, i have a problem regarding the rendering.Whenever I use legendflex, the margin becomes too large ('TightInset' does not work anymore). Do you any suggestion how to solve this?

t = [0:0.1:2*pi]
a = sin(t);
b = cos(t);

co = [
0 0.4470 0.7410
0.1986 0.7214 0.6310 ]
set(groot,'defaultAxesColorOrder',co)

plot(t,a,t,b,'linewidth',3)

legendflex(gca, {'Sin','Cosinus'},'xscale', 4,...
'anchor', {'sw','sw'}, ...
'buffer', [10 10],...
'Fontsize',20)

f1 = figure(1);
set(f1, 'Position', [0, 0, 1920, 1080]);
set(gcf,'Units','inches');
screenposition = get(gcf,'Position');
set(gcf,'PaperPosition',[0 0 screenposition(3:4)],'PaperSize',[screenposition(3:4)]);
set(gca,'LooseInset',get(gca,'TightInset'))

Kelly Kearney

@Andrew Winter,
Can you please post the exact code you used where a legend did not appear in the intended position? Or open a GitHub issue (follow the "Learn More" link on this page)... that's the easiest way for me to track bugs and enhancement requests.

Andrew Winter

Tried using this function today and at first glance it's great! However, I'm running into the same issue as Joerg Huschke from April 20th 2015, where the "outside" location options do not work correctly and the legend remains inside the axis box (it seems to default to the "northeast" location instead for some reason). I'm using Matlab R2015b.

Amit Sharma

Hey, I am running R2017a and when I try to use 'nrow' or 'ncol' half the lines in the legend do not show. Here is a simple example to demonstrate the problem.
clear; clc; clf;
clear all;
numlines = 10;
r = rand(10,numlines);
h =plot(r);
labels = {'$^2\Sigma$','$^2\Pi$','$^2\Sigma$','$^2\Pi$','$^2\Delta$','$^2\Sigma$','$^2\Pi$','$^2\Pi$','$^2\Delta$','$^2\Sigma$'};
legendflex(h,labels,'FontSize',12,'xscale', 1.0,'ncol',2,'Interpreter','latex');

This function is great for smaller, compact plots that barely have room for a legend. Thanks!

Kelly Kearney

@Hirad Soltani: I suggest doing the sorting prior to creating the legend. The label positions are calculated based on various properties of the legend text (such as extent), and so things can’t be rearranged easily; your best bet is to delete and then redraw the legend.

I'd like to resort my legend based on some order; however, it does not let me refresh the legend when the legend is created by legendflex command. is there any way to refresh the legends? thanks

How do I make it work with only markers? I have a plot with 6 markers and 6 colors. I have two legends, the first showing colors the and the second showing different markers. I want to change the color of all markers in the second legend to black, but I'm not able to do it using |object_h| for that legend, when the markers are filled. When they are not filled, I can change the color.

Daniel de Malmazet

Thanks

Usman Tahir Virk

Awesome! Thanks.

Takko Vendetti

Excellent! Thank you for this.

Emre Kantar

Mathijs Cox

dongming yao

great. It works for me.

chenxi wang

Stephan

@Kelly
Thanks for the best customizable legend I have tried so far.
I just want to mention one problem I noticed: Labels including subscript characters (e.g. 'T_{level1}') cause a vertical misalignment between the labels and the symbols. To me it seems like the calculation of the variables "ysymnew" and "ytext" should rather be based on "textExtent(:,4)" to fully account for these cases.
Other than that it's an excellent piece of code you created!

Matilda

@Kelly
Thanks Kelly, I look forward to it !

Kelly Kearney

@Neil,

Can you please post some code to reproduce this problem? What type of graphics objects are you labeling? What version of Matlab, and what operating system are you working on? I would definitely like to fix this, but have been unable to reproduce the issue myself (Matlab R2016b, Mac OS X). Here's the example I used, and both the pdf and png images from all 3 export methods (print, saveas, and export_fig) include properly-rendered legends:

n = 22;
data = rand(100,n);
lbl = cellstr(num2str((1:n)'));

h = plot(data);
legendflex(h, lbl, 'ncol', 4);

saveas(gcf, 'lftest1.png');
saveas(gcf, 'lftest1.pdf');
print('lftest2', '-dpng');
print('lftest2', '-dpdf');
export_fig(gcf, 'lftest3', '-png', '-pdf');

If you have an example that demonstrates the problem, I'd really appreciate if you could open an issue on the GitHub page (https://github.com/kakearney/legendflex-pkg/issues).

Neil Tandon

columnlegend worked better for my needs.

Neil Tandon

I am generating a legend with 4 columns and 22 total items. The legend displays fine in MATLAB's figure window, but when I export the figure to pdf (or any other format I've tried), the text is all jumbled in one corner of the figure. It doesn't matter if I use export_fig or print. Others have noted similar problems, but I have not seen solutions provided. I can export other figures containing MATLAB's built-in legend without any problems.

Kelly Kearney

@Matilda,

Thanks for pointing out this shortfall of legendflex. The function does not currently support multi-line entries, and adding that capability will require some reworking of the code's underlying logic. I've added this as an open bug issue on the GitHub repository, and will try to add a fix when I get some time.

Matilda

Thank you for this great function !
I have been trying to use this function to create a legend where one of the legend entry is on two lines.
1) Using the default legend function of matlab, I would write :
legend('a','b',{'c1','c2'}) or legend('a','b',['c1',char(10),'c2'])
2) I tried using these in legendflex and it returns an error. I have tried to debug the error and I got to display a two lines legend entry but the legend entries and legend symbols are no longer aligned correctly.

Did anyone tried to plot a multi-line legend entry using legendflex ? I have more details on the errors if needed.

Thanks a lot !

Alan Green

Alber Filba

Great function! Finally I can make a legend as small as I want!

Brian Scannell

Excellent - spent hours manipulating a legend for a scatter plot only to find that all the changes were lost when I saved the figure as a png file. Within minutes of downloading legendflex I had created the legend exactly as I wanted it and it looks the same int he saved png file. Absolutely excellent. Thank you.

fantastic

DomJung

Neil Dalchau

Can't believe I hadn't rated this function yet. It's fantastic. And Kelly is great at fixing bugs and pushing new versions. You can rely on this one...

Neil Dalchau

Heiko

Hi Kelly, I found a hiding issue by uiPanels when applying the function within uiPanels for recent Matlab Releases. As a solution I added a 'parent' input Parameter, i.e. hnew.leg = axes(..., 'parent', Opt.parent), whereras Opt.parent = figh in case no 'parent' is provided.

Benjamin Green

@Kelly,
Thanks for the quick reply, I have made some personal modifications to get the function to work, however they are just a crude band-aid and not a real solution to the problem.
I look forward to seeing the update.

Kelly Kearney

@Benjamin,

Thank you for pointing out this issue. I've traced the issue to a change in the way legend.m parses input and output arguments in the R2016a release. I've added this to the known issues for legendflex.m (https://github.com/kakearney/legendflex-pkg/issues/6), and will work on updating the function as soon as possible.

Benjamin Green

Great Tool, I've been using it for a while to overcome the inadequacies of the built in Legend function. However i have noticed that in r2016a, that the font input is no longer being respected. None of the text properties are being implemented. eg Font, Font Size Interpreter,Not sure if I'm doing something wrong because the same code works correctly in r2014b.

OlivierMegel

Maryam GH

Roman

Applying this very useful function in 2016a - contrary to earlier ML-versions - results in:
Warning: NARGCHK will be removed in a future release. Use NARGINCHK or NARGOUTCHK instead. Warning: NARGCHK will be removed in a future release. Use NARGINCHK or NARGOUTCHK instead.

Plugging NARGINCHK in getpos.m function brings error messages too many output ...

Kesh Ikuma

Oops, never mind. I somehow downloaded an older version of the file...

Kesh Ikuma

Nice work. But there is a bug on the first line (l.210): ID'ing HG2 handles. The handle argument is checked with isnumeric call which is no longer appropriate. Suggest simply using

all(ishghandle(varargin{1}))

Kelly Kyriakou

Very usefull!!!
How I could make it visilbe off and then on again?

Søren Enemark

I have suggested a possible solution/work-around at https://github.com/kakearney/legendflex-pkg/issues/5
If VerticalAlignment is set to 'middle', it is relatively robust towards changes of FontSize...

Kelly Kearney

@Soren,

Latex/Tex is an ongoing problem with this function, but unfortunately one that doesn't have an easy, all-situations fix. Some workarounds are suggested in the above link.

Søren Enemark

Very nice function!
However, I would like to use latex (and not tex) as textinterpreter. E.g. by set(groot,'defaultTextInterpreter','latex'). This is possible with the function as it is, but it results in vertical misaligment between the plot line and the tag text, as I see it. Can you help me?

Jakob

Ok, I found why it was not working for me, it was my fault (but not a very obvious one): I used stem with the option 'filled' plus additional name-value-pairs (color etc.). This works at first and also when adding a legend if I dont request the objects as output. But the documentation on 'stem' shows that you should not use 'filled' plus more options. Doing it seems to mess up subtle things internally...

Jakob

Great function. I am currently experiencing an error though, but I think it might be a problem of the matlab legend function rather than of legendflex. Maybe you can still give me a hint on how to solve it:

The problem appears with a plot that has three data sequences plottet using stem. The legendflex codeline that calls the matlab legend function produces a

Warning: Error updating Legend. Following is the chain of causes of the error:
Property color not found in class matlab.graphics.primitive.world.Marker, or is not present in all elements of the array of class
matlab.graphics.primitive.world.Marker.

> In defaulterrorcallback at 12
In C:\Program Files\MATLAB\R2014b\toolbox\matlab\scribe\private\legendHGUsingMATLABClasses.p>find_legend_info at 382
In C:\Program Files\MATLAB\R2014b\toolbox\matlab\scribe\private\legendHGUsingMATLABClasses.p>legendHGUsingMATLABClasses at 251
In legend at 118
In legendflex at 307

..., the legend-entries are not displayed and the returned h.obj is an empty double array. This creates an error later, when your code tries to get the Extent of the objects in h.obj (textExtent).
If I just use legend (not legendflex), I get the same warning if I request the legend-objects as outputs. If I dont, there is no warning and the legend-entries get displayed normally.

Do you have an idea? Thanks!

Elizabeth Jones

Great function. I haven't used all the capabilities, but I got a quick legend in three rows without any errors.

Kelly Kearney

The resize function bug has now been fixed. To clarify, the feval messiness is just a wrapper function to call all the callback functions in succession (though I messed up the order of inputs in my earlier implementation, hence the error).

Kelly Kearney

@Joerg,

Thanks for the feedback. I'll look into the resize function bug as soon as possible. Trying to rewrite this function so it's compatible with both old and new handle graphics has been more difficult than I expected, and obviously I haven't worked out all the bugs yet.

I've also added your enhancement requests to my Issues list on the GitHub page, and will hopefully be able to add both options sometime soon.

Joerg Huschke

I really appreciate this function. However, I have several issues with it, tried with R2011b:

1) I don't see it correctly handles figures already having a resize function defined. For me the function produces error:
"
Error using horzcat
The following error occurred converting from cell to function_handle:
Error using function_handle
Too many output arguments.

Error in legendflexNew (line 640)
set(figh, 'ResizeFcn', cellfun(@(x)feval(x,h,e), [rsz @updatelegfigresize]));
"

Which is quite obvious, as rsz is forced to be a cell just a few lines above this code line. When I fix this to
set(figh, 'ResizeFcn', cellfun(@(x)feval(x,h,e), [rsz {@updatelegfigresize}]));

I get the next error for this line, because the 'e' in the feval is undefined. I have no idea what the intention of this feval is Why should the resizefcn be set to the result of the evaluation of @updatelegfigresize or of the function stored in rsz, rather than the function itself. Totally confused… This problem did not exist in version of 2011 I had been using so far.

2) The function should support the 'location' input like mathworks original legend does. Basically translate it to the Anchor/Buffer pair as provided in the function help text. I've already done this in my private extension of the function.

3 When using the 'outside' variants of the legend location then the reference axis to which the legend shall be associated is not reduced to make room for the outside-legend, unlike for the mathworks original legend. So currently the outside positions are not really usable unless I shrink the axis myself before.

I'm really looking forward to a legend function where I don't have to make compromises, like now I have to go either for the mathworks-provided one with its extremely limited functionality or with the older legendflex (as the current version does not work for me at all, see issue 1 above) but then cannot use outside locations. Would it be possible to fix this?

Eric

@Kelly

Thanks, that was definitely the issue. Forgot to add that new folder to my path. Great submission BTW.

Kelly Kearney

@Eric

Looks like you're using an old version of getpos. Check that you're using the version distributed with this package, and not a version downloaded separately (the original one, FEX entry 13927, hasn't been updated for HG2 graphics; my version has).

Eric

Crashes at legpospx = getpos(h.leg, 'px'); (line 415) with

ll = plot(0:1:3,magic(4));
legendflex(ll,{'1','2','3','4'})

in Matlab 2015b with error message:

Undefined operator '~' for input arguments of type 'matlab.ui.Figure'.
Error in getpos (line 160)
if ~href % HREF is the Root object (no 'Position' property)
Error in legendflex (line 415)
legpospx = getpos(h.leg, 'px');

Ken Campbell

Kelly, thanks for posting this excellent contribution. I had my own crude code for working with legends but this is much better. Your code is flexible, has lots of useful options, and is supported by good documentation.

Kelly Kearney

@Jonathan,

It's difficult to diagnose what's going wrong there without knowing what type of objects you're trying to label. I recently updated the code to better work with the new handle graphics. If you still see this error, can you email a full example that reproduces it?

Jonathan

Hi, Fantastic script. I have had no issues running it on R2014a. However, I upgraded to R2015a, and have since encountered a problem when I run:

lbl = {previous, current, 'raw data (limited QC)',seasonal,'+/-1std','+2std'};
[hgGroup1,now,c,mm,hgGroup2,ext]= legendflex(lbl, 'ref', gcf,...
'anchor', {'nw','nw'}, ...
'buffer',[140 -76], ...
'nrow',3, ...
'ncol',3, ...
'fontsize',10);

The returned error is:
Undefined function or variable "idx".

Error in legendflex (line 545)
ynorm = (xy{2}- (1-idx*rowHeightNm))./rowHeightNm;

Not clear to me what I'm doing wrong.

Thanks

Kelly Kearney

@Igstam,

You're right, I wrote the contour workaround pretty hastily and didn't test it for the no-handle input option. Corrected version on its way!

lgstarn

Hi, I love your script. pre-R2014b it worked great but of course old version broke with the update. So I downloaded the new version, but I see what appears to be a bug in your code. On the line:

iscont = strcmp(get(legin{1}, 'type'), 'contour');

I get the error:
Error using get
Conversion to double from cell is not possible.

Error in legendflex (line 293)
iscont = strcmp(get(legin{1}, 'type'), 'contour');

That's because I'm calling it as so:

strcell = {'U','V'};

buffer = [20 0];
legendflex(strcell,'FontSize',12,'anchor', [4 4], 'buffer',buffer);

so legin{1} is a string cell, and the get call does not work. The workaround I put in place was to surround the whole thing with a check if legin{1} is a string cell:

% Create a temporary legend to get all the objects
if (~all(cellfun(@ischar, legin{1})))
iscont = strcmp(get(legin{1}, 'type'), 'contour');
if any(iscont)
% Not sure why, but 2014b needs contour labels to be listed last in a
% legend, or everything goes haywire

[srt, isrt] = sort(iscont);
legin{1} = legin{1}(isrt);
legin{2} = legin{2}(isrt);
end
end

It's kind of messy to have legin{1} mean different things with the way you call it with the M or h syntax. I recommend refactoring legin{1} to have consistent meaning after you get done parsing the args.

Kelly Kearney

@Arnold,

The box around the legend content should expand as necessary to fit all content, including the title. If you have an example where this doesn't happen, could you email me?

arnold

it would be great if the box around the legend would scale with a given title.

Secondly, it would also be handy to be able to easily append something to all entries, like a unit [m] or something.

Kelly Kearney

@Leo,

The disappearing legends you're seeing are due to the subplot command (which deletes any existing axes overlapping its intended space). See https://github.com/kakearney/legendflex-pkg/issues/1 for a full explanation.

Leo Simon

It's a great program, and does exactly what I need it to do. There appears to be a small problem, which can easily be avoided but it would be great if you could fix it. When using subplots, if you use the "anchor" flag *and* have a legend label that is long enough to extend outside of the plot area, then when you open a new subplot, the old legend disappears. The following example illustrates the problem. As noted, it only arises when the anchor and the longLegend flags are turned on. Obviously, one can get around this problem by increasing the 'Position' dimensions of the subplot

close all;
legFontSize = 5;
legXScale = 0.5;
longLegend = 1;
anchorOn = 1;
for ii=1:15;
subplot(3,5,ii);
hold on;
h{ii}(1) = plot(1:10);
h{ii}(2) = plot(2:11);
if longLegend;
currentLegend = {'This is a very very very long legend','nothing'};
else;
currentLegend = {'This','nothing'};
end;
if anchorOn;
legendflex(h{ii},currentLegend,'anchor',{'nw','nw'},'fontSize',legFontSize,'xScale',legXScale);
else;
legendflex(h{ii},currentLegend,'fontSize',legFontSize,'xScale',legXScale);
end;
if ii==2; keyboard;end;
end;

Leo Simon

Warwick

Very useful and much appreciated.

Kelly Kearney

@Warwick:
The code to reproduce the sample image is now available in the example on the GitHub page (follow the kakearney/legendflex-pkg link, and scroll to the bottom of the page).

Martin

I'm using Matlab R2013a. If I use the example code I get the following error:

figure;
b = bar(rand(10,5),'stacked'); colormap(summer); hold on
x = plot(1:10,5*rand(10,1),'marker','square','markersize',12,...
'markeredgecolor','y','markerfacecolor',[.6 0 .6],...
'linestyle','-','color','r','linewidth',2); hold off
lbl = {'Carrots','Peas','Peppers','Green Beans','Cucumbers','Eggplant'};
% Rather than covering up data or resizing the axis, let's squeeze the
% legend into the margin at the top of the figure;
legendflex([b,x], lbl, 'ref', gcf, ...
'anchor', {'n','n'}, ...
'buffer',[0 0], ...
'nrow',2, ...
'fontsize',8);

I get this error:

Undefined function 'getpos' for input arguments of type 'double'.

Error in legendflex_pre2014b (line 351)
legpospx = getpos(h.leg, 'px');

Error in legendflex (line 209)
[tmp{:}] = legendflex_pre2014b(varargin{:});

Error in rfefe (line 11)
legendflex([b,x], lbl, 'ref', gcf, ...

What is the problem? Anybody knows?

thx

Warwick

Could you include example code for your quiver plot at Figure C? Thanks.

Kelly Kearney

@Chris,

Yes, I think that was due to a residual R2014b bug. I've done a bit of cleanup over the last few weeks, and your example works fine using my most recent version, which I just uploaded. Email me if you still have problems with this latest version.

Chris Garner

This works in 2013b:

clear all

xmin=1;
xmax=10;
r = 18;
c=5;
x=xmin+rand(r,c)*(xmax-xmin);

b = bar(x,'stacked');

lbl = {'1','2','3','4','5'};

legendflex(b, lbl, 'ref', gcf, ...
'anchor', {'n','n'}, ...
'buffer',[15 0], ...
'nrow',1, ...
'fontsize',10,'box','off',...
'anchor', [6 6]);

but I get this error in 2014b:

Error using legendflex (line 228)
Legend labels must be a cell array of
strings

Error in test (line 13)
legendflex(b, lbl, 'ref', gcf, ...

Is this due to change in ML graphics?

Can your code help me enlarge legend marker sizes? I have a scatter plot with tiny dots but I want the legend to show the dots larger so one can see the colors better.
thanks!

Jose

Just in case somebody else would like to use the latex interpreter and an horizontal legend. A quick fix is to either add $^{~}$ or $_{~}$ if one wants to adjust the top or bottom height of the word, respectively. This worked for me.

Jose

@ Kelly

thanks for the quick reply. I guess I will have to find some quick fix for this :)

Kelly Kearney

@Jose,

Legendflex does not always play nicely with the Latex interpreter, because the Latex interpreter interacts unpredictably with the Extent property of text objects (which I rely on to reposition everything). I'm uploading a small text document to this function to explain this. The document offers a few workarounds if text is running into each other or overflowing the legend box, but I'm not sure I have a good suggestion for misaligned baselines, as in your case. My crystal ball suggests things will be a bit better in the 2014b release, but still not perfect.

Jose

great work! I believe I found a bug with the current program.

When trying to use it with the latex interpreter and having an horizontal legend, apparently, all word items in the legend must have the same height, otherwise the legend becomes not properly aligned.

Here is an example code:

close all
figure,
p1 = plot(1,1,'b-','LineWidth',linewidthl)
hold on
p2 = plot(1,1,'r--','LineWidth',linewidthl)
hold on
p3 = plot(1,1,'g--','LineWidth',1.5)

legendflex([p1,p2,p3], {'P','Ap','R'}, 'ref', gcf,...
'anchor', {'n','n'},...
'buffer', [0 0],...
'box','off','nrow',1,...
'Interpreter','latex','FontSize',fontleg)

Since Ap does not have the same heigth as P and R, Ap becomes unaligned. However, if Ap is switched to Ac, then all is OK. Anybody has experienced this problem before?

arnold

works great but it would still be good to be able to move it around like the normal legend since you never know which position might obscure your data. I'd really like that option

Tobias Benjamin Gram

How can I add extra white space after the legend text? The bounding box is to close to the text on the right hand side of the box.

Great! Definitely flexible. Thanks!

Kelly Kearney

@Werner,

You're right, this function relies on the current convention of graphics handles being numeric... I'll update it if and when HG2 is officially released (and perhaps by then legends will be flexible enough that this function won't be needed anymore).

Werner

Unfortunately this doesn't work for HG2 x(. But great submission!

Brett

This is what I was looking for, but I had to fix it to work well for axes embedded in complicated layouts involving uipanels, uitabs, etc. I use the excellent GUI Layout Toolbox from here on the file exchange to have lots of resizeable panels for example.

The problem is that this function assumes the legend should be placed relative to the figure. Unfortunately if a nested uipanel is used as a reference, the getpos() function does not correctly return the figure coordinates relative to the anchor. To get the global figure coordinates for a nested object, the GUI's hierarchy must be traversed, and the left and bottom coordinates summed for each parent up to the figure level. Each control's position is relative to it's parent, not to the figure.

Fortunately there's a simpler solution. I made the legend a child of the 'ref' object and fixed anywhere that temporary axes were made to the figure to be to the ref object instead. I also added a check that if this parent object is an axes, to get it's parent since the legend axes can't be a child of another axes. Finally, in the case of a plot in a densely nested GUI, the resize function should be on the parent component, not on the entire figure. This way when I drag the boundaries between uipanels to resize them, the legend and axes resize with them. I'm still experimenting with it, but this seems to solve my problems thus far.

I'll wait on permission to post my code since the author listed a copyright in the source.

Great addon to Matlab. Especially the shrinking function removing the unwanted space between legend marker and legend text. Thanks

James Ackman

Indeed, this function allows for flexible positioning of legend data without auto-resizing of your plot axes.

And as a bonus it is a great way to make a static legend-- as workaround for the often discussed problems of slow graphics performance when updating dynamic plot data with the base legend() command.

James Ackman

Kesh Ikuma

Thanks for sharing a very useful function!

I have one issue though. I'm creating an EPS figure using the built-in PRINT function (in conjunction with my submitted EPS Toolbox), and the legendflex texts overlap while built-in legend gets the exact same texts properly resized. It appears to be that the print resolution is not reflected properly (96 desektop dpi to e.g., 300 print dpi) Any clue how to work around this? If you'd like to see what I described, please contact me via email. Thanks!

Henrik

Thank you very much Kelly, this fixed the problem

Kelly Kearney

@Henrik: Technically, legends are axes (both regular legends and those generated by legendflex). It looks like Convert4publication.m filters out legends based on their Tag property (on line 153). If you add the following line in the same location, it should also filter out legendflex axes:

h(arrayfun(@(a) isappdata(a, 'legflex'), h)) = [];

Henrik

Hi
This is exactly what I have been looking for to help make nice plots. However, I have a problem with this function in combination with convertplot4publication: http://www.mathworks.com/matlabcentral/fileexchange/34741-convertplot4publication
I'm hoping you could be able to help me with this?
For some reason it sees the legend as a subplot.. Here's an example of the problem:

y1 = randn(1000,1);
plot(y1)
legh={'leg1'};
legendflex(legh,'xscale',0.5)
ConvertPlot4Publication('testPlot')

Kelly Kearney

@Leo,

The warning you are receiving is one issued by the print function whenever it prints a figure with a ResizeFcn, and legendflex does add a resize function to figures. Setting your figure's 'PaperPositionMode' to 'auto' will avoid it (and save you the step of manually setting the page size). Or you could use a function like export_fig (FEX #23629), which in my opinion produces much better output without the quirks of print.

Leo Simon

Thanks very much for this program, it's very helpful. I'm having one problem, which may or may not be specific to legendflex. I'm using it to print my legend to a separate figure. I want to resize my legend to be the same size as a regular figure. I can do this on screen with

set(gcf,'Position',...);

To match on paper what I have on screen I would normally use

set(gcf,'PaperPosition',...);

But when I set 'PaperPosition' to the desired size, to preseve the larger sized legend, and then print, I get this message:

Warning: Positioning Figure for ResizeFcn. Set PaperPositionMode to 'auto' (match figure screen size) to avoid resizing and this warning.

I've never had this problem before, so wondering if it's something special about legendflex.

Any advice would be most appreciated!

Leo

Kelly Kearney

A workaround for the vertical positioning issues with subscripts/superscripts pointed out by William: render the legend with Interpreter set to 'none', then reset it to 'tex' afterwards. You may need to use a larger text size on the initial render as well, and reset to the correct size afterwards (e.g. if text hits box). It's not a perfect fix, and introduces extra horizontal space between columns, but I haven't found a better way to deal with the issues of produced by tex formatting in legends.

William

This is exactly what I was looking for, thank you! It worked perfectly for the first application (legends in subplots).

When I tried to use it the second time, I noticed a bug. When I used the legend with subscripts, e.g., 'e_pp', the marker positions are shifted downward. Is there anyway to correct this?

Thanks again, I appreciate your work!

Eric

I am running into issues when using the PRINT command as follows:

print(fig,'-depsc2','-r600',fname.eps)

It creates the eps fine but the legend boxes are not in th eproper location. I can fix that be setting the figure PaperPositionMode to 'auto', but I like to have the flexibility of resizing before I print. Also, sometimes (like with a single row legends, the end of the text is clipped by the legend box.

Eric

I am running into issues when using the PRINT command as follows:

print(fig,'-depsc2','-r600',fname.eps)

It creates the eps fine but the legend boxes are not in th eproper location. I can fix that be setting the figure PaperPositionMode to 'auto', but I like to have the flexibility of resizing before I print. Also, sometimes (like with a single row legends, the end of the text is clipped by the legend box.

alexancer

Clint Noack

Erik Johnson

If legendflex is called like:

[legend_h, object_h, plot_h, text_str] = legendflex(h, strs, 'ncol', ncols, ... );

then a workaround for the column offset is:

% fix legendflex's subsequent column locations
h = findobj(object_h, 'type', 'line');
y = get(h, {'YData'});
y = repmat(ans(1:ceil(length(y)/2/ncols)*2,:), ncols, 1);
y = ans(1:length(h),:);
set(h, {'YData'}, y);

Erik Johnson

A VERY useful function. However, I get slight offsets in the vertical locations of the lines/markers in different columns. Have you seen this?

For example, the code:

nrows = 1;
ncols = 3;
n = nrows * ncols;
x = repmat([0;1], 1, n);
y = [zeros(1,n); (1:n)/n];
h = plot(x, y);
strs = num2cell(char('`'+(1:n)), 1)';
[legax,legh] = legendflex(h, strs, 'ncol', ncols);

get(findobj(legh,'type','line','LineStyle','-'),{'YData'});
cat(1,ans{:}); yline=reshape(ans(:,1),[],3)
get(findobj(legh,'type','line','LineStyle','none'),{'YData'});
cat(1,ans{:}); ymarker=reshape(ans(:,1),[],3)
get(findobj(legh,'type','text'),{'Position'});
cat(1,ans{:}); ytext=reshape(ans(:,2),[],3)

gives the results:

yline = [9.5388 9.2710 9.0032]
ymarker = [9.5388 9.2710 9.0032]
ytext = [4.3091 4.3091 4.3091]

showing that the text items are set at consistent locations, but the lines and markers are slightly offset. (Change nrows to, say, 8 and the same vertical offset happens on all elements of subsequent columns.)

Any suggestions?

P.S. This is in MATLAB 7.14.0.739 (R2012a) on a Mac.

Chris

Very nice - thanks!
I wonder if anyone knows how to go a step beyond. That is, my legends are often like tables which I currently hand make as figures within figures. The table-like legend has row and column headings with only the symbols/line types in the cells instead of a label for every symbol/line. This is because the symbol itself might change to represent one variable and whether it's filled or open might represent another so it's identity is a combination of the row and column headings in the table.

Dominic

Camille Couzi

thanks a lot for this very useful code!!!

Mukhtar Ullah

Thanks Kelly for your explanation. I am not sure what will I loose if I keep those lines commented out. At the moment, I am avoiding any figure resize when both legendflex and panel are at play.

Kelly Kearney

@Mukhtar Ullah

Regarding making legendflex play nicely with panel, I admit I never considered the possibility that a figure's resize function might be linked to an object method. I don't do object-oriented coding myself, so I have very little knowledge of the internal workings, scope, etc. of objects. So no promises to fix this, though I will look into it if I find some spare time.

Mukhtar Ullah

I just realized that it is possible to find the legendflex axes by
ax = getappdata(hfig, 'legflexchildren')
I now have another problem. When I use legendflex together with panel
http://www.mathworks.co.uk/matlabcentral/fileexchange/20003-panel
any attempt to resize the figure results in the following error
Error using panel.resizeCallback
Cannot access method 'resizeCallback' in class 'panel'.

Error in legendflex>resizefig (line 637)
feval(Lf.oldrsz, hfig, ed);

Error using drawnow
Error while evaluating figure ResizeFcn

It turned out that commenting line 637 solves my problem.
Any idea of fixing this in a future release?

Thanks

Mukhtar Ullah

Thank you Kelly for clarifying that. It occurred to me because with legend, it is possible to set properties such as interpreter, string,..using set directly on the axes handle.
A minor addition to legendflex will make it easier to find its handle using findobj and findall function. For example, if you add a tag 'legendflex' to the axes, findobj(gcf, 'tag', 'legendflex') would locate the axes.

Mukhtar

Kelly Kearney

@Mukhtar Ullah,

I don't actually manually set the interpreter in this function, so it relies on the default. You can actually change this after the fact using set, as long as you return the object_h handles (see last syntax option); the first n indices of this array will correspond to the n text objects in the legend. This slightly obscure way of returning handles was chosen to be consistent with the original legend command.

Mukhtar Ullah

This function is the first serious fix to the long lasting problems Matlab has with legends.
The text interpreter is set none by this function and you could only change it when calling the function, and not later using SET. Is there a good reason for that?
Mukhtar

Ben

Could you provide the example of getting the result as the screenshoot shows? I think the simple examples in the .m file is not fully demonstrate the capability of your function. Wish you could add more examples.

Philipp

Mario Liverpool

Very useful indeed!

Kelly Kearney

Vertical alignment bug has been corrected, and I added some checks to make sure the figure resize doesn't crash when you remove a legend. Thanks to Jason for discovering these bugs.

Jason Kaeding

I guess it didn't take my first bug submission...

The vertical positioning of the patches is incorrect in the following code:

figure;
area(1:10,rand(10,10))
legendflex(gca,cellstr(num2str(column(1:10))),'ncol',5,'anchor',[6 2],'buffer',[0 -10])

Jason Kaeding

Another bug: if I delete the legend then resize my figure, I get an error...

??? Error using ==> getappdata
Invalid object handle

Error in ==> legendflex>resizefig at 621
Lf = getappdata(ax(ia), 'legflex');

??? Error while evaluating figure ResizeFcn

Claudio

Well done! Very useful!

Dan

##### MATLAB Release Compatibility
Created with R2010a
Compatible with any release
##### Platform Compatibility
Windows macOS Linux