@@ -2746,8 +2746,10 @@ def calc_arrow(uvw, angle=15):
27462746
27472747 quiver3D = quiver
27482748
2749- def voxels (self , filled , ** kwargs ):
2749+ def voxels (self , * args , ** kwargs ):
27502750 """
2751+ ax.voxels([x, y, z,] /, filled, **kwargs)
2752+
27512753 Plot a set of filled voxels
27522754
27532755 All voxels are plotted as 1x1x1 cubes on the axis, with filled[0,0,0]
@@ -2758,10 +2760,16 @@ def voxels(self, filled, **kwargs):
27582760
27592761 Parameters
27602762 ----------
2761- filled : np.array
2763+ filled : 3D np.array of bool
27622764 A 3d array of values, with truthy values indicating which voxels
27632765 to fill
27642766
2767+ x, y, z : 3D np.array, optional
2768+ The coordinates of the corners of the voxels. This should broadcast
2769+ to a shape one larger in every dimension than the shape of `filled`.
2770+ These arguments can be used to plot non-cubic voxels.
2771+ If not specified, defaults to increasing integers along each axis.
2772+
27652773 facecolors, edgecolors : array_like
27662774 The color to draw the faces and edges of the voxels. This parameter
27672775 can be:
@@ -2791,10 +2799,33 @@ def voxels(self, filled, **kwargs):
27912799
27922800 .. plot:: gallery/mplot3d/voxels.py
27932801 .. plot:: gallery/mplot3d/voxels_rgb.py
2802+ .. plot:: gallery/mplot3d/voxels_torus.py
2803+ .. plot:: gallery/mplot3d/voxels_numpy_logo.py
27942804 """
2805+
2806+ # work out which signature we should be using, and use it to parse
2807+ # the arguments. Name must be voxels for the correct error message
2808+ if len (args ) >= 3 :
2809+ # underscores indicate position only
2810+ def voxels (__x , __y , __z , filled , ** kwargs ):
2811+ return (__x , __y , __z ), filled , kwargs
2812+ else :
2813+ def voxels (filled , ** kwargs ):
2814+ return None , filled , kwargs
2815+
2816+ xyz , filled , kwargs = voxels (* args , ** kwargs )
2817+
27952818 # check dimensions
27962819 if filled .ndim != 3 :
27972820 raise ValueError ("Argument filled must be 3-dimensional" )
2821+ size = np .array (filled .shape , dtype = np .intp )
2822+
2823+ # check xyz coordinates, which are one larger than the filled shape
2824+ coord_shape = tuple (size + 1 )
2825+ if xyz is None :
2826+ x , y , z = np .indices (coord_shape )
2827+ else :
2828+ x , y , z = (broadcast_to (c , coord_shape ) for c in xyz )
27982829
27992830 def _broadcast_color_arg (color , name ):
28002831 if np .ndim (color ) in (0 , 1 ):
@@ -2821,25 +2852,21 @@ def _broadcast_color_arg(color, name):
28212852 edgecolors = _broadcast_color_arg (edgecolors , 'edgecolors' )
28222853
28232854 # always scale to the full array, even if the data is only in the center
2824- self .auto_scale_xyz (
2825- [0 , filled .shape [0 ]],
2826- [0 , filled .shape [1 ]],
2827- [0 , filled .shape [2 ]]
2828- )
2855+ self .auto_scale_xyz (x , y , z )
28292856
28302857 # points lying on corners of a square
28312858 square = np .array ([
28322859 [0 , 0 , 0 ],
28332860 [0 , 1 , 0 ],
28342861 [1 , 1 , 0 ],
28352862 [1 , 0 , 0 ]
2836- ])
2863+ ], dtype = np . intp )
28372864
28382865 voxel_faces = defaultdict (list )
28392866
28402867 def permutation_matrices (n ):
28412868 """ Generator of cyclic permutation matices """
2842- mat = np .eye (n , dtype = int )
2869+ mat = np .eye (n , dtype = np . intp )
28432870 for i in range (n ):
28442871 yield mat
28452872 mat = np .roll (mat , 1 , axis = 0 )
@@ -2848,7 +2875,7 @@ def permutation_matrices(n):
28482875 # render
28492876 for permute in permutation_matrices (3 ):
28502877 # find the set of ranges to iterate over
2851- pc , qc , rc = permute .T .dot (filled . shape [: 3 ] )
2878+ pc , qc , rc = permute .T .dot (size )
28522879 pinds = np .arange (pc )
28532880 qinds = np .arange (qc )
28542881 rinds = np .arange (rc )
@@ -2890,7 +2917,20 @@ def permutation_matrices(n):
28902917
28912918 # iterate over the faces, and generate a Poly3DCollection for each voxel
28922919 polygons = {}
2893- for coord , faces in voxel_faces .items ():
2920+ for coord , faces_inds in voxel_faces .items ():
2921+ # convert indices into 3D positions
2922+ if xyz is None :
2923+ faces = faces_inds
2924+ else :
2925+ faces = []
2926+ for face_inds in faces_inds :
2927+ ind = face_inds [:, 0 ], face_inds [:, 1 ], face_inds [:, 2 ]
2928+ face = np .empty (face_inds .shape )
2929+ face [:, 0 ] = x [ind ]
2930+ face [:, 1 ] = y [ind ]
2931+ face [:, 2 ] = z [ind ]
2932+ faces .append (face )
2933+
28942934 poly = art3d .Poly3DCollection (faces ,
28952935 facecolors = facecolors [coord ],
28962936 edgecolors = edgecolors [coord ],
0 commit comments