diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1289f6e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# MATLAB binaries +*.mlx -crlf -diff -merge +*.mat -crlf -diff -merge +*.fig -crlf -diff -merge +*.p -crlf -diff -merge +*.slx -crlf -diff -merge +*.mdl -crlf -diff -merge + +# other binaries +*.png -crlf -diff -merge \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9364c54..be8cd74 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,4 @@ slprj/ # MATLAB App Files -*.mlapp - -# MATLAB figure files -*.fig \ No newline at end of file +*.mlapp \ No newline at end of file diff --git a/Readme.md b/Readme.md index e1c7c48..830d335 100644 --- a/Readme.md +++ b/Readme.md @@ -1,157 +1,190 @@ -[![MATLAB FEX](https://img.shields.io/badge/MATLAB%20FEX-legtools-brightgreen.svg)](http://www.mathworks.com/matlabcentral/fileexchange/57241-hg2-legend-tools) ![Minimum Version](https://img.shields.io/badge/Requires-R2014b%20%28v8.4%29-orange.svg) +[![MATLAB FEX](https://img.shields.io/badge/MATLAB%20FEX-legtools-brightgreen.svg)](http://www.mathworks.com/matlabcentral/fileexchange/57241-hg2-legend-tools) ![R2016b support](https://img.shields.io/badge/supports-R2016b%20(v9.1)-brightgreen.svg) ![Minimum Version](https://img.shields.io/badge/requires-R2014b%20(v8.4)-orange.svg) -# LEGTOOLS -`legtools` is a MATLAB class definition providing the user with a set of methods to modify existing Legend objects. +# `legtools` +`legtools` is a MATLAB class of methods to modify existing Legend objects. -This is an HG2 specific implementation and requires MATLAB R2014b or newer. +`legtools` requires MATLAB R2014b or newer. ## Methods -* [`append`](#append) - Add one or more entries to the end of the legend -* [`permute`](#permute) - Rearrange the legend entries -* [`remove`](#remove) - Remove one or more legend entries -* [`adddummy`](#adddummy) - Add legend entries for one or more unsupported graphics objects +Name | Description +----------|-------------- +[`append`](#append) | Append entries to legend +[`permute`](#permute) | Rearrange legend entries +[`remove`](#remove) | Remove entries from legend +[`adddummy`](#adddummy) | Add dummy entries to legend -### *legtools*.**append**(*legendhandle*, *newStrings*) -#### Description -Append string(s), `newStrings`, to the specified `Legend` object, `legendhandle`. `newStrings` can be a 1D character array or a 1D cell array of strings. Character arrays are treated as a single string. If multiple `Legend` objects are specified, only the first will be modified. - -The legend will only be updated with the new strings if the number of strings in the existing legend plus the number of strings in `newStrings` is the same as the number of plots on the associated `Axes` object (e.g. if you have 2 lineseries and 2 legend entries already no changes will be made). +### `legtools.`*`append`*`(lh, newStrings)` +#### Syntax +`legtools.append(lh, newStrings)` appends strings specified +by `newStrings` to the Legend object specified by `lh`. +`newStrings` can be a 1D character array or a 1D cell array +of strings. Character arrays are treated as a single +string. From MATLAB R2016b onwards the string data type is +also supported. If multiple `Legend` objects are specified +in `lh`, only the first will be modified. + +The total number of entries, i.e. the number of current +entries in the legend plus the number of entries in +`newStrings`, can exceed the number of graphics objects in +the axes. However, any extra entries to append will not be +added to the legend. For example, if you have plotted two +lines and the current legend contains one entry, appending +three new entries will only append the first of them. #### Examples -##### Adding one legend entry - % Sample data - x = 1:10; - y1 = x; - y2 = x + 1; - - % Plot a thing! - figure - plot(x, y1, 'ro'); - lh = legend('Circle', 'Location', 'NorthWest'); - - % Add a thing! - hold on - plot(x, y2, 'bs'); - legtools.append(lh, 'Square') - -![append1](https://github.com/sco1/sco1.github.io/blob/master/legtools/append1.png) +##### Append one legend entry +```matlab +% Plot a sine! +figure +fplot(@sin) +lh = legend('sine'); + +% Append to axes and legend! +hold on +fplot(@cos) +legtools.append(lh, 'cosine') +``` +![append1](../readme/img/append1.png) #### Adding two legend entries - - % Sample data - x = 1:10; - y1 = x; - y2 = x + 1; - y3 = x + 2; - - % Plot a thing! - figure - plot(x, y1, 'ro'); - lh = legend('Circle', 'Location', 'NorthWest'); - - % Add two things! - hold on - plot(x, y2, 'bs', x, y3, 'g+'); - legtools.append(lh, {'Square', 'Plus'}) - -![append2](https://github.com/sco1/sco1.github.io/blob/master/legtools/append2.png) +```matlab +% Plot a sine! +figure +fplot(@sin) +lh = legend('sine'); + +% Add two things! +hold on +fplot(@cos) +fplot(@tan) +legtools.append(lh, {'cosine', 'tangent'}) +``` +![append2](../readme/img/append2.png) -### *legtools*.**permute**(*legendhandle*, *newOrder*) -#### Description -Rearrange the entries of the specified `Legend` object, `legendhandle`, so they are in the order specified by the vector `newOrder`. `newOrder` must be the same length as the number of legend entries in `legendhandle`. All elements of order must be unique, real, positive, integer values. +### `legtools.`*`permute`*`(legendhandle, newOrder)` +#### Syntax +`legtools.permute(lh, order)` rarranges the entries of the +Legend object specified by `lh` in the order specified by +`order`. `order` must be a vector with the same number of +elements as the number of entries in the specified legend. +All elements in order must be unique, real and positive +integers. #### Example - % Sample data - x = 1:10; - y1 = x; - y2 = x + 1; - y3 = x + 2; - - % Plot a thing! - figure - plot(x, y1, 'ro', x, y2, 'bs', x, y3, 'g+'); - lh = legend({'One', 'Two', 'Three'}, 'Location', 'NorthWest'); - - legtools.permute(lh, [3, 1, 2]); - -![permute](https://github.com/sco1/sco1.github.io/blob/master/legtools/permute.png) +```matlab +% Plot a thing! +figure +fplot(@sin) +hold on +fplot(@cos) +fplot(@tan) +legend sine cosine tangent +lh = legend; + +% Rearrange legend entries! +legtools.permute(lh, [3, 1, 2]) +``` +![permute](../readme/img/permute.png) -### *legtools*.**remove**(*legendhandle*, *removeidx*) -#### Description -Remove the legend entries of the `Legend` object, `legendhandle`, at the locations specified by `removeidx`. All elements of `removeidx` must be real, positive, integer values. +### `legtools.`*`remove`*`(legendhandle, removeidx)` +#### Syntax +`legtools.remove(lhm, remidx)` removes the legend entries from +the legend specified in `lh` at the locations specified by +`remidx`. All elements of `remidx` must be real and positive +integers. -If `removeidx` specifies all the legend entries the `Legend` object, `legendhandle`, is deleted. - -If a legend entry to be removed is one generated by `legtools.adddummy`, its corresponding Chart Line Object will also be deleted. +If `remidx` specifies all the legend entries, the legend +object is deleted. #### Example - % Sample data - x = 1:10; - y1 = x; - y2 = x + 1; - y3 = x + 2; - - % Plot a thing! - figure - plot(x, y1, 'ro', x, y2, 'bs', x, y3, 'g+'); - lh = legend({'One', 'Two', 'Three'}, 'Location', 'NorthWest'); - - legtools.remove(lh, [3, 1]); - -![remove](https://github.com/sco1/sco1.github.io/blob/master/legtools/remove.png) +```matlab +% Plot a thing! +figure +fplot(@sin) +hold on +fplot(@cos) +fplot(@tan) +legend sine cosine tangent +lh = legend; + +% Remove entries one and three! +legtools.remove(lh, [3, 1]) +``` +![remove](../readme/img/remove.png) -### *legtools*.**addummy**(*legendhandle*, *newStrings*, *plotParams*) -#### Description -`adddummy` appends strings, `newStrings`, to the Legend Object, `lh`, for graphics objects that are not supported by `legend`. - -For a single dummy legend entry, `plotParams` is defined as a cell array of strings that follow MATLAB's `plot` syntax. Entries can be either a `LineSpec` or a series of Name/Value pairs. For multiple dummy legend entries, `plotParams` is defined as a cell array of cells where each top-level cell corresponds to a string in `newStrings`. - -`adddummy` adds a Chart Line Object to the parent axes of `lh` consisting of a single `NaN` value. Nothing is rendered in the axes but it provides a valid object for `legend` to include. `legtools.remove` will remove this Chart Line Object if its legend entry is removed. - +### `legtools.`*`adddummy`*`(legendhandle, newStrings, plotParams)` +#### Syntax +`legtools.adddummy(lh, newStrings)` appends strings, specified +by `newStrings`, to the Legend object, specified by `lh`, for +graphics objects that are not supported by legend. The +default line specification for a plot is used for the dummy +entries in the legend, i.e. a line. + +`legtools.adddummy(lh, newStrings, plotParams)` additionally +uses plot parameters specified in `plotParams` for the +creation of the dummy legend entries. + +The `plotParams` input argument can have multiple formats. +All formats are based on the LineSpec and Name-Value pair +arguments syntax of the built-in [`plot`](https://mathworks.com/help/matlab/ref/plot.html) function. `plotParams` +can be in the following formats (with example parameters): +- absent (like in the first syntax) +- empty, e.g. `''`, `[]` or `{}` +- one set of plot parameters for all dummy entries, e.g.: + - `legtools.adddummy(lh, newStrings, ':', 'Color' ,'red')`. This is the regular `plot` syntax. + - `legtools.adddummy(lh, newStrings, {'Color','red'})`. This is one set of plot parameters in a cell. +- two or more sets of plot parameters, e.g.: + - `legtools.adddummy(lh, newStrings, {'k'}, {'--b'})`. These are two sets of plot parameters, each in a cell. + - `legtools.adddummy(lh, newStrings, {{'r'}, {':m'}})`. These are two sets of plot parameters, each in a cell in a cell. + +For more than two dummies, the previous syntaxes can be +extended with additional sets of plot parameters. + +`legtools.adddummy` adds an invisible point to the parent +axes of the legend. More specifically, it adds a `Line` +object to the parent axes of `lh` consisting of a single `NaN` +value so nothing is visibly changed in the axes while +providing a valid object to include in the legend. + +`legtools.remove` deletes dummy `Line` objects when their +corresponding legend entries are removed. #### Examples -##### Add legend entry for single annotation - % Sample data - x = 1:10; - y1 = x; - - % Plot a thing! - figure - plot(x, y1); - lh = legend('My Data', 'Location', 'NorthWest'); - - % Add a box! - dim = [0.4 0.4 0.2 0.2]; - annotation('rectangle', dim, 'Color', 'red') - - % Add a legend entry for the box! - legtools.adddummy(lh, 'A Red Rectangle', {'Color', 'red'}) - -![addummy](https://github.com/sco1/sco1.github.io/blob/master/legtools/adddummy.png) - -##### Add legend entries for multiple annotations - - % Sample data - x = 1:10; - y = x; - - % Plot a thing! - plot(x, y); - lh = legend('My Data', 'Location', 'NorthWest'); - - % Add a box and a circle! - dim1 = [0.5 0.6 0.2 0.2]; - annotation('rectangle', dim1, 'Color', 'red') - dim2 = [0.3 0.4 0.2 0.2]; - annotation('ellipse', dim2, 'Color', 'green') - - % Add legend entries! - newStrings = {'A Red Rectangle', 'A Green Ellipse'}; - plotParams = {{'Color', 'Red'}, {'Color', 'green'}}; - legtools.adddummy(lh, newStrings, plotParams) - -![addummy2](https://github.com/sco1/sco1.github.io/blob/master/legtools/adddummy2.png) +##### Add dummy legend entry for single annotation +```matlab +% Plot a thing! +figure +fplot(@sin) +lh = legend('sin'); + +% Add a box! +dim = [0.4 0.4 0.2 0.2]; +annotation('rectangle', dim, 'Color', 'red') + +% Add a legend entry for the box! +legtools.adddummy(lh, 'rectangle', 'Color', 'red') +``` +![addummy](../readme/img/adddummy1.png) + +##### Add dummy legend entries for multiple annotations +```matlab +% Plot a thing! +fplot(@sin) +lh = legend('sine'); + +% Add a box and a circle! +dim1 = [0.5 0.6 0.2 0.2]; +annotation('rectangle', dim1, 'Color', 'red') +dim2 = [0.3 0.4 0.2 0.2]; +annotation('ellipse', dim2, 'Color', 'green') + +% Add legend entries! +newStrings = {'rectangle', 'ellipse'}; +plotParams = {{'Color', 'red'}, {'g'}}; +legtools.adddummy(lh, newStrings, plotParams) +``` +![addummy2](../readme/img/adddummy2.png) diff --git a/legtools.m b/legtools.m index 7a09afd..e335b9a 100644 --- a/legtools.m +++ b/legtools.m @@ -1,253 +1,420 @@ classdef legtools - % LEGTOOLS is a MATLAB class definition providing the user with a set of - % methods to modify existing Legend objects. + %LEGTOOLS A class of methods to modify existing Legend objects. % - % This is an HG2 specific implementation and requires MATLAB R2014b or - % newer. + % LEGTOOLS requires MATLAB R2014b or newer. % - % legtools methods: - % append - Add one or more entries to the end of the legend - % permute - Rearrange the legend entries - % remove - Remove one or more legend entries - % adddummy - Add one or more entries to the legend for unsupported graphics objects + % LEGTOOLS methods: + % append - Append entries to legend + % permute - Rearrange legend entries + % remove - Remove entries from legend + % adddummy - Add dummy entries to legend % - % See also legend + % See also legend methods function obj = legtools % Dummy constructor so we don't return an empty class instance clear obj - end - end + end % of constructor + end % of methods methods (Static) function append(lh, newStrings) - % APPEND appends strings, newStrings, to the specified Legend - % object, lh. newStrings can be a 1D character array or a 1D - % cell array of strings. Character arrays are treated as a - % single string. If multiple Legend objects are specified, only - % the first will be modified. + %LEGTOOLS.APPEND Append entries to legend % - % The legend will only be updated with the new strings if the - % number of strings in the existing legend plus the number of - % strings in newStrings is the same as the number of plots on - % the associated axes object (e.g. if you have 2 lineseries and - % 2 legend entries already no changes will be made). - legtools.verchk() - lh = legtools.handlecheck('append', lh); + % LEGTOOLS.APPEND(lh,newStrings) appends strings specified + % by newStrings to the Legend object specified by lh. + % newStrings can be a 1D character array or a 1D cell array + % of strings. Character arrays are treated as a single + % string. From MATLAB R2016b onwards the string data type is + % also supported. If multiple Legend objects are specified + % in lh, only the first will be modified. + % + % The total number of entries, i.e. the number of current + % entries in the legend plus the number of entries in + % newStrings, can exceed the number of graphics objects in + % the axes. However, any extra entries to append will not be + % added to the legend. For example, if you have plotted two + % lines and the current legend contains one entry, appending + % three new entries will only append the first of them. - % Make sure newString exists & isn't empty - if ~exist('newStrings', 'var') || isempty(newStrings) - error('legtools:append:EmptyStringInput', ... - 'No strings provided' ... - ); - end + % Check number of input arguments + narginchk(2,2) + + % Check MATLAB version + legtools.verchk + + % Check legend handle + lh = legtools.handlecheck('append', lh); + % Check new strings newStrings = legtools.strcheck('append', newStrings); % To make sure we target the right axes, pull the legend's % PlotChildren and get their parent axes object - parentaxes = lh.PlotChildren(1).Parent; + ax = lh.PlotChildren(1).Parent; - % Get line object handles - plothandles = flipud(parentaxes.Children); % Flip so order matches + % Get graphics object handles + axchildren = flip(ax.Children); % Flip so order matches + legchildren = lh.PlotChildren; - % Update legend with line object handles & new string array - newlegendstr = [lh.String newStrings]; % Need to generate this before adding new plot objects - lh.PlotChildren = plothandles; - lh.String = newlegendstr; - end - + % Sort the children of the future legend object in an order + % depending on current legend PlotChildren property, because + % this may not be in the same order as axchildren, e.g. after + % permuting the legend entries + [~,~,icurrent] = intersect(legchildren,axchildren,'stable'); + [~,idiff] = setdiff(axchildren,legchildren,'stable'); + ifuture = [icurrent;idiff]; + axchildren = axchildren(ifuture); + + % Strings desired order for future legend + newstr = [lh.String, newStrings]; + + % Update legend with graphics object handles & new string array + lh.PlotChildren = axchildren; + lh.String = newstr; + end % of append method function permute(lh, order) - % PERMUTE rearranges the entries of the specified Legend - % object, lh, so they are then the order specified by the - % vector order. order must be the same length as the number of - % legend entries in lh. All elements of order must be unique, - % real, positive, integer values. - legtools.verchk() + %LEGTOOLS.PERMUTE Rearrange legend entries + % + % LEGTOOLS.PERMUTE(lh,order) rarranges the entries of the + % Legend object specified by lh in the order specified by + % order. order must be a vector with the same number of + % elements as the number of entries in the specified legend. + % All elements in order must be unique, real and positive + % integers. + + % Check number of input arguments + narginchk(2,2) - % TODO: Add check for presence of order + % Check MATLAB version + legtools.verchk + % Check legend handle lh = legtools.handlecheck('permute', lh); % Catch length & uniqueness issues with order, let MATLAB deal - % with the rest. - if numel(order) ~= numel(lh.String) - error('legtools:permute:TooManyIndices', ... - 'Number of values in order must match the number of legend strings' ... - ); - end + % with the rest + assert( ... + numel(order) == numel(lh.String), ... + 'legtools:permute:TooManyIndices', ... + ['Number of values in order must match number ' ... + 'of legend strings.'] ... + ) - if numel(unique(order)) < numel(lh.String) - error('legtools:permute:NotEnoughUniqueIndices', ... - 'order must contain enough unique indices to index all legend strings' ... - ); - end + assert( ... + numel(unique(order)) == numel(lh.String), ... + 'legtools:permute:NotEnoughUniqueIndices', ... + ['Input argument order must contain enough unique ' ... + 'indices to index all legend strings.'] ... + ) % Permute the legend data source(s) and string(s) % MATLAB has a listener on the PlotChildren so when their order % is modified the string order is changed with it lh.PlotChildren = lh.PlotChildren(order); - end - + end % of permute method function remove(lh, remidx) - % REMOVE removes the legend entries of the legend object, lh, - % at the locations specified by remidx. All elements of remidx - % must be real, positive, integer values. + %LEGTOOLS.REMOVE Remove entries from legend % - % If remidx specifies all the legend entries, the legend - % object is deleted - legtools.verchk() + % LEGTOOLS.REMOVE(lhm,remidx) removes the legend entries from + % the legend specified in lh at the locations specified by + % remidx. All elements of remidx must be real and positive + % integers. + % + % If remidx specifies all the legend entries, the legend + % object is deleted. + + % Check number of input arguments + narginchk(2,2) + + % Check MATLAB version + legtools.verchk + + % Check legend handle lh = legtools.handlecheck('remove', lh); % Catch length issues, let MATLAB deal with the rest - if numel(unique(remidx)) > numel(lh.String) - error('legtools:remove:TooManyIndices', ... - 'Number of unique values in remidx exceeds number of legend entries' ... - ); - end + assert( ... + numel(unique(remidx)) <= numel(lh.String), ... + 'legtools:remove:TooManyIndices', ... + ['Number of unique values in remidx exceeds ' ... + 'number of legend entries.'] ... + ) + + assert( ... + max(remidx) <= numel(lh.String), ... + 'legtools:remove:BadSubscript', ... + 'Index in remidx exceeds number of legend entries.' ... + ) + % Remove specified legend entries if numel(unique(remidx)) == numel(lh.String) - delete(lh); - warning('legtools:remove:LegendDeleted', ... - 'All legend entries specified for removal, deleting Legend Object' ... - ); + delete(lh) else - % Check legend entries to be removed for dummy lineseries + % Check legend entries to be removed for dummy graphics % objects and delete them - count = 1; - for ii = remidx - % Our dummy lineseries contain a single NaN YData entry - if length(lh.PlotChildren(ii).YData) == 1 && isnan(lh.PlotChildren(ii).YData) + lc = lh.PlotChildren; + obj2delete = gobjects(numel(remidx)); + for ii = numel(remidx):-1:1 + ir = remidx(ii); + % Our dummy lineseries have the UserData property set + % to 'legtools.dummy' + if strcmp(lc(ir).UserData,'legtools.dummy') % Deleting the graphics object here also deletes it % from the legend, which screws up the one-liner % plot children removal. Instead store the objects % to be deleted and delete them after the legend is % properly modified - objtodelete(count) = lh.PlotChildren(ii); - count = count + 1; + obj2delete(ii) = lc(ir); end end lh.PlotChildren(remidx) = []; - delete(objtodelete); + delete(obj2delete); end - end + end % of remove method - function adddummy(lh, newStrings, plotParams) - % ADDDUMMY appends strings, newStrings, to the Legend Object, - % lh, for graphics objects that are not supported by legend. + function adddummy(lh, newStrings, varargin) + %LEGTOOLS.ADDDUMMY Add dummy entries to legend % - % For a single dummy legend entry, plotParams is defined as a - % cell array of strings that follow MATLAB's PLOT syntax. - % Entries can be either a LineSpec or a series of Name/Value - % pairs. For multiple dummy legend entries, plotParams is - % defined as a cell array of cells where each top-level cell - % corresponds to a string in newStrings. + % LEGTOOLS.ADDDUMMY(lh,newStrings) appends strings, specified + % by newStrings, to the Legend object, specified by lh, for + % graphics objects that are not supported by legend. The + % default line specification for a plot is used for the dummy + % entries in the legend, i.e. a line. % - % ADDDUMMY adds a Chart Line Object to the parent axes of lh - % consisting of a single NaN value so nothing is rendered in - % the axes but it provides a valid object for legend to include + % LEGTOOLS.ADDDUMMY(lh,newStrings,plotParams) additionally + % uses plot parameters specified in plotParams for the + % creation of the dummy legend entries. % - % LEGTOOLS.REMOVE will remove this Chart Line Object if its - % legend entry is removed. - - legtools.verchk() - lh = legtools.handlecheck('addummy', lh); - - % Make sure newStrings exists & isn't empty - if ~exist('newStrings', 'var') || isempty(newStrings) - error('legtools:adddummy:EmptyStringInput', ... - 'No string provided' ... - ); - end + % The plotParams input argument can have multiple formats. + % All formats are based on the LineSpec and Name-Value pair + % arguments syntax of the built-in plot function. plotParams + % can be in the following formats (with example parameters): + % - absent (like in the first syntax) + % - empty, e.g. '', [] or {} + % - one set of plot parameters for all dummy entries, e.g.: + % - LEGTOOLS.ADDDUMMY(lh, newStrings, ':', 'Color' ,'red') + % This is the regular plot syntax. + % - LEGTOOLS.ADDDUMMY(lh, newStrings, {'Color','red'}) + % This is one set of plot parameters in a cell. + % - two or more sets of plot parameters, e.g.: + % - LEGTOOLS.ADDDUMMY(lh, newStrings, {'k'}, {'--b'}) + % These are two sets of plot parameters, each in a cell. + % - LEGTOOLS.ADDDUMMY(lh, newStrings, {{'r'}, {':m'}}) + % These are two sets of plot parameters, each in a cell + % in a cell. + % For more than two dummies, the previous syntaxes can be + % extended with additional sets of plot parameters. + % + % LEGTOOLS.ADDDUMMY adds an invisible point to the parent + % axes of the legend. More specifically, it adds a Line + % object to the parent axes of lh consisting of a single NaN + % value so nothing is visibly changed in the axes while + % providing a valid object to include in the legend. + % + % LEGTOOLS.REMOVE deletes dummy Line objects when their + % corresponding legend entries are removed. - newStrings = legtools.strcheck('adddummy', newStrings); + % Check number of input arguments + narginchk(2,inf) - % See if we have a character input for the single addition case - % and put it into a cell - if ischar(plotParams) - plotParams = {{plotParams}}; - end + % Check MATLAB version + legtools.verchk + + % Check legend handle + lh = legtools.handlecheck('adddummy', lh); - % TODO: More plotParams error checking + % Check new strings + newStrings = legtools.strcheck('adddummy', newStrings); + nnew = numel(newStrings); + + % Check and set plot parameters + plotParams = legtools.checkPlotParams(varargin,nnew); parentaxes = lh.PlotChildren(1).Parent; - hold(parentaxes, 'on'); - for ii = 1:length(newStrings) - plot(parentaxes, NaN, plotParams{ii}{:}); % Leave input validation up to plot + + % Hold parent axes + if ishold(parentaxes) + washold = true; + else + washold = false; + hold(parentaxes, 'on'); end - hold(parentaxes, 'off'); - legtools.append(lh, newStrings); % Add legend entries - end - - end + for ii = 1:nnew + plot(parentaxes, NaN, ... + plotParams{ii}{:}, ... % Leave validation up to plot + 'UserData', 'legtools.dummy') + end + + % Restore previous hold state + if ~washold, hold(parentaxes, 'off'); end + + % Append dummy entries to legend + legtools.append(lh, newStrings); + end % of adddummy method + end % of Static methods methods (Static, Access = private) - function verchk() + function verchk % Throw error if we're not using R2014b or newer if verLessThan('matlab','8.4') error('legtools:UnsupportedMATLABver', ... - 'MATLAB releases prior to R2014b are not supported' ... - ); + 'MATLAB releases prior to R2014b are not supported.') end - end + end % of verchk method - function [lh] = handlecheck(src, lh) + function lh = handlecheck(src, lh) % Make sure lh exists and is a legend object - if ~isa(lh, 'matlab.graphics.illustration.Legend') - msgID = sprintf('legtools:%s:InvalidLegendHandle', src); - error(msgID, 'Invalid legend handle provided'); - end + assert( ... + ~isempty(lh) && isgraphics(lh,'legend') && isvalid(lh), ... + sprintf('legtools:%s:InvalidLegendHandle', src), ... + 'Invalid legend handle provided.' ... + ) - % Pick first legend handle if more than one is passed + % Keep first legend handle if more than one is passed if numel(lh) > 1 - msgID = sprintf('legtools:%s:TooManyLegends', src); - warning(msgID, ... - '%u Legend objects specified, modifying the first one only', ... - numel(lh) ... - ); + warning( ... + sprintf('legtools:%s:TooManyLegends', src), ... + ['%u Legend objects specified, ' ... + 'modifying the first one only.'], ... + numel(lh) ... + ) lh = lh(1); end - end + end % of handlecheck method - function [newString] = strcheck(src, newString) + function newString = strcheck(src, newString) + % Make sure newString exists & isn't empty + assert( ... + ~isempty(newString), ... + 'legtools:append:EmptyStringInput', ... + 'No strings provided.' ... + ) + % Validate the input strings if ischar(newString) % Input string is a character array, assume it's a single % string and dump into a cell - newString = {newString}; + newString = cellstr(newString); end - % Check to see if we now have a cell array - if ~iscell(newString) - msgID = sprintf('legtools:%s:InvalidLegendString', src); - error(msgID, ... - 'Invalid Data Type Passed: %s\n\nData must be of type(s): %s, %s', ... - class(newString), class({}), class('') ... - ); + % Message identifier for cellstr assertion below + msgID = sprintf('legtools:%s:InvalidLegendString', src); + + % Check MATLAB version for support for string class + if verLessThan('matlab','9.1') + msgArgs = { ... + ['Invalid data type passed: '... + '%s\nData must be any of the following types: ' ... + '%s, %s'], ... + class(newString), class(cell.empty), class(char) ... + }; + else + % MATLAB R2016b and newer support the string data type + % Force conversion to cell array of strings + newString = cellstr(newString); + msgArgs = { ... + ['Invalid data type passed: %s\n'... + 'Data must be any of the following types: ' ... + '%s, %s, %s'], ... + class(newString), ... + class(string), class(cell.empty), class(char) ... + }; end + % Check if we now have a cell array of strings + assert(iscellstr(newString), msgID, msgArgs{:}) + % Check shape of newStrings and make sure it's 1D - if size(newString, 1) > 1 - newString = reshape(newString', 1, []); - end + newString = newString(:)'; + end % of strcheck method + + function plotParams = checkPlotParams(plotParams,nnew) + % Check plot parameter set format in plotParams, can be: + % - empty ({}, [], '', etc.) + % - one set + % - in plot syntax + % - in a cell + % - two or more sets + % - in one cell + % - in two or more cells - % Check to make sure we're only passing strings - for ii = 1:length(newString) - % Check for characters, let MATLAB handle errors for data - % types not compatible with num2str - if ~ischar(newString{ii}) - msgID = sprintf('legtools:%s:ConvertingInvalidLegendString', src); - warning(msgID, ... - 'Input legend ''string'' is of type %s, converting to %s', ... - class(newString{ii}), class('') ... - ); - newString{ii} = num2str(newString{ii}); + if nnew>1 + if isempty(plotParams) + % plotParams is an empty set of plot parameters for all new + % dummy entries + plotParams = repmat({{}},1,nnew); + else + % plotParams is not empty, check for one or more sets + if numel(plotParams)==1 + % check if cell or string + if iscellstr(plotParams) + % plotParams is one set in string format, + % repeat nnew times in cell format + plotParams = repmat({plotParams},1,nnew); + elseif iscellstr(plotParams{1}(1)) + % plotParams is one set in cell format, repeat + % nnew times as is + plotParams = repmat(plotParams,1,nnew); + else + % plotParams contains one or more sets in cell + % format + if numel(plotParams{1})==1 + % plotParams contains one set, repeat nnew + % times + plotParams = repmat(plotParams{1},1,nnew); + else + % plotParams contains multiple sets in + % cells, uncell them + plotParams = plotParams{:}; + assert( ... + numel(plotParams) == nnew, ... + 'legtools:adddummy:TooManyPlotParamSets', ... + 'Too many plot parameter sets specified.' ... + ) + end + end + else + % plotParams may be one set of plot parameters in a + % cell array, or more than one set, each in a cell + if iscellstr(plotParams(1)) + % plotParams is one set, repeat nnew times + plotParams = repmat({plotParams},1,nnew); + else + % plotParams contains more than one set in + % cells, so do nothing, but assert the number + % is correct + assert( ... + numel(plotParams) == nnew, ... + 'legtools:adddummy:TooManyPlotParamSets', ... + 'Too many plot parameter sets specified.' ... + ) + end + end + end + else + % In the single addition case, the input may be a cell of + % strings + if iscellstr(plotParams) + % Single addition case, plotParams contains a character + % array + plotParams = {plotParams}; + end + + % Make sure plotParams is in cell format if it is empty + if isempty(plotParams{1}) + plotParams = {{}}; + end + + % Make sure plotParams is a cell of cells + if ~iscell(plotParams{1}) + plotParams = {plotParams}; end end - end - end + end % of checkPlotParams method + end % of Static, Access = private methods end \ No newline at end of file