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