Code covered by the BSD License  

Highlights from
legendflex: a more flexible legend

4.63636

4.6 | 22 ratings Rate this file 106 Downloads (last 30 days) File Size: 18.8 KB File ID: #31092
image thumbnail

legendflex: a more flexible legend

by

 

18 Apr 2011 (Updated )

Create a legend with more flexible positioning and labeling capabilities

Editor's Notes:

This file was selected as MATLAB Central Pick of the Week

| Watch this File

File Information
Description

With legendflex, you can:
- position the legend relative to any object in any figure, not just the parent axis of whatever is being labeled
 
- 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
Note: R2014b-ready, sort of. Due to some bugs with figure resize callbacks in R2014b, you will encounter some crazy appearing/disappearing legend objects if you resize a figure in 2014b. Everything should settle out correctly after a few seconds, and I'm hoping to eventually recode to avoid these issues. Also, there appear to be a few bugs in the R2014b version of the legend command, which propagate through to this function (for example, trying to display a legend entry for a contour object results in a blank entry). I haven't been able to find any workarounds for that yet; please email me if you notice particular instances where legends are not rendered properly in 2014b.

Acknowledgements

Setpos 1.2 Getpos 1.2 inspired this file.

MATLAB release MATLAB 7.10 (R2010a)
Tags for This File   Please login to tag files.
Please login to add a comment or rating.
Comments and Ratings (44)
25 Aug 2014 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.

25 Aug 2014 Jose

@ Kelly

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

25 Aug 2014 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.

25 Aug 2014 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?

11 Aug 2014 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

24 Jul 2014 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.

04 May 2014 Adam Danz

Great! Definitely flexible. Thanks!

05 Sep 2013 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).

05 Sep 2013 Werner

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

13 Aug 2013 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.

08 May 2013 Tobias Benjamin Gram

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

17 Apr 2013 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.

17 Apr 2013 James Ackman  
09 Apr 2013 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!

20 Mar 2013 Henrik

Thank you very much Kelly, this fixed the problem

18 Mar 2013 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)) = [];

18 Mar 2013 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')

14 Mar 2013 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.

14 Mar 2013 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

18 Jan 2013 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.

18 Jan 2013 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!

04 Oct 2012 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.

04 Oct 2012 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.

24 Aug 2012 alexancer  
25 Jul 2012 Clint Noack  
12 Jul 2012 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);

11 Jul 2012 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.

27 Apr 2012 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.

09 Apr 2012 Dominic  
07 Mar 2012 Camille Couzi

thanks a lot for this very useful code!!!

24 Jan 2012 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.

19 Jan 2012 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.

18 Jan 2012 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

14 Jan 2012 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

10 Jan 2012 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.

10 Jan 2012 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

10 Aug 2011 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.

10 Aug 2011 Philipp  
08 Aug 2011 Mario Liverpool

Very useful indeed!

17 Jun 2011 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.

10 Jun 2011 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])

10 Jun 2011 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

16 May 2011 Claudio

Well done! Very useful!

29 Apr 2011 Dan  
Updates
20 Apr 2011

Figure resize now re-links legend and labeled objects with updated line/patch properties.

16 Jun 2011

bug fixes: fixed error in function resize if you delete the legend, fixed minor vertical alignment issue

24 Jun 2011

Fixed bug where very wide titles were not completely enclosed in legend box.

11 Apr 2013

Now respects manual changes made by user (including legend height, width, and units) when resizing.

10 Jul 2014

- updated for R2014b graphics

12 Aug 2014

Added parameter to modify padding between legend box and text/symbols

26 Aug 2014

Added note regarding legendflex with the Latex interpreter

Contact us