@@ -75,7 +75,7 @@ def get_dir_vector(zdir):
7575
7676def _viewlim_mask (xs , ys , zs , axes ):
7777 """
78- Return original points with points outside the axes view limits masked .
78+ Return the mask of the points outside the axes view limits.
7979
8080 Parameters
8181 ----------
@@ -86,19 +86,16 @@ def _viewlim_mask(xs, ys, zs, axes):
8686
8787 Returns
8888 -------
89- xs_masked, ys_masked, zs_masked : np.ma .array
90- The masked points.
89+ mask : np.array
90+ The mask of the points.
9191 """
9292 mask = np .logical_or .reduce ((xs < axes .xy_viewLim .xmin ,
9393 xs > axes .xy_viewLim .xmax ,
9494 ys < axes .xy_viewLim .ymin ,
9595 ys > axes .xy_viewLim .ymax ,
9696 zs < axes .zz_viewLim .xmin ,
9797 zs > axes .zz_viewLim .xmax ))
98- xs_masked = np .ma .array (xs , mask = mask )
99- ys_masked = np .ma .array (ys , mask = mask )
100- zs_masked = np .ma .array (zs , mask = mask )
101- return xs_masked , ys_masked , zs_masked
98+ return mask
10299
103100
104101class Text3D (mtext .Text ):
@@ -1062,16 +1059,36 @@ def get_vector(self, segments3d):
10621059 return self ._get_vector (segments3d )
10631060
10641061 def _get_vector (self , segments3d ):
1065- """Optimize points for projection."""
1066- if len (segments3d ):
1067- xs , ys , zs = np .vstack (segments3d ).T
1068- else : # vstack can't stack zero arrays.
1069- xs , ys , zs = [], [], []
1070- ones = np .ones (len (xs ))
1071- self ._vec = np .array ([xs , ys , zs , ones ])
1062+ """Optimize points for projection.
10721063
1073- indices = [0 , * np .cumsum ([len (segment ) for segment in segments3d ])]
1074- self ._segslices = [* map (slice , indices [:- 1 ], indices [1 :])]
1064+ Parameters
1065+ ----------
1066+ segments3d : NumPy array or list of NumPy arrays
1067+ List of vertices of the boundary of every segment. If all paths are
1068+ of equal length and this argument is a NumPy arrray, then it should
1069+ be of shape (num_faces, num_vertices, 3).
1070+ """
1071+ if isinstance (segments3d , np .ndarray ):
1072+ if segments3d .ndim != 3 or segments3d .shape [- 1 ] != 3 :
1073+ raise ValueError ("segments3d must be a MxNx3 array, but got " +
1074+ "shape {}" .format (segments3d .shape ))
1075+ if isinstance (segments3d , np .ma .MaskedArray ):
1076+ self ._faces = segments3d .data
1077+ self ._invalid_vertices = segments3d .mask .any (axis = - 1 )
1078+ else :
1079+ self ._faces = segments3d
1080+ self ._invalid_vertices = False
1081+ else :
1082+ num_faces = len (segments3d )
1083+ num_verts = np .fromiter (map (len , segments3d ), dtype = np .intp )
1084+ max_verts = num_verts .max (initial = 0 )
1085+ segments = np .empty ((num_faces , max_verts , 3 ))
1086+ for i , face in enumerate (segments3d ):
1087+ segments [i , :len (face )] = face
1088+ self ._faces = segments
1089+ self ._invalid_vertices = np .arange (max_verts ) >= num_verts [:, None ]
1090+ assert self ._invalid_vertices is False or \
1091+ self ._invalid_vertices .shape == self ._faces .shape [:- 1 ]
10751092
10761093 def set_verts (self , verts , closed = True ):
10771094 """
@@ -1133,64 +1150,77 @@ def do_3d_projection(self):
11331150 self ._facecolor3d = self ._facecolors
11341151 if self ._edge_is_mapped :
11351152 self ._edgecolor3d = self ._edgecolors
1153+
1154+
1155+ needs_masking = self ._invalid_vertices is not False
1156+ num_faces = len (self ._faces )
1157+ mask = self ._invalid_vertices
1158+
1159+ # Some faces might contain masked vertices, so we want to ignore any
1160+ # errors that those might cause
1161+ with np .errstate (invalid = 'ignore' , divide = 'ignore' ):
1162+ pfaces = proj3d ._proj_transform_vectors (self ._faces , self .axes .M )
1163+
11361164 if self ._axlim_clip :
1137- xs , ys , zs = _viewlim_mask (* self ._vec [0 :3 ], self .axes )
1138- if self ._vec .shape [0 ] == 4 : # Will be 3 (xyz) or 4 (xyzw)
1139- w_masked = np .ma .masked_where (zs .mask , self ._vec [3 ])
1140- vec = np .ma .array ([xs , ys , zs , w_masked ])
1141- else :
1142- vec = np .ma .array ([xs , ys , zs ])
1143- else :
1144- vec = self ._vec
1145- txs , tys , tzs = proj3d ._proj_transform_vec (vec , self .axes .M )
1146- xyzlist = [(txs [sl ], tys [sl ], tzs [sl ]) for sl in self ._segslices ]
1165+ viewlim_mask = _viewlim_mask (self ._faces [..., 0 ], self ._faces [..., 1 ],
1166+ self ._faces [..., 2 ], self .axes )
1167+ if np .any (viewlim_mask ):
1168+ needs_masking = True
1169+ mask = mask | viewlim_mask
1170+
1171+ pzs = pfaces [..., 2 ]
1172+ if needs_masking :
1173+ pzs = np .ma .MaskedArray (pzs , mask = mask )
11471174
11481175 # This extra fuss is to re-order face / edge colors
11491176 cface = self ._facecolor3d
11501177 cedge = self ._edgecolor3d
1151- if len (cface ) != len ( xyzlist ) :
1152- cface = cface .repeat (len ( xyzlist ) , axis = 0 )
1153- if len (cedge ) != len ( xyzlist ) :
1178+ if len (cface ) != num_faces :
1179+ cface = cface .repeat (num_faces , axis = 0 )
1180+ if len (cedge ) != num_faces :
11541181 if len (cedge ) == 0 :
11551182 cedge = cface
11561183 else :
1157- cedge = cedge .repeat (len (xyzlist ), axis = 0 )
1158-
1159- if xyzlist :
1160- # sort by depth (furthest drawn first)
1161- z_segments_2d = sorted (
1162- ((self ._zsortfunc (zs .data ), np .ma .column_stack ([xs , ys ]), fc , ec , idx )
1163- for idx , ((xs , ys , zs ), fc , ec )
1164- in enumerate (zip (xyzlist , cface , cedge ))),
1165- key = lambda x : x [0 ], reverse = True )
1166-
1167- _ , segments_2d , self ._facecolors2d , self ._edgecolors2d , idxs = \
1168- zip (* z_segments_2d )
1169- else :
1170- segments_2d = []
1171- self ._facecolors2d = np .empty ((0 , 4 ))
1172- self ._edgecolors2d = np .empty ((0 , 4 ))
1173- idxs = []
1184+ cedge = cedge .repeat (num_faces , axis = 0 )
1185+
1186+ face_z = self ._zsortfunc (pzs , axis = - 1 )
1187+ if needs_masking :
1188+ face_z = face_z .data
1189+ face_order = np .argsort (face_z , axis = - 1 )[::- 1 ]
11741190
1191+ faces_2d = pfaces [face_order , :, :2 ]
11751192 if self ._codes3d is not None :
1176- codes = [self ._codes3d [idx ] for idx in idxs ]
1177- PolyCollection .set_verts_and_codes (self , segments_2d , codes )
1193+ if needs_masking :
1194+ segment_mask = ~ mask [face_order , :]
1195+ faces_2d = [face [mask , :] for face , mask
1196+ in zip (faces_2d , segment_mask )]
1197+ codes = [self ._codes3d [idx ] for idx in face_order ]
1198+ PolyCollection .set_verts_and_codes (self , faces_2d , codes )
1199+ else :
1200+ if needs_masking :
1201+ invalid_vertices_2d = np .broadcast_to (
1202+ mask [face_order , :, None ],
1203+ faces_2d .shape )
1204+ faces_2d = np .ma .MaskedArray (
1205+ faces_2d , mask = invalid_vertices_2d )
1206+ PolyCollection .set_verts (self , faces_2d , self ._closed )
1207+
1208+ self ._facecolors2d = cface [face_order ]
1209+ if len (self ._edgecolor3d ) == len (cface ):
1210+ self ._edgecolors2d = cedge [face_order ]
11781211 else :
1179- PolyCollection .set_verts (self , segments_2d , self ._closed )
1180-
1181- if len (self ._edgecolor3d ) != len (cface ):
11821212 self ._edgecolors2d = self ._edgecolor3d
11831213
11841214 # Return zorder value
11851215 if self ._sort_zpos is not None :
11861216 zvec = np .array ([[0 ], [0 ], [self ._sort_zpos ], [1 ]])
11871217 ztrans = proj3d ._proj_transform_vec (zvec , self .axes .M )
11881218 return ztrans [2 ][0 ]
1189- elif tzs .size > 0 :
1219+ elif pzs .size > 0 :
11901220 # FIXME: Some results still don't look quite right.
11911221 # In particular, examine contourf3d_demo2.py
11921222 # with az = -54 and elev = -45.
1193- return np .min (tzs )
1223+ return np .min (pzs )
11941224 else :
11951225 return np .nan
11961226
0 commit comments