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