@@ -97,6 +97,30 @@ def _viewlim_mask(xs, ys, zs, axes):
9797 return mask
9898
9999
100+ def _scale_invalid_mask (xs , ys , zs , axes ):
101+ """
102+ Return the mask of points whose coordinates are invalid for the axis
103+ scale they live on (e.g. <=0 on a log axis).
104+
105+ Parameters
106+ ----------
107+ xs, ys, zs : array-like
108+ The points to check, in data coordinates.
109+ axes : Axes3D
110+ The axes whose scales are queried.
111+
112+ Returns
113+ -------
114+ mask : np.ndarray
115+ Boolean array, ``True`` where any of x/y/z is out of its scale's
116+ valid domain.
117+ """
118+ return np .logical_or .reduce ((
119+ np .logical_not (axes .xaxis ._scale .val_in_range (xs )),
120+ np .logical_not (axes .yaxis ._scale .val_in_range (ys )),
121+ np .logical_not (axes .zaxis ._scale .val_in_range (zs ))))
122+
123+
100124class Text3D (mtext .Text ):
101125 """
102126 Text object with 3D position and direction.
@@ -191,8 +215,10 @@ def set_3d_properties(self, z=0, zdir='z', axlim_clip=False):
191215
192216 @artist .allow_rasterization
193217 def draw (self , renderer ):
218+ mask = _scale_invalid_mask (self ._x , self ._y , self ._z , self .axes )
194219 if self ._axlim_clip :
195- mask = _viewlim_mask (self ._x , self ._y , self ._z , self .axes )
220+ mask |= _viewlim_mask (self ._x , self ._y , self ._z , self .axes )
221+ if np .any (mask ):
196222 pos3d = np .ma .array ([self ._x , self ._y , self ._z ],
197223 mask = mask , dtype = float ).filled (np .nan )
198224 else :
@@ -328,9 +354,12 @@ def get_data_3d(self):
328354
329355 @artist .allow_rasterization
330356 def draw (self , renderer ):
357+ scale_mask = _scale_invalid_mask (* self ._verts3d , self .axes )
331358 if self ._axlim_clip :
359+ scale_mask |= _viewlim_mask (* self ._verts3d , self .axes )
360+ if np .any (scale_mask ):
332361 mask = np .broadcast_to (
333- _viewlim_mask ( * self . _verts3d , self . axes ) ,
362+ scale_mask ,
334363 (len (self ._verts3d ), * self ._verts3d [0 ].shape )
335364 )
336365 xs3d , ys3d , zs3d = np .ma .array (self ._verts3d ,
@@ -424,10 +453,13 @@ class Collection3D(Collection):
424453 def do_3d_projection (self ):
425454 """Project the points according to renderer matrix."""
426455 vs_list = [vs for vs , _ in self ._3dverts_codes ]
456+ masks = [_scale_invalid_mask (* vs .T , self .axes ) for vs in vs_list ]
427457 if self ._axlim_clip :
428- vs_list = [np .ma .array (vs , mask = np .broadcast_to (
429- _viewlim_mask (* vs .T , self .axes ), vs .shape ))
430- for vs in vs_list ]
458+ masks = [m | _viewlim_mask (* vs .T , self .axes )
459+ for m , vs in zip (masks , vs_list )]
460+ vs_list = [np .ma .array (vs , mask = np .broadcast_to (m , vs .shape ))
461+ if np .any (m ) else vs
462+ for vs , m in zip (vs_list , masks )]
431463 xyzs_list = [proj3d ._scale_proj_transform (
432464 vs [:, 0 ], vs [:, 1 ], vs [:, 2 ], self .axes ) for vs in vs_list ]
433465 self ._paths = [mpath .Path (np .ma .column_stack ([xs , ys ]), cs )
@@ -520,6 +552,14 @@ def do_3d_projection(self):
520552 if np .ma .isMA (segments ) and segments .mask is not np .ma .nomask :
521553 mask = segments .mask
522554
555+ scale_mask = _scale_invalid_mask (segments [..., 0 ],
556+ segments [..., 1 ],
557+ segments [..., 2 ],
558+ self .axes )
559+ if np .any (scale_mask ):
560+ mask |= np .broadcast_to (scale_mask [..., np .newaxis ],
561+ (* scale_mask .shape , 3 ))
562+
523563 if self ._axlim_clip :
524564 viewlim_mask = _viewlim_mask (segments [..., 0 ],
525565 segments [..., 1 ],
@@ -612,12 +652,15 @@ def get_path(self):
612652
613653 def do_3d_projection (self ):
614654 s = self ._segment3d
655+ xs0 , ys0 , zs0 = zip (* s )
656+ mask = _scale_invalid_mask (xs0 , ys0 , zs0 , self .axes )
615657 if self ._axlim_clip :
616- mask = _viewlim_mask (* zip (* s ), self .axes )
658+ mask |= _viewlim_mask (xs0 , ys0 , zs0 , self .axes )
659+ if np .any (mask ):
617660 xs , ys , zs = np .ma .array (zip (* s ),
618661 dtype = float , mask = mask ).filled (np .nan )
619662 else :
620- xs , ys , zs = zip ( * s )
663+ xs , ys , zs = xs0 , ys0 , zs0
621664 vxs , vys , vzs , vis = proj3d ._scale_proj_transform_clip (xs , ys , zs , self .axes )
622665 self ._path2d = mpath .Path (np .ma .column_stack ([vxs , vys ]))
623666 return min (vzs )
@@ -672,12 +715,15 @@ def set_3d_properties(self, path, zs=0, zdir='z', axlim_clip=False):
672715
673716 def do_3d_projection (self ):
674717 s = self ._segment3d
718+ xs0 , ys0 , zs0 = zip (* s )
719+ mask = _scale_invalid_mask (xs0 , ys0 , zs0 , self .axes )
675720 if self ._axlim_clip :
676- mask = _viewlim_mask (* zip (* s ), self .axes )
721+ mask |= _viewlim_mask (xs0 , ys0 , zs0 , self .axes )
722+ if np .any (mask ):
677723 xs , ys , zs = np .ma .array (zip (* s ),
678724 dtype = float , mask = mask ).filled (np .nan )
679725 else :
680- xs , ys , zs = zip ( * s )
726+ xs , ys , zs = xs0 , ys0 , zs0
681727 vxs , vys , vzs , vis = proj3d ._scale_proj_transform_clip (xs , ys , zs , self .axes )
682728 self ._path2d = mpath .Path (np .ma .column_stack ([vxs , vys ]), self ._code3d )
683729 return min (vzs )
@@ -816,8 +862,10 @@ def set_3d_properties(self, zs, zdir, axlim_clip=False):
816862 self .stale = True
817863
818864 def do_3d_projection (self ):
865+ mask = _scale_invalid_mask (* self ._offsets3d , self .axes )
819866 if self ._axlim_clip :
820- mask = _viewlim_mask (* self ._offsets3d , self .axes )
867+ mask |= _viewlim_mask (* self ._offsets3d , self .axes )
868+ if np .any (mask ):
821869 xs , ys , zs = np .ma .array (self ._offsets3d , mask = mask )
822870 else :
823871 xs , ys , zs = self ._offsets3d
@@ -1038,8 +1086,10 @@ def do_3d_projection(self):
10381086 for xyz in self ._offsets3d :
10391087 if np .ma .isMA (xyz ):
10401088 mask = mask | xyz .mask
1089+ mask = mask | _scale_invalid_mask (* self ._offsets3d , self .axes )
10411090 if self ._axlim_clip :
10421091 mask = mask | _viewlim_mask (* self ._offsets3d , self .axes )
1092+ if np .any (mask ):
10431093 mask = np .broadcast_to (mask ,
10441094 (len (self ._offsets3d ), * self ._offsets3d [0 ].shape ))
10451095 xyzs = np .ma .array (self ._offsets3d , mask = mask )
@@ -1377,9 +1427,11 @@ def do_3d_projection(self):
13771427 if self ._edge_is_mapped :
13781428 self ._edgecolor3d = self ._edgecolors
13791429
1380- needs_masking = np .any (self ._invalid_vertices )
13811430 num_faces = len (self ._faces )
1382- mask = self ._invalid_vertices
1431+ mask = self ._invalid_vertices | _scale_invalid_mask (
1432+ self ._faces [..., 0 ], self ._faces [..., 1 ],
1433+ self ._faces [..., 2 ], self .axes )
1434+ needs_masking = np .any (mask )
13831435
13841436 # Some faces might contain masked vertices, so we want to ignore any
13851437 # errors that those might cause
0 commit comments