changeset 26211:69bd0cfbd123

Add color and marker cycling and (bug #39036). * line.m: Rewrite documentation to make calling forms clearer and how they differ. * __line__.m: Rename variable 'h' to 'hp' (parent handle). Completely overhaul input validation. More quickly separate out low-level calling forms and plot them. Use cellindexmat rather than cellfun for increased performance. When there are multiple lines call __next_line_style__ and __next_line_color__ to implement color and marker cycling.
author Rik <rik@octave.org>
date Wed, 12 Dec 2018 15:01:21 -0800
parents da75dfccf14b
children 2be1833a93a5
files scripts/plot/draw/line.m scripts/plot/draw/private/__line__.m
diffstat 2 files changed, 118 insertions(+), 74 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/plot/draw/line.m	Wed Dec 12 11:25:38 2018 -0800
+++ b/scripts/plot/draw/line.m	Wed Dec 12 15:01:21 2018 -0800
@@ -19,18 +19,32 @@
 ## -*- texinfo -*-
 ## @deftypefn  {} {} line ()
 ## @deftypefnx {} {} line (@var{x}, @var{y})
-## @deftypefnx {} {} line (@var{x}, @var{y}, @var{property}, @var{value}, @dots{})
 ## @deftypefnx {} {} line (@var{x}, @var{y}, @var{z})
-## @deftypefnx {} {} line (@var{x}, @var{y}, @var{z}, @var{property}, @var{value}, @dots{})
-## @deftypefnx {} {} line (@var{property}, @var{value}, @dots{})
+## @deftypefnx {} {} line ("xdata", @var{x}, "ydata", @var{y})
+## @deftypefnx {} {} line ("xdata", @var{x}, "ydata", @var{y}, "zdata", @var{z})
+## @deftypefnx {} {} line (@dots{}, @var{property}, @var{value})
 ## @deftypefnx {} {} line (@var{hax}, @dots{})
 ## @deftypefnx {} {@var{h} =} line (@dots{})
-## Create line object from @var{x} and @var{y} (and possibly @var{z}) and
-## insert in the current axes.
+## Create a line object from @var{x} and @var{y} (and possibly @var{z}) and
+## insert it in the current axes.
+##
+## In the standard calling form the data @var{x}, @var{y}, and @var{z} may be
+## scalars, vectors, or matrices.  In the case of matrix inputs, @code{line}
+## will attempt to orient scalars and vectors so the results can be plotted.
+## This requires that one of the dimensions of the vector match either the
+## number of rows or the number of columns of the matrix.
+## 
+## In the low-level calling form (50% higher performance) where the data is
+## specified by name (@code{line ("xdata", @var{x}, @dots{})}) the data must be
+## vectors.  If no data is specified (@code{line ()}) then
+## @w{@code{@var{x} == @var{y} = [0, 1]}}.
 ##
 ## Multiple property-value pairs may be specified for the line object, but they
 ## must appear in pairs.
 ##
+## If called with only @var{property}/@var{value} pairs then any unspecified
+## properties use their default values as specified on the root object.
+##
 ## If the first argument @var{hax} is an axes handle, then plot into this axes,
 ## rather than the current axes returned by @code{gca}.
 ##
@@ -39,6 +53,9 @@
 ##
 ## Programming Note: The full list of properties is documented at
 ## @ref{Line Properties,,Line Properties}.
+##
+## The function @code{line} differs from @code{plot} in that line objects are
+## inserted in to the current axes without first clearing the plot.
 ## @seealso{image, patch, rectangle, surface, text}
 ## @end deftypefn
 
--- a/scripts/plot/draw/private/__line__.m	Wed Dec 12 11:25:38 2018 -0800
+++ b/scripts/plot/draw/private/__line__.m	Wed Dec 12 15:01:21 2018 -0800
@@ -17,20 +17,22 @@
 ## <https://www.gnu.org/licenses/>.
 
 ## -*- texinfo -*-
-## @deftypefn {} {@var{h} =} __line__ (@var{p}, @dots{})
-## Undocumented internal function.
+## @deftypefn {} {@var{h} =} __line__ (@var{parent}, @dots{})
+## Create line object with parent @var{parent}.
+##
+## Return handle @var{h} to created line objects.
 ## @end deftypefn
 
-## __line__ (p, x, y, z)
+## __line__ (hp, x, y, z)
 ## Create line object from x, y, and z with parent p.
 ## Return handle to line object.
 
 ## Author: jwe
 
-function h = __line__ (p, varargin)
+function h = __line__ (hp, varargin)
 
-  nvargs = numel (varargin);
-
+  nvargs = nargin - 1;     # remove argument 'hp'
+  have_data_prop = false;
   if (nvargs > 1 && ! ischar (varargin{1}) && ! ischar (varargin{2}))
     if (nvargs > 2 && ! ischar (varargin{3}))
       num_data_args = 3;
@@ -39,19 +41,9 @@
     endif
   else
     num_data_args = 0;
-  endif
-
-  if (num_data_args > 0 && ! size_equal (varargin{1:num_data_args}))
-    n = 1:num_data_args;
-    m = cellfun (@numel, varargin(1:num_data_args));
-    [~, m] = max (m);
-    b = ones (size (varargin{m(1)}));
-    try
-      varargin(n) = cellfun (@(x) bsxfun (@times, b, x), varargin(n),
-                             "uniformoutput", false);
-    catch
-      error ("line: number of X, Y, and Z points must be equal");
-    end_try_catch
+    if (any (strcmpi ("xdata", varargin(1:2:end))))
+      have_data_prop = true;
+    endif
   endif
 
   if (rem (nvargs - num_data_args, 2) != 0)
@@ -63,64 +55,99 @@
     other_args = varargin(num_data_args+1:end);
   endif
 
-  nlines = 0;
-  nvecpts = 0;
-  ismat = false (1, 3);
-  for i = 1:num_data_args
-    tmp = varargin{i}(:,:);
-    if (isvector (tmp))
-      nlines = max (1, nlines);
-      if (! isscalar (tmp))
-        if (nvecpts == 0)
-          nvecpts = numel (tmp);
-        elseif (nvecpts != numel (tmp))
-          error ("line: data size mismatch");
+  if (num_data_args == 0)
+    if (have_data_prop)
+      ## Low-level calling form with a single line
+      handles(1) = __go_line__ (hp, other_args{:});
+    else
+      ## line called without any data, use default data.
+      handles(1) = __go_line__ (hp, "xdata", [0, 1], "ydata", [0, 1],
+                                    other_args{:});
+    endif
+  else
+    ## Normal case, extract and validate input data args
+    ismat = false (1,3);
+
+    ## Initialize loop variables
+    tmpdata = varargin{1}(:,:);  # N-D arrays -> 2-D arrays
+    if (isvector (tmpdata))
+      tmpdata = tmpdata(:);      # Use column vectors by default
+    else
+      ismat(1) = true;
+    endif
+    [nr, nc] = size (tmpdata);
+    if (islogical (tmpdata))
+      tmpdata = uint8 (tmpdata);
+    elseif (iscomplex (tmpdata))
+      tmpdata = real (tmpdata);
+    endif
+    varargin{1} = tmpdata;
+    reorient_done = false;
+
+    for i = 2:num_data_args
+      tmpdata = varargin{i}(:,:);  # N-D arrays -> 2-D arrays
+
+      if (isvector (tmpdata))
+        tmpdata = tmpdata(:);      # Use column vectors by default
+        [r, c] = size (tmpdata);
+        if (nr == r)
+          ## Do nothing, properly oriented
+        elseif (nc == r && nc > 1 && ! reorient_done)
+          ## Re-orient first matrix
+          varargin{i-1} = varargin{i-1}.';
+          [nr, nc] = deal (nc, nr);  # swap rows and columns.
+          reorient_done = true;
+        else
+          error ("line: number of X, Y, and Z points must be equal");
+        endif
+      else
+        ismat(i) = true;
+        [r, c] = size (tmpdata);
+        if (nr == r && (nc == c || nc == 1))
+          ## Do nothing, properly oriented
+          nc = max (nc, c);
+        elseif (nr == c)
+          tmpdata = tmpdata.';
+          [nr, nc] = deal (c, r);  # swap rows and columns.
+        else
+          error ("line: number of X, Y, and Z points must be equal");
         endif
       endif
-    else
-      ismat(i) = true;
-      nlines = max (columns (tmp), nlines);
-    endif
-    varargin{i} = tmp;
-  endfor
 
-  if (num_data_args == 0)
-    varargin = {[0, 1], [0, 1]};
-    num_data_args = 2;
-    nlines = 1;
-  endif
+      ## Convert logical or complex inputs
+      if (islogical (tmpdata))
+        tmpdata = uint8 (tmpdata);
+      elseif (iscomplex (tmpdata))
+        tmpdata = real (tmpdata);
+      endif
+      varargin{i} = tmpdata;
+
+    endfor
+
+    nlines = nc;
 
-  handles = zeros (nlines, 1);
-
-  data = cell (1, 3);
-
-  if (num_data_args > 1)
+    data = cell (1, 3);
     data(1:num_data_args) = varargin(1:num_data_args);
-    for i = 1:num_data_args
-      if (islogical (data{i}))
-        data(i) = double (data{i});
-      elseif (iscomplex (data{i}))
-        data(i) = real (data{i});
-      endif
+    data_args = {"xdata", data{1}, "ydata", data{2}, "zdata", data{3}};
+    mask = [false, ismat(1), false, ismat(2), false, ismat(3)];
+
+    handles = zeros (nlines, 1);
+    for i = 1:nlines
+      data_args(mask) = cellindexmat (data(ismat), ":", i);
+
+      ## FIXME: Technically, it may not be the right thing to do to rotate
+      ##        the style if the options in other_args specify a color
+      ##        or linestyle.  The plot will be made correctly, but the next
+      ##        call to line may not use the correct value.
+      [linestyle, marker] = __next_line_style__ ();
+      color = __next_line_color__ ();
+
+      handles(i) = __go_line__ (hp, data_args{:},
+                                "color", color, "linestyle", linestyle,
+                                "marker", marker, other_args{:});
     endfor
   endif
 
-  data_args = reshape ({"xdata", "ydata", "zdata"; data{:}}, [1, 6]);
-  mask = reshape ([false(1,3); ismat], [1, 6]);
-
-  for i = 1:nlines
-    tmp = data(ismat);
-    if (! size_equal (tmp)
-        || (nvecpts != 0 && any (nvecpts != cellfun ("size", tmp, 1))))
-      error ("line: data size_mismatch");
-    endif
-    data_args(mask) = cellfun (@(x) x(:,i), data(ismat),
-                               "uniformoutput", false);
-
-    handles(i) = __go_line__ (p, data_args{:}, other_args{:});
-
-  endfor
-
   if (nargout > 0)
     h = handles;
   endif