Mercurial > octave
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