Skip to content

Commit 341809c

Browse files
committed
Implement full clipping for Poly3DCollection
1 parent a55ad73 commit 341809c

File tree

1 file changed

+46
-14
lines changed

1 file changed

+46
-14
lines changed

lib/mpl_toolkits/mplot3d/art3d.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,33 @@ def _viewlim_mask(xs, ys, zs, axes):
9797
return mask
9898

9999

100+
def _clip_to_axes_bbox(axes, paths):
101+
"""
102+
Return an Axes-clipped copy of paths.
103+
104+
Parameters
105+
----------
106+
axes : Axes3D
107+
The axes to clip the paths against.
108+
paths : array-like
109+
A list of polygons, each comprised of a path of vertices. May also be a 3D NumPy
110+
array, if all polygons contain paths of the same length.
111+
112+
Returns
113+
-------
114+
list of np.ndarray
115+
A copy of *paths* with each polygon clipped to the *axes*.
116+
"""
117+
result = mpath._path.clip_paths_to_box(
118+
paths,
119+
[axes.xy_viewLim.intervalx,
120+
axes.xy_viewLim.intervaly,
121+
axes.zz_viewLim.intervalx],
122+
True) # TODO: Pass in mask?
123+
124+
return result
125+
126+
100127
class Text3D(mtext.Text):
101128
"""
102129
Text object with 3D position and direction.
@@ -1339,22 +1366,28 @@ def do_3d_projection(self):
13391366
if self._edge_is_mapped:
13401367
self._edgecolor3d = self._edgecolors
13411368

1342-
needs_masking = np.any(self._invalid_vertices)
1343-
num_faces = len(self._faces)
1344-
mask = self._invalid_vertices
1369+
if self._axlim_clip:
1370+
# TODO: Apply `self._invalid_vertices` for proper masking.
1371+
pfaces = _clip_to_axes_bbox(self.axes, self._faces)
1372+
num_faces = len(pfaces)
1373+
num_verts = np.fromiter(map(len, pfaces), dtype=np.intp)
1374+
max_verts = num_verts.max(initial=0)
1375+
segments = np.empty((num_faces, max_verts, 3))
1376+
for i, face in enumerate(pfaces):
1377+
segments[i, :len(face)] = face
1378+
pfaces = segments
1379+
mask = np.arange(max_verts) >= num_verts[:, None]
1380+
needs_masking = np.any(mask)
1381+
else:
1382+
pfaces = self._faces
1383+
needs_masking = np.any(self._invalid_vertices)
1384+
num_faces = len(self._faces)
1385+
mask = self._invalid_vertices
13451386

13461387
# Some faces might contain masked vertices, so we want to ignore any
13471388
# errors that those might cause
13481389
with np.errstate(invalid='ignore', divide='ignore'):
1349-
pfaces = proj3d._proj_transform_vectors(self._faces, self.axes.M)
1350-
1351-
if self._axlim_clip:
1352-
viewlim_mask = _viewlim_mask(self._faces[..., 0], self._faces[..., 1],
1353-
self._faces[..., 2], self.axes)
1354-
if np.any(viewlim_mask):
1355-
needs_masking = True
1356-
mask = mask | viewlim_mask
1357-
1390+
pfaces = proj3d._proj_transform_vectors(pfaces, self.axes.M)
13581391
pzs = pfaces[..., 2]
13591392
if needs_masking:
13601393
pzs = np.ma.MaskedArray(pzs, mask=mask)
@@ -1385,8 +1418,7 @@ def do_3d_projection(self):
13851418
if self._codes3d is not None and len(self._codes3d) > 0:
13861419
if needs_masking:
13871420
segment_mask = ~mask[face_order, :]
1388-
faces_2d = [face[mask, :] for face, mask
1389-
in zip(faces_2d, segment_mask)]
1421+
faces_2d = [face[mask, :] for face, mask in zip(faces_2d, segment_mask)]
13901422
codes = [self._codes3d[idx] for idx in face_order]
13911423
PolyCollection.set_verts_and_codes(self, faces_2d, codes)
13921424
else:

0 commit comments

Comments
 (0)