1########################################################################
2##
3## Copyright (C) 2010-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn  {} {} legend ()
28## @deftypefnx {} {} legend @var{command}
29## @deftypefnx {} {} legend (@var{str1}, @var{str2}, @dots{})
30## @deftypefnx {} {} legend (@var{charmat})
31## @deftypefnx {} {} legend (@{@var{cellstr}@})
32## @deftypefnx {} {} legend (@dots{}, @var{property}, @var{value}, @dots{})
33## @deftypefnx {} {} legend (@var{hobjs}, @dots{})
34## @deftypefnx {} {} legend ("@var{command}")
35## @deftypefnx {} {} legend (@var{hax}, @dots{})
36## @deftypefnx {} {} legend (@var{hleg}, @dots{})
37## @deftypefnx {} {@var{hleg, hplt} =} legend (@dots{})
38##
39## Display a legend for the current axes using the specified strings as labels.
40##
41## Legend entries may be specified as individual character string arguments,
42## a character array, or a cell array of character strings.  When label names
43## might be confused with legend properties, or @var{command} arguments,
44## the labels should be protected by specifying them as a cell array of
45## strings.
46##
47## If the first argument @var{hax} is an axes handle, then add a legend to this
48## axes, rather than the current axes returned by @code{gca}.
49##
50## If the first argument @var{hleg} is a legend handle, then operate on this
51## legend rather than the legend of the current axes.
52##
53## Legend labels are associated with the axes' children; The first label is
54## assigned to the first object that was plotted in the axes, the second label
55## to the next object plotted, etc.  To label specific data objects, without
56## labeling all objects, provide their graphic handles in the input
57## @var{hobjs}.
58##
59## The following customizations are available using @var{command}:
60##
61## @table @asis
62## @item @qcode{"show"}
63##   Show legend on the plot
64##
65## @item @qcode{"hide"}
66##   Hide legend on the plot
67##
68## @item @qcode{"toggle"}
69##   Toggle between @qcode{"hide"} and @qcode{"show"}
70##
71## @item @qcode{"boxon"}
72##   Show a box around legend (default)
73##
74## @item @qcode{"boxoff"}
75##   Hide the box around legend
76##
77## @item @qcode{"right"}
78##   Place label text to the right of the keys (default)
79##
80## @item @qcode{"left"}
81##   Place label text to the left of the keys
82##
83## @item @qcode{"off"}
84##   Delete the legend object
85## @end table
86##
87## The @code{legend} function creates a graphics object which has various
88## properties that can be manipulated with @code{get}/@code{set}.
89## Alternatively, properties can be set directly when calling @code{legend} by
90## including @var{property}/@var{value} pairs.  If using this calling form, the
91## labels must be specified as a cell array of strings.  Graphics object
92## properties are documented in detail at @ref{Graphics Object Properties}.
93##
94## Following is a subset of supported legend properties:
95## @c The following table is obtained by copying the output of
96## @c genpropdoc ("legend", "", {"autoupdate", "box", "location", "numcolumns", "orientation", "string", "textcolor"})
97##
98## @table @asis
99##
100## @item @code{autoupdate}: @qcode{"off"} | @{@qcode{"on"}@}
101## Control whether the number of legend items is updated automatically when
102## objects are added to (or deleted from) the peer axes.  For example:
103##
104## @example
105## @group
106## ## Create a single plot with its legend.
107## figure ();
108## plot (1:10);
109## legend ("Slope 1");
110## ## Add another plot and specify its displayname so that
111## ## the legend is correctly updated.
112## hold on;
113## plot ((1:10) * 2, "displayname", "Slope 2");
114## ## Stop automatic updates for further plots.
115## legend ("autoupdate", "off");
116## plot ((1:10) * 3);
117## @end group
118## @end example
119##
120## @item @code{box}: @qcode{"off"} | @{@qcode{"on"}@}
121## Control whether the legend has a surrounding box.
122##
123## @item @code{location}: @qcode{"best"} | @qcode{"bestoutside"} |
124## @qcode{"east"} | @qcode{"eastoutside"} | @qcode{"none"} | @qcode{"north"} |
125## @{@qcode{"northeast"}@} | @qcode{"northeastoutside"} |
126## @qcode{"northoutside"} | @qcode{"northwest"}| @qcode{"northwestoutside"} |
127## @qcode{"south"} | @qcode{"southeast"} | @qcode{"southeastoutside"} |
128## @qcode{"southoutside"} | @qcode{"southwest"} | @qcode{"southwestoutside"} |
129## @qcode{"west"} | @qcode{"westoutside"}
130## Control the location of the legend.
131##
132## @item @code{numcolumns}: scalar interger, def. @code{1}
133## Control the number of columns used in the layout of the legend items.
134## For example:
135##
136## @example
137## @group
138## figure ();
139## plot (rand (30));
140## legend ("numcolumns", 3);
141## @end group
142## @end example
143##
144## Setting @code{numcolumns} also forces the @code{numcolumnsmode} property
145## to be set to @qcode{"manual"}.
146##
147## @item @code{orientation}: @qcode{"horizontal"} | @{@qcode{"vertical"}@}
148## Control whether the legend items are arranged vertically (column-wise) or
149## horizontally (row-wise).
150##
151## @item @code{string}: string | cell array of strings
152## List of labels for the legend items.  For example:
153##
154## @example
155## @group
156## figure ();
157## plot (rand (20));
158## ## Let legend choose names automatically
159## hl = legend ();
160## ## Selectively change some names
161## str = get (hl, "string");
162## str(1:5:end) = "Garbage";
163## set (hl, "string", str);
164## @end group
165## @end example
166##
167## @item @code{textcolor}: colorspec, def. @code{[0   0   0]}
168## Control the color of the text strings for legend item.
169##
170## @end table
171##
172## The full list of supported legend specific properties can be found at
173## @ref{Legend Properties, , Legend Properties}.
174##
175## A legend is implemented as an additional axes object with the @code{tag}
176## property set to @qcode{"legend"}.  Properties of the legend object may be
177## manipulated directly by using @code{set}.
178##
179## The optional output value @var{hleg} is a handle to the legend object.
180##
181## Implementation Note: The legend label text is either provided in the call to
182## @code{legend} or is taken from the @code{DisplayName} property of the
183## graphics objects.  Only data objects, such as line, patch, and surface, have
184## this property whereas axes, figures, etc.@: do not so they are never present
185## in a legend.  If no labels or @code{DisplayName} properties are available,
186## then the label text is simply @qcode{"data1"}, @qcode{"data2"}, @dots{},
187## @nospell{@qcode{"dataN"}}.
188##
189## The legend @code{FontSize} property is initially set to 90% of the axes
190## @code{FontSize} to which it is attached.  Use @code{set} to override this
191## if necessary.
192## @end deftypefn
193
194function [hleg, hleg_obj, hplot, labels] = legend (varargin)
195
196  ## Use the old legend code to handle gnuplot toolkit
197  if (strcmp (graphics_toolkit (), "gnuplot"))
198    [hleg, hleg_obj, hplot, labels] = __gnuplot_legend__ (varargin{:});
199    return;
200  endif
201
202  ## FIXME: This function needs to be locked to avoid bug #59439.  Remove this
203  ##        lock once that bug is properly fixed.
204  mlock ();
205
206  opts = parse_opts (varargin{:});
207
208  hl = opts.legend_handle;
209
210  ## Fix property/value pairs
211  pval = opts.propval(:)';
212
213  if (! isempty (opts.action))
214
215    do_set_box = isempty (hl);
216
217    switch (opts.action)
218      case "boxoff"
219        tmp_pval = {"box", "off"};
220        do_set_box = false;
221
222      case "boxon"
223        tmp_pval = {"box", "on"};
224        do_set_box = false;
225
226      case "hide"
227        tmp_pval = {"visible", "off"};
228
229      case "show"
230        tmp_pval = {"visible", "on"};
231
232      case "toggle"
233        if (! isempty (hl))
234          if (strcmp (get (hl, "visible"), "on"))
235            tmp_pval = {"visible", "off"};
236          else
237            tmp_pval = {"visible", "on"};
238          endif
239        endif
240
241      case "left"
242          tmp_pval = {"textposition", "left"};
243
244      case "right"
245          tmp_pval = {"textposition", "right"};
246
247      case "off"
248        if (! isempty (hl))
249          delete (hl);
250        endif
251        return;
252
253    endswitch
254
255    pval = [tmp_pval, pval];
256    if (do_set_box)
257      pval = [pval, "box", "on"];
258    endif
259
260  endif
261
262  if (isempty (hl))
263
264    hl = axes ("parent", get (opts.axes_handles(1), "parent"), ...
265               "tag", "legend", "handlevisibility", "off", ...
266               "ydir", "reverse", "position", [.5 .5 .3 .3], ...
267               "fontsize", 0.9 * get (opts.axes_handles(1), "fontsize"), ...
268               "clim", get (opts.axes_handles(1), "clim"), ...
269               "colormap", get (opts.axes_handles(1), "colormap"), ...
270               "xtick", [], "ytick", [], "box", "on");
271
272    ## FIXME: Use the axes appdata to store its peer legend handle
273    ## rather that adding a public property and change all uses.
274    for htmp = opts.axes_handles
275      try
276        addproperty ("__legend_handle__", htmp, "handle", hl);
277      catch
278        set (htmp, "__legend_handle__", hl);
279      end_try_catch
280    endfor
281
282    ## Add and update legend specific properties
283    addproperties (hl);
284    try
285      set (hl, "string", opts.obj_labels, pval{:});
286    catch ee
287      delete (hl);
288      set (opts.axes_handles, "__legend_handle__", []);
289      rethrow (ee);
290    end_try_catch
291
292    ## Update legend layout
293    setappdata (hl, "__axes_handle__", opts.axes_handles,
294                    "__next_label_index__", opts.next_idx,
295                    "__peer_objects__", opts.obj_handles);
296    update_location_cb (hl, [], false);
297    update_edgecolor_cb (hl);
298    update_numchild_cb (hl);
299    update_layout_cb (hl, [], true);
300
301    ## Dummy invisible object that deletes the legend when "newplot" is called
302    ht = __go_text__ (opts.axes_handles(1), "tag", "__legend_watcher__",
303                      "visible", "off", "handlevisibility", "off",
304                      "deletefcn", {@reset_cb, hl});
305
306    ## Listeners to foreign objects properties are stored for later
307    ## deletion in "delfunction"
308    hax = opts.axes_handles(1);
309    hf = ancestor (hax, "figure");
310
311    add_safe_listener (hl, hf, "colormap", ...
312                       @() set (hl, "colormap", get (hax, "colormap")));
313
314    add_safe_listener (hl, hax, "position", {@maybe_update_layout_cb, hl});
315    add_safe_listener (hl, hax, "tightinset", ...
316                       @(h) update_layout_cb (get (h, "__legend_handle__")));
317    add_safe_listener (hl, hax, "clim", ...
318                       @(hax) set (hl, "clim", get (hax, "clim")));
319    add_safe_listener (hl, hax, "colormap", ...
320                       @(hax) set (hl, "colormap", get (hax, "colormap")));
321    add_safe_listener (hl, hax, "fontsize", ...
322                       @(hax) set (hl, "fontsize", 0.9*get (hax, "fontsize")));
323    add_safe_listener (hl, hax, "children", {@legend_autoupdate_cb, hl});
324
325    ## Listeners to legend properties
326    props = {"fontsize", "fontweight", "fontname", "interpreter", ...
327             "textposition", "numcolumnsmode", "numcolumns", "orientation"};
328
329    for ii = 1:numel (props)
330      addlistener (hl, props{ii}, {@update_layout_cb, true});
331    endfor
332
333    addlistener (hl, "autoupdate", @update_numchild_cb);
334
335    addlistener (hl, "beingdeleted", @delete_legend_cb);
336
337    addlistener (hl, "box", @update_box_cb);
338
339    addlistener (hl, "edgecolor", @update_edgecolor_cb);
340
341    addlistener (hl, "location", @update_location_cb);
342
343    addlistener (hl, "position", @update_position_cb);
344
345    addlistener (hl, "string", @update_string_cb);
346
347    addlistener (hl, "textcolor", ...
348                 @(h) set (findobj (h, "type", "text"), ...
349                           "color", get (hl, "textcolor")));
350
351    addlistener (hl, "visible", @update_visible_cb);
352
353  else
354
355    ## FIXME: This will trigger the execution of update_layout_cb for each
356    ## watched property.  Should we suspend its execution with yet another
357    ## appdata bool property for performance?
358
359    ## Update properties
360    setappdata (hl, "__peer_objects__", opts.obj_handles);
361    if (! isempty (opts.obj_labels))
362      set (hl ,"string", opts.obj_labels, pval{:})
363    elseif (! isempty (pval))
364      set (hl, pval{:});
365    endif
366
367  endif
368
369  if (nargout > 0)
370    hleg = hl;
371    ## These ones are needed for backward compatibility
372    hleg_obj = get (hl, "children");
373    hplot = getappdata (hl, "__peer_objects__");
374    labels = get (hl, "string");
375  endif
376
377  set (hl, "handlevisibility", "on");
378
379endfunction
380
381function update_box_cb (hl)
382
383  if (strcmp (get (hl, "box"), "on"))
384    if (strcmp (get (hl, "color"), "none"))
385      set (hl, "color", "w");
386    endif
387  else
388    set (hl, "color", "none");
389  endif
390
391endfunction
392
393function update_location_cb (hl, ~, do_layout = true)
394
395  if (strcmp (get (hl, "location"), "best"))
396    warning ("Octave:legend:unimplemented-location",
397             ["legend: 'best' not yet implemented for location ", ...
398              "specifier, using 'northeast' instead\n"]);
399  endif
400
401  if (do_layout)
402    update_layout_cb (hl);
403  endif
404
405endfunction
406
407function update_edgecolor_cb (hl)
408
409  ecolor = get (hl, "edgecolor");
410  set (hl, "xcolor", ecolor, "ycolor", ecolor);
411
412endfunction
413
414function update_position_cb (hl)
415
416  updating = getappdata (hl, "__updating_layout__");
417  if (isempty (updating) || ! updating)
418    set (hl, "location", "none");
419  endif
420
421endfunction
422
423function update_string_cb (hl)
424
425  ## Check that the number of legend item and the number of labels match
426  ## before calling update_layout_cb.
427  persistent updating = false;
428
429  if (! updating)
430    updating = true;
431    unwind_protect
432      str = get (hl, "string");
433      nstr = numel (str);
434
435      obj = getappdata (hl, "__peer_objects__");
436      nobj = numel (obj);
437
438      if (ischar (str) && nobj != 1)
439        setappdata (hl, "__peer_objects__", obj(1));
440      elseif (iscellstr (str) && nobj != nstr)
441        if (nobj > nstr)
442          setappdata (hl, "__peer_objects__", obj(1:nstr));
443        elseif (nobj == 1)
444          set (hl, "string", str{1});
445        else
446          set (hl, "string", str(1:nobj));
447        endif
448      endif
449      update_layout_cb (hl, [], true);
450    unwind_protect_cleanup
451      updating = false;
452    end_unwind_protect
453  endif
454
455endfunction
456
457function update_visible_cb (hl)
458
459  location = get (hl, "location");
460  if (strcmp (location(end:-1:end-3), "edis"))
461    update_layout_cb (hl);
462  endif
463
464endfunction
465
466function reset_cb (ht, evt, hl, deletelegend = true)
467
468  if (ishghandle (hl))
469    listeners = getappdata (hl, "__listeners__");
470    for ii = 1:numel (listeners)
471      if (ishghandle (listeners{ii}{1}))
472        dellistener (listeners{ii}{:});
473      endif
474    endfor
475
476    if (deletelegend)
477      delete (hl);
478    endif
479  endif
480
481endfunction
482
483function delete_legend_cb (hl)
484
485  reset_cb ([], [], hl, false);
486
487  hax = getappdata (hl, "__axes_handle__");
488  for h = hax(:)'
489    units = get (h, "units");
490    set (h, "units", getappdata (hl, "__original_units__"), ...
491            "looseinset", getappdata (hl, "__original_looseinset__"), ...
492            "units", units, "__legend_handle__", []);
493  endfor
494
495endfunction
496
497function add_safe_listener (hl, varargin)
498
499  addlistener (varargin{:});
500
501  lsn = getappdata (hl, "__listeners__");
502  lsn = [lsn, {varargin}];
503  setappdata (hl, "__listeners__", lsn);
504
505endfunction
506
507function addproperties (hl)
508
509  persistent default = {"north", "northoutside", ...
510                        "south", "southoutside", ...
511                        "east", "eastoutside", ...
512                        "west", "westoutside", ...
513                        "{northeast}", "northeastoutside", ...
514                        "northwest", "northwestoutside", ...
515                        "southeast", "southeastoutside", ...
516                        "southwest", "southwestoutside", ...
517                        "best", "bestoutside", ...
518                        "none"};
519
520  addproperty ("location", hl, "radio", strjoin (default(:), "|"));
521
522  addproperty ("orientation", hl, "radio", "{vertical}|horizontal");
523
524  addproperty ("numcolumns", hl, "double", 1);
525
526  addproperty ("numcolumnsmode", hl, "radio", "{auto}|manual");
527
528  addlistener (hl, "numcolumns", @(h) set (h, "numcolumnsmode", "manual"));
529
530  addproperty ("autoupdate", hl, "radio", "{on}|off");
531
532  addproperty ("string", hl, "textstring", {});
533
534  addproperty ("interpreter", hl, "textinterpreter");
535
536  addproperty ("edgecolor", hl, "color", [.15 .15 .15]);
537
538  addproperty ("textcolor", hl, "color", "k");
539
540  addproperty ("textposition", hl, "radio", "left|{right}");
541
542  addproperty ("itemhitfcn", hl, "axesbuttondownfcn");
543
544endfunction
545
546function maybe_update_layout_cb (h, d, hl)
547
548  persistent updating = false;
549
550  if (! updating)
551
552    unwind_protect
553      updating = true;
554      units = get (h, "units");
555      set (h, "units", "points");
556      pos = get (h, "position");
557      set (h, "units", units);
558      old_pos = getappdata (hl, "__peer_axes_position__");
559
560      if (! all (pos == old_pos))
561        update_layout_cb (hl);
562        setappdata (hl, "__peer_axes_position__", pos);
563      endif
564    unwind_protect_cleanup
565      updating = false;
566    end_unwind_protect
567
568  endif
569
570endfunction
571
572function update_numchild_cb (hl)
573
574  if (strcmp (get (hl, "autoupdate"), "on"))
575
576    hax = getappdata (hl, "__axes_handle__");
577    kids = get (hax, "children");
578    if (iscell (kids))
579      nkids = numel (cell2mat (get (hax, "children")));
580    else
581      nkids = numel (get (hax, "children"));
582    endif
583
584    setappdata (hl, "__total_num_children__", nkids);
585
586  endif
587
588endfunction
589
590function legend_autoupdate_cb (hax, d, hl)
591
592  ## Get all current children including eventual peer plotyy axes children
593  try
594    hax = get (hax, "__plotyy_axes__");
595    kids = cell2mat (get (hax, "children"));
596  catch
597    kids = get (hax, "children");
598  end_try_catch
599
600  is_deletion = (getappdata (hl, "__total_num_children__") > numel (kids));
601  setappdata (hl, "__total_num_children__", numel (kids));
602
603  ## Remove item for deleted object
604  current_obj = getappdata (hl, "__peer_objects__");
605  [~, iold, inew] = setxor (current_obj, kids);
606  current_obj(iold) = [];
607
608  if (isempty (current_obj))
609    delete (hl);
610    return;
611  endif
612
613  if (! is_deletion && strcmp (get (hl, "autoupdate"), "on"))
614
615    ## We only expect 1 new child
616    kids = kids(min (inew));
617
618    ## FIXME: if the latest child is an hggroup, we cannot label it since this
619    ## function is called before the hggroup has been properly populated.
620    persistent valid_types = {"line", "patch", "surface"};
621    if (! any (strcmp (get (kids, "type"), valid_types)))
622      kids = [];
623    endif
624
625  else
626    kids = [];
627  endif
628
629  if (any (iold) || any (kids))
630    setappdata (hl, "__peer_objects__", [current_obj; kids]);
631    set (hl, "string", displayname_or_default ([current_obj; kids], hl));
632  endif
633
634endfunction
635
636function opts = parse_opts (varargin)
637
638  action = "";
639  legend_handle = [];
640  axes_handles = [];
641  obj_handles = [];
642  obj_labels = {};
643
644  nargs = numel (varargin);
645
646  ## Find peer axes
647  if (nargs > 0)
648    if (! ishghandle (varargin{1}))
649      [axes_handles, varargin, nargs] = __plt_get_axis_arg__ ("legend",
650                                                              varargin{:});
651    elseif (strcmp (get (varargin{1}, "type"), "axes"))
652      if (strcmp (get (varargin{1}, "tag"), "legend"))
653        legend_handle = varargin{1};
654        varargin(1) = [];
655        nargs--;
656        axes_handles = getappdata (legend_handle, "__axes_handle__");
657      else
658        [axes_handles, varargin, nargs] = __plt_get_axis_arg__ ("legend",
659                                                                varargin{:});
660      endif
661    endif
662    if (isempty (axes_handles))
663      axes_handles = gca ();
664    endif
665  else
666    axes_handles = gca ();
667  endif
668
669  ## Special handling for plotyy which has two axes objects
670  if (isprop (axes_handles, "__plotyy_axes__"))
671    axes_handles = [axes_handles get(axes_handles, "__plotyy_axes__").'];
672    ## Remove duplicates while preserving order
673    [~, n] = unique (axes_handles, "first");
674    axes_handles = axes_handles(sort (n));
675  endif
676
677  ## Find any existing legend object associated with axes
678  if (isempty (legend_handle))
679    try
680      legend_handle = get (axes_handles, "__legend_handle__");
681      if (iscell (legend_handle))
682        legend_handle = unique (cell2mat (legend_handle));
683      endif
684    end_try_catch
685  endif
686
687  ## Legend actions
688  actions = {"show", "hide", "toggle", "boxon", ...
689             "boxoff", "right", "left", "off"};
690  if (nargs > 0 && ischar (varargin{1})
691      && any (strcmp (varargin{1}, actions)))
692    action = varargin{1};
693    if (nargs > 1)
694      warning ("Octave:legend:ignoring-extra-argument",
695               'legend: ignoring extra arguments after "%s"', action);
696    endif
697    nargs = 0;
698  endif
699
700  ## Now remove property-value pairs for compatibility.
701  propval = {};
702  warn_propval = "";
703  persistent legend_props = {"location", "orientation", "numcolumns", ...
704                             "numcolumnsmode", "textposition", ...
705                             "position", "units", "autoupdate", ...
706                             "string", "title", "interpreter", ...
707                             "fontname", "fontsize", "fontweight", ...
708                             "fontangle", "textcolor", "color", ...
709                             "edgecolor", "box", "linewidth", ...
710                             "visible", "uicontextmenu", "selected", ...
711                             "selectionhighlight", "itemhitfcn", ...
712                             "buttondownfcn", "createfcn", "deletefcn" ...
713                             "interruptible", "busyaction", ...
714                             "pickableparts", "hittest", ...
715                             "beingdeleted", "parent", "children", ...
716                             "handlevisibility", "tag", "type", ...
717                             "userdata"};
718  isprp = @(prop) (ischar (prop) && any (strcmpi (legend_props, prop)));
719  idx = find (cellfun (isprp, varargin));
720  if (! isempty (idx))
721    idx = idx(1);
722    propval = varargin(idx:end);
723    warn_propval = varargin{idx};
724    varargin(idx:end) = [];
725    nargs = idx-1;
726  endif
727
728  ## List plot objects that can be handled
729  warn_extra_obj = false;
730  persistent valid_types = {"line", "patch", "surface", "hggroup"};
731
732  if (nargs > 0 && all (ishghandle (varargin{1})))
733
734    ## List of plot objects to label given as first argument
735    obj_handles = varargin{1};
736    types = get (obj_handles, "type");
737    if (! iscell (types))
738      types = {types};
739    endif
740
741    idx = cellfun (@(s) any (strcmp (s, valid_types)), types);
742    if (! all (idx))
743      error ("Octave:legend:bad-object",
744             "legend: objects of type \"%s\" can't be labeled",
745             types(! idx){1});
746    endif
747    varargin(1) = [];
748    nargs--;
749    warn_extra_obj = true;
750
751  elseif (nargs > 0 || isempty (legend_handle))
752
753    ## Find list of plot objects from axes "children"
754    if (isscalar (axes_handles))
755      obj_handles = flipud (get (axes_handles, "children")(:));
756    else
757      tmp = get (axes_handles(:), "children");
758      obj_handles = [flipud(tmp{1}); flipud(tmp{2})];
759    endif
760
761    if (isempty (obj_handles))
762      error ("Octave:legend:no-object", "legend: no valid object to label");
763    endif
764
765    idx = arrayfun (@(h) any (strcmp (get (h, "type"), valid_types)), ...
766                              obj_handles);
767    obj_handles(! idx) = [];
768
769    if (isempty (obj_handles))
770      error ("Octave:legend:no-object", "legend: no valid object to label");
771    endif
772
773  else
774    obj_handles = getappdata (legend_handle, "__peer_objects__");
775  endif
776
777  nobj = numel (obj_handles);
778
779  ## List labels
780  next_idx = 1;
781  if (nargs > 0)
782
783    if (iscellstr (varargin{1}))
784      obj_labels = varargin{1};
785      varargin(1) = [];
786      nargs--;
787    elseif (ischar (varargin{1}) && rows (varargin{1}) > 1)
788      obj_labels = cellstr (varargin{1});
789      varargin(1) = [];
790      nargs--;
791    elseif (all (cellfun (@ischar, varargin)))
792      obj_labels = varargin;
793      varargin = {};
794      nargs = 0;
795    endif
796
797    if (nargs > 0)
798      print_usage ("legend");
799    endif
800
801    nlab = numel (obj_labels);
802    if (nlab != nobj)
803      if (nobj > nlab)
804        obj_handles = obj_handles(1:nlab);
805
806        msg = "legend: ignoring extra objects.";
807        if (! isempty (warn_propval))
808          msg = [msg ' "' warn_propval '" interpreted as a property ' , ...
809                 "name. Use a cell array of strings to specify labels ", ...
810                 "that match a legend property name."];
811        endif
812        if (warn_extra_obj)
813          warning ("Octave:legend:object-label-mismatch", msg);
814        endif
815      else
816        obj_labels = obj_labels(1:nobj);
817        warning ("Octave:legend:object-label-mismatch",
818                 "legend: ignoring extra labels.");
819      endif
820    endif
821  else
822    [tmp_labels, next_idx] = displayname_or_default (obj_handles,
823                                                     legend_handle);
824    if (isempty (legend_handle)
825        || ! isequal (tmp_labels, get (legend_handle, "string")))
826      obj_labels = tmp_labels;
827    endif
828
829  endif
830
831  opts.action = action;
832  opts.axes_handles = axes_handles;
833  opts.obj_handles = obj_handles;
834  opts.obj_labels = obj_labels;
835  opts.legend_handle = legend_handle;
836  opts.propval = propval;
837  opts.next_idx = next_idx;
838
839endfunction
840
841function [labels, next_idx] = displayname_or_default (hplots, hl = [])
842
843  next_idx = 1;
844  if (! isempty (hl))
845    next_idx = getappdata (hl, "__next_label_index__");
846  endif
847
848  ## Use the displayname property
849  labels = get (hplots, "displayname");
850  if (! iscell (labels))
851    labels = {labels};
852  endif
853
854  ## Fallback to automatic names for empty labels
855  empty_label_idx = cellfun (@isempty, labels);
856
857  if (any (empty_label_idx) && ! isempty (hl))
858    ## Empty strings must not be blindly replaced by data%d. If there exist
859    ## an old text object that was affected an empty string, keep it as is.
860    kids = get (hl, "children");
861    htext = kids(strcmp (get (kids, "type"), "text"));
862    old_objects = get (htext, "peer_object");
863    if (iscell (old_objects))
864      old_objects = cell2mat (old_objects);
865    endif
866
867    for h = hplots(empty_label_idx).'
868      idx = (h == old_objects);
869      if (any (idx))
870        labels(hplots == h) = get (htext(idx), "string");
871        empty_label_idx(hplots == h) = false;
872      endif
873    endfor
874
875  endif
876
877  if (any (empty_label_idx))
878    default = arrayfun (@(ii) sprintf ("data%d", ii), ...
879                        [next_idx:(next_idx + sum (empty_label_idx) - 1)], ...
880                        "uniformoutput", false)(:);
881    labels(empty_label_idx) = default;
882  endif
883
884  next_idx += sum (empty_label_idx);
885
886  if (! isempty (hl))
887    setappdata (hl, "__next_label_index__", next_idx);
888  endif
889
890endfunction
891
892function update_layout_cb (hl, ~, update_item = false)
893  updating = getappdata (hl, "__updating_layout__");
894  if (! isempty (updating) && updating)
895    return;
896  endif
897
898  setappdata(hl, "__updating_layout__", true);
899
900  ## Scale limits so that item positions are expressed in points, from
901  ## top to bottom and from left to right or reverse depending on textposition
902  units = get (hl, "units");
903  set (hl, "units", "points");
904
905  unwind_protect
906
907    if (update_item)
908      pos = get (hl, "position")(3:4);
909      set (hl, "xlim",  [0, pos(1)], "ylim",  [0, pos(2)]);
910
911      textright = strcmp (get (hl, "textposition"), "right");
912      set (hl, "ydir", "reverse", ...
913               "xdir", ifelse (textright, "normal", "reverse"));
914
915      ## Create or reuse text and icon graphics objects
916      objlist = texticon_objects (hl, textright);
917      nitem = rows (objlist);
918
919      ## Prepare the array of text/icon pairs and update their position
920      sz = update_texticon_position (hl, objlist);
921    else
922      sz = [diff(get (hl, "xlim")), diff(get (hl, "ylim"))];
923    endif
924
925    ## Place the legend
926    update_legend_position (hl, sz);
927
928  unwind_protect_cleanup
929    set (hl, "units", units);
930    setappdata(hl, "__updating_layout__", false);
931  end_unwind_protect
932
933endfunction
934
935function objlist = texticon_objects (hl, textright)
936
937  ## Delete or set invisible obsolete or unused text/icon objects.
938  old_kids = get (hl, "children")(:).';
939  old_peer_objects = cell2mat (get (old_kids, "peer_object"))(:).';
940  unused = ! ishghandle (old_peer_objects);
941  delete (old_kids(unused));
942  old_kids(unused) = [];
943  old_peer_objects(unused) = [];
944
945  new_peer_objects = getappdata (hl, "__peer_objects__")(:).';
946
947  unused = arrayfun (@(h) ! any (h == new_peer_objects), old_peer_objects);
948  set (old_kids(unused), "visible", "off");
949
950  ## Text properties
951  string = get (hl , "string");
952  if (! iscell (string))
953    string = {string};
954  endif
955
956  txtprops = {"textcolor", "fontsize", "fontweight", "fontname", ...
957              "interpreter"};
958  txtvals = get (hl, txtprops);
959  txtprops{1} = "color";
960  txtprops = [txtprops, "horizontalalignment"];
961  txtvals = [txtvals, ifelse(textright, "left", "right")];
962
963  ## Create or reuse text/icon objects as needed
964  nitem = numel (new_peer_objects);
965  objlist = NaN (nitem, 2);
966
967  for ii = 1:nitem
968
969    str = string{ii};
970    hplt = new_peer_objects(ii);
971
972    idx = (old_peer_objects == hplt);
973
974    if (any (idx))
975      tmp = old_kids(idx);
976      idx = strcmp (get (tmp, "type"), "text");
977
978      htxt = tmp(idx);
979      hicon = tmp(! idx);
980
981      set (htxt, "visible", "on", "string", str, ...
982                 [txtprops(:)'; txtvals(:)']{:});
983      set (hicon, "visible", "on");
984
985    else
986      [htxt, hicon] = create_item (hl, str, [txtprops(:)'; txtvals(:)'], hplt);
987      add_safe_listener (hl, hplt, "displayname", {@update_displayname_cb, hl});
988    endif
989
990    set (hplt, "displayname", str);
991
992    objlist(ii,:) = [htxt, hicon];
993  endfor
994
995endfunction
996
997function [htxt, hicon] = create_item (hl, str, txtpval, hplt)
998
999  typ = get (hplt, "type");
1000
1001  ## For unknown hggroups use the first child that can be labeled
1002  persistent known_creators = {"__contour__", "__errplot__", "__quiver__", ...
1003                               "__scatter__", "__stem__"};
1004  base_hplt = hplt;
1005
1006  if (strcmp (typ, "hggroup"))
1007    creator = getappdata (hplt, "__creator__");
1008    kids = get (hplt, "children");
1009    if (any (strcmp (known_creators, creator)))
1010      typ = creator;
1011      switch (creator)
1012        case "__contour__"
1013          hplt = [kids(end), kids(1)];
1014        case {"__errplot__", "__quiver__", "__stem__"}
1015          hplt = kids(2:-1:1);
1016        otherwise
1017          hplt = kids(1);
1018      endswitch
1019    else
1020      types = get (kids, "type");
1021      if (! iscell (types))
1022        types = {types};
1023      endif
1024
1025      idx = cellfun (@(s) any (strcmp (s, {"line", "patch", "surface"})), ...
1026                     types);
1027      hplt = kids(idx)(1);
1028      typ = types(idx){1};
1029    endif
1030  endif
1031
1032  persistent lprops = {"color", "linestyle", "linewidth"};
1033  persistent mprops = {"color", "marker", "markeredgecolor", ...
1034                       "markerfacecolor", "markersize"};
1035  persistent pprops = {"edgecolor", "facecolor", "cdata", ...
1036                       "linestyle", "linewidth", ...
1037                       "marker", "markeredgecolor", ...
1038                       "markerfacecolor", "markersize"};
1039
1040  switch (typ)
1041    case {"line", "__errplot__", "__quiver__", "__stem__"}
1042
1043      ## Main line
1044      vals = get (hplt(1), lprops);
1045      hicon = __go_line__ (hl, [lprops; vals]{:});
1046      addproperty ("markerxdata", hicon, "double", 0);
1047      addproperty ("markerydata", hicon, "double", 0);
1048
1049      ## Additional line for the marker
1050      vals = get (hplt(end), mprops);
1051      hmarker = __go_line__ (hl, "handlevisibility", "off", ...
1052                             "xdata", 0, "ydata", 0, [mprops; vals]{:});
1053      update_marker_cb (hmarker);
1054
1055      ## Listeners
1056      safe_property_link (hplt(1), hicon, lprops);
1057      safe_property_link (hplt(end), hmarker, mprops);
1058      addlistener (hicon, "ydata", ...
1059                   @(h) set (hmarker, "ydata", get (h, "markerydata")));
1060      addlistener (hicon, "xdata", ...
1061                   @(h) set (hmarker, "xdata", get (h, "markerxdata")));
1062      addlistener (hmarker, "markersize", @update_marker_cb);
1063      add_safe_listener (hl, hplt(1), "beingdeleted",
1064                         @() delete ([hicon hmarker]))
1065      if (! strcmp (typ, "__errplot__"))
1066        setappdata (hicon, "__creator__", typ);
1067      else
1068        setappdata (hicon, "__creator__", typ, ...
1069                    "__format__", get (base_hplt, "format"));
1070      endif
1071
1072    case {"patch", "surface", "__scatter__"}
1073
1074      vals = get (hplt, pprops);
1075
1076      hicon = __go_patch__ (hl, [pprops; vals]{:});
1077
1078      ## Listeners
1079      safe_property_link (hplt(1), hicon, pprops);
1080
1081      setappdata (hicon, "__creator__", typ);
1082
1083    case "__contour__"
1084
1085      ## Main patch
1086
1087      vals = get (hplt(1), pprops);
1088      hicon = __go_patch__ (hl, [pprops; vals]{:});
1089
1090      addproperty ("innerxdata", hicon, "any", 0);
1091      addproperty ("innerydata", hicon, "any", 0);
1092
1093      ## Additional patch for the inner contour
1094      vals = get (hplt(end), pprops);
1095      htmp =  __go_patch__ (hl, "handlevisibility", "off", ...
1096                            "xdata", 0, "ydata", 0, [pprops; vals]{:});
1097
1098      ## Listeners
1099      safe_property_link (hplt(1), hicon, pprops);
1100      safe_property_link (hplt(end), htmp, pprops);
1101      addlistener (hicon, "ydata", ...
1102                   @(h) set (htmp, "ydata", get (h, "innerydata")));
1103      addlistener (hicon, "xdata", ...
1104                   @(h) set (htmp, "xdata", get (h, "innerxdata")));
1105      add_safe_listener (hl, hplt(1), "beingdeleted",
1106                         @() delete ([hicon htmp]))
1107
1108      setappdata (hicon, "__creator__", typ);
1109
1110  endswitch
1111
1112  htxt = __go_text__ (hl, "string", str, txtpval{:});
1113
1114  addproperty ("peer_object", htxt, "double", base_hplt);
1115  addproperty ("peer_object", hicon, "double", base_hplt);
1116
1117endfunction
1118
1119function safe_property_link (h1, h2, props)
1120  for ii = 1:numel (props)
1121    prop = props{ii};
1122    lsn = {h1, prop, @(h) set (h2, prop, get (h, prop))};
1123    addlistener (lsn{:});
1124    addlistener (h2, "beingdeleted", @() dellistener (lsn{:}));
1125  endfor
1126endfunction
1127
1128function update_displayname_cb (h, ~, hl)
1129
1130  updating = getappdata (hl, "__updating_layout__");
1131  if (! isempty (updating) && updating)
1132    return;
1133  endif
1134
1135  str = get (hl, "string");
1136  if (! iscell (str))
1137    str = {str};
1138  endif
1139
1140  str{h == getappdata (hl, "__peer_objects__")} = get (h, "displayname");
1141
1142  set (hl ,"string", str);
1143
1144endfunction
1145
1146function update_marker_cb (h)
1147
1148  if (get (h, "markersize") > 8)
1149    set (h, "markersize", 8);
1150  endif
1151
1152endfunction
1153
1154function sz = update_texticon_position (hl, objlist)
1155
1156  ## margins in points
1157  persistent hmargin = 3;
1158  persistent vmargin = 3;
1159  persistent icon_width = 15;
1160
1161  units = get (hl, "fontunits");
1162  set (hl, "fontunits", "points");
1163  icon_height = 0.7 * get (hl, "fontsize");
1164  set (hl, "fontunits", units);
1165
1166  ext = get (objlist(:,1), "extent");
1167  markers = get (objlist(:,2), "marker");
1168  markersz = get (objlist(:,2), "markersize");
1169  types = get (objlist(:,2), "type");
1170
1171  ## Simple case of 1 text/icon pair
1172  nitem = rows (objlist);
1173  txticon = zeros (nitem, 4);
1174  if (nitem == 1)
1175    ext = abs (ext(:,3:4));
1176    types = {types};
1177    markers = {markers};
1178    markersz = {markersz};
1179  else
1180    ext = abs (cell2mat (ext)(:,3:4));
1181  endif
1182
1183  autolayout = strcmp (get (hl, "numcolumnsmode"), "auto");
1184  xmax = ymax = 0;
1185  iter = 1;
1186
1187  if (strcmp (get (hl, "orientation"), "vertical"))
1188
1189    if (autolayout)
1190      ncol = 1;
1191    else
1192      ncol = min (nitem, get (hl, "numcolumns"));
1193    endif
1194
1195    nrow = ceil (nitem / ncol);
1196
1197    rowheights = arrayfun (@(idx) max([icon_height; ext(idx:nrow:end, 2)]), ...
1198                           1:nrow);
1199    x = hmargin;
1200    for ii = 1:ncol
1201      y = vmargin;
1202      for jj = 1:nrow
1203        if (iter > nitem)
1204          continue;
1205        endif
1206
1207        hg = rowheights(jj);
1208        dx = 0;
1209        if (! strcmp (markers{iter}, "none"))
1210          dx = markersz{iter}/2;
1211        endif
1212
1213        ybase = y + hg / 2;
1214        y0 = y + hg/2 - icon_height/2 + dx;
1215        y1 = y + hg/2 + icon_height/2 - dx;
1216
1217        update_icon_position (objlist(iter,2), [x+dx, x+icon_width-dx], ...
1218                              [y0, y1]);
1219
1220        set (objlist(iter,1), "position", [x+icon_width+hmargin, ybase, 0]);
1221
1222        xmax = max ([xmax, x+icon_width+2*hmargin+ext(iter,1)]);
1223        y += (vmargin + hg);
1224        iter++;
1225      endfor
1226      ymax = max ([ymax, y]);
1227      x = xmax + 2*hmargin;
1228    endfor
1229
1230  else
1231
1232    if (autolayout)
1233      ncol = nitem;
1234    else
1235      ncol = min (nitem, get (hl, "numcolumns"));
1236    endif
1237
1238    nrow = ceil (nitem / ncol);
1239
1240    colwidth = arrayfun (@(idx) max(ext(idx:ncol:end, 1)),
1241                         1:ncol);
1242    y = vmargin;
1243    for ii = 1:nrow
1244      x = hmargin;
1245
1246      endidx = min (iter+ncol-1, nitem);
1247      hg = max ([icon_height; ext(iter:endidx,2)]);
1248
1249      for jj = 1:ncol
1250        if (iter > nitem)
1251          continue;
1252        endif
1253
1254        wd = colwidth(jj);
1255
1256        dx = 0;
1257        if (! strcmp (markers{iter}, "none"))
1258          dx = markersz{iter}/2;
1259        endif
1260
1261        ybase = y + hg / 2;
1262        y0 = y + hg/2 - icon_height/2 + dx;
1263        y1 = y + hg/2 + icon_height/2 - dx;
1264
1265        update_icon_position (objlist(iter,2), [x+dx, x+icon_width-dx], ...
1266                              [y0, y1]);
1267
1268        set (objlist(iter,1), "position", [x+icon_width+hmargin, ybase, 0]);
1269
1270        ymax = max ([ymax, ybase+hg/2+vmargin]);
1271        x += (3*hmargin + icon_width + wd);
1272        iter++;
1273      endfor
1274      xmax = max ([xmax, x-hmargin]);
1275      y = ymax + vmargin;
1276    endfor
1277
1278  endif
1279
1280  sz = [xmax, ymax];
1281
1282endfunction
1283
1284function update_icon_position (hicon, xdata, ydata)
1285  creator = getappdata (hicon, "__creator__");
1286  switch (creator)
1287    case "line"
1288      set (hicon, "markerxdata", mean (xdata), "markerydata", mean (ydata), ...
1289           "xdata", xdata, "ydata", [mean(ydata), mean(ydata)]);
1290    case {"patch", "surface"}
1291      set (hicon, ...
1292           "xdata", [xdata, fliplr(xdata)], ...
1293           "ydata", [ydata; ydata](:).');
1294    case "__contour__"
1295      ## Draw two patches
1296      x0 = xdata(1);
1297      x1 = xdata(2);
1298      xm = mean (xdata);
1299      y0 = ydata(1);
1300      y1 = ydata(2);
1301      ym = mean (ydata);
1302
1303      xdata = [x0, x1, x1, x0];
1304      ydata = [y0, y0, y1, y1];
1305      set (hicon, ...
1306           "innerxdata", (xdata-xm) * 0.6 + xm, ...
1307           "innerydata", (ydata-ym) * 0.4 + ym, ...
1308           "xdata", xdata, "ydata", ydata);
1309    case "__errplot__"
1310      x0 = xdata(1);
1311      x1 = xdata(2);
1312      xm = mean (xdata);
1313      y0 = ydata(1);
1314      y1 = ydata(2);
1315      ym = mean (ydata);
1316
1317      fmt = getappdata (hicon, "__format__");
1318      if (strcmp (fmt, "yerr"))
1319        xdata = [xm, xm, xm-2, xm+2, xm, xm, xm-2, xm+2];
1320        ydata = [ym, y0, y0, y0, y0, y1, y1, y1];
1321      elseif (strcmp (fmt, "xerr"))
1322        xdata = [x0+2, x0+2, x0+2, x1-2, x1-2, x1-2, x1-2];
1323        ydata = [ym+2, ym-2, ym, ym, ym+2, ym-2, ym];
1324      elseif (strcmp (fmt, "xyerr"))
1325        xdata = [x0+2, x0+2, x0+2, x1-2, x1-2, x1-2, x1-2, ...
1326                 xm, xm, xm-2, xm+2, xm, xm, xm-2, xm+2];
1327        ydata = [ym+2, ym-2, ym, ym, ym+2, ym-2, ym, ...
1328                 ym, y0, y0, y0, y0, y1, y1, y1];
1329      elseif (strncmp (fmt, "box", 3))
1330        xdata = [x0+2, x1-2, x1-2, x0+2, x0+2];
1331        ydata = [y0, y0, y1, y1, y0];
1332      else
1333        xdata = [x0, x1];
1334        ydata = [ym, ym];
1335      endif
1336
1337      set (hicon, "markerxdata", xm, "markerydata", ym, ...
1338           "xdata", xdata, "ydata", ydata);
1339
1340    case "__quiver__"
1341      ## Draw an arrow
1342      x0 = xdata(1);
1343      x1 = xdata(2);
1344      y0 = mean (ydata);
1345      xdata = [x0, x1, x1-2, x1, x1-2];
1346      ydata = [y0, y0, y0+2, y0, y0-2];
1347      set (hicon, "markerxdata", x0, "markerydata", y0, ...
1348           "xdata", xdata, "ydata", ydata);
1349    case "__scatter__"
1350      set (hicon, "xdata", mean(xdata), "ydata", mean(ydata));
1351    case "__stem__"
1352      xdata(2) -= (get (get (hicon, "peer_object"), "markersize") / 2);
1353      set (hicon, "markerxdata", xdata(2), "markerydata", mean (ydata), ...
1354           "xdata", xdata, "ydata", [mean(ydata), mean(ydata)]);
1355  endswitch
1356endfunction
1357
1358function pos = boxposition (axpos, pba, pbam, dam)
1359
1360  pos = axpos;
1361
1362  if (strcmp (pbam, "auto") && strcmp (dam, "auto"))
1363    return;
1364  endif
1365
1366  pbratio = pba(1)/pba(2);
1367  posratio = axpos(3)/axpos(4);
1368
1369  if (pbratio != posratio)
1370    if (posratio < pbratio)
1371      pos(4) = pos(3) / pbratio;
1372      pos(2) += (axpos(4) - pos(4)) / 2;
1373    else
1374      pos(3) = pos(4) * pbratio;
1375      pos(1) += (axpos(3) - pos(3)) / 2;
1376    endif
1377  endif
1378
1379endfunction
1380
1381function update_legend_position (hl, sz)
1382
1383  persistent hmargin = 6;
1384  persistent vmargin = 6;
1385
1386  location = get (hl, "location");
1387  outside = strcmp (location(end-3:end), "side");
1388  if (outside)
1389    location = location(1:end-7);
1390  endif
1391
1392  if (strcmp (location, "best"))
1393    orientation = get (hl, "orientation");
1394    if (outside)
1395      if (strcmp (orientation, "vertical"))
1396        location = "northeast";
1397      else
1398        location = "south";
1399      endif
1400    else
1401      ## FIXME: implement "best" inside properly
1402      location = "northeast";
1403    endif
1404  endif
1405
1406  haxes = getappdata (hl, "__axes_handle__");
1407  hax = haxes(end);
1408  units = get (hax, "units");
1409
1410  unwind_protect
1411    ## Restore the original looseinset first and set units to points.
1412    li = getappdata (hl, "__original_looseinset__");
1413    if (isempty (li))
1414      li = get (hax, "looseinset");
1415      setappdata (hl, "__original_looseinset__", li);
1416      setappdata (hl, "__original_units__", units);
1417    endif
1418
1419    if (strcmp (get (hl, "visible"), "on"))
1420      set (hax, "units", getappdata (hl, "__original_units__"),
1421                "looseinset", li,
1422                "units", "points");
1423    else
1424      ## Return early for invible legends
1425      set (hax, "units", getappdata (hl, "__original_units__"),
1426                "looseinset", li,
1427                "units", units);
1428      return;
1429    endif
1430
1431    [li, axpos, pbam, pba, dam] = get (hax, {"looseinset", "position", ...
1432                                             "plotboxaspectratiomode", ...
1433                                             "plotboxaspectratio", ...
1434                                             "dataaspectratiomode"}){:};
1435
1436    axpos = boxposition (axpos, pba, pbam, dam);
1437
1438    lpos = [get(hl, "position")(1:2), sz];
1439
1440    if (! outside)
1441
1442      switch (location)
1443        case "southwest"
1444          lpos(1) = axpos(1) + hmargin;
1445          lpos(2) = axpos(2) + vmargin;
1446        case "west"
1447          lpos(1) = axpos(1) + hmargin;
1448          lpos(2) = axpos(2) + axpos(4)/2 - lpos(4)/2;
1449        case "northwest"
1450          lpos(1) = axpos(1) + hmargin;
1451          lpos(2) = axpos(2) + axpos(4) - lpos(4) - vmargin;
1452        case "north"
1453          lpos(1) = axpos(1) + axpos(3)/2 - lpos(3)/2;
1454          lpos(2) = axpos(2) + axpos(4) - lpos(4) - vmargin;
1455        case "northeast"
1456          lpos(1) = axpos(1) + axpos(3) - lpos(3) - hmargin;
1457          lpos(2) = axpos(2) + axpos(4) - lpos(4) - vmargin;
1458        case "east"
1459          lpos(1) = axpos(1) + axpos(3) - lpos(3) - hmargin;
1460          lpos(2) = axpos(2) + axpos(4)/2 - lpos(4)/2;
1461        case "southeast"
1462          lpos(1) = axpos(1) + axpos(3) - lpos(3) - hmargin;
1463          lpos(2) = axpos(2) + vmargin;
1464        case "south"
1465          lpos(1) = axpos(1) + axpos(3)/2 - lpos(3)/2;
1466          lpos(2) = axpos(2) + vmargin;
1467      endswitch
1468
1469    else
1470
1471      ## FIXME: Is there a simpler way to know the size of the box
1472      ##        enclosing labels than temporarily changing the
1473      ##        plotboxaspectratiomode
1474      if (strcmp (pbam, "auto"))
1475        ti = get (haxes, "tightinset");
1476      else
1477        set (haxes, "plotboxaspectratiomode", "auto");
1478        ti = get (haxes, "tightinset");
1479        set (haxes, "plotboxaspectratio", pba);
1480      endif
1481
1482      if (iscell (ti))
1483        ti = max (cell2mat (ti));
1484      endif
1485
1486      switch (location)
1487        case "southwest"
1488          dx = lpos(3) + hmargin + ti(1);
1489          if (axpos(1) < (dx + hmargin))
1490            li(1) = min (dx + hmargin, 0.95 * (axpos(1) + axpos(3)));
1491            set (hax, "looseinset", li);
1492            axpos = boxposition (get (hax, "position"), pba, pbam, dam);
1493          endif
1494          lpos(1) = axpos(1) - dx;
1495          lpos(2) = axpos(2);
1496        case "west"
1497          dx = lpos(3) + hmargin + ti(1);
1498          if (axpos(1) < (dx + hmargin))
1499            li(1) = min (dx + hmargin, 0.95 * (axpos(1) + axpos(3)));
1500            set (hax, "looseinset", li);
1501            axpos = boxposition (get (hax, "position"), pba, pbam, dam);
1502          endif
1503          lpos(1) = axpos(1) - dx;
1504          lpos(2) = axpos(2) + axpos(4)/2 - lpos(4)/2;
1505        case "northwest"
1506          dx = lpos(3) + hmargin + ti(1);
1507          if (axpos(1) < (dx + hmargin))
1508            li(1) = min (dx + hmargin, 0.95 * (axpos(1) + axpos(3)));
1509            set (hax, "looseinset", li);
1510            axpos = boxposition (get (hax, "position"), pba, pbam, dam);
1511          endif
1512          lpos(1) = axpos(1) - dx;
1513          lpos(2) = axpos(2) + axpos(4) - lpos(4);
1514        case "north"
1515          dy = lpos(4) + vmargin + ti(4);
1516          if (li(4) < (dy + vmargin))
1517            li(4) = min (dy + vmargin, axpos(4));
1518            set (hax, "looseinset", li);
1519            axpos = boxposition (get (hax, "position"), pba, pbam, dam);
1520          endif
1521          lpos(1) = axpos(1) + axpos(3)/2 - lpos(3)/2;
1522          lpos(2) = axpos(2) + axpos(4) + vmargin + ti(4);
1523        case "northeast"
1524          dx = lpos(3) + hmargin + ti(3);
1525          if (li(3) < (dx + hmargin))
1526            li(3) = min (dx + hmargin, axpos(3));
1527            set (hax, "looseinset", li);
1528            axpos = boxposition (get (hax, "position"), pba, pbam, dam);
1529          endif
1530          lpos(1) = axpos(1) + axpos(3) + hmargin + ti(3);
1531          lpos(2) = axpos(2) + axpos(4) - lpos(4);
1532        case "east"
1533          dx = lpos(3) + hmargin + ti(3);
1534          if (li(3) < (dx + hmargin))
1535            li(3) = min (dx + hmargin, axpos(3));
1536            set (hax, "looseinset", li);
1537            axpos = boxposition (get (hax, "position"), pba, pbam, dam);
1538          endif
1539          lpos(1) = axpos(1) + axpos(3) + hmargin + ti(3);
1540          lpos(2) = axpos(2) + axpos(4)/2 - lpos(4)/2;
1541        case "southeast"
1542          dx = lpos(3) + hmargin + ti(3);
1543          if (li(3) < (dx + hmargin))
1544            li(3) = min (dx + hmargin, axpos(3));
1545            set (hax, "looseinset", li);
1546            axpos = boxposition (get (hax, "position"), pba, pbam, dam);
1547          endif
1548          lpos(1) = axpos(1) + axpos(3) + hmargin + ti(3);
1549          lpos(2) = axpos(2);
1550        case "south"
1551          dy = lpos(4) + vmargin + ti(2);
1552          if (li(2) < (dy + vmargin))
1553            li(2) = min (dy + vmargin, 0.95 * (axpos(2) + axpos(4)));
1554            set (hax, "looseinset", li);
1555            axpos = boxposition (get (hax, "position"), pba, pbam, dam);
1556          endif
1557          lpos(1) = axpos(1) + axpos(3)/2 - lpos(3)/2;
1558          lpos(2) = axpos(2) - lpos(4) - vmargin - ti(2);
1559      endswitch
1560    endif
1561
1562    set (hl, "xlim", [0, sz(1)], "ylim", [0, sz(2)], ...
1563             "position", lpos);
1564
1565    setappdata (hl, "__peer_axes_position__", axpos);
1566
1567  unwind_protect_cleanup
1568    set (hax, "units", units);
1569  end_unwind_protect
1570
1571endfunction
1572
1573
1574%!demo
1575%! clf;
1576%! plot (rand (2));
1577%! title ("legend called with string inputs for labels");
1578%! h = legend ("foo", "bar");
1579%! set (h, "fontsize", 20, "location", "northeastoutside");
1580
1581%!demo
1582%! clf;
1583%! plot (rand (2));
1584%! title ("legend called with cell array of strings");
1585%! h = legend ({"cellfoo", "cellbar"});
1586%! set (h, "fontsize", 20, "location", "northeast");
1587
1588%!demo
1589%! clf;
1590%! plot (rand (3));
1591%! title ("legend () without inputs creates default labels");
1592%! h = legend ();
1593
1594%!demo
1595%! clf;
1596%! x = 0:1;
1597%! hline = plot (x,x,";I am Blue;", x,2*x, x,3*x,";I am yellow;");
1598%! h = legend ();
1599%! set (h, "location", "northeastoutside");
1600%! ## Placing legend inside returns axes to original size
1601%! set (h, "location", "northeast");
1602%! title ("Blue and Yellow keys, with Orange missing");
1603
1604%!demo
1605%! clf;
1606%! plot (1:10, 1:10, 1:10, fliplr (1:10));
1607%! title ("incline is blue and decline is orange");
1608%! legend ({"I am blue", "I am orange"}, "location", "east");
1609%! legend hide
1610%! legend show
1611
1612%!demo
1613%! clf;
1614%! plot (1:10, 1:10, 1:10, fliplr (1:10));
1615%! title ("Legend with keys in horizontal orientation");
1616%! legend ({"I am blue", "I am orange"}, ...
1617%!         "location", "east", "orientation", "horizontal");
1618%! legend boxoff
1619%! legend boxon
1620
1621%!demo
1622%! clf;
1623%! plot (1:10, 1:10, 1:10, fliplr (1:10));
1624%! title ("Legend with box off");
1625%! legend ({"I am blue", "I am orange"}, "location", "east");
1626%! legend boxoff
1627
1628%!demo
1629%! clf;
1630%! plot (1:10, 1:10, 1:10, fliplr (1:10));
1631%! title ("Legend with text to the left of key");
1632%! legend ({"I am blue", "I am orange"}, "location", "east");
1633%! legend left
1634
1635%!demo
1636%! clf;
1637%! plot (1:10, 1:10, 1:10, fliplr (1:10));
1638%! title ({"Use properties to place legend text to the left of key", ...
1639%!         "Legend text color is magenta"});
1640%! h = legend ({"I am blue", "I am orange"}, "location", "east");
1641%! legend ("right");
1642%! set (h, "textposition", "left");
1643%! set (h, "textcolor", [1, 0, 1]);
1644
1645%!demo
1646%! clf;
1647%! plot (1:10, 1:10, 1:10, fliplr (1:10));
1648%! title ("Legend is hidden");
1649%! legend ({"I am blue", "I am orange"}, "location", "east");
1650%! legend hide
1651
1652%!demo
1653%! clf;
1654%! x = 0:1;
1655%! plot (x,x,";I am Blue;", x,2*x,";I am Orange;", x,3*x,";I am Yellow;");
1656%! title ({"Labels are embedded in call to plot", ...
1657%!         "Legend is hidden and then shown"});
1658%! legend boxon
1659%! legend hide
1660%! legend show
1661
1662%!demo
1663%! clf;
1664%! x = 0:1;
1665%! plot (x,x,  x,2*x, x,3*x);
1666%! title ("Labels with interpreted Greek text");
1667%! h = legend ('\alpha', '\beta=2\alpha', '\gamma=3\alpha');
1668%! set (h, "interpreter", "tex");
1669
1670%!demo
1671%! clf;
1672%! plot (rand (2));
1673%! title ("Labels with TeX interpreter turned off");
1674%! h = legend ("Hello_World", "foo^bar");
1675%! set (h, "interpreter", "none");
1676
1677%!demo
1678%! clf;
1679%! labels = {};
1680%! colororder = get (gca, "colororder");
1681%! for i = 1:5
1682%!   h = plot (1:100, i + rand (100,1)); hold on;
1683%!   set (h, "color", colororder(i,:));
1684%!   labels = {labels{:}, ["Signal ", num2str(i)]};
1685%! endfor
1686%! hold off;
1687%! title ({"Signals with random offset and uniform noise";
1688%!         "Legend shown below and outside of plot"});
1689%! xlabel ("Sample Nr [k]"); ylabel ("Amplitude [V]");
1690%! legend (labels, "location", "southoutside");
1691
1692%!demo
1693%! clf;
1694%! x = linspace (0, 10);
1695%! plot (x, x);
1696%! hold on;
1697%! stem (x, x.^2, "g");
1698%! title ("First created object gets first label");
1699%! legend ("linear");
1700%! hold off;
1701
1702%!demo
1703%! clf;
1704%! x = linspace (0, 10);
1705%! plot (x, x, x, x.^2);
1706%! title ("First created object gets first label");
1707%! legend ("linear");
1708
1709%!demo
1710%! clf;
1711%! x = linspace (0, 10);
1712%! plot (x, x, x, x.^2);
1713%! title ("Labels are applied in order of object creation");
1714%! legend ("linear", "quadratic");
1715
1716%!demo
1717%! clf;
1718%! subplot (2,1,1);
1719%! rand_2x3_data1 = [0.341447, 0.171220, 0.284370; 0.039773, 0.731725, 0.779382];
1720%! bar (rand_2x3_data1);
1721%! ylim ([0, 1.0]);
1722%! title ("legend() works for bar graphs (hggroups)");
1723%! legend ({"1st Bar", "2nd Bar", "3rd Bar"}, "location", "northwest");
1724%! subplot (2,1,2);
1725%! x = linspace (0, 10, 20);
1726%! stem (x, 0.5+x.*rand (size (x))/max (x), "markeredgecolor", [0, 0.7, 0]);
1727%! hold on;
1728%! stem (x+10/(2*20), x.*(1.0+rand (size (x)))/max (x));
1729%! xlim ([0, 10+10/(2*20)]);
1730%! title ("legend() works for stem plots (hggroups)");
1731%! legend ({"Multicolor", "Unicolor"}, "location", "northwest");
1732
1733%!demo
1734%! clf;
1735%! colormap (cool (64));
1736%! surf (peaks ());
1737%! legend ("peaks()");
1738%! title ("legend() works for surface objects too");
1739
1740%!demo
1741%! clf reset;  # needed to undo colormap assignment in previous demo
1742%! rand_2x3_data2 = [0.44804, 0.84368, 0.23012; 0.72311, 0.58335, 0.90531];
1743%! bar (rand_2x3_data2);
1744%! ylim ([0, 1.2]);
1745%! title ('"left" option places colors to the left of text label');
1746%! legend ("1st Bar", "2nd Bar", "3rd Bar");
1747%! legend left;
1748
1749%!demo
1750%! clf;
1751%! x = 0:0.1:7;
1752%! h = plot (x,sin(x), x,cos(x), x,sin(x.^2/10), x,cos(x.^2/10));
1753%! title ("Only the sin() objects have keylabels");
1754%! legend (h([1, 3]), {"sin (x)", "sin (x^2/10)"}, "location", "southwest");
1755
1756%!demo
1757%! clf;
1758%! x = 0:0.1:10;
1759%! plot (x, sin (x), ";sin (x);");
1760%! hold on;
1761%! plot (x, cos (x), ";cos (x);");
1762%! hold off;
1763%! title ("legend constructed from multiple plot calls");
1764
1765%!demo
1766%! clf;
1767%! x = 0:0.1:10;
1768%! plot (x, sin (x), ";sin (x);");
1769%! hold on;
1770%! plot (x, cos (x), ";cos (x);");
1771%! hold off;
1772%! title ("Specified label text overrides previous labels");
1773%! legend ({"Sine", "Cosine"}, "location", "northeastoutside");
1774
1775%!demo
1776%! clf;
1777%! x = 0:10;
1778%! plot (x, rand (11));
1779%! axis ([0, 10, 0, 1]);
1780%! xlabel ("Indices");
1781%! ylabel ("Random Values");
1782%! title ('Legend "off" deletes the legend');
1783%! legend (cellstr (num2str ((0:10)')), "location", "northeastoutside");
1784%! pause (1);
1785%! legend off;
1786
1787%!demo
1788%! clf;
1789%! x = (1:5)';
1790%! subplot (2,2,1);
1791%!  plot (x, rand (numel (x)));
1792%!  legend (cellstr (num2str (x)), "location", "northwestoutside");
1793%! subplot (2,2,2);
1794%!  plot (x, rand (numel (x)));
1795%!  legend (cellstr (num2str (x)), "location", "northeastoutside");
1796%! subplot (2,2,3);
1797%!  plot (x, rand (numel (x)));
1798%!  legend (cellstr (num2str (x)), "location", "southwestoutside");
1799%! subplot (2,2,4);
1800%!  plot (x, rand (numel (x)));
1801%!  legend (cellstr (num2str (x)), "location", "southeastoutside");
1802%! ## Legend works on a per axes basis for each subplot
1803
1804%!demo
1805%! clf;
1806%! plot (rand (2));
1807%! title ("legend() will warn if extra labels are specified");
1808%! legend ("Hello", "World", "foo", "bar");
1809
1810%!demo
1811%! clf;
1812%! x = 0:10;
1813%! y1 = rand (size (x));
1814%! y2 = rand (size (x));
1815%! [ax, h1, h2] = plotyy (x, y1, x, y2);
1816%! title ({"plotyy legend test #1", "Blue label to left axis, Orange label to right axis"});
1817%! drawnow ();
1818%! legend ("Blue", "Orange", "location", "south");
1819
1820%!demo
1821%! clf;
1822%! x = 0:10;
1823%! y1 = rand (size (x));
1824%! y2 = rand (size (x));
1825%! [ax, h1, h2] = plotyy (x, y1, x, y2);
1826%! ylabel (ax(1), {"Blue", "Y", "Axis"});
1827%! title ('plotyy legend test #2: "westoutside" adjusts to ylabel');
1828%! drawnow ();
1829%! legend ([h1, h2], {"Blue", "Orange"}, "location", "westoutside");
1830
1831%!demo
1832%! clf;
1833%! x = 0:10;
1834%! y1 = rand (size (x));
1835%! y2 = rand (size (x));
1836%! [ax, h1, h2] = plotyy (x, y1, x, y2);
1837%! ylabel (ax(2), {"Orange", "Y", "Axis"});
1838%! title ('plotyy legend test #3: "eastoutside" adjusts to ylabel');
1839%! drawnow ();
1840%! legend ([h1, h2], {"Blue", "Orange"}, "location", "eastoutside");
1841
1842%!demo
1843%! clf;
1844%! plot (1:10, 1:10);
1845%! title ("a very long label can sometimes cause problems");
1846%! legend ("hello very big world", "location", "northeastoutside");
1847
1848%!demo  # bug 36408
1849%! clf;
1850%! subplot (3,1,1);
1851%!  plot (rand (1,4));
1852%!  xlabel xlabel;
1853%!  ylabel ylabel;
1854%!  title ("Subplots adjust to the legend placed outside");
1855%!  legend ({"1"}, "location", "northeastoutside");
1856%! subplot (3,1,2);
1857%!  plot (rand (1,4));
1858%!  xlabel xlabel;
1859%!  ylabel ylabel;
1860%!  legend ({"1234567890"}, "location", "eastoutside");
1861%! subplot (3,1,3);
1862%!  plot (rand (1,4));
1863%!  xlabel xlabel;
1864%!  ylabel ylabel;
1865%!  legend ({"12345678901234567890"}, "location", "southeastoutside");
1866
1867%!demo  # bug 36408
1868%! clf;
1869%! subplot (3,1,1);
1870%!  plot (rand (1,4));
1871%!  title ("Subplots adjust to the legend placed outside");
1872%!  legend ({"1"}, "location", "northwestoutside");
1873%! subplot (3,1,2);
1874%!  plot (rand (1,4));
1875%!  legend ({"1234567890"}, "location", "westoutside");
1876%! subplot (3,1,3);
1877%!  plot (rand (1,4));
1878%!  legend ({"12345678901234567890"}, "location", "southwestoutside");
1879
1880%!demo  # bug 36408
1881%! clf;
1882%! subplot (3,1,1);
1883%!  plot (rand (1,4));
1884%!  set (gca (), "yaxislocation", "right");
1885%!  xlabel ("xlabel");
1886%!  ylabel ("ylabel");
1887%!  title ("Subplots adjust to the legend placed outside");
1888%!  legend ({"1"}, "location", "northeastoutside");
1889%! subplot (3,1,2);
1890%!  plot (rand (1,4));
1891%!  set (gca (), "yaxislocation", "right");
1892%!  xlabel ("xlabel");
1893%!  ylabel ("ylabel");
1894%!  legend ({"1234567890"}, "location", "eastoutside");
1895%! subplot (3,1,3);
1896%!  plot (rand (1,4));
1897%!  set (gca (), "yaxislocation", "right");
1898%!  xlabel ("xlabel");
1899%!  ylabel ("ylabel");
1900%!  legend ({"12345678901234567890"}, "location", "southeastoutside");
1901
1902%!demo  # bug 36408
1903%! clf;
1904%! subplot (3,1,1);
1905%!  plot (rand (1,4));
1906%!  set (gca (), "yaxislocation", "right");
1907%!  xlabel ("xlabel");
1908%!  ylabel ("ylabel");
1909%!  title ("Subplots adjust to the legend placed outside");
1910%!  legend ({"1"}, "location", "northwestoutside");
1911%! subplot (3,1,2);
1912%!  plot (rand (1,4));
1913%!  set (gca (), "yaxislocation", "right");
1914%!  xlabel ("xlabel");
1915%!  ylabel ("ylabel");
1916%!  legend ({"1234567890"}, "location", "westoutside");
1917%! subplot (3,1,3);
1918%!  plot (rand (1,4));
1919%!  set (gca (), "yaxislocation", "right");
1920%!  xlabel ("xlabel");
1921%!  ylabel ("ylabel");
1922%!  legend ({"12345678901234567890"}, "location", "southwestoutside");
1923
1924%!demo  # bug 36408;
1925%! clf;
1926%! subplot (3,1,1);
1927%!  plot (rand (1,4));
1928%!  set (gca (), "xaxislocation", "top");
1929%!  xlabel ("xlabel");
1930%!  ylabel ("ylabel");
1931%!  title ("Subplots adjust to the legend placed outside");
1932%!  legend ({"1"}, "location", "northwestoutside");
1933%! subplot (3,1,2);
1934%!  plot (rand (1,4));
1935%!  set (gca (), "xaxislocation", "top");
1936%!  xlabel ("xlabel");
1937%!  ylabel ("ylabel");
1938%!  legend ({"1234567890"}, "location", "westoutside");
1939%! subplot (3,1,3);
1940%!  plot (rand (1,4));
1941%!  set (gca (), "xaxislocation", "top");
1942%!  xlabel ("xlabel");
1943%!  ylabel ("ylabel");
1944%!  legend ({"12345678901234567890"}, "location", "southwestoutside");
1945
1946%!demo  # bug 39697
1947%! clf;
1948%! plot (1:10);
1949%! legend ("Legend Text");
1950%! title ({"Multi-line", "titles", "are *not* a", "problem"});
1951
1952## Test input validation
1953%!test
1954%! hf = figure ("visible", "off");
1955%! unwind_protect
1956%!   try
1957%!     legend ();
1958%!   catch
1959%!     [~, id] = lasterr ();
1960%!     assert (id, "Octave:legend:no-object");
1961%!   end_try_catch
1962%! unwind_protect_cleanup
1963%!   close (hf);
1964%! end_unwind_protect
1965
1966%!test
1967%! hf = figure ("visible", "off");
1968%! unwind_protect
1969%!   axes ();
1970%!   try
1971%!     legend ();
1972%!   catch
1973%!     [~, id] = lasterr ();
1974%!     assert (id, "Octave:legend:no-object");
1975%!   end_try_catch
1976%! unwind_protect_cleanup
1977%!   close (hf);
1978%! end_unwind_protect
1979
1980%!test
1981%! hf = figure ("visible", "off");
1982%! unwind_protect
1983%!   axes ();
1984%!   light ();
1985%!   try
1986%!     legend ();
1987%!   catch
1988%!     [~, id] = lasterr ();
1989%!     assert (id, "Octave:legend:no-object");
1990%!   end_try_catch
1991%! unwind_protect_cleanup
1992%!   close (hf);
1993%! end_unwind_protect
1994
1995%!test
1996%! hf = figure ("visible", "off");
1997%! unwind_protect
1998%!   axes ();
1999%!   hli = light ();
2000%!   try
2001%!     legend (hli);
2002%!   catch
2003%!     [~, id] = lasterr ();
2004%!     assert (id, "Octave:legend:bad-object");
2005%!   end_try_catch
2006%! unwind_protect_cleanup
2007%!   close (hf);
2008%! end_unwind_protect
2009
2010%!test
2011%! hf = figure ("visible", "off");
2012%! unwind_protect
2013%!   axes ();
2014%!   hplot = plot (rand (3));
2015%!   try
2016%!     legend (hplot, struct);
2017%!   catch
2018%!     [~, id] = lasterr ();
2019%!     assert (id, "Octave:invalid-fun-call");
2020%!   end_try_catch
2021%! unwind_protect_cleanup
2022%!   close (hf);
2023%! end_unwind_protect
2024
2025%!test
2026%! hf = figure ("visible", "off");
2027%! unwind_protect
2028%!   axes ();
2029%!   hplot = plot (rand (3));
2030%!   try
2031%!     legend ("a", "b", "c", hplot);
2032%!   catch
2033%!     [~, id] = lasterr ();
2034%!     assert (id, "Octave:invalid-fun-call");
2035%!   end_try_catch
2036%! unwind_protect_cleanup
2037%!   close (hf);
2038%! end_unwind_protect
2039
2040## Test bugs in previous implementation
2041%!testif ; any (strcmp (graphics_toolkit (), {"fltk", "qt"})) <*39697>
2042%! hf = figure ("visible", "off");
2043%! unwind_protect
2044%!   axes ("units", "normalized");
2045%!   plot (1:10);
2046%!   hl = legend ("Legend Text", "units", "normalized");
2047%!   title ({'Multi-line', 'titles', 'are a', 'problem'});
2048%!   pos = get (gca, "position");
2049%!   axtop = sum (pos(2:2:4));
2050%!   pos = get (hl, "position");
2051%!   legtop = sum (pos(2:2:4));
2052%!   assert (legtop < axtop);
2053%! unwind_protect_cleanup
2054%!   close (hf);
2055%! end_unwind_protect
2056
2057%!testif HAVE_FREETYPE <*40333>
2058%! hf = figure ("visible", "off");
2059%! unwind_protect
2060%!   hax = axes ("units", "normalized", "fontsize", 10);
2061%!   hold on;  # preserve properties of hax in call to plot()
2062%!   plot (hax, 1:10);
2063%!   hl = legend ("Legend Text", "units", "normalized");
2064%!   pos = get (gca, "position");
2065%!   set (hf, "position", [0, 0, 200, 200]);
2066%!   set (hl, "fontsize", 20);
2067%!   assert (get (gca, "position"), pos, 2*eps);
2068%! unwind_protect_cleanup
2069%!   close (hf);
2070%! end_unwind_protect
2071