changeset 15443:015cc3d1f389

legend.m: Overhaul function and add support for automatic data labels. * legend.m: Add support for automatic data labels (data1, data2,...) when no labels are specified and no DisplayName properties exist. Overhaul documentation. Add more comments to code about what is happening. Re-order demos and add a title to each plot which describes what is being shown.
author Rik <rik@octave.org>
date Tue, 25 Sep 2012 13:28:53 -0700
parents e5a07d7aafcc
children 0b6c29cb53d0
files scripts/plot/legend.m
diffstat 1 files changed, 153 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/plot/legend.m
+++ b/scripts/plot/legend.m
@@ -26,13 +26,14 @@
 ## @deftypefnx {Function File} {} legend (@var{hobjs}, @dots{})
 ## @deftypefnx {Function File} {} legend (@var{hax}, @var{hobjs}, @dots{})
 ## @deftypefnx {Function File} {} legend ("@var{option}")
+## @deftypefnx {Function File} {[@var{hleg}, @var{hleg_obj}, @var{hplot}, @var{labels}] =} legend (@dots{})
 ##
 ## Display a legend for the axes with handle @var{hax}, or the current axes,
 ## using the specified strings as labels.  Legend entries may be specified
 ## as individual character string arguments, a character array, or a cell
 ## array of character strings.  If the handles, @var{hobjs}, are not specified
 ## then the legend's strings will be associated with the axes' descendants.
-## Legend works on line graphs, bar graphs, etc.
+## @code{legend} works on line graphs, bar graphs, etc.
 ## A plot must exist before legend is called.
 ##
 ## The optional parameter @var{pos} specifies the location of the legend
@@ -74,8 +75,8 @@
 ## @end multitable
 ##
 ## The optional parameter @var{orient} determines if the key elements
-## are placed vertically or horizontally.  The allowed values are "vertical"
-## or "horizontal" with the default being "vertical".
+## are placed vertically or horizontally.  The allowed values are
+## "vertical" (default) or "horizontal".
 ##
 ## The following customizations are available using @var{option}:
 ##
@@ -96,14 +97,35 @@
 ##   Hide the box around legend
 ##
 ## @item "left"
-##   Place text to the left of the keys
+##   Place label text to the left of the keys
 ##
 ## @item "right"
-##   Place text to the right of the keys
+##   Place label text to the right of the keys
 ##
 ## @itemx "off"
 ##   Delete the legend object
 ## @end table
+##
+## The optional output values are
+##
+## @table @var
+## @item hleg
+##   The graphics handle of the legend object.
+##
+## @item hleg_obj
+##   Graphics handles to the text and line objects which make up the legend.
+##
+## @item hplot
+##   Graphics handles to the plot objects which were used in making the legend.
+##
+## @item labels
+##   A cell array of strings of the labels in the legend.
+## @end table 
+##
+## The legend label text is either provided in the call to @code{legend} or
+## is taken from the DisplayName property of graphics objects.  If no
+## labels or DisplayNames are available, then the label text is simply
+## "data1", "data2", @dots{}, "dataN".
 ## @end deftypefn
 
 function [hlegend2, hobjects2, hplot2, text_strings2] = legend (varargin)
@@ -122,6 +144,7 @@
     ca = gca ();
   endif
 
+  ## Special handling for plotyy which has two axes objects
   if (ishandle (ca) && isprop (ca, "__plotyy_axes__"))
     plty = get (ca, "__plotyy_axes__");
     if (isscalar (plty) && ishandle (plty))
@@ -159,6 +182,7 @@
   textpos = "default";
   box = "default";
 
+  ## Process old way of specifying position with a number rather than a string.
   if (nargs > 0)
     pos = varargin{nargs};
     if (isnumeric (pos) && isscalar (pos) && pos == fix (pos))
@@ -172,13 +196,14 @@
     endif
   endif
 
+  ## Find position and orientation property/value pairs
   while (nargs > 1)
     pos = varargin{nargs-1};
     str = varargin{nargs};
-    if (strcmpi (pos, "location")  && ischar (str))
+    if (strcmpi (pos, "location") && ischar (str))
       position = lower (str);
       nargs -= 2;
-    elseif (strcmpi (pos, "orientation")  && ischar (str))
+    elseif (strcmpi (pos, "orientation") && ischar (str))
       orientation = lower (str);
       nargs -= 2;
     else
@@ -193,7 +218,7 @@
       error ("legend: unrecognized legend orientation");
   endswitch
 
