Mercurial > octave
comparison scripts/plot/draw/private/__quiver__.m @ 32074:03fe0b635d2e
quiver/quiver3: Overhaul input processing, validation, and add BISTs.
* scripts/plot/draw/private/__quiver__.m: Overhaul numeric input validation.
Simplify input classification using numeric input count switch statements
and avoid quiver3 miscount due to scale factor. Add error messages for all
valid numeric input combinations including vector x,y,z and scale factor.
Move newplot command from quiver/quiver3 into __quiver__ after numeric input
validation. Add hax as an output argument to return any changes back to
calling function.
* scripts/plot/draw/quiver.m: Remove newplot call. Update __quiver__ call
to include hax as a return variable. Update docstring with note that line
style and name-value pairs can both be provided but linstyle must appear
first. Add BISTs to check standard inputs with single and multiple arrows,
arrowhead shape, vector and array inputs, proper treatment of scaling factor
"off", some simple input styles, and input validation BISTs to cover all
numeric input errors. Added known failing BIST for linestyle+pair
arrowhead showing when it should stay off (bug #64143).
* scripts/plot/draw/quiver3.m: Remove newplot call. Update __quiver__ call
to include hax as a return variable. Update docstring with note that line
style and name-value pairs can both be provided but linstyle must appear
first. Add BISTs to check standard inputs with single and multiple arrows,
vector and array inputs, and input validation BISTs to cover all numeric
input errors.
* etc/NEWS.9.md: Update quiver/quiver3 improvement description under General
Improvements.
author | Nicholas R. Jankowski <jankowski.nicholas@gmail.com> |
---|---|
date | Wed, 03 May 2023 22:52:33 -0400 |
parents | ada96a467a28 |
children | 7dcb6b4a4218 |
comparison
equal
deleted
inserted
replaced
32073:24752aa8be11 | 32074:03fe0b635d2e |
---|---|
26 ## -*- texinfo -*- | 26 ## -*- texinfo -*- |
27 ## @deftypefn {} {@var{hg} =} __quiver__ (@dots{}) | 27 ## @deftypefn {} {@var{hg} =} __quiver__ (@dots{}) |
28 ## Undocumented internal function. | 28 ## Undocumented internal function. |
29 ## @end deftypefn | 29 ## @end deftypefn |
30 | 30 |
31 function hg = __quiver__ (varargin) | 31 function [hax, hg]= __quiver__ (varargin) |
32 | |
33 hax = varargin{1}; | 32 hax = varargin{1}; |
34 is3d = varargin{2}; | 33 is3d = varargin{2}; |
35 | 34 |
36 autoscale = 0.9; | 35 autoscale = 0.9; |
37 ## Matlab uses 0.2, but Octave's algorithm produces equivalent visual | 36 ## Matlab uses 0.2, but Octave's algorithm produces equivalent visual |
38 ## results if arrowsize=0.33. Since this is just a non-dimensional | 37 ## results if arrowsize=0.33. Since this is just a non-dimensional |
39 ## scaling factor we scale the arrowsize property value by 0.33/0.20 | 38 ## scaling factor we scale the arrowsize property value by 0.33/0.20 |
40 ## in order to get equivalent visual results while keeping equivalent | 39 ## in order to get equivalent visual results while keeping equivalent |
41 ## property values. | 40 ## property values. |
42 arrowsize = 0.20; | 41 arrowsize = 0.20; |
43 firstnonnumeric = find (cellfun ("ischar", varargin(3:nargin)), 1); | 42 lastnumeric = find (cellfun ("ischar", varargin(3:nargin)), 1) - 1; |
44 if (isempty (firstnonnumeric)) | 43 |
45 firstnonnumeric = Inf; | 44 if (isempty (lastnumeric)) |
46 | 45 lastnumeric = nargin; |
47 ## Recast non-float inputs as doubles to avoid erroneous plots. | 46 ## Recast non-float inputs as doubles to avoid erroneous plots. |
48 varargin(3:end) = cellfun ('double', varargin(3:end), ... | 47 varargin(3:end) = cellfun ('double', varargin(3:end), ... |
49 "UniformOutput", false); | 48 "UniformOutput", false); |
50 else | 49 else |
51 firstnonnumeric += 2; | 50 |
52 | 51 lastnumeric += 2; |
53 ## Recast non-float inputs as doubles. | 52 ## Recast non-float inputs as doubles to avoid erroneous plots. |
54 varargin(3:firstnonnumeric-1) = cellfun ('double', | 53 varargin(3:lastnumeric) = cellfun ('double', |
55 varargin(3:firstnonnumeric-1), "UniformOutput", false); | 54 varargin(3:lastnumeric), "UniformOutput", false); |
56 | 55 |
57 ## Check for scaling factor "off" and set it to 0. | 56 ## Check for scaling factor "off" and set it to 0. |
58 if (strcmpi (varargin{firstnonnumeric}, "off")) | 57 if ((nargin > lastnumeric) && strcmpi (varargin{lastnumeric+1}, "off")) |
59 varargin(firstnonnumeric) = 0; | 58 varargin(++lastnumeric) = 0; |
60 | 59 endif |
61 if ((firstnonnumeric) == nargin) | 60 endif |
62 firstnonnumeric = Inf; | 61 |
62 if (is3d) | |
63 ## quiver3 3D input validation. | |
64 switch (lastnumeric) | |
65 case {6,7} | |
66 [z, u, v, w] = deal (varargin{3:6}); | |
67 if (isvector (z) && ! isvector (u)) | |
68 if (! size_equal (u, v, w)) | |
69 error ("quiver3: U, V, and W must be the same size"); | |
70 elseif (numel(z) != size (u, 3)) | |
71 error (["quiver3: Z vector length must equal size of ", ... | |
72 "U, V, and W in dim 3"]); | |
73 endif | |
74 [x, y, z] = meshgrid (1 : columns (u), 1 : rows (u), z); | |
75 else | |
76 if (! size_equal (z, u, v, w)) | |
77 error ("quiver3: Z, U, V, and W must be the same size"); | |
78 endif | |
79 [x, y] = meshgrid (1 : columns (u), 1 : rows (u), 1:size (u, 3)); | |
80 endif | |
81 | |
82 case {8,9} | |
83 [x, y, z, u, v, w] = deal (varargin{3:8}); | |
84 if (isvector (x) && isvector (y) && isvector (z) && ! isvector (u)) | |
85 if (! size_equal (u, v, w)) | |
86 error ("quiver3: U, V, and W must be the same size"); | |
87 elseif (numel(x) != columns (u)) | |
88 error (["quiver3: X vector length must equal number of ", ... | |
89 "columns in U, V, and W"]); | |
90 elseif (numel(y) != rows (u)) | |
91 error (["quiver3: Y vector length must equal number of ", ... | |
92 "rows in U, V, and W"]); | |
93 elseif (numel(z) != size (u, 3)) | |
94 error (["quiver3: Z vector length must equal size of ", ... | |
95 "U, V, and W in dim 3"]); | |
96 endif | |
97 [x, y, z] = meshgrid (x, y, z); | |
98 | |
99 elseif (! size_equal (x, y, z, u, v, w)) | |
100 error ("quiver3: X, Y, Z, U, V, and W must be the same size"); | |
101 endif | |
102 otherwise | |
103 ## too few or too many numeric inputs before first style input | |
104 print_usage ("quiver3"); | |
105 endswitch | |
106 | |
107 else | |
108 ## quiver 2D input validation. | |
109 switch (lastnumeric) | |
110 case {4,5} | |
111 [u, v] = deal (varargin{3:4}); | |
112 if (! size_equal (u, v)) | |
113 error ("quiver: U and V must be the same size"); | |
114 endif | |
115 [x, y] = meshgrid (1:columns (u), 1:rows (u)); | |
116 | |
117 case {6,7} # | |
118 [x, y, u, v] = deal (varargin{3:6}); | |
119 | |
120 if (isvector (x) && isvector (y) && ... | |
121 (! isvector (u) || ! isvector (v) )) | |
122 if (! size_equal (u, v)) | |
123 error ("quiver: U and V must be the same size"); | |
124 elseif (numel (x) != columns (u)) | |
125 error (["quiver: X vector length must equal number of ", ... | |
126 "columns in U and V"]); | |
127 elseif (numel (y) != rows (u)) | |
128 error (["quiver: Y vector length must equal number of ", ... | |
129 "rows in U and V"]); | |
130 endif | |
131 [x, y] = meshgrid (x, y); | |
132 elseif (! size_equal (x, y, u, v)) | |
133 error ("quiver: X, Y, U, and V must be the same size"); | |
134 endif | |
135 otherwise | |
136 ## too few or too many numeric inputs before first style input | |
137 print_usage ("quiver"); | |
138 endswitch | |
139 endif | |
140 | |
141 if (rem (lastnumeric, 2)) | |
142 autoscale = varargin{lastnumeric}; # Last odd input is scale factor. | |
143 | |
144 if (autoscale < 0 || ! isscalar (autoscale)) | |
145 if (is3d) | |
146 error (["quiver3: scaling factor must be a non-negative scalar ", ... | |
147 "or 'off'"]); | |
63 else | 148 else |
64 firstnonnumeric++; | 149 error (["quiver: scaling factor must be a non-negative scalar ", ... |
65 endif | 150 "or 'off'"]); |
66 endif | 151 endif |
67 endif | 152 endif |
68 | 153 endif |
69 ioff = 3; | 154 |
70 if (nargin < (6 + is3d) || firstnonnumeric < (6 + is3d)) | 155 ioff = lastnumeric + 1; |
71 if (is3d) | |
72 z = varargin{ioff++}; | |
73 endif | |
74 u = varargin{ioff++}; | |
75 v = varargin{ioff++}; | |
76 if (is3d) | |
77 w = varargin{ioff++}; | |
78 endif | |
79 if (is3d) | |
80 if (! size_equal (z, u, v, w)) | |
81 error ("quiver3: Z, U, V, and W must be the same size"); | |
82 endif | |
83 else | |
84 if (! size_equal (u, v)) | |
85 error ("quiver: U and V must be the same size"); | |
86 endif | |
87 endif | |
88 [x, y] = meshgrid (1:columns (u), 1:rows (u)); | |
89 | |
90 if (nargin >= ioff && isnumeric (varargin{ioff}) | |
91 && isscalar (varargin{ioff})) | |
92 autoscale = varargin{ioff++}; | |
93 endif | |
94 else | |
95 x = varargin{ioff++}; | |
96 y = varargin{ioff++}; | |
97 if (is3d) | |
98 z = varargin{ioff++}; | |
99 endif | |
100 u = varargin{ioff++}; | |
101 v = varargin{ioff++}; | |
102 if (is3d) | |
103 w = varargin{ioff++}; | |
104 if (isvector (x) && isvector (y) && isvector (z) | |
105 && (! isvector (u) || ! isvector (v) || ! isvector (w))) | |
106 [x, y, z] = meshgrid (x, y, z); | |
107 endif | |
108 else | |
109 if (isvector (x) && isvector (y) && (! isvector (u) || ! isvector (v))) | |
110 [x, y] = meshgrid (x, y); | |
111 endif | |
112 endif | |
113 if (is3d) | |
114 if (! size_equal (x, y, z, u, v, w)) | |
115 error ("quiver3: X, Y, Z, U, V, and W must be the same size"); | |
116 endif | |
117 else | |
118 if (! size_equal (x, y, u, v)) | |
119 error ("quiver: X, Y, U, and V must be the same size"); | |
120 endif | |
121 endif | |
122 | |
123 if (nargin >= ioff && isnumeric (varargin{ioff}) | |
124 && isscalar (varargin{ioff})) | |
125 autoscale = varargin{ioff++}; | |
126 endif | |
127 endif | |
128 | |
129 have_filled = false; | 156 have_filled = false; |
130 have_line_spec = false; | 157 have_line_spec = false; |
131 args = {}; | 158 args = {}; |
132 while (ioff <= nargin) | 159 while (ioff <= nargin) |
133 arg = varargin{ioff++}; | 160 arg = varargin{ioff++}; |
191 if (is3d) | 218 if (is3d) |
192 ww = s * w; | 219 ww = s * w; |
193 endif | 220 endif |
194 endif | 221 endif |
195 | 222 |
223 hax = newplot (hax); | |
196 hstate = get (hax, "nextplot"); | 224 hstate = get (hax, "nextplot"); |
197 unwind_protect | 225 unwind_protect |
198 if (have_line_spec) | 226 if (have_line_spec) |
199 ls = linespec.linestyle; | 227 ls = linespec.linestyle; |
200 lc = linespec.color; | 228 lc = linespec.color; |
350 addproperty ("hittestarea", hg, "radio", "on|{off}", "off"); | 378 addproperty ("hittestarea", hg, "radio", "on|{off}", "off"); |
351 | 379 |
352 if (! isempty (args)) | 380 if (! isempty (args)) |
353 set (hg, args{:}); | 381 set (hg, args{:}); |
354 endif | 382 endif |
383 | |
355 unwind_protect_cleanup | 384 unwind_protect_cleanup |
356 set (hax, "nextplot", hstate); | 385 set (hax, "nextplot", hstate); |
357 end_unwind_protect | 386 end_unwind_protect |
358 | 387 |
359 endfunction | 388 endfunction |