Mercurial > octave
view scripts/plot/appearance/legend.m @ 30875:5d3faba0342e
doc: Ensure documentation lists output argument when it exists for all m-files.
For new users of Octave it is best to show explicit calling forms
in the documentation and to show a return argument when it exists.
* bp-table.cc, shift.m, accumarray.m, accumdim.m, bincoeff.m, bitcmp.m,
bitget.m, bitset.m, blkdiag.m, celldisp.m, cplxpair.m, dblquad.m, flip.m,
fliplr.m, flipud.m, idivide.m, int2str.m, interpft.m, logspace.m, num2str.m,
polyarea.m, postpad.m, prepad.m, randi.m, repmat.m, rng.m, rot90.m, rotdim.m,
structfun.m, triplequad.m, uibuttongroup.m, uicontrol.m, uipanel.m,
uipushtool.m, uitoggletool.m, uitoolbar.m, waitforbuttonpress.m, help.m,
__additional_help_message__.m, hsv.m, im2double.m, im2frame.m, javachk.m,
usejava.m, argnames.m, char.m, formula.m, inline.m, __vectorize__.m, findstr.m,
flipdim.m, strmatch.m, vectorize.m, commutation_matrix.m, cond.m, cross.m,
duplication_matrix.m, expm.m, orth.m, rank.m, rref.m, trace.m, vech.m, cast.m,
compare_versions.m, delete.m, dir.m, fileattrib.m, grabcode.m, gunzip.m,
inputname.m, license.m, list_primes.m, ls.m, mexext.m, movefile.m,
namelengthmax.m, nargoutchk.m, nthargout.m, substruct.m, swapbytes.m, ver.m,
verLessThan.m, what.m, fminunc.m, fsolve.m, fzero.m, optimget.m, __fdjac__.m,
matlabroot.m, savepath.m, campos.m, camroll.m, camtarget.m, camup.m, camva.m,
camzoom.m, clabel.m, diffuse.m, legend.m, orient.m, rticks.m, specular.m,
thetaticks.m, xlim.m, xtickangle.m, xticklabels.m, xticks.m, ylim.m,
ytickangle.m, yticklabels.m, yticks.m, zlim.m, ztickangle.m, zticklabels.m,
zticks.m, ellipsoid.m, isocolors.m, isonormals.m, stairs.m, surfnorm.m,
__actual_axis_position__.m, __pltopt__.m, close.m, graphics_toolkit.m, pan.m,
print.m, printd.m, __ghostscript__.m, __gnuplot_print__.m, __opengl_print__.m,
rotate3d.m, subplot.m, zoom.m, compan.m, conv.m, poly.m, polyaffine.m,
polyder.m, polyint.m, polyout.m, polyreduce.m, polyvalm.m, roots.m, prefdir.m,
prefsfile.m, profexplore.m, profexport.m, profshow.m, powerset.m, unique.m,
arch_rnd.m, arma_rnd.m, autoreg_matrix.m, bartlett.m, blackman.m, detrend.m,
durbinlevinson.m, fftconv.m, fftfilt.m, fftshift.m, fractdiff.m, hamming.m,
hanning.m, hurst.m, ifftshift.m, rectangle_lw.m, rectangle_sw.m, triangle_lw.m,
sinc.m, sinetone.m, sinewave.m, spectral_adf.m, spectral_xdf.m, spencer.m,
ilu.m, __sprand__.m, sprand.m, sprandn.m, sprandsym.m, treelayout.m, beta.m,
betainc.m, betaincinv.m, betaln.m, cosint.m, expint.m, factorial.m, gammainc.m,
gammaincinv.m, lcm.m, nthroot.m, perms.m, reallog.m, realpow.m, realsqrt.m,
sinint.m, hadamard.m, hankel.m, hilb.m, invhilb.m, magic.m, pascal.m, rosser.m,
toeplitz.m, vander.m, wilkinson.m, center.m, corr.m, cov.m, discrete_cdf.m,
discrete_inv.m, discrete_pdf.m, discrete_rnd.m, empirical_cdf.m,
empirical_inv.m, empirical_pdf.m, empirical_rnd.m, kendall.m, kurtosis.m,
mad.m, mean.m, meansq.m, median.m, mode.m, moment.m, range.m, ranks.m,
run_count.m, skewness.m, spearman.m, statistics.m, std.m, base2dec.m,
bin2dec.m, blanks.m, cstrcat.m, deblank.m, dec2base.m, dec2bin.m, dec2hex.m,
hex2dec.m, index.m, regexptranslate.m, rindex.m, strcat.m, strjust.m,
strtrim.m, strtrunc.m, substr.m, untabify.m, __have_feature__.m,
__prog_output_assert__.m, __run_test_suite__.m, example.m, fail.m, asctime.m,
calendar.m, ctime.m, date.m, etime.m:
Add return arguments to @deftypefn macros where they were missing. Rename
variables in functions (particularly generic "retval") to match documentation.
Rename some return variables for (hopefully) better clarity (e.g., 'ax' to 'hax'
to indicate it is a graphics handle to an axes object).
author | Rik <rik@octave.org> |
---|---|
date | Wed, 30 Mar 2022 20:40:27 -0700 |
parents | 1aa0456ecb18 |
children | 472df5147221 |
line wrap: on
line source
######################################################################## ## ## Copyright (C) 2010-2022 The Octave Project Developers ## ## See the file COPYRIGHT.md in the top-level directory of this ## distribution or <https://octave.org/copyright/>. ## ## This file is part of Octave. ## ## Octave is free software: you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation, either version 3 of the License, or ## (at your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, see ## <https://www.gnu.org/licenses/>. ## ######################################################################## ## -*- texinfo -*- ## @deftypefn {} {} legend () ## @deftypefnx {} {} legend @var{command} ## @deftypefnx {} {} legend (@var{str1}, @var{str2}, @dots{}) ## @deftypefnx {} {} legend (@var{charmat}) ## @deftypefnx {} {} legend (@{@var{cellstr}@}) ## @deftypefnx {} {} legend (@dots{}, @var{property}, @var{value}, @dots{}) ## @deftypefnx {} {} legend (@var{hobjs}, @dots{}) ## @deftypefnx {} {} legend ("@var{command}") ## @deftypefnx {} {} legend (@var{hax}, @dots{}) ## @deftypefnx {} {} legend (@var{hleg}, @dots{}) ## @deftypefnx {} {@var{hleg} =} legend (@dots{}) ## ## Display a legend for the current axes using the specified strings as labels. ## ## Legend entries may be specified as individual character string arguments, ## a character array, or a cell array of character strings. When label names ## might be confused with legend properties, or @var{command} arguments, ## the labels should be protected by specifying them as a cell array of ## strings. ## ## If the first argument @var{hax} is an axes handle, then add a legend to this ## axes, rather than the current axes returned by @code{gca}. ## ## If the first argument @var{hleg} is a legend handle, then operate on this ## legend rather than the legend of the current axes. ## ## Legend labels are associated with the axes' children; The first label is ## assigned to the first object that was plotted in the axes, the second label ## to the next object plotted, etc. To label specific data objects, without ## labeling all objects, provide their graphic handles in the input ## @var{hobjs}. ## ## The following customizations are available using @var{command}: ## ## @table @asis ## @item @qcode{"show"} ## Show legend on the plot ## ## @item @qcode{"hide"} ## Hide legend on the plot ## ## @item @qcode{"toggle"} ## Toggle between @qcode{"hide"} and @qcode{"show"} ## ## @item @qcode{"boxon"} ## Show a box around legend (default) ## ## @item @qcode{"boxoff"} ## Hide the box around legend ## ## @item @qcode{"right"} ## Place label text to the right of the keys (default) ## ## @item @qcode{"left"} ## Place label text to the left of the keys ## ## @item @qcode{"off"} ## Delete the legend object ## @end table ## ## The @code{legend} function creates a graphics object which has various ## properties that can be manipulated with @code{get}/@code{set}. ## Alternatively, properties can be set directly when calling @code{legend} by ## including @var{property}/@var{value} pairs. If using this calling form, the ## labels must be specified as a cell array of strings. Graphics object ## properties are documented in detail at @ref{Graphics Object Properties}. ## ## Following is a subset of supported legend properties: ## @c The following table is obtained by copying the output of ## @c genpropdoc ("legend", "", {"autoupdate", "box", "location", "numcolumns", "orientation", "string", "textcolor"}) ## ## @table @asis ## ## @item @code{autoupdate}: @qcode{"off"} | @{@qcode{"on"}@} ## Control whether the number of legend items is updated automatically when ## objects are added to (or deleted from) the peer axes. For example: ## ## @example ## @group ## ## Create a single plot with its legend. ## figure (); ## plot (1:10); ## legend ("Slope 1"); ## ## Add another plot and specify its displayname so that ## ## the legend is correctly updated. ## hold on; ## plot ((1:10) * 2, "displayname", "Slope 2"); ## ## Stop automatic updates for further plots. ## legend ("autoupdate", "off"); ## plot ((1:10) * 3); ## @end group ## @end example ## ## @item @code{box}: @qcode{"off"} | @{@qcode{"on"}@} ## Control whether the legend has a surrounding box. ## ## @item @code{location}: @qcode{"best"} | @qcode{"bestoutside"} | ## @qcode{"east"} | @qcode{"eastoutside"} | @qcode{"none"} | @qcode{"north"} | ## @{@qcode{"northeast"}@} | @qcode{"northeastoutside"} | ## @qcode{"northoutside"} | @qcode{"northwest"}| @qcode{"northwestoutside"} | ## @qcode{"south"} | @qcode{"southeast"} | @qcode{"southeastoutside"} | ## @qcode{"southoutside"} | @qcode{"southwest"} | @qcode{"southwestoutside"} | ## @qcode{"west"} | @qcode{"westoutside"} ## Control the location of the legend. ## ## @item @code{numcolumns}: scalar interger, def. @code{1} ## Control the number of columns used in the layout of the legend items. ## For example: ## ## @example ## @group ## figure (); ## plot (rand (30)); ## legend ("numcolumns", 3); ## @end group ## @end example ## ## Setting @code{numcolumns} also forces the @code{numcolumnsmode} property ## to be set to @qcode{"manual"}. ## ## @item @code{orientation}: @qcode{"horizontal"} | @{@qcode{"vertical"}@} ## Control whether the legend items are arranged vertically (column-wise) or ## horizontally (row-wise). ## ## @item @code{string}: string | cell array of strings ## List of labels for the legend items. For example: ## ## @example ## @group ## figure (); ## plot (rand (20)); ## ## Let legend choose names automatically ## hl = legend (); ## ## Selectively change some names ## str = get (hl, "string"); ## str(1:5:end) = "Garbage"; ## set (hl, "string", str); ## @end group ## @end example ## ## @item @code{textcolor}: colorspec, def. @code{[0 0 0]} ## Control the color of the text strings for legend item. ## ## @end table ## ## The full list of supported legend specific properties can be found at ## @ref{Legend Properties, , Legend Properties}. ## ## A legend is implemented as an additional axes object with the @code{tag} ## property set to @qcode{"legend"}. Properties of the legend object may be ## manipulated directly by using @code{set}. ## ## The optional output value @var{hleg} is a handle to the legend object. ## ## Implementation Note: The legend label text is either provided in the call to ## @code{legend} or is taken from the @code{DisplayName} property of the ## graphics objects. Only data objects, such as line, patch, and surface, have ## this property whereas axes, figures, etc.@: do not so they are never present ## in a legend. If no labels or @code{DisplayName} properties are available, ## then the label text is simply @qcode{"data1"}, @qcode{"data2"}, @dots{}, ## @nospell{@qcode{"dataN"}}. ## ## The legend @code{FontSize} property is initially set to 90% of the axes ## @code{FontSize} to which it is attached. Use @code{set} to override this ## if necessary. ## @end deftypefn function [hleg, hleg_obj, hplot, labels] = legend (varargin) ## Use the old legend code to handle gnuplot toolkit if (strcmp (graphics_toolkit (), "gnuplot")) if (nargout > 0) [hleg, hleg_obj, hplot, labels] = __gnuplot_legend__ (varargin{:}); else __gnuplot_legend__ (varargin{:}); endif return; endif ## FIXME: This function needs to be locked to avoid bug #59439. Remove this ## lock once that bug is properly fixed. mlock (); opts = parse_opts (varargin{:}); hl = opts.legend_handle; ## Fix property/value pairs pval = opts.propval(:)'; if (! isempty (opts.action)) do_set_box = isempty (hl); switch (opts.action) case "boxoff" tmp_pval = {"box", "off"}; do_set_box = false; case "boxon" tmp_pval = {"box", "on"}; do_set_box = false; case "hide" tmp_pval = {"visible", "off"}; case "show" tmp_pval = {"visible", "on"}; case "toggle" if (! isempty (hl)) if (strcmp (get (hl, "visible"), "on")) tmp_pval = {"visible", "off"}; else tmp_pval = {"visible", "on"}; endif endif case "left" tmp_pval = {"textposition", "left"}; case "right" tmp_pval = {"textposition", "right"}; case "off" if (! isempty (hl)) delete (hl); endif return; endswitch pval = [tmp_pval, pval]; if (do_set_box) pval = [pval, "box", "on"]; endif endif if (isempty (hl)) hl = axes ("parent", get (opts.axes_handles(1), "parent"), ... "tag", "legend", "handlevisibility", "off", ... "ydir", "reverse", "position", [.5 .5 .3 .3], ... "fontsize", 0.9 * get (opts.axes_handles(1), "fontsize"), ... "clim", get (opts.axes_handles(1), "clim"), ... "colormap", get (opts.axes_handles(1), "colormap"), ... "xtick", [], "ytick", [], "box", "on"); ## FIXME: Use the axes appdata to store its peer legend handle ## rather that adding a public property and change all uses. for htmp = opts.axes_handles try addproperty ("__legend_handle__", htmp, "handle", hl); catch set (htmp, "__legend_handle__", hl); end_try_catch endfor ## Add and update legend specific properties addproperties (hl); try set (hl, "string", opts.obj_labels, pval{:}); catch ee delete (hl); set (opts.axes_handles, "__legend_handle__", []); rethrow (ee); end_try_catch ## Update legend layout setappdata (hl, "__axes_handle__", opts.axes_handles, "__next_label_index__", opts.next_idx, "__peer_objects__", opts.obj_handles); update_location_cb (hl, [], false); update_edgecolor_cb (hl); update_numchild_cb (hl); update_layout_cb (hl, [], true); ## Dummy invisible object that deletes the legend when "newplot" is called ht = __go_text__ (opts.axes_handles(1), "tag", "__legend_watcher__", "visible", "off", "handlevisibility", "off", "deletefcn", {@reset_cb, hl}); ## Listeners to foreign objects properties are stored for later ## deletion in "reset_cb" hax = opts.axes_handles(1); hf = ancestor (hax, "figure"); add_safe_listener (hl, hf, "colormap", ... @(~, ~) set (hl, "colormap", get (hax, "colormap"))); add_safe_listener (hl, hax, "position", {@maybe_update_layout_cb, hl}); add_safe_listener (hl, hax, "tightinset", ... @(h, ~) update_layout_cb (get (h, "__legend_handle__"))); add_safe_listener (hl, hax, "clim", ... @(hax, ~) set (hl, "clim", get (hax, "clim"))); add_safe_listener (hl, hax, "colormap", ... @(hax, ~) set (hl, "colormap", get (hax, "colormap"))); add_safe_listener (hl, hax, "fontsize", ... @(hax, ~) set (hl, "fontsize", 0.9*get (hax, "fontsize"))); add_safe_listener (hl, hax, "children", {@legend_autoupdate_cb, hl}); ## Listeners to legend properties props = {"fontsize", "fontweight", "fontname", "interpreter", ... "textposition", "numcolumnsmode", "numcolumns", "orientation"}; for ii = 1:numel (props) addlistener (hl, props{ii}, {@update_layout_cb, true}); endfor addlistener (hl, "autoupdate", @update_numchild_cb); addlistener (hl, "beingdeleted", @delete_legend_cb); addlistener (hl, "box", @update_box_cb); addlistener (hl, "edgecolor", @update_edgecolor_cb); addlistener (hl, "location", @update_location_cb); addlistener (hl, "position", @update_position_cb); addlistener (hl, "string", @update_string_cb); addlistener (hl, "textcolor", ... @(h, ~) set (findobj (h, "type", "text"), ... "color", get (hl, "textcolor"))); addlistener (hl, "visible", @update_visible_cb); else ## FIXME: This will trigger the execution of update_layout_cb for each ## watched property. Should we suspend its execution with yet another ## appdata bool property for performance? ## Update properties setappdata (hl, "__peer_objects__", opts.obj_handles); if (! isempty (opts.obj_labels)) set (hl ,"string", opts.obj_labels, pval{:}) elseif (! isempty (pval)) set (hl, pval{:}); endif endif if (nargout > 0) hleg = hl; ## These ones are needed for backward compatibility hleg_obj = get (hl, "children"); hplot = getappdata (hl, "__peer_objects__"); labels = get (hl, "string"); endif set (hl, "handlevisibility", "on"); endfunction function update_box_cb (hl, ~) if (strcmp (get (hl, "box"), "on")) if (strcmp (get (hl, "color"), "none")) set (hl, "color", "w"); endif else set (hl, "color", "none"); endif endfunction function update_location_cb (hl, ~, do_layout = true) if (strcmp (get (hl, "location"), "best")) warning ("Octave:legend:unimplemented-location", ["legend: 'best' not yet implemented for location ", ... "specifier, using 'northeast' instead\n"]); endif if (do_layout) update_layout_cb (hl); endif endfunction function update_edgecolor_cb (hl, ~) ecolor = get (hl, "edgecolor"); set (hl, "xcolor", ecolor, "ycolor", ecolor); endfunction function update_position_cb (hl, ~) updating = getappdata (hl, "__updating_layout__"); if (isempty (updating) || ! updating) if (! strcmp (get (hl, "location"), "none")) set (hl, "location", "none"); else update_layout_cb (hl); endif endif endfunction function update_string_cb (hl, ~) ## Check that the number of legend item and the number of labels match ## before calling update_layout_cb. persistent updating = false; if (! updating) updating = true; unwind_protect str = get (hl, "string"); nstr = numel (str); obj = getappdata (hl, "__peer_objects__"); nobj = numel (obj); if (ischar (str) && nobj != 1) setappdata (hl, "__peer_objects__", obj(1)); elseif (iscellstr (str) && nobj != nstr) if (nobj > nstr) setappdata (hl, "__peer_objects__", obj(1:nstr)); elseif (nobj == 1) set (hl, "string", str{1}); else set (hl, "string", str(1:nobj)); endif endif update_layout_cb (hl, [], true); unwind_protect_cleanup updating = false; end_unwind_protect endif endfunction function update_visible_cb (hl, ~) location = get (hl, "location"); if (strcmp (location(end:-1:end-3), "edis")) update_layout_cb (hl); endif endfunction function reset_cb (ht, ~, hl, deletelegend = true) if (ishghandle (hl)) listeners = getappdata (hl, "__listeners__"); for ii = 1:numel (listeners) if (ishghandle (listeners{ii}{1})) dellistener (listeners{ii}{:}); endif endfor if (deletelegend) delete (hl); endif endif endfunction function delete_legend_cb (hl, ~) reset_cb ([], [], hl, false); hax = getappdata (hl, "__axes_handle__"); for h = hax(:)' units = get (h, "units"); set (h, "units", getappdata (hl, "__original_units__"), ... "looseinset", getappdata (hl, "__original_looseinset__"), ... "units", units, "__legend_handle__", []); endfor endfunction function add_safe_listener (hl, varargin) addlistener (varargin{:}); lsn = getappdata (hl, "__listeners__"); lsn = [lsn, {varargin}]; setappdata (hl, "__listeners__", lsn); endfunction function addproperties (hl) persistent default = {"north", "northoutside", ... "south", "southoutside", ... "east", "eastoutside", ... "west", "westoutside", ... "{northeast}", "northeastoutside", ... "northwest", "northwestoutside", ... "southeast", "southeastoutside", ... "southwest", "southwestoutside", ... "best", "bestoutside", ... "none"}; addproperty ("location", hl, "radio", strjoin (default(:), "|")); addproperty ("orientation", hl, "radio", "{vertical}|horizontal"); addproperty ("numcolumns", hl, "double", 1); addproperty ("numcolumnsmode", hl, "radio", "{auto}|manual"); addlistener (hl, "numcolumns", @(h, ~) set (h, "numcolumnsmode", "manual")); addproperty ("autoupdate", hl, "radio", "{on}|off"); addproperty ("string", hl, "textstring", {}); addproperty ("interpreter", hl, "textinterpreter"); addproperty ("edgecolor", hl, "color", [.15 .15 .15]); addproperty ("textcolor", hl, "color", "k"); addproperty ("textposition", hl, "radio", "left|{right}"); addproperty ("itemhitfcn", hl, "axesbuttondownfcn"); endfunction function maybe_update_layout_cb (h, ~, hl) persistent updating = false; if (! updating) unwind_protect updating = true; units = get (h, "units"); set (h, "units", "points"); pos = get (h, "position"); set (h, "units", units); old_pos = getappdata (hl, "__peer_axes_position__"); if (! all (pos == old_pos)) update_layout_cb (hl); setappdata (hl, "__peer_axes_position__", pos); endif unwind_protect_cleanup updating = false; end_unwind_protect endif endfunction function update_numchild_cb (hl, ~) if (strcmp (get (hl, "autoupdate"), "on")) hax = getappdata (hl, "__axes_handle__"); kids = get (hax, "children"); if (iscell (kids)) nkids = numel (cell2mat (get (hax, "children"))); else nkids = numel (get (hax, "children")); endif setappdata (hl, "__total_num_children__", nkids); endif endfunction function legend_autoupdate_cb (hax, ~, hl) ## Get all current children including eventual peer plotyy axes children try hax = get (hax, "__plotyy_axes__"); kids = cell2mat (get (hax, "children")); catch kids = get (hax, "children"); end_try_catch is_deletion = (getappdata (hl, "__total_num_children__") > numel (kids)); setappdata (hl, "__total_num_children__", numel (kids)); ## Remove item for deleted object current_obj = getappdata (hl, "__peer_objects__"); [~, iold, inew] = setxor (current_obj, kids); current_obj(iold) = []; if (isempty (current_obj)) delete (hl); return; endif if (! is_deletion && strcmp (get (hl, "autoupdate"), "on")) ## We only expect 1 new child kids = kids(min (inew)); ## FIXME: if the latest child is an hggroup, we cannot label it since this ## function is called before the hggroup has been properly populated. persistent valid_types = {"line", "patch", "scatter", "surface"}; if (! any (strcmp (get (kids, "type"), valid_types))) kids = []; endif else kids = []; endif if (any (iold) || any (kids)) setappdata (hl, "__peer_objects__", [current_obj; kids]); set (hl, "string", displayname_or_default ([current_obj; kids], hl)); endif endfunction function opts = parse_opts (varargin) action = ""; legend_handle = []; axes_handles = []; obj_handles = []; obj_labels = {}; nargs = numel (varargin); ## Find peer axes if (nargs > 0) if (! ishghandle (varargin{1})) [axes_handles, varargin, nargs] = __plt_get_axis_arg__ ("legend", varargin{:}); elseif (strcmp (get (varargin{1}, "type"), "axes")) if (strcmp (get (varargin{1}, "tag"), "legend")) legend_handle = varargin{1}; varargin(1) = []; nargs--; axes_handles = getappdata (legend_handle, "__axes_handle__"); else [axes_handles, varargin, nargs] = __plt_get_axis_arg__ ("legend", varargin{:}); endif endif if (isempty (axes_handles)) axes_handles = gca (); endif else axes_handles = gca (); endif ## Special handling for plotyy which has two axes objects if (isprop (axes_handles, "__plotyy_axes__")) axes_handles = [axes_handles get(axes_handles, "__plotyy_axes__").']; ## Remove duplicates while preserving order [~, n] = unique (axes_handles, "first"); axes_handles = axes_handles(sort (n)); endif ## Find any existing legend object associated with axes if (isempty (legend_handle)) try legend_handle = get (axes_handles, "__legend_handle__"); if (iscell (legend_handle)) legend_handle = unique (cell2mat (legend_handle)); endif end_try_catch endif ## Legend actions actions = {"show", "hide", "toggle", "boxon", ... "boxoff", "right", "left", "off"}; if (nargs > 0 && ischar (varargin{1}) && any (strcmp (varargin{1}, actions))) action = varargin{1}; if (nargs > 1) warning ("Octave:legend:ignoring-extra-argument", 'legend: ignoring extra arguments after "%s"', action); endif nargs = 0; endif ## Now remove property-value pairs for compatibility. propval = {}; warn_propval = ""; persistent legend_props = {"location", "orientation", "numcolumns", ... "numcolumnsmode", "textposition", ... "position", "units", "autoupdate", ... "string", "title", "interpreter", ... "fontname", "fontsize", "fontweight", ... "fontangle", "textcolor", "color", ... "edgecolor", "box", "linewidth", ... "visible", "uicontextmenu", "selected", ... "selectionhighlight", "itemhitfcn", ... "buttondownfcn", "createfcn", "deletefcn" ... "interruptible", "busyaction", ... "pickableparts", "hittest", ... "beingdeleted", "parent", "children", ... "handlevisibility", "tag", "type", ... "userdata"}; isprp = @(prop) (ischar (prop) && any (strcmpi (legend_props, prop))); idx = find (cellfun (isprp, varargin)); if (! isempty (idx)) idx = idx(1); propval = varargin(idx:end); warn_propval = varargin{idx}; varargin(idx:end) = []; nargs = idx-1; endif ## List plot objects that can be handled warn_extra_obj = false; persistent valid_types = {"hggroup", "line", "patch", "scatter", "surface"}; if (nargs > 0 && all (ishghandle (varargin{1}))) ## List of plot objects to label given as first argument obj_handles = varargin{1}; types = get (obj_handles, "type"); if (! iscell (types)) types = {types}; endif idx = cellfun (@(s) any (strcmp (s, valid_types)), types); if (! all (idx)) error ("Octave:legend:bad-object", "legend: objects of type \"%s\" can't be labeled", types(! idx){1}); endif varargin(1) = []; nargs--; warn_extra_obj = true; elseif (nargs > 0 || isempty (legend_handle)) ## Find list of plot objects from axes "children" if (isscalar (axes_handles)) obj_handles = flipud (get (axes_handles, "children")(:)); else tmp = get (axes_handles(:), "children"); obj_handles = [flipud(tmp{1}); flipud(tmp{2})]; endif if (isempty (obj_handles)) error ("Octave:legend:no-object", "legend: no valid object to label"); endif idx = arrayfun (@(h) any (strcmp (get (h, "type"), valid_types)), ... obj_handles); obj_handles(! idx) = []; if (isempty (obj_handles)) error ("Octave:legend:no-object", "legend: no valid object to label"); endif else obj_handles = getappdata (legend_handle, "__peer_objects__"); endif nobj = numel (obj_handles); ## List labels next_idx = 1; if (nargs > 0) if (iscellstr (varargin{1})) obj_labels = varargin{1}; varargin(1) = []; nargs--; elseif (ischar (varargin{1}) && rows (varargin{1}) > 1) obj_labels = cellstr (varargin{1}); varargin(1) = []; nargs--; elseif (all (cellfun (@ischar, varargin))) obj_labels = varargin; varargin = {}; nargs = 0; endif if (nargs > 0) print_usage ("legend"); endif nlab = numel (obj_labels); if (nlab != nobj) if (nobj > nlab) obj_handles = obj_handles(1:nlab); msg = "legend: ignoring extra objects."; if (! isempty (warn_propval)) msg = [msg ' "' warn_propval '" interpreted as a property ' , ... "name. Use a cell array of strings to specify labels ", ... "that match a legend property name."]; endif if (warn_extra_obj) warning ("Octave:legend:object-label-mismatch", msg); endif else obj_labels = obj_labels(1:nobj); warning ("Octave:legend:object-label-mismatch", "legend: ignoring extra labels."); endif endif else [tmp_labels, next_idx] = displayname_or_default (obj_handles, legend_handle); if (isempty (legend_handle) || ! isequal (tmp_labels, get (legend_handle, "string"))) obj_labels = tmp_labels; endif endif opts.action = action; opts.axes_handles = axes_handles; opts.obj_handles = obj_handles; opts.obj_labels = obj_labels; opts.legend_handle = legend_handle; opts.propval = propval; opts.next_idx = next_idx; endfunction function [labels, next_idx] = displayname_or_default (hplots, hl = []) next_idx = 1; if (! isempty (hl)) next_idx = getappdata (hl, "__next_label_index__"); endif ## Use the displayname property labels = get (hplots, "displayname"); if (! iscell (labels)) labels = {labels}; endif ## Fallback to automatic names for empty labels empty_label_idx = cellfun (@isempty, labels); if (any (empty_label_idx) && ! isempty (hl)) ## Empty strings must not be blindly replaced by data%d. If there exist ## an old text object that was affected an empty string, keep it as is. kids = get (hl, "children"); htext = kids(strcmp (get (kids, "type"), "text")); old_objects = get (htext, "peer_object"); if (iscell (old_objects)) old_objects = cell2mat (old_objects); endif for h = hplots(empty_label_idx).' idx = (h == old_objects); if (any (idx)) labels(hplots == h) = get (htext(idx), "string"); empty_label_idx(hplots == h) = false; endif endfor endif if (any (empty_label_idx)) default = arrayfun (@(ii) sprintf ("data%d", ii), ... [next_idx:(next_idx + sum (empty_label_idx) - 1)], ... "uniformoutput", false)(:); labels(empty_label_idx) = default; endif next_idx += sum (empty_label_idx); if (! isempty (hl)) setappdata (hl, "__next_label_index__", next_idx); endif endfunction function update_layout_cb (hl, ~, update_item = false) updating = getappdata (hl, "__updating_layout__"); if (! isempty (updating) && updating) return; endif setappdata (hl, "__updating_layout__", true); ## Scale limits so that item positions are expressed in points, from ## top to bottom and from left to right or reverse depending on textposition units = get (hl, "units"); set (hl, "units", "points"); unwind_protect pos = get (hl, "position"); if (update_item) set (hl, "xlim", [0, pos(3)], "ylim", [0, pos(4)]); textright = strcmp (get (hl, "textposition"), "right"); set (hl, "ydir", "reverse", ... "xdir", ifelse (textright, "normal", "reverse")); ## Create or reuse text and icon graphics objects objlist = texticon_objects (hl, textright); nitem = rows (objlist); ## Prepare the array of text/icon pairs and update their position sz = update_texticon_position (hl, objlist); else sz = getappdata (hl, "__item_bouding_box__"); endif ## Place the legend if (! strcmp (get (hl, "location"), "none")) update_legend_position (hl, sz); else ## Custom location: Adapt width and height if necessary. [x0, y0, x1, y1] = deal (0, 0, sz(1), sz(2)); if (sz(1) > pos(3)) pos(3) = sz(1); else dx = pos(3)-sz(1); x0 -= dx/2; x1 += dx/2; endif if (sz(2) > pos(4)) pos(4) = sz(2); else dy = pos(4)-sz(2); y0 -= dy/2; y1 += dy/2; endif set (hl, "position", pos, "xlim", [x0 x1], "ylim", [y0 y1]); endif unwind_protect_cleanup set (hl, "units", units); setappdata (hl, "__updating_layout__", false); end_unwind_protect endfunction function objlist = texticon_objects (hl, textright) ## Delete or set invisible obsolete or unused text/icon objects. old_kids = get (hl, "children")(:).'; old_peer_objects = cell2mat (get (old_kids, "peer_object"))(:).'; unused = ! ishghandle (old_peer_objects); delete (old_kids(unused)); old_kids(unused) = []; old_peer_objects(unused) = []; new_peer_objects = getappdata (hl, "__peer_objects__")(:).'; unused = arrayfun (@(h) ! any (h == new_peer_objects), old_peer_objects); set (old_kids(unused), "visible", "off"); ## Text properties string = get (hl , "string"); if (! iscell (string)) string = {string}; endif txtprops = {"textcolor", "fontsize", "fontweight", "fontname", ... "interpreter"}; txtvals = get (hl, txtprops); txtprops{1} = "color"; txtprops = [txtprops, "horizontalalignment"]; txtvals = [txtvals, ifelse(textright, "left", "right")]; ## Create or reuse text/icon objects as needed nitem = numel (new_peer_objects); objlist = NaN (nitem, 2); for ii = 1:nitem str = string{ii}; hplt = new_peer_objects(ii); idx = (old_peer_objects == hplt); if (any (idx)) tmp = old_kids(idx); idx = strcmp (get (tmp, "type"), "text"); htxt = tmp(idx); hicon = tmp(! idx); set (htxt, "visible", "on", "string", str, ... [txtprops(:)'; txtvals(:)']{:}); set (hicon, "visible", "on"); else [htxt, hicon] = create_item (hl, str, [txtprops(:)'; txtvals(:)'], hplt); add_safe_listener (hl, hplt, "displayname", {@update_displayname_cb, hl}); endif set (hplt, "displayname", str); objlist(ii,:) = [htxt, hicon]; endfor endfunction function [htxt, hicon] = create_item (hl, str, txtpval, hplt) typ = get (hplt, "type"); ## For unknown hggroups use the first child that can be labeled persistent known_creators = {"__contour__", "__errplot__", "__quiver__", ... "__stem__"}; base_hplt = hplt; if (strcmp (typ, "hggroup")) creator = getappdata (hplt, "__creator__"); kids = get (hplt, "children"); if (any (strcmp (known_creators, creator))) typ = creator; switch (creator) case "__contour__" hplt = [kids(end), kids(1)]; case {"__errplot__", "__quiver__", "__stem__"} hplt = kids(2:-1:1); otherwise hplt = kids(1); endswitch else types = get (kids, "type"); if (! iscell (types)) types = {types}; endif idx = cellfun (@(s) any (strcmp (s, {"line", "patch", "surface"})), ... types); hplt = kids(idx)(1); typ = types(idx){1}; endif endif persistent lprops = {"color", "linestyle", "linewidth"}; persistent mprops = {"color", "marker", "markeredgecolor", ... "markerfacecolor", "markersize"}; persistent pprops = {"edgecolor", "facecolor", "cdata", ... "linestyle", "linewidth", ... "marker", "markeredgecolor", ... "markerfacecolor", "markersize"}; persistent sprops = {"marker", "markeredgecolor", ... "markerfacecolor"}; switch (typ) case {"line", "__errplot__", "__quiver__", "__stem__"} ## Main line vals = get (hplt(1), lprops); hicon = __go_line__ (hl, [lprops; vals]{:}, ... "pickableparts", "all", ... "buttondownfcn", ... {@execute_itemhit, hl, hplt, "icon"}); addproperty ("markerxdata", hicon, "double", 0); addproperty ("markerydata", hicon, "double", 0); ## Additional line for the marker vals = get (hplt(end), mprops); hmarker = __go_line__ (hl, "handlevisibility", "off", ... "xdata", 0, "ydata", 0, [mprops; vals]{:}, ... "pickableparts", "all", ... "buttondownfcn", ... {@execute_itemhit, hl, hplt, "icon"}); addproperty ("markertruesize", hmarker, "double", NaN); update_marker_cb (hmarker); ## Listeners safe_property_link (hplt(1), hicon, lprops); safe_property_link (hplt(end), hmarker, mprops); addlistener (hicon, "ydata", ... @(h, ~) set (hmarker, "ydata", get (h, "markerydata"))); addlistener (hicon, "xdata", ... @(h, ~) set (hmarker, "xdata", get (h, "markerxdata"))); addlistener (hicon, "visible", ... @(h, ~) set (hmarker, "visible", get (h, "visible"))); addlistener (hmarker, "markersize", {@update_marker_cb, true}); addlistener (hmarker, "marker", {@update_marker_cb, false}); add_safe_listener (hl, hplt(1), "beingdeleted", @(~, ~) delete ([hicon hmarker])) if (! strcmp (typ, "__errplot__")) setappdata (hicon, "__creator__", typ); else setappdata (hicon, "__creator__", typ, ... "__format__", get (base_hplt, "format")); endif case {"patch", "surface"} vals = get (hplt, pprops); hicon = __go_patch__ (hl, [pprops; vals]{:}); ## Listeners safe_property_link (hplt(1), hicon, pprops); setappdata (hicon, "__creator__", typ); case "scatter" all_sprops = [sprops, "sizedata", "cdata"]; vals = get (hplt, all_sprops); ## sizedata and cdata may be N-by-1 vectors or N-by-3 (RGB) martrices. ## Take the average for the icon. vals {end-1} = mean (vals {end-1}, 1); vals {end} = mean (vals {end}, 1); hicon = __go_scatter__ (hl, [all_sprops; vals]{:}, ... "pickableparts", "all", ... "buttondownfcn", ... {@execute_itemhit, hl, hplt, "icon"}); ## Simple Listeners safe_property_link (hplt(1), hicon, sprops); ## Listener to sizedata lsn = {hplt(1), "sizedata", @(h, ~) set (hicon, "sizedata", ... mean (get (h, "sizedata")))}; addlistener (lsn{:}); addlistener (hicon, "beingdeleted", @(~, ~) dellistener (lsn{:})); ## Listener to cdata lsn = {hplt(1), "cdata", @(h, ~) set (hicon, "cdata", ... mean (get (h, "cdata"), 1))}; addlistener (lsn{:}); addlistener (hicon, "beingdeleted", @(~, ~) dellistener (lsn{:})); setappdata (hicon, "__creator__", typ); case "__contour__" ## Main patch vals = get (hplt(1), pprops); hicon = __go_patch__ (hl, [pprops; vals]{:}, ... "pickableparts", "all", ... "buttondownfcn", ... {@execute_itemhit, hl, hplt, "icon"}); addproperty ("innerxdata", hicon, "any", 0); addproperty ("innerydata", hicon, "any", 0); ## Additional patch for the inner contour vals = get (hplt(end), pprops); htmp = __go_patch__ (hl, "handlevisibility", "off", ... "xdata", 0, "ydata", 0, [pprops; vals]{:}, ... "pickableparts", "all", ... "buttondownfcn", ... {@execute_itemhit, hl, hplt, "icon"}); ## Listeners safe_property_link (hplt(1), hicon, pprops); safe_property_link (hplt(end), htmp, pprops); addlistener (hicon, "ydata", ... @(h, ~) set (htmp, "ydata", get (h, "innerydata"))); addlistener (hicon, "xdata", ... @(h, ~) set (htmp, "xdata", get (h, "innerxdata"))); addlistener (hicon, "visible", ... @(h, ~) set (htmp, "visible", get (h, "visible"))); add_safe_listener (hl, hplt(1), "beingdeleted", @(~, ~) delete ([hicon htmp])) setappdata (hicon, "__creator__", typ); endswitch htxt = __go_text__ (hl, txtpval{:}, "string", str, ... "pickableparts", "all", ... "buttondownfcn", {@execute_itemhit, hl, hplt, "label"}); set (htxt, "buttondownfcn", {@execute_itemhit, hl, hplt, "label"}); addproperty ("peer_object", htxt, "double", base_hplt); addproperty ("peer_object", hicon, "double", base_hplt); endfunction function execute_itemhit (h, ~, hl, hplt, region) fcn = get (hl, "itemhitfcn"); if (! isempty (fcn)) evt = struct ("Peer", hplt, "Region", region, ... "SelectionType", get (gcbf (), "selectiontype"), ... "Source", hl, "EventName", "ItemHit"); fcn (hl, evt) endif endfunction function safe_property_link (h1, h2, props) for ii = 1:numel (props) prop = props{ii}; lsn = {h1, prop, @(h, ~) set (h2, prop, get (h, prop))}; addlistener (lsn{:}); addlistener (h2, "beingdeleted", @(~, ~) dellistener (lsn{:})); endfor endfunction function update_displayname_cb (h, ~, hl) updating = getappdata (hl, "__updating_layout__"); if (! isempty (updating) && updating) return; endif str = get (hl, "string"); if (! iscell (str)) str = {str}; endif str{h == getappdata (hl, "__peer_objects__")} = get (h, "displayname"); set (hl ,"string", str); endfunction ## Enforce maximum size of marker so it doesn't overflow legend key function update_marker_cb (h, ~, sz_updated = true) persistent is_updating = false; if (is_updating) return; endif if (sz_updated) ## Size was changed sz = get (h, "markersize"); set (h, "markertruesize", sz); # store true marker size if (sz > 8) is_updating = true; mark = get (h, "marker"); if (strcmp (mark, '.')) set (h, "markersize", min ([sz, 24])); else set (h, "markersize", 8); endif is_updating = false; endif else ## Marker style was changed sz = get (h, "markertruesize"); if (sz > 8) is_updating = true; mark = get (h, "marker"); if (strcmp (mark, '.')) set (h, "markersize", min ([sz, 24])); else set (h, "markersize", 8); endif is_updating = false; endif endif endfunction function sz = update_texticon_position (hl, objlist) ## margins in points persistent hmargin = 3; persistent vmargin = 3; persistent icon_width = 15; units = get (hl, "fontunits"); set (hl, "fontunits", "points"); icon_height = 0.7 * get (hl, "fontsize"); set (hl, "fontunits", units); types = get (objlist(:,2), "type"); ext = get (objlist(:,1), "extent"); markers = get (objlist(:,2), "marker"); is_scatter = strcmp (types, "scatter"); if (! any (is_scatter)) markersz = get (objlist(:,2), "markersize"); elseif (rows (objlist) == 1) markersz = mean (get (objlist(1,2), "sizedata").^0.5); else markersz = cell (rows (objlist), 1); for ii = 1:rows (objlist) if (! is_scatter(ii)) markersz{ii} = get (objlist(ii,2), "markersize"); else markersz{ii} = mean (get (objlist(ii,2), "sizedata").^0.5); endif endfor endif ## Simple case of 1 text/icon pair nitem = rows (objlist); txticon = zeros (nitem, 4); if (nitem == 1) ext = abs (ext(:,3:4)); types = {types}; markers = {markers}; markersz = {markersz}; else ext = abs (cell2mat (ext)(:,3:4)); endif autolayout = strcmp (get (hl, "numcolumnsmode"), "auto"); xmax = ymax = 0; iter = 1; if (strcmp (get (hl, "orientation"), "vertical")) if (autolayout) ncol = 1; else ncol = min (nitem, get (hl, "numcolumns")); endif nrow = ceil (nitem / ncol); rowheights = arrayfun (@(idx) max([icon_height; ext(idx:nrow:end, 2); vertcat(markersz(idx:nrow:end){:})]), ... 1:nrow); x = hmargin; for ii = 1:ncol y = vmargin; for jj = 1:nrow if (iter > nitem) continue; endif hg = rowheights(jj); dx = 0; if (! strcmp (markers{iter}, "none")) dx = markersz{iter}/2; endif ybase = y + hg / 2; y0 = ybase - max (icon_height, dx)/2 + dx; y1 = ybase + max (icon_height, dx)/2 - dx; update_icon_position (objlist(iter,2), [x+dx, x+icon_width-dx], ... [y0, y1]); set (objlist(iter,1), "position", [x+icon_width+hmargin, ybase, 0]); xmax = max ([xmax, x+icon_width+2*hmargin+ext(iter,1)]); y += (vmargin + hg); iter++; endfor ymax = max ([ymax, y]); x = xmax + 2*hmargin; endfor else if (autolayout) ncol = nitem; else ncol = min (nitem, get (hl, "numcolumns")); endif nrow = ceil (nitem / ncol); colwidth = arrayfun (@(idx) max (ext(idx:ncol:end, 1)), 1:ncol); y = vmargin; for ii = 1:nrow x = hmargin; endidx = min (iter+ncol-1, nitem); hg = max ([icon_height; ext(iter:endidx,2); ... vertcat(markersz{:})]); for jj = 1:ncol if (iter > nitem) continue; endif wd = colwidth(jj); dx = 0; if (! strcmp (markers{iter}, "none")) dx = markersz{iter}/2; endif ybase = y + hg / 2; y0 = ybase - max (icon_height, dx)/2 + dx; y1 = ybase + max (icon_height, dx)/2 - dx; update_icon_position (objlist(iter,2), [x+dx, x+icon_width-dx], ... [y0, y1]); set (objlist(iter,1), "position", [x+icon_width+hmargin, ybase, 0]); ymax = max ([ymax, ybase+hg/2+vmargin]); x += (3*hmargin + icon_width + wd); iter++; endfor xmax = max ([xmax, x-hmargin]); y = ymax + vmargin; endfor endif sz = [xmax, ymax]; setappdata (hl, "__item_bouding_box__", sz); endfunction function update_icon_position (hicon, xdata, ydata) creator = getappdata (hicon, "__creator__"); switch (creator) case "line" set (hicon, "markerxdata", mean (xdata), "markerydata", mean (ydata), ... "xdata", xdata, "ydata", [mean(ydata), mean(ydata)]); case {"patch", "surface"} set (hicon, ... "xdata", [xdata, fliplr(xdata)], ... "ydata", [ydata; ydata](:).'); case "__contour__" ## Draw two patches x0 = xdata(1); x1 = xdata(2); xm = mean (xdata); y0 = ydata(1); y1 = ydata(2); ym = mean (ydata); xdata = [x0, x1, x1, x0]; ydata = [y0, y0, y1, y1]; set (hicon, ... "innerxdata", (xdata-xm) * 0.6 + xm, ... "innerydata", (ydata-ym) * 0.4 + ym, ... "xdata", xdata, "ydata", ydata); case "__errplot__" x0 = xdata(1); x1 = xdata(2); xm = mean (xdata); y0 = ydata(1); y1 = ydata(2); ym = mean (ydata); fmt = getappdata (hicon, "__format__"); if (strcmp (fmt, "yerr")) xdata = [xm, xm, xm-2, xm+2, xm, xm, xm-2, xm+2]; ydata = [ym, y0, y0, y0, y0, y1, y1, y1]; elseif (strcmp (fmt, "xerr")) xdata = [x0+2, x0+2, x0+2, x1-2, x1-2, x1-2, x1-2]; ydata = [ym+2, ym-2, ym, ym, ym+2, ym-2, ym]; elseif (strcmp (fmt, "xyerr")) xdata = [x0+2, x0+2, x0+2, x1-2, x1-2, x1-2, x1-2, ... xm, xm, xm-2, xm+2, xm, xm, xm-2, xm+2]; ydata = [ym+2, ym-2, ym, ym, ym+2, ym-2, ym, ... ym, y0, y0, y0, y0, y1, y1, y1]; elseif (strncmp (fmt, "box", 3)) xdata = [x0+2, x1-2, x1-2, x0+2, x0+2]; ydata = [y0, y0, y1, y1, y0]; else xdata = [x0, x1]; ydata = [ym, ym]; endif set (hicon, "markerxdata", xm, "markerydata", ym, ... "xdata", xdata, "ydata", ydata); case "__quiver__" ## Draw an arrow x0 = xdata(1); x1 = xdata(2); y0 = mean (ydata); xdata = [x0, x1, x1-2, x1, x1-2]; ydata = [y0, y0, y0+2, y0, y0-2]; set (hicon, "markerxdata", x0, "markerydata", y0, ... "xdata", xdata, "ydata", ydata); case "scatter" set (hicon, "xdata", mean (xdata), "ydata", mean (ydata)); case "__stem__" xdata(2) -= (get (get (hicon, "peer_object"), "markersize") / 2); set (hicon, "markerxdata", xdata(2), "markerydata", mean (ydata), ... "xdata", xdata, "ydata", [mean(ydata), mean(ydata)]); endswitch endfunction function pos = boxposition (axpos, pba, pbam, dam) pos = axpos; if (strcmp (pbam, "auto") && strcmp (dam, "auto")) return; endif pbratio = pba(1)/pba(2); posratio = axpos(3)/axpos(4); if (pbratio != posratio) if (posratio < pbratio) pos(4) = pos(3) / pbratio; pos(2) += (axpos(4) - pos(4)) / 2; else pos(3) = pos(4) * pbratio; pos(1) += (axpos(3) - pos(3)) / 2; endif endif endfunction function update_legend_position (hl, sz) persistent hmargin = 6; persistent vmargin = 6; location = get (hl, "location"); outside = strcmp (location(end-3:end), "side"); if (outside) location = location(1:end-7); endif if (strcmp (location, "best")) orientation = get (hl, "orientation"); if (outside) if (strcmp (orientation, "vertical")) location = "northeast"; else location = "south"; endif else ## FIXME: implement "best" inside properly location = "northeast"; endif endif haxes = getappdata (hl, "__axes_handle__"); hax = haxes(end); units = get (hax, "units"); unwind_protect ## Restore the original looseinset first and set units to points. li = getappdata (hl, "__original_looseinset__"); if (isempty (li)) li = get (hax, "looseinset"); setappdata (hl, "__original_looseinset__", li); setappdata (hl, "__original_units__", units); endif if (strcmp (get (hl, "visible"), "on")) set (hax, "units", getappdata (hl, "__original_units__"), "looseinset", li, "units", "points"); else ## Return early for invible legends set (hax, "units", getappdata (hl, "__original_units__"), "looseinset", li, "units", units); return; endif [li, axpos, pbam, pba, dam] = get (hax, {"looseinset", "position", ... "plotboxaspectratiomode", ... "plotboxaspectratio", ... "dataaspectratiomode"}){:}; axpos = boxposition (axpos, pba, pbam, dam); lpos = [get(hl, "position")(1:2), sz]; if (! outside) switch (location) case "southwest" lpos(1) = axpos(1) + hmargin; lpos(2) = axpos(2) + vmargin; case "west" lpos(1) = axpos(1) + hmargin; lpos(2) = axpos(2) + axpos(4)/2 - lpos(4)/2; case "northwest" lpos(1) = axpos(1) + hmargin; lpos(2) = axpos(2) + axpos(4) - lpos(4) - vmargin; case "north" lpos(1) = axpos(1) + axpos(3)/2 - lpos(3)/2; lpos(2) = axpos(2) + axpos(4) - lpos(4) - vmargin; case "northeast" lpos(1) = axpos(1) + axpos(3) - lpos(3) - hmargin; lpos(2) = axpos(2) + axpos(4) - lpos(4) - vmargin; case "east" lpos(1) = axpos(1) + axpos(3) - lpos(3) - hmargin; lpos(2) = axpos(2) + axpos(4)/2 - lpos(4)/2; case "southeast" lpos(1) = axpos(1) + axpos(3) - lpos(3) - hmargin; lpos(2) = axpos(2) + vmargin; case "south" lpos(1) = axpos(1) + axpos(3)/2 - lpos(3)/2; lpos(2) = axpos(2) + vmargin; endswitch else ## FIXME: Is there a simpler way to know the size of the box ## enclosing labels than temporarily changing the ## plotboxaspectratiomode if (strcmp (pbam, "auto")) ti = get (haxes, "tightinset"); else set (haxes, "plotboxaspectratiomode", "auto"); ti = get (haxes, "tightinset"); set (haxes, "plotboxaspectratio", pba); endif if (iscell (ti)) ti = max (cell2mat (ti)); endif switch (location) case "southwest" dx = lpos(3) + hmargin + ti(1); if (axpos(1) < (dx + hmargin)) li(1) = min (dx + hmargin, 0.95 * (axpos(1) + axpos(3))); set (hax, "looseinset", li); axpos = boxposition (get (hax, "position"), pba, pbam, dam); endif lpos(1) = axpos(1) - dx; lpos(2) = axpos(2); case "west" dx = lpos(3) + hmargin + ti(1); if (axpos(1) < (dx + hmargin)) li(1) = min (dx + hmargin, 0.95 * (axpos(1) + axpos(3))); set (hax, "looseinset", li); axpos = boxposition (get (hax, "position"), pba, pbam, dam); endif lpos(1) = axpos(1) - dx; lpos(2) = axpos(2) + axpos(4)/2 - lpos(4)/2; case "northwest" dx = lpos(3) + hmargin + ti(1); if (axpos(1) < (dx + hmargin)) li(1) = min (dx + hmargin, 0.95 * (axpos(1) + axpos(3))); set (hax, "looseinset", li); axpos = boxposition (get (hax, "position"), pba, pbam, dam); endif lpos(1) = axpos(1) - dx; lpos(2) = axpos(2) + axpos(4) - lpos(4); case "north" dy = lpos(4) + vmargin + ti(4); if (li(4) < (dy + vmargin)) li(4) = min (dy + vmargin, axpos(4)); set (hax, "looseinset", li); axpos = boxposition (get (hax, "position"), pba, pbam, dam); endif lpos(1) = axpos(1) + axpos(3)/2 - lpos(3)/2; lpos(2) = axpos(2) + axpos(4) + vmargin + ti(4); case "northeast" dx = lpos(3) + hmargin + ti(3); if (li(3) < (dx + hmargin)) li(3) = min (dx + hmargin, axpos(3)); set (hax, "looseinset", li); axpos = boxposition (get (hax, "position"), pba, pbam, dam); endif lpos(1) = axpos(1) + axpos(3) + hmargin + ti(3); lpos(2) = axpos(2) + axpos(4) - lpos(4); case "east" dx = lpos(3) + hmargin + ti(3); if (li(3) < (dx + hmargin)) li(3) = min (dx + hmargin, axpos(3)); set (hax, "looseinset", li); axpos = boxposition (get (hax, "position"), pba, pbam, dam); endif lpos(1) = axpos(1) + axpos(3) + hmargin + ti(3); lpos(2) = axpos(2) + axpos(4)/2 - lpos(4)/2; case "southeast" dx = lpos(3) + hmargin + ti(3); if (li(3) < (dx + hmargin)) li(3) = min (dx + hmargin, axpos(3)); set (hax, "looseinset", li); axpos = boxposition (get (hax, "position"), pba, pbam, dam); endif lpos(1) = axpos(1) + axpos(3) + hmargin + ti(3); lpos(2) = axpos(2); case "south" dy = lpos(4) + vmargin + ti(2); if (li(2) < (dy + vmargin)) li(2) = min (dy + vmargin, 0.95 * (axpos(2) + axpos(4))); set (hax, "looseinset", li); axpos = boxposition (get (hax, "position"), pba, pbam, dam); endif lpos(1) = axpos(1) + axpos(3)/2 - lpos(3)/2; lpos(2) = axpos(2) - lpos(4) - vmargin - ti(2); endswitch endif set (hl, "xlim", [0, sz(1)], "ylim", [0, sz(2)], ... "position", lpos); setappdata (hl, "__peer_axes_position__", axpos); unwind_protect_cleanup set (hax, "units", units); end_unwind_protect endfunction %!demo %! clf; %! plot (rand (2)); %! title ("legend called with string inputs for labels"); %! h = legend ("foo", "bar"); %! set (h, "fontsize", 20, "location", "northeastoutside"); %!demo %! clf; %! plot (rand (2)); %! title ("legend called with cell array of strings"); %! h = legend ({"cellfoo", "cellbar"}); %! set (h, "fontsize", 20, "location", "northeast"); %!demo %! clf; %! plot (rand (3)); %! title ("legend () without inputs creates default labels"); %! h = legend (); %!demo %! clf; %! x = 0:1; %! hline = plot (x,x,";I am Blue;", x,2*x, x,3*x,";I am yellow;"); %! h = legend (); %! set (h, "location", "northeastoutside"); %! ## Placing legend inside returns axes to original size %! set (h, "location", "northeast"); %! title ("Blue and Yellow keys, with Orange missing"); %!demo %! clf; %! plot (1:10, 1:10, 1:10, fliplr (1:10)); %! title ("incline is blue and decline is orange"); %! legend ({"I am blue", "I am orange"}, "location", "east"); %! legend hide %! legend show %!demo %! clf; %! plot (1:10, 1:10, 1:10, fliplr (1:10)); %! title ("Legend with keys in horizontal orientation"); %! legend ({"I am blue", "I am orange"}, ... %! "location", "east", "orientation", "horizontal"); %! legend boxoff %! legend boxon %!demo %! clf; %! plot (1:10, 1:10, 1:10, fliplr (1:10)); %! title ("Legend with box off"); %! legend ({"I am blue", "I am orange"}, "location", "east"); %! legend boxoff %!demo %! clf; %! plot (1:10, 1:10, 1:10, fliplr (1:10)); %! title ("Legend with text to the left of key"); %! legend ({"I am blue", "I am orange"}, "location", "east"); %! legend left %!demo %! clf; %! plot (1:10, 1:10, 1:10, fliplr (1:10)); %! title ({"Use properties to place legend text to the left of key", ... %! "Legend text color is magenta"}); %! h = legend ({"I am blue", "I am orange"}, "location", "east"); %! legend ("right"); %! set (h, "textposition", "left"); %! set (h, "textcolor", [1, 0, 1]); %!demo %! clf; %! plot (1:10, 1:10, 1:10, fliplr (1:10)); %! title ("Legend is hidden"); %! legend ({"I am blue", "I am orange"}, "location", "east"); %! legend hide %!demo %! clf; %! x = 0:1; %! plot (x,x,";I am Blue;", x,2*x,";I am Orange;", x,3*x,";I am Yellow;"); %! title ({"Labels are embedded in call to plot", ... %! "Legend is hidden and then shown"}); %! legend boxon %! legend hide %! legend show %!demo %! clf; %! x = 0:1; %! plot (x,x, x,2*x, x,3*x); %! title ("Labels with interpreted Greek text"); %! h = legend ('\alpha', '\beta=2\alpha', '\gamma=3\alpha'); %! set (h, "interpreter", "tex"); %!demo %! clf; %! plot (rand (2)); %! title ("Labels with TeX interpreter turned off"); %! h = legend ("Hello_World", "foo^bar"); %! set (h, "interpreter", "none"); %!demo %! clf; %! labels = {}; %! colororder = get (gca, "colororder"); %! for i = 1:5 %! h = plot (1:100, i + rand (100,1)); hold on; %! set (h, "color", colororder(i,:)); %! labels = {labels{:}, ["Signal ", num2str(i)]}; %! endfor %! hold off; %! title ({"Signals with random offset and uniform noise"; %! "Legend shown below and outside of plot"}); %! xlabel ("Sample Nr [k]"); ylabel ("Amplitude [V]"); %! legend (labels, "location", "southoutside"); %!demo %! clf; %! x = linspace (0, 10); %! plot (x, x); %! hold on; %! stem (x, x.^2, "g"); %! title ("First created object gets first label"); %! legend ("linear"); %! hold off; %!demo %! clf; %! x = linspace (0, 10); %! plot (x, x, x, x.^2); %! title ("First created object gets first label"); %! legend ("linear"); %!demo %! clf; %! x = linspace (0, 10); %! plot (x, x, x, x.^2); %! title ("Labels are applied in order of object creation"); %! legend ("linear", "quadratic"); %!demo %! clf; %! subplot (2,1,1); %! rand_2x3_data1 = [0.341447, 0.171220, 0.284370; 0.039773, 0.731725, 0.779382]; %! bar (rand_2x3_data1); %! ylim ([0, 1.0]); %! title ("legend() works for bar graphs (hggroups)"); %! legend ({"1st Bar", "2nd Bar", "3rd Bar"}, "location", "northwest"); %! subplot (2,1,2); %! x = linspace (0, 10, 20); %! stem (x, 0.5+x.*rand (size (x))/max (x), "markeredgecolor", [0, 0.7, 0]); %! hold on; %! stem (x+10/(2*20), x.*(1.0+rand (size (x)))/max (x)); %! xlim ([0, 10+10/(2*20)]); %! title ("legend() works for stem plots (hggroups)"); %! legend ({"Multicolor", "Unicolor"}, "location", "northwest"); %!demo %! clf; %! colormap (cool (64)); %! surf (peaks ()); %! legend ("peaks()"); %! title ("legend() works for surface objects too"); %!demo %! clf reset; # needed to undo colormap assignment in previous demo %! rand_2x3_data2 = [0.44804, 0.84368, 0.23012; 0.72311, 0.58335, 0.90531]; %! bar (rand_2x3_data2); %! ylim ([0, 1.2]); %! title ('"left" option places colors to the left of text label'); %! legend ("1st Bar", "2nd Bar", "3rd Bar"); %! legend left; %!demo %! clf; %! x = 0:0.1:7; %! h = plot (x,sin (x), x,cos (x), x,sin (x.^2/10), x,cos (x.^2/10)); %! title ("Only the sin() objects have keylabels"); %! legend (h([1, 3]), {"sin (x)", "sin (x^2/10)"}, "location", "southwest"); %!demo %! clf; %! x = 0:0.1:10; %! plot (x, sin (x), ";sin (x);"); %! hold on; %! plot (x, cos (x), ";cos (x);"); %! hold off; %! title ("legend constructed from multiple plot calls"); %!demo %! clf; %! x = 0:0.1:10; %! plot (x, sin (x), ";sin (x);"); %! hold on; %! plot (x, cos (x), ";cos (x);"); %! hold off; %! title ("Specified label text overrides previous labels"); %! legend ({"Sine", "Cosine"}, "location", "northeastoutside"); %!demo %! clf; %! x = 0:10; %! plot (x, rand (11)); %! axis ([0, 10, 0, 1]); %! xlabel ("Indices"); %! ylabel ("Random Values"); %! title ('Legend "off" deletes the legend'); %! legend (cellstr (num2str ((0:10)')), "location", "northeastoutside"); %! pause (1); %! legend off; %!demo %! clf; %! x = (1:5)'; %! subplot (2,2,1); %! plot (x, rand (numel (x))); %! legend (cellstr (num2str (x)), "location", "northwestoutside"); %! subplot (2,2,2); %! plot (x, rand (numel (x))); %! legend (cellstr (num2str (x)), "location", "northeastoutside"); %! subplot (2,2,3); %! plot (x, rand (numel (x))); %! legend (cellstr (num2str (x)), "location", "southwestoutside"); %! subplot (2,2,4); %! plot (x, rand (numel (x))); %! legend (cellstr (num2str (x)), "location", "southeastoutside"); %! ## Legend works on a per axes basis for each subplot %!demo %! clf; %! plot (rand (2)); %! title ("legend() will warn if extra labels are specified"); %! legend ("Hello", "World", "foo", "bar"); %!demo %! clf; %! x = 0:10; %! y1 = rand (size (x)); %! y2 = rand (size (x)); %! [ax, h1, h2] = plotyy (x, y1, x, y2); %! title ({"plotyy legend test #1", "Blue label to left axis, Orange label to right axis"}); %! drawnow (); %! legend ("Blue", "Orange", "location", "south"); %!demo %! clf; %! x = 0:10; %! y1 = rand (size (x)); %! y2 = rand (size (x)); %! [ax, h1, h2] = plotyy (x, y1, x, y2); %! ylabel (ax(1), {"Blue", "Y", "Axis"}); %! title ('plotyy legend test #2: "westoutside" adjusts to ylabel'); %! drawnow (); %! legend ([h1, h2], {"Blue", "Orange"}, "location", "westoutside"); %!demo %! clf; %! x = 0:10; %! y1 = rand (size (x)); %! y2 = rand (size (x)); %! [ax, h1, h2] = plotyy (x, y1, x, y2); %! ylabel (ax(2), {"Orange", "Y", "Axis"}); %! title ('plotyy legend test #3: "eastoutside" adjusts to ylabel'); %! drawnow (); %! legend ([h1, h2], {"Blue", "Orange"}, "location", "eastoutside"); %!demo %! clf; %! plot (1:10, 1:10); %! title ("a very long label can sometimes cause problems"); %! legend ("hello very big world", "location", "northeastoutside"); %!demo # bug 36408 %! clf; %! subplot (3,1,1); %! plot (rand (1,4)); %! xlabel xlabel; %! ylabel ylabel; %! title ("Subplots adjust to the legend placed outside"); %! legend ({"1"}, "location", "northeastoutside"); %! subplot (3,1,2); %! plot (rand (1,4)); %! xlabel xlabel; %! ylabel ylabel; %! legend ({"1234567890"}, "location", "eastoutside"); %! subplot (3,1,3); %! plot (rand (1,4)); %! xlabel xlabel; %! ylabel ylabel; %! legend ({"12345678901234567890"}, "location", "southeastoutside"); %!demo # bug 36408 %! clf; %! subplot (3,1,1); %! plot (rand (1,4)); %! title ("Subplots adjust to the legend placed outside"); %! legend ({"1"}, "location", "northwestoutside"); %! subplot (3,1,2); %! plot (rand (1,4)); %! legend ({"1234567890"}, "location", "westoutside"); %! subplot (3,1,3); %! plot (rand (1,4)); %! legend ({"12345678901234567890"}, "location", "southwestoutside"); %!demo # bug 36408 %! clf; %! subplot (3,1,1); %! plot (rand (1,4)); %! set (gca (), "yaxislocation", "right"); %! xlabel ("xlabel"); %! ylabel ("ylabel"); %! title ("Subplots adjust to the legend placed outside"); %! legend ({"1"}, "location", "northeastoutside"); %! subplot (3,1,2); %! plot (rand (1,4)); %! set (gca (), "yaxislocation", "right"); %! xlabel ("xlabel"); %! ylabel ("ylabel"); %! legend ({"1234567890"}, "location", "eastoutside"); %! subplot (3,1,3); %! plot (rand (1,4)); %! set (gca (), "yaxislocation", "right"); %! xlabel ("xlabel"); %! ylabel ("ylabel"); %! legend ({"12345678901234567890"}, "location", "southeastoutside"); %!demo # bug 36408 %! clf; %! subplot (3,1,1); %! plot (rand (1,4)); %! set (gca (), "yaxislocation", "right"); %! xlabel ("xlabel"); %! ylabel ("ylabel"); %! title ("Subplots adjust to the legend placed outside"); %! legend ({"1"}, "location", "northwestoutside"); %! subplot (3,1,2); %! plot (rand (1,4)); %! set (gca (), "yaxislocation", "right"); %! xlabel ("xlabel"); %! ylabel ("ylabel"); %! legend ({"1234567890"}, "location", "westoutside"); %! subplot (3,1,3); %! plot (rand (1,4)); %! set (gca (), "yaxislocation", "right"); %! xlabel ("xlabel"); %! ylabel ("ylabel"); %! legend ({"12345678901234567890"}, "location", "southwestoutside"); %!demo # bug 36408; %! clf; %! subplot (3,1,1); %! plot (rand (1,4)); %! set (gca (), "xaxislocation", "top"); %! xlabel ("xlabel"); %! ylabel ("ylabel"); %! title ("Subplots adjust to the legend placed outside"); %! legend ({"1"}, "location", "northwestoutside"); %! subplot (3,1,2); %! plot (rand (1,4)); %! set (gca (), "xaxislocation", "top"); %! xlabel ("xlabel"); %! ylabel ("ylabel"); %! legend ({"1234567890"}, "location", "westoutside"); %! subplot (3,1,3); %! plot (rand (1,4)); %! set (gca (), "xaxislocation", "top"); %! xlabel ("xlabel"); %! ylabel ("ylabel"); %! legend ({"12345678901234567890"}, "location", "southwestoutside"); %!demo # bug 39697 %! clf; %! plot (1:10); %! legend ("Legend Text"); %! title ({"Multi-line", "titles", "are *not* a", "problem"}); %!demo # bug 61814 %! clf; %! data = [ [1:5]' , [5:-1:1]', 2.5*ones(5,1) ]; %! hp = plot (data); %! set (hp(1), "marker", 'x', "markersize", 15); %! set (hp(2), "marker", 'o', "markersize", 30); %! set (hp(3), "marker", '.', "markersize", 30); %! legend ({"data1", "data2", "data3"}, "location", "north"); %! set (hp(2), "marker", '.'); %! set (hp(3), "marker", 'o'); %! title ("Marker sizes do not overflow legend box"); ## Test input validation %!test %! hf = figure ("visible", "off"); %! unwind_protect %! try %! legend (); %! catch %! [~, id] = lasterr (); %! assert (id, "Octave:legend:no-object"); %! end_try_catch %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ("visible", "off"); %! unwind_protect %! axes (); %! try %! legend (); %! catch %! [~, id] = lasterr (); %! assert (id, "Octave:legend:no-object"); %! end_try_catch %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ("visible", "off"); %! unwind_protect %! axes (); %! light (); %! try %! legend (); %! catch %! [~, id] = lasterr (); %! assert (id, "Octave:legend:no-object"); %! end_try_catch %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ("visible", "off"); %! unwind_protect %! axes (); %! hli = light (); %! try %! legend (hli); %! catch %! [~, id] = lasterr (); %! assert (id, "Octave:legend:bad-object"); %! end_try_catch %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ("visible", "off"); %! unwind_protect %! axes (); %! hplot = plot (rand (3)); %! try %! legend (hplot, struct); %! catch %! [~, id] = lasterr (); %! assert (id, "Octave:invalid-fun-call"); %! end_try_catch %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ("visible", "off"); %! unwind_protect %! axes (); %! hplot = plot (rand (3)); %! try %! legend ("a", "b", "c", hplot); %! catch %! [~, id] = lasterr (); %! assert (id, "Octave:invalid-fun-call"); %! end_try_catch %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test bugs in previous implementation %!testif ; any (strcmp (graphics_toolkit (), {"fltk", "qt"})) <*39697> %! hf = figure ("visible", "off"); %! unwind_protect %! axes ("units", "normalized"); %! plot (1:10); %! hl = legend ("Legend Text", "units", "normalized"); %! title ({'Multi-line', 'titles', 'are a', 'problem'}); %! pos = get (gca, "position"); %! axtop = sum (pos(2:2:4)); %! pos = get (hl, "position"); %! legtop = sum (pos(2:2:4)); %! assert (legtop < axtop); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!testif HAVE_FREETYPE <*40333> %! hf = figure ("visible", "off"); %! unwind_protect %! hax = axes ("units", "normalized", "fontsize", 10); %! hold on; # preserve properties of hax in call to plot() %! plot (hax, 1:10); %! hl = legend ("Legend Text", "units", "normalized"); %! pos = get (gca, "position"); %! set (hf, "position", [0, 0, 200, 200]); %! set (hl, "fontsize", 20); %! assert (get (gca, "position"), pos, 2*eps); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect