PHD Project - Driver energy prediction
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

353 lines
15 KiB

3 years ago
%% |legendflex.m|: a more flexible, customizable legend
% Author: Kelly Kearney
%
% This repository includes the code for the |legendflex.m| Matlab function,
% along with all dependent functions required to run it.
%
% This function offers a more flexible version of the legend command. It
% offers a different method of positioning the legend, 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
%
% This function should support all types of plot objects.
%
% *Legend positioning*
%
% Unlike in the default legend command, where the legend is positioned
% relative to the labeled objects' parent axis according to one of 16
% location strings, this function positions the legend based on two anchor
% points (one on either the figure or a child object of a figure, and one
% on the legend itself) and a buffer (or offset) between these two anchor
% points. The anchor points refer to the corners and centers of each
% side of the box surrounding the reference object and the legend itself;
% they can be refered to either as numbers (1-8, clockwise from northwest
% corner) or strings ('nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'). The
% position of the legend is determined by these two points and the distance
% between them, defined in the 'buffer' variable, which by default is
% measured in pixels. So the combination of
%
% (..., 'ref', gca, 'anchor', [3 3], 'buffer', [-10 -10])
%
% means that you want the northeast corner of the current axis to be
% aligned with the northeast corner of the legend, but with the legend
% shifted 10 pixels to the left and down.
%
% This method of positioning can be particularly useful when labeling a
% figure that includes many subplots that share a common color scheme,
% where the "best" location for a legend is not necessarily within the
% bounds of an axis. Unlike the legend command, the axes in the figure are
% never resized (and it is up to the user to check that the legend fits on
% the figure in the specified location). In addition to being easier than
% manually positioning a legend, this function updates the legend location
% when the figure is resized, preserving the desired alignment. The
% following anchor/buffer combinations, when used with the default
% reference and a buffer unit of pixels, approximately replicate the
% typical legend locations:
%
% Specifier Anchor Buffer
%
% north [2 2] [ 0 -10]
% south [6 6] [ 0 10]
% east [4 4] [-10 0]
% west [8 8] [ 10 0]
% northeast [3 3] [-10 -10]
% northwest [1 1] [ 10 -10]
% southeast [5 5] [-10 10]
% southwest [7 7] [ 10 10]
% northoutside* [2 6] [ 0 10]
% southoutside* [6 2] [ 0 -10]
% eastoutside* [3 8] [ 10 0]
% westoutside* [8 3] [-10 0]
% northeastoutside* [3 1] [ 10 0]
% northwestoutside* [1 3] [-10 0]
% southeastoutside* [5 7] [ 10 0]
% southwestoutside* [7 5] [-10 0]
%
% *placed outside axis rather than resizing plot box
%
%% Getting started
%
% *Prerequisites*
%
% This function requires Matlab R14 or later.
%
% *Downloading and installation*
%
% This code can be downloaded from <https://github.com/kakearney/legendflex-pkg/ Github>
% or the
% <http://www.mathworks.com/matlabcentral/fileexchange/31092
% MatlabCentral File Exchange>. The File Exchange entry is updated daily
% from the GitHub repository.
%
% *Matlab Search Path*
%
% The following folders need to be added to your Matlab Search path (via
% |addpath|, |pathtool|, etc.):
%
% legendflex-pkg/legendflex
% legendflex-pkg/setgetpos_V1.2
%% Syntax
%
% legendflex(M, param1, val1, ...)
% legendflex(h, M, param1, val1, ...)
% [legend_h,object_h,plot_h,text_str] = legendflex(...)
%
% Input variables:
%
% * |M|: cell array of strings, labels for legend
% * |h|: handle of axis or handle(s) of object(s) to be labeled. If
% this is an axis handle, all children of the axis will be
% included in the legend. If not included, current axis is
% used.
%
% Optional input variables (passed as parameter/value pairs): [default]
%
% * |ncol|: number of columns, or 0 to indicate as many as necessary
% given the # of labeled objects [1 if nrow is 0, 0
% otherwise]
% * |nrow|: number of rows, or 0 to indicate as many as necessary
% given the # of labeled objects [0]
% * |ref|: handle of object used to position the legend. This can be
% either a figure or a child object of a figure (and does not
% need to relate in any way to the objects being labeled).
% If not included, the reference will be to the axis that a
% normal legend would be associated with (usually the parent
% axis of the labeled objects, unless objects from multiple
% axes are passed, in which case it's the parent object of
% the first labeled object).
% * |anchor|: 1 x 2 array specifying which points of the reference object
% and new legend, respectively, to anchor to each other.
% Anchor points can be described using either numbers (in a 1
% x 2 double array) or directional strings (in a 1 x 2 cell
% array) as follows:
% 1 = 'nw' = upper left corner,
% 2 = 'n' = center of top edge,
% 3 = 'ne' = upper right corner,
% 4 = 'e' = center of right edge,
% 5 = 'se' = bottom right corner,
% 6 = 's' = center of bottom edge,
% 7 = 'sw' = bottom left corner,
% 8 = 'w' = center of left edge,
% [[3 3], i.e. {'ne' 'ne'}]
% * |buffer|: 1 x 2 array of horizontal and vertical distance,
% respectively, from the reference anchor point to the legend
% anchor point. Distance is measured in units specified by
% bufferunit. [[-10 -10]]
% * |bufferunit|: unit for buffer distance. Note that this property only
% affects the units used to position the legend, not the
% units for the legend itself (which is always a fixed size,
% based on the space needed to encapsulate the specified
% symbols and text). The 'normalized' units are normalized
% to size of the figure. ['pixels']
% * |box|: 'on' or 'off', specifies whether to enclose legend objects
% in a box ['on']
% * |xscale|: scalar value indicating scale factor to apply to the width
% required by each symbol, relative to the size used by
% legend. For example, 0.5 will shorten the lines/patches by
% half. [1]
% * |title|: A title string to be added inside the legend box, centered,
% above all legend entries. This can be either a string or a
% cell array of strings; the latter will produce a multi-line
% title. If empty, no title is added. ['']
% * |padding|: 1 x 3 array, pixel spacing added to beginning of each
% column (before symbol), between symbol and text, and after
% text, respectively. Usually, the default provides the
% spacing typical of a regular legend, but occassionally the
% extent properties wrap a little too close to text, making
% things look crowded; in these cases you can try unsquishing
% (or squishing, via use of negative values) things via this
% parameter. [2 1 1]
% * |nolisten|: logical scalar. If true, don't add the event listeners.
% The event listeners update the legend objects when you
% change a property of the labeled objects (such as line
% style, color, etc.). However, the updating requires the
% legend to be redrawn, which can really slow things down,
% especially if you're labelling lots of objects that get
% changed together (if you change the line width of 100
% labeled lines, the legend gets redrawn 100 times). In more
% recent releases, this also occurs when printing to file, so
% I recommend setting this to true if you plan to print a
% legend with a large number of labeled objects. The legend
% will still be redrawn on figure resize regardless of the
% value of this parameter. [false]
%
% In addition to these legendflex-specific parameters, this function will
% accept any parameter accepted by the original legend function (e.g.
% font properties) except 'location', 'boxon', 'boxoff', or 'hide'.
%
% Output variables:
%
% * |legend_h|: handle of the legend axis. It is not linked to an axis or
% graphics objects in the same way as a Matlab legend.
% However, on figure resize, all properties of the legend
% objects are checked for changes, so adjusting the figure
% size can re-link the legend to the labeled objects after
% you have made changes to those objects.
% * |object_h|: handles of the line, patch, and text graphics objects
% created in the legend
% * |plot_h|: handles of the lines and other objects labeled in this
% legend
% * |text_str|: cell array of the text strings used in the legend
%% Examples
%
% First, let's create a subplot with 10 lines, 5 solid and 5 dashed, which
% cycle through 5 colors:
figure('color','w');
for iax = 1:3
ax(iax) = subplot(2,2,iax);
end
linespec = [repmat({'r';'b';'g';'c';'m'},2,1), ...
[repmat({'-'}, 5, 1); repmat({'--'}, 5, 1)]];
x = [0 10];
y = (1:10)'*x;
lbl = cellstr(num2str((1:10)'));
hln(:,1) = plot(ax(1), x, y);
set(hln(:,1), {'color','linestyle'}, linespec);
%%
% Now add a legend in the upper left corner, with the entries arranged in a
% 4 x 3 grid so it doesn't interfere with the data. We've also decreased
% the horizontal space used by each legend line:
[hl(1).leg, hl(1).obj, hl(1).hout, hl(1).mout] = ...
legendflex(hln(:,1), lbl, 'anchor', {'nw','nw'}, ...
'buffer', [5 -5], ...
'ncol', 3, ...
'fontsize', 8, ...
'xscale', 0.8, ...
'box', 'off');
%%
% Plot the same lines in the second subplot. But this time, let's add two
% legends: one for color, and one for line style. Note that in this case,
% the second legend is positioned relative to the first, rather than
% relative to the axis itself:
hln(:,2) = plot(ax(2), x, y);
set(hln(:,2), {'color','linestyle'}, linespec);
[hl(2).leg, hl(2).obj, hl(2).hout, hl(2).mout] = ...
legendflex(hln(1:5,2), lbl(1:5), ...
'anchor', {'nw','nw'}, ...
'buffer', [5 -5], ...
'fontsize',8, ...
'xscale',0.5, ...
'title', 'Color');
[hl(3).leg, hl(3).obj, hl(3).hout, hl(3).mout] = ...
legendflex(hln([1 6],2), {'thing 1', 'thing 2'}, ...
'ref', hl(2).leg, ...
'anchor', {'ne','nw'}, ...
'buffer', [0 0], ...
'fontsize', 8', ...
'title', 'Line');
%%
% Our final subplot simply shows that this function will handle all object
% types. We plot a |contourf| plot overlaid with a |quiver| plot, and
% label both above the subplot axis.
%
% _Well, almost any graphics object. In 2014b, there are some rendering
% bugs when legend is called with multiple outputs that can cause weird
% stuff to happen when labeling contour objects; these sorts of issues may
% continue as the Mathworks updates their graphics further._
[X,Y] = meshgrid(-2:.2:2);
Z = X.*exp(-X.^2 - Y.^2);
[DX,DY] = gradient(Z,.2,.2);
axes(ax(3));
hold on;
[c,hcont] = contourf(X,Y,Z);
hquiv = quiver(X,Y,DX,DY);
[hl(4).leg, hl(4).obj, hl(4).hout, hl(4).mout] = ...
legendflex([hcont hquiv], {'contour', 'quiver'}, ...
'anchor',{'ne','se'}, ...
'buffer',[0, 0.01], ...
'bufferunit', 'normalized');
%% A note on legendflex with LateX
%
% Unfortunately, the Latex renderer doesn't play very nicely with
% legendflex. It's something that bugs me in my own work too, but I've
% never been able to come up with a good workaround that would position
% things properly. The legendflex function repositions everything using
% the 'Extent' property of all the text in the original legend. However,
% the extent property of latex-rendered text doesn't always match up with
% the actual space taken up by the text... not quite sure why this is, and
% therefore I don't have a reliable way to calculate what that real space
% is.
%
% Here's an example using plain text objects. Ideally, the red boxes would
% surround each text object, but in the Latex case, the Extent often leaves
% space above or below, or practically overlaps the text.
figure;
lax(1) = subplot(2,1,1);
lax(2) = subplot(2,1,2);
txt = {'Data 1', '$\frac{1}{2}$', '$var_{ij}^{k}$'};
nt = length(txt);
na = length(lax);
set(lax, 'xlim', [0 nt+1], 'ylim', [0 nt+1]);
for ii = 1:na
ht(ii,:) = text(1:nt,1:nt,txt, 'parent', lax(ii), ...
'interpreter', 'none', ...
'fontsize', 14);
end
set(ht(2,:), 'interpreter', 'latex');
for ii = 1:na
for it = 1:nt
ex = get(ht(ii,it), 'extent');
rectangle('position', ex, 'parent', lax(ii), 'edgecolor', 'r');
end
end
%%
% Becuase of this, you really need to play around with properties (like padding) in order
% to get a legendflex legend that uses latex and looks decent. Sometimes
% generating the legend first, then setting the latex rendering afterwards
% will help a bit. Other times I generate the legend using a larger font
% size, then shrink the text back down after it's been positioned. None of
% these hacks are ideal, but they're the best I've been able to come up
% with.
%% Contributions
%
% Community contributions to this package are welcome!
%
% To report bugs, please submit
% <https://github.com/kakearney/legendflex-pkg/issues an issue> on GitHub and
% include:
%
% * your operating system
% * your version of Matlab and all relevant toolboxes (type |ver| at the Matlab command line to get this info)
% * code/data to reproduce the error or buggy behavior, and the full text of any error messages received
%
% Please also feel free to submit enhancement requests, or to send pull
% requests (via GitHub) for bug fixes or new features.
%
% I do monitor the MatlabCentral FileExchange entry for any issues raised
% in the comments, but would prefer to track issues on GitHub.
%