# HG changeset patch # User Rik # Date 1411747373 25200 # Node ID ba167badef9f3e6fdc7b7d0b1f9793cda338bf69 # Parent 71da5cce37d6a5a1acaeab1b7aa0d17c70f0ff87 Deprecate delaunay3 and extend delaunay to 3-D inputs. * NEWS: Announce deprecation of delaunay3. Announce extension of delaunay to 3-D inputs. * scripts/deprecated/delaunay3.m: Moved from geometry/. Print warning about deprecation when executing function for the first time. * scripts/deprecated/module.mk: Add deprecated delaunay3.m to build system. * scripts/geometry/module.mk: Remove from geometry directory build system * delaunay.m: Redo docstring to mention 2-D and 3-D inputs. Overhaul function to accept 3-D inputs. Add %!error input validation tests. * delaunayn, tetramesh.m: Remove seealso reference to delaunay3. * geometry.txi, install.txi: Remove delaunay3 from manual. diff -r 71da5cce37d6 -r ba167badef9f NEWS --- a/NEWS Fri Sep 26 08:54:40 2014 -0700 +++ b/NEWS Fri Sep 26 09:02:53 2014 -0700 @@ -25,6 +25,12 @@ has continuous 1st and 2nd derivatives while 'pchip' only has a continuous 1st derivative. + ** The delaunay function has been extended to accept 3-D inputs for + Matlab compatibility. The delaunay function no longer plots the + triangulation if no output argument is requested, instead, the + triangulation is always returned. The delaunay3 function which + handles 3-D inputs has been deprecated in favor of delaunay. + ** Integer formats used in the printf family of functions now work for 64-bit integers and are more compatible with Matlab when printing non-integer values. Now instead of truncating, Octave will switch @@ -60,6 +66,10 @@ ** isprime has been extended to operate on negative and complex inputs. + ** The following functions now support N-dimensional arrays: + + fliplr flipud rot90 + ** The new warning ID "Octave:data-file-in-path" replaces the three previous separate warning IDs "Octave:fopen-file-in-path", "Octave:load-file-in-path", and "Octave:md5sum-file-in-path". @@ -101,6 +111,7 @@ Function | Replacement -------------------|------------------ bicubic | interp2 + delaunay3 | delaunay find_dir_in_path | dir_in_loadpath finite | isfinite fmod | rem @@ -136,12 +147,6 @@ been removed from Octave 4.2. Replacement classes are (struct array) or for a single structure. - ** The following functions have now support for N-dimensional arrays: - - fliplr - flipud - rot90 - Summary of important user-visible changes for version 4.0: --------------------------------------------------------- diff -r 71da5cce37d6 -r ba167badef9f doc/interpreter/geometry.txi --- a/doc/interpreter/geometry.txi Fri Sep 26 08:54:40 2014 -0700 +++ b/doc/interpreter/geometry.txi Fri Sep 26 09:02:53 2014 -0700 @@ -50,15 +50,11 @@ @DOCSTRING(delaunay) -The 3- and N-dimensional extension of the Delaunay triangulation are -given by @code{delaunay3} and @code{delaunayn} respectively. -@code{delaunay3} returns a set of tetrahedra that satisfy the +For 3-D inputs @code{delaunay} returns a set of tetrahedra that satisfy the Delaunay circum-circle criteria. Similarly, @code{delaunayn} returns the N-dimensional simplex satisfying the Delaunay circum-circle criteria. The N-dimensional extension of a triangulation is called a tessellation. -@DOCSTRING(delaunay3) - @DOCSTRING(delaunayn) An example of a Delaunay triangulation of a set of points is @@ -417,12 +413,10 @@ An important use of the Delaunay tessellation is that it can be used to interpolate from scattered data to an arbitrary set of points. To do this the N-simplex of the known set of points is calculated with -@code{delaunay}, @code{delaunay3} or @code{delaunayn}. Then the -simplices in to which the desired points are found are -identified. Finally the vertices of the simplices are used to -interpolate to the desired points. The functions that perform this -interpolation are @code{griddata}, @code{griddata3} and -@code{griddatan}. +@code{delaunay} or @code{delaunayn}. Then the simplices in to which the +desired points are found are identified. Finally the vertices of the simplices +are used to interpolate to the desired points. The functions that perform this +interpolation are @code{griddata}, @code{griddata3} and @code{griddatan}. @DOCSTRING(griddata) @@ -444,8 +438,7 @@ @end example @noindent -that interpolates from a random scattering of points, to a uniform -grid. +that interpolates from a random scattering of points, to a uniform grid. @ifnotinfo The output of the above can be seen in @ref{fig:griddata}. diff -r 71da5cce37d6 -r ba167badef9f doc/interpreter/install.txi --- a/doc/interpreter/install.txi Fri Sep 26 08:54:40 2014 -0700 +++ b/doc/interpreter/install.txi Fri Sep 26 09:02:53 2014 -0700 @@ -288,8 +288,7 @@ @item Qhull Computational geometry library (@url{http://www.qhull.org}). Qhull is required to provide the functions @code{convhull}, @code{convhulln}, -@code{delaunay}, @code{delaunay3}, @code{delaunayn}, @code{voronoi}, and -@code{voronoin}. +@code{delaunay}, @code{delaunayn}, @code{voronoi}, and @code{voronoin}. @item QRUPDATE QR factorization updating library diff -r 71da5cce37d6 -r ba167badef9f scripts/deprecated/delaunay3.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/deprecated/delaunay3.m Fri Sep 26 09:02:53 2014 -0700 @@ -0,0 +1,88 @@ +## Copyright (C) 1999-2013 Kai Habel +## +## This file is part of Octave. +## +## Octave is free software; you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or (at +## your option) any later version. +## +## Octave is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Octave; see the file COPYING. If not, see +## . + +## -*- texinfo -*- +## @deftypefn {Function File} {@var{tetr} =} delaunay3 (@var{x}, @var{y}, @var{z}) +## @deftypefnx {Function File} {@var{tetr} =} delaunay3 (@var{x}, @var{y}, @var{z}, @var{options}) +## +## @code{delaunay3} is deprecated and will be removed in Octave version 4.6. +## Please use @code{delaunay} in all new code. +## +## Compute the Delaunay triangulation for a 3-D set of points. +## The return value @var{tetr} is a set of tetrahedrons which satisfies the +## Delaunay circum-circle criterion, i.e., only a single data point from +## [@var{x}, @var{y}, @var{z}] is within the circum-circle of the defining +## tetrahedron. +## +## The set of tetrahedrons @var{tetr} is a matrix of size [n, 4]. Each +## row defines a tetrahedron and the four columns are the four vertices +## of the tetrahedron. The value of @code{@var{tetr}(i,j)} is an index into +## @var{x}, @var{y}, @var{z} for the location of the j-th vertex of the i-th +## tetrahedron. +## +## An optional fourth argument, which must be a string or cell array of strings, +## contains options passed to the underlying qhull command. +## See the documentation for the Qhull library for details +## @url{http://www.qhull.org/html/qh-quick.htm#options}. +## The default options are @code{@{"Qt", "Qbb", "Qc", "Qz"@}}. +## +## If @var{options} is not present or @code{[]} then the default arguments are +## used. Otherwise, @var{options} replaces the default argument list. +## To append user options to the defaults it is necessary to repeat the +## default arguments in @var{options}. Use a null string to pass no arguments. +## +## @seealso{delaunay, delaunayn, convhull, voronoi, tetramesh} +## @end deftypefn + +## Author: Kai Habel + +function tetr = delaunay3 (x, y, z, options) + + persistent warned = false; + if (! warned) + warned = true; + warning ("Octave:deprecated-function", + "delaunay3 is obsolete and will be removed from a future version of Octave, please use delaunay instead"); + endif + + if (nargin < 3 || nargin > 4) + print_usage (); + endif + + if (! (isvector (x) && isvector (y) && isvector (z) + && length (x) == length (y) && length (x) == length (z))) + error ("delaunay: X, Y, and Z must be the same size"); + elseif (nargin == 4 && ! (ischar (options) || iscellstr (options))) + error ("delaunay3: OPTIONS must be a string or cell array of strings"); + endif + + if (nargin == 3) + tetr = delaunayn ([x(:), y(:), z(:)]); + else + tetr = delaunayn ([x(:), y(:), z(:)], options); + endif + +endfunction + + +%!testif HAVE_QHULL +%! x = [-1, -1, 1, 0, -1]; y = [-1, 1, 1, 0, -1]; z = [0, 0, 0, 1, 1]; +%! assert (sortrows (sort (delaunay3 (x, y, z), 2)), [1,2,3,4;1,2,4,5]) + +%% FIXME: Need input validation tests + diff -r 71da5cce37d6 -r ba167badef9f scripts/deprecated/module.mk --- a/scripts/deprecated/module.mk Fri Sep 26 08:54:40 2014 -0700 +++ b/scripts/deprecated/module.mk Fri Sep 26 09:02:53 2014 -0700 @@ -2,6 +2,7 @@ deprecated_FCN_FILES = \ deprecated/bicubic.m \ + deprecated/delaunay3.m \ deprecated/find_dir_in_path.m \ deprecated/finite.m \ deprecated/fmod.m \ diff -r 71da5cce37d6 -r ba167badef9f scripts/geometry/delaunay.m --- a/scripts/geometry/delaunay.m Fri Sep 26 08:54:40 2014 -0700 +++ b/scripts/geometry/delaunay.m Fri Sep 26 09:02:53 2014 -0700 @@ -17,22 +17,32 @@ ## . ## -*- texinfo -*- -## @deftypefn {Function File} {} delaunay (@var{x}, @var{y}) -## @deftypefnx {Function File} {} delaunay (@var{x}) -## @deftypefnx {Function File} {} delaunay (@dots{}, @var{options}) -## @deftypefnx {Function File} {@var{tri} =} delaunay (@dots{}) -## Compute the Delaunay triangulation for a 2-D set of points. -## The return value @var{tri} is a set of triangles which satisfies the -## Delaunay circum-circle criterion, i.e., only a single data point from -## [@var{x}, @var{y}] is within the circum-circle of the defining triangle. -## The input @var{x} may also be a matrix with two columns where the first -## column contains x-data and the second y-data. +## @deftypefn {Function File} {@var{tri} =} delaunay (@var{x}, @var{y}) +## @deftypefnx {Function File} {@var{tetr} =} delaunay (@var{x}, @var{y}, @var{z}) +## @deftypefnx {Function File} {@var{tri} =} delaunay (@var{x}) +## @deftypefnx {Function File} {@var{tri} =} delaunay (@dots{}, @var{options}) +## Compute the Delaunay triangulation for a 2-D or 3-D set of points. ## -## The set of triangles @var{tri} is a matrix of size [n, 3]. Each -## row defines a triangle and the three columns are the three vertices -## of the triangle. The value of @code{@var{tri}(i,j)} is an index into -## @var{x} and @var{y} for the location of the j-th vertex of the i-th -## triangle. +## For 2-D sets, the return value @var{tri} is a set of triangles which +## satisfies the Delaunay circum-circle criterion, i.e., only a single data +## point from [@var{x}, @var{y}] is within the circum-circle of the defining +## triangle. The set of triangles @var{tri} is a matrix of size [n, 3]. Each +## row defines a triangle and the three columns are the three vertices of the +## triangle. The value of @code{@var{tri}(i,j)} is an index into @var{x} and +## @var{y} for the location of the j-th vertex of the i-th triangle. +## +## For 3-D sets, the return value @var{tetr} is a set of tetrahedrons which +## satisfies the Delaunay circum-circle criterion, i.e., only a single data +## point from [@var{x}, @var{y}, @var{z}] is within the circum-circle of the +## defining tetrahedron. The set of tetrahedrons is a matrix of size [n, 4]. +## Each row defines a tetrahedron and the four columns are the four vertices of +## the tetrahedron. The value of @code{@var{tetr}(i,j)} is an index into +## @var{x}, @var{y}, @var{z} for the location of the j-th vertex of the i-th +## tetrahedron. +## +## The input @var{x} may also be a matrix with two or three columns where the +## first column contains x-data, the second y-data, and the optional third +## column contains z-data. ## ## The optional last argument, which must be a string or cell array of strings, ## contains options passed to the underlying qhull command. @@ -45,63 +55,84 @@ ## To append user options to the defaults it is necessary to repeat the ## default arguments in @var{options}. Use a null string to pass no arguments. ## -## If no output argument is specified the resulting Delaunay triangulation -## is plotted along with the original set of points. -## ## @example ## @group ## x = rand (1, 10); ## y = rand (1, 10); -## T = delaunay (x, y); -## VX = [ x(T(:,1)); x(T(:,2)); x(T(:,3)); x(T(:,1)) ]; -## VY = [ y(T(:,1)); y(T(:,2)); y(T(:,3)); y(T(:,1)) ]; +## tri = delaunay (x, y); +## triplot (tri, x, y); +## hold on; +## plot (x, y, "r*"); ## axis ([0,1,0,1]); -## plot (VX, VY, "b", x, y, "r*"); ## @end group ## @end example -## @seealso{delaunay3, delaunayn, convhull, voronoi, triplot, trimesh, trisurf} +## @seealso{delaunayn, convhull, voronoi, triplot, trimesh, tetramesh, trisurf} ## @end deftypefn ## Author: Kai Habel function tri = delaunay (varargin) - if (nargin < 1 || nargin > 3) + if (nargin < 1 || nargin > 4) print_usage (); endif + z = []; options = []; switch (nargin) case 1 - if (! ismatrix (varargin{1}) || columns (varargin{1}) != 2) - error ("delaunay: X must be a matrix with 2 columns"); + if (! ismatrix (varargin{1}) + || (columns (varargin{1}) != 2 && columns (varargin{1}) != 3)) + error ("delaunay: X must be a matrix with 2 or 3 columns"); else x = varargin{1}(:,1); y = varargin{1}(:,2); + if (columns (varargin{1}) == 3) + z = varargin{1}(:,3); + endif endif case 2 if (isnumeric (varargin{2})) x = varargin{1}; y = varargin{2}; - elseif (ischar (varargin{2}) || iscellstr (varargin{2})) + elseif (! (ischar (varargin{2}) || iscellstr (varargin{2}))) + error ("delaunay: OPTIONS must be a string or cell array of strings"); + else options = varargin{2}; - if (! ismatrix (varargin{1}) && columns (varargin{1}) != 2) - error ("delaunay: X must be a matrix with 2 columns"); + ncols = columns (varargin{1}); + + if (! ismatrix (varargin{1}) || (ncols != 2 && ncols != 3)) + error ("delaunay: X must be a matrix with 2 or 3 columns"); else x = varargin{1}(:,1); y = varargin{1}(:,2); + if (ncols == 3) + z = varargin{1}(:,3); + endif endif - else - error ("delaunay: OPTIONS must be a string or cell array of strings"); endif case 3 + if (isnumeric (varargin{3})) + x = varargin{1}; + y = varargin{2}; + z = varargin{3}; + elseif (! (ischar (varargin{3}) || iscellstr (varargin{3}))) + error ("delaunay: OPTIONS must be a string or cell array of strings"); + else + x = varargin{1}; + y = varargin{2}; + options = varargin{3}; + endif + + case 4 x = varargin{1}; y = varargin{2}; - options = varargin{3}; + z = varargin{3}; + options = varargin{4}; if (! (ischar (options) || iscellstr (options))) error ("delaunay: OPTIONS must be a string or cell array of strings"); @@ -109,20 +140,16 @@ endswitch - if (! (isequal(size(x),size(y)))) - error ("delaunay: X and Y must be the same size"); - endif - - T = delaunayn ([x(:), y(:)], options); - - if (nargout == 0) - x = x(:).'; - y = y(:).'; - VX = [ x(T(:,1)); x(T(:,2)); x(T(:,3)); x(T(:,1)) ]; - VY = [ y(T(:,1)); y(T(:,2)); y(T(:,3)); y(T(:,1)) ]; - plot (VX, VY, "b", x, y, "r*"); + if (isempty (z)) + if (! size_equal (x, y)) + error ("delaunay: X and Y must be the same size"); + endif + tri = delaunayn ([x(:), y(:)], options); else - tri = T; + if (! size_equal (x, y, z)) + error ("delaunay: X, Y, and Z must be the same size"); + endif + tri = delaunayn ([x(:), y(:), z(:)], options); endif endfunction @@ -134,11 +161,11 @@ %! rand ("state", 1); %! x = rand (1,10); %! y = rand (1,10); -%! T = delaunay (x,y); -%! VX = [ x(T(:,1)); x(T(:,2)); x(T(:,3)); x(T(:,1)) ]; -%! VY = [ y(T(:,1)); y(T(:,2)); y(T(:,3)); y(T(:,1)) ]; +%! tri = delaunay (x,y); %! clf; -%! plot (VX,VY,"b", x,y,"r*"); +%! triplot (tri, x, y); +%! hold on; +%! plot (x, y, "r*"); %! axis ([0,1,0,1]); %!testif HAVE_QHULL @@ -165,5 +192,19 @@ %! y = [5 7 8; 1 2 3]; %! assert (sortrows (sort (delaunay (x, y), 2)), [1,2,4;1,3,4;1,3,5;3,4,6]); -%% FIXME: Need input validation tests +## Test 3-D input +%!testif HAVE_QHULL +%! x = [-1, -1, 1, 0, -1]; y = [-1, 1, 1, 0, -1]; z = [0, 0, 0, 1, 1]; +%! assert (sortrows (sort (delaunay (x, y, z), 2)), [1,2,3,4;1,2,4,5]) +%% Input validation tests +%!error delaunay () +%!error delaunay (1,2,3,4,5) +%!error delaunay (ones (2,4)) +%!error delaunay (ones (2,2), struct()) +%!error delaunay (ones (2,4), "") +%!error delaunay (ones (2,2), ones (2,2), struct()) +%!error delaunay (ones (2,2), ones (2,2), ones (2,2), struct()) +%!error delaunay (1, [1 2]) +%!error delaunay (1, [1 2], [1 2]) + diff -r 71da5cce37d6 -r ba167badef9f scripts/geometry/delaunay3.m --- a/scripts/geometry/delaunay3.m Fri Sep 26 08:54:40 2014 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -## Copyright (C) 1999-2013 Kai Habel -## -## This file is part of Octave. -## -## Octave is free software; you can redistribute it and/or modify it -## under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 3 of the License, or (at -## your option) any later version. -## -## Octave is distributed in the hope that it will be useful, but -## WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -## General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Octave; see the file COPYING. If not, see -## . - -## -*- texinfo -*- -## @deftypefn {Function File} {@var{tetr} =} delaunay3 (@var{x}, @var{y}, @var{z}) -## @deftypefnx {Function File} {@var{tetr} =} delaunay3 (@var{x}, @var{y}, @var{z}, @var{options}) -## Compute the Delaunay triangulation for a 3-D set of points. -## The return value @var{tetr} is a set of tetrahedrons which satisfies the -## Delaunay circum-circle criterion, i.e., only a single data point from -## [@var{x}, @var{y}, @var{z}] is within the circum-circle of the defining -## tetrahedron. -## -## The set of tetrahedrons @var{tetr} is a matrix of size [n, 4]. Each -## row defines a tetrahedron and the four columns are the four vertices -## of the tetrahedron. The value of @code{@var{tetr}(i,j)} is an index into -## @var{x}, @var{y}, @var{z} for the location of the j-th vertex of the i-th -## tetrahedron. -## -## An optional fourth argument, which must be a string or cell array of strings, -## contains options passed to the underlying qhull command. -## See the documentation for the Qhull library for details -## @url{http://www.qhull.org/html/qh-quick.htm#options}. -## The default options are @code{@{"Qt", "Qbb", "Qc", "Qz"@}}. -## -## If @var{options} is not present or @code{[]} then the default arguments are -## used. Otherwise, @var{options} replaces the default argument list. -## To append user options to the defaults it is necessary to repeat the -## default arguments in @var{options}. Use a null string to pass no arguments. -## -## @seealso{delaunay, delaunayn, convhull, voronoi, tetramesh} -## @end deftypefn - -## Author: Kai Habel - -function tetr = delaunay3 (x, y, z, options) - - if (nargin < 3 || nargin > 4) - print_usage (); - endif - - if (! (isvector (x) && isvector (y) && isvector (z) - && length (x) == length (y) && length (x) == length (z))) - error ("delaunay: X, Y, and Z must be the same size"); - elseif (nargin == 4 && ! (ischar (options) || iscellstr (options))) - error ("delaunay3: OPTIONS must be a string or cell array of strings"); - endif - - if (nargin == 3) - tetr = delaunayn ([x(:), y(:), z(:)]); - else - tetr = delaunayn ([x(:), y(:), z(:)], options); - endif - -endfunction - - -%!testif HAVE_QHULL -%! x = [-1, -1, 1, 0, -1]; y = [-1, 1, 1, 0, -1]; z = [0, 0, 0, 1, 1]; -%! assert (sortrows (sort (delaunay3 (x, y, z), 2)), [1,2,3,4;1,2,4,5]) - -%% FIXME: Need input validation tests - diff -r 71da5cce37d6 -r ba167badef9f scripts/geometry/delaunayn.m --- a/scripts/geometry/delaunayn.m Fri Sep 26 08:54:40 2014 -0700 +++ b/scripts/geometry/delaunayn.m Fri Sep 26 09:02:53 2014 -0700 @@ -47,7 +47,7 @@ ## To append user options to the defaults it is necessary to repeat the ## default arguments in @var{options}. Use a null string to pass no arguments. ## -## @seealso{delaunay, delaunay3, convhulln, voronoin, trimesh, tetramesh} +## @seealso{delaunay, convhulln, voronoin, trimesh, tetramesh} ## @end deftypefn function T = delaunayn (pts, varargin) @@ -75,7 +75,7 @@ ## prod(1:n) is dropped. idx = []; [nt, n] = size (T); - ## FIXME: Vectorize this for loop or convert to delaunayn to .oct function + ## FIXME: Vectorize this for loop or convert delaunayn to .oct function for i = 1:nt X = pts(T(i,1:end-1),:) - pts(T(i,2:end),:); if (abs (det (X)) / sqrt (sum (X .^ 2, 2)) < 1e3 * myeps) diff -r 71da5cce37d6 -r ba167badef9f scripts/geometry/module.mk --- a/scripts/geometry/module.mk Fri Sep 26 08:54:40 2014 -0700 +++ b/scripts/geometry/module.mk Fri Sep 26 09:02:53 2014 -0700 @@ -2,7 +2,6 @@ geometry_FCN_FILES = \ geometry/convhull.m \ - geometry/delaunay3.m \ geometry/delaunayn.m \ geometry/delaunay.m \ geometry/dsearch.m \ diff -r 71da5cce37d6 -r ba167badef9f scripts/plot/draw/tetramesh.m --- a/scripts/plot/draw/tetramesh.m Fri Sep 26 08:54:40 2014 -0700 +++ b/scripts/plot/draw/tetramesh.m Fri Sep 26 09:02:53 2014 -0700 @@ -43,7 +43,7 @@ ## @qcode{"visible"} property @qcode{"on"} or @qcode{"off"}. ## ## Type @code{demo tetramesh} to see examples on using @code{tetramesh}. -## @seealso{trimesh, delaunay3, delaunayn, patch} +## @seealso{trimesh, delaunay, delaunayn, patch} ## @end deftypefn ## Author: Martin Helm @@ -130,7 +130,7 @@ %! x = [x(:); 0]; %! y = [y(:); 0]; %! z = [z(:); 0]; -%! tetra = delaunay3 (x, y, z); +%! tetra = delaunay (x, y, z); %! X = [x(:) y(:) z(:)]; %! colormap (jet (64)); %! h = tetramesh (tetra, X); @@ -147,7 +147,7 @@ %! x = [x(:); 0]; %! y = [y(:); 0]; %! z = [z(:); 0]; -%! tetra = delaunay3 (x, y, z); +%! tetra = delaunay (x, y, z); %! X = [x(:) y(:) z(:)]; %! colormap (gray (256)); %! tetramesh (tetra, X, 21:20:241, 'EdgeColor', 'w');