-  ## Validate the position type is valid
+  ## Validate the position type
   outside = false;
   inout = strfind (position, "outside");
   if (! isempty (inout))
@@ -213,6 +238,7 @@
       error ("legend: unrecognized legend position");
   endswitch
 
+  ## Find any existing legend object on figure
   hlegend = [];
   fkids = get (fig, "children");
   for i = 1 : numel (fkids)
@@ -270,20 +296,25 @@
             nargs--;
         endswitch
       else
+        ## Character matrix of labels
         varargin = cellstr (arg);
         nargs = numel (varargin);
       endif
     elseif (iscellstr (arg))
+      ## Cell array of labels
       varargin = arg;
       nargs = numel (varargin);
     else
       error ("legend: expecting argument to be a character string");
     endif
   elseif (nargs > 1 && iscellstr (varargin{1}))
+    ## Cell array of labels followed by property/value pairs
     varargin = {varargin{1}{:}, varargin{2:end}};
     nargs = numel (varargin);
   endif
 
+  have_labels = (nargs > 0);
+
   if (strcmp (show, "off"))
     if (! isempty (hlegend))
       set (findobj (hlegend), "visible", "off");
@@ -310,8 +341,9 @@
     if (! isempty (hlegend))
       set (hlegend, "box", "off", "visible", "off");
     endif
-  elseif (nargs == 0 && !(strcmp (position, "default") &&
-                          strcmp (orientation, "default")))
+  elseif (! have_labels && !(strcmp (position, "default") &&
+                             strcmp (orientation, "default")))
+    ## Changing location or orientation of existing legend
     if (! isempty (hlegend))
       hax = getfield (get (hlegend, "userdata"), "handle");
       [hplots, text_strings] = __getlegenddata__ (hlegend);
@@ -336,12 +368,15 @@
       endif
     endif
   else
+    ## Create new legend
     hobjects = [];
     hplots  = [];
     text_strings = {};
 
-    if (nargs > 0)
+    if (have_labels)
+      ## Check for valid data that can be labeled.
       have_data = false;
+      have_dname = false;
       for k = 1 : nkids
         typ = get (kids(k), "type");
         if (strcmp (typ, "line") || strcmp (typ, "surface")
@@ -354,10 +389,40 @@
       if (! have_data)
         warning ("legend: plot data is empty; setting key labels has no effect");
       endif
-    endif
+    else
+      ## No labels.  Search for DisplayName property.
+      have_dname = false;
+      for k = 1 : nkids
+        hkid = kids(k);
+        typ = get (hkid, "type");
+        if (strcmp (typ, "line") || strcmp (typ, "surface")
+            || strcmp (typ, "patch"))
+          if (! isempty (get (hkid, "displayname")))
+            have_dname = true;
+            break;
+          endif
+        elseif (strcmp (typ, "hggroup"))
+          hgkids = get (hkid, "children");
+          for j = 1 : length (hgkids)
+            hgobj = get (hgkids(j));
+            if (isfield (hgobj, "displayname") && ! isempty (hgobj.displayname))
+              have_dname = true;
+              break;  # break from j-loop over hgkids
+            endif
+          endfor
+          if (have_dname)
+            break;  # break from k loop over nkids
+          endif
+        endif  # elseif hggroup
+      endfor   # for loop k = 1 : nkids
+    endif      # else branch of if (have_labels)
 
-    if (strcmp (textpos, "default"))
+    if (have_labels || ! have_dname)
       k = nkids;
+      if (! have_labels)
+        varargin = arrayfun (@(x) sprintf ("data%d", x), [1:nkids]', "uniformoutput", false);
+        nargs = nkids;
+      endif
       for i = 1 : nargs
         arg = varargin{i};
         if (ischar (arg))
@@ -373,14 +438,18 @@
               for j = 1 : length (hgkids)
                 hgobj = get (hgkids(j));
                 if (isfield (hgobj, "displayname"))
-                  set (hgkids(j), "displayname", arg);
+                  if (have_labels)
+                    set (hgkids(j), "displayname", arg);
+                  endif
                   hplots = [hplots, hgkids(j)];
                   text_strings = {text_strings{:}, arg};
                   break;
                 endif
               endfor
             else
-              set (kids(k), "displayname", arg);
+              if (have_labels)
+                set (kids(k), "displayname", arg);
+              endif
               hplots = [hplots, kids(k)];
               text_strings = {text_strings{:}, arg};
             endif
@@ -395,10 +464,11 @@
           error ("legend: expecting argument to be a character string");
         endif
       endfor
-      if (i < nargs)
+      if (have_labels && i < nargs)
         warning ("legend: ignoring extra labels");
       endif
     else
+      ## No labels specified but objects have DisplayName property set.
       k = nkids;
       while (k > 0)
         typ = get (kids(k), "type");
@@ -994,38 +1064,20 @@
 
 
 %!demo
-%! plot (rand (2))
-%! legend ({'foo'}, 'bar', 'boxoff')
-%! title ('legend() should warn about an extra label')
-
-%!demo
-%! plot (rand (2,2)) ;
-%! h = legend ('a', 'b') ;
-%! legend ('right') ;
-%! set (h, 'textposition', 'left')
-%! set (h, 'textposition', 'right')
-%! set (h, 'textcolor', [1 0 1])
+%! plot (rand (2));
+%! title ('legend called with cellstr and string inputs for labels');
+%! legend ({'foo'}, 'bar');
 
 %!demo
-%! clf;
-%! x = 0:1;
-%! plot (x,x,';I am Blue;', x,2*x,';I am Green;', x,3*x,';I am Red;');
-%! legend boxon
-%! legend hide
-%! legend show
-
-%!demo
-%! clf;
-%! x = 0:1;
-%! plot (x, x, ';\alpha;',  ...
-%!       x, 2*x, ';\beta=2\alpha;',  ...
-%!       x, 3*x, ';\gamma=3\alpha;');
+%! plot (rand (3));
+%! title ('legend() without inputs creates default labels');
+%! legend ();
 
 %!demo
 %! clf;
 %! x = 0:1;
 %! plot (x,x,';I am Blue;', x,2*x, x,3*x,';I am Red;');
-%! title ('Blue and Green keys, with Green missing');
+%! title ('Blue and Red keys, with Green missing');
 
 %!demo
 %! clf;
@@ -1036,9 +1088,9 @@
 %!demo
 %! clf;
 %! plot (1:10, 1:10, 1:10, fliplr (1:10));
-%! title ('Legend is hidden')
-%! legend ({'I am blue', 'I am green'}, 'location', 'east');
-%! legend hide;
+%! title ('Legend with keys in horizontal orientation');
+%! legend ({'I am blue', 'I am green'}, 'location', 'east', 'orientation', 'horizontal');
+%! legend boxoff;
 
 %!demo
 %! clf;
@@ -1050,15 +1102,55 @@
 %!demo
 %! clf;
 %! plot (1:10, 1:10, 1:10, fliplr (1:10));
-%! title ('Legend with text to the right');
+%! title ('Legend with text to the right of key');
 %! legend ({'I am blue', 'I am green'}, 'location', 'east');
 %! legend right;
 
 %!demo
 %! clf;
+%! plot (1:10, 1:10, 1:10, fliplr (1:10));
+%! title ('Using properties to have legend text shown to the right of key');
+%! h = legend ({'I am blue', 'I am green'}, 'location', 'east');
+%! legend ('left');
+%! set (h, 'textposition', 'right');
+%! set (h, 'textcolor', [1 0 1]);
+
+%!demo
+%! clf;
+%! plot (1:10, 1:10, 1:10, fliplr (1:10));
+%! title ('Legend is hidden')
+%! legend ({'I am blue', 'I am green'}, 'location', 'east');
+%! legend hide;
+
+%!demo
+%! clf;
+%! x = 0:1;
+%! plot (x,x,';I am Blue;', x,2*x,';I am Green;', x,3*x,';I am Red;');
+%! title ('labels embedded in call to plot');
+%! legend boxon
+%! legend hide
+%! legend show
+
+%!demo
+%! clf;
+%! x = 0:1;
+%! plot (x, x, ';\alpha;',  ...
+%!       x, 2*x, ';\beta=2\alpha;',  ...
+%!       x, 3*x, ';\gamma=3\alpha;');
+%! title ('labels with interpreted Greek text');
+
+%!demo
+%! clf;
+%! plot (rand (2));
+%! title ('Labels with TeX interpreter turned off');
+%! h = legend ('Hello_World', 'foo^bar');
+%! set (h, 'interpreter', 'none');
+
+%!demo
+%! clf;
 %! plot (1:10, 1:10);
 %! title ('a very long label can sometimes cause problems');
-%! legend ('hello world', 'location', 'northeastoutside');
+%! legend ('hello very big world', 'location', 'northeastoutside');
 
 %!demo
 %! clf;
@@ -1070,7 +1162,8 @@
 %!   labels = {labels{:}, ['Signal ', num2str(i)]};
 %! end
 %! hold off;
-%! title ('Signals with random offset and uniform noise');
+%! title ({'Signals with random offset and uniform noise';
+%!         'Legend shown below and outside of plot'});
 %! xlabel ('Sample Nr [k]'); ylabel ('Amplitude [V]');
 %! legend (labels, 'location', 'southoutside');
 
@@ -1080,6 +1173,7 @@
 %! plot (x, x);
 %! hold on;
 %! stem (x, x.^2, 'g');
+%! title ('First created object gets first label');
 %! legend ('linear');
 %! hold off;
 
@@ -1087,12 +1181,14 @@
 %! clf;
 %! x = linspace (0, 10);
 %! plot (x, x, x, x.^2);
+%! title ('First created object gets first label');
 %! legend ('linear');
 
 %!demo
 %! clf;
 %! x = linspace (0, 10);
 %! plot (x, x, x, x.^2);
+%! title ('Labels are applied in order of object creation');
 %! legend ('linear', 'quadratic');
 
 %!demo
@@ -1100,6 +1196,7 @@
 %! rand_2x3_data1 = [0.341447, 0.171220, 0.284370; 0.039773, 0.731725, 0.779382];
 %! bar (rand_2x3_data1);
 %! ylim ([0 1.0]);
+%! title ('legend() works for bar graphs (hgobjects)');
 %! legend ({'1st Bar', '2nd Bar', '3rd Bar'});
 
 %!demo
@@ -1107,6 +1204,7 @@
 %! rand_2x3_data2 = [0.44804, 0.84368, 0.23012; 0.72311, 0.58335, 0.90531];
 %! bar (rand_2x3_data2);
 %! ylim ([0 1.2]);
+%! title ('legend() works for bar graphs (hgobjects)');
 %! legend ('1st Bar', '2nd Bar', '3rd Bar');
 %! legend right;
 
@@ -1124,6 +1222,7 @@
 %! hold all;
 %! plot (x, cos (x), ';cos (x);');
 %! hold off;
+%! title ('legend constructed from multiple plot calls');
 
 %!demo
 %! clf;
@@ -1132,7 +1231,8 @@
 %! hold all;
 %! plot (x, cos (x), ';cos (x);');
 %! hold off;
-%! legend ({'sin (x)', 'cos (x)'}, 'location', 'northeastoutside');
+%! title ('Specified label text overrides previous labels');
+%! legend ({'Sine', 'Cosine'}, 'location', 'northeastoutside');
 
 %!demo
 %! clf;
@@ -1140,7 +1240,7 @@
 %! plot (x, rand (11));
 %! xlabel ('Indices');
 %! ylabel ('Random Values');
-%! title ('Legend ''off'' should delete the legend');
+%! title ('Legend ''off'' deletes the legend');
 %! legend (cellstr (num2str ((1:10)')), 'location', 'northeastoutside');
 %! legend off;
 %! axis ([0, 10, 0 1]);
@@ -1164,21 +1264,15 @@
 %!demo
 %! clf;
 %! plot (rand (2));
-%! title ('Warn of extra labels');
+%! title ('legend() will warn if extra labels are specified');
 %! legend ('Hello', 'World', 'interpreter', 'foobar');
 
 %!demo
-%! clf;
-%! plot (rand (2));
-%! title ('Turn off TeX interpreter');
-%! h = legend ('Hello_World', 'foo^bar');
-%! set (h, 'interpreter', 'none');
-
-%!demo
 %! x = 0:10;
 %! y1 = rand (size (x));
 %! y2 = rand (size (x));
 %! [ax, h1, h2] = plotyy (x, y1, x, y2);
+%! title ('plotyy legend test #1: Blue and Green labels');
 %! legend ([h1, h2], {'Blue', 'Green'}, 'location', 'south');
 
 %!demo
@@ -1186,6 +1280,7 @@
 %! y1 = rand (size (x));
 %! y2 = rand (size (x));
 %! [ax, h1, h2] = plotyy (x, y1, x, y2);
+%! title ('plotyy legend test #2: Blue and Green labels');
 %! legend ({'Blue', 'Green'}, 'location', 'south');
 
 %!demo
@@ -1193,5 +1288,6 @@
 %! y1 = rand (size (x));
 %! y2 = rand (size (x));
 %! [ax, h1, h2] = plotyy (x, y1, x, y2);
+%! title ('plotyy legend test #3: Blue and Green labels');
 %! legend ('Blue', 'Green', 'location', 'south');