Skip to content

Commit b73d8dc

Browse files
Implement dynamic clipping to axes box for 3D plots
WIP
1 parent 62a5ba4 commit b73d8dc

File tree

8 files changed

+358
-107
lines changed

8 files changed

+358
-107
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Data in 3D plots can now be dynamically clipped to the axes view limits
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
All 3D plotting functions now support the `axlim_clip` keyword argument, which
5+
will clip the data to the axes view limits, hiding all data outside those
6+
bounds. This clipping will be dynamically applied in real time while panning
7+
and zooming.
8+
9+
Please note that if one vertex of a line segment or 3D patch is clipped, the
10+
entire segment or patch will be hidden. Not being able to show partial lines
11+
or patches such that they are "smoothly" cut off at the boundaries of the view
12+
box is a limitation of the current renderer.
13+
14+
.. plot::
15+
:include-source: true
16+
:alt: Example of default behavior (left) and axlim_clip=True (right)
17+
18+
import matplotlib.pyplot as plt
19+
import numpy as np
20+
21+
fig, axs = plt.subplots(1, 2, subplot_kw={"projection": "3d"})
22+
np.random.seed(1)
23+
ps = np.random.rand(25, 3)
24+
25+
axs[0].plot(ps[:, 0], ps[:, 1], ps[:, 2], c='k', alpha=0.5)
26+
axs[0].scatter(ps[:, 0], ps[:, 1], ps[:, 2])
27+
axs[0].set(xlim=(0.25, 0.75), ylim=(0, 1), zlim=(0, 1),
28+
title='axlim_clip=False (default)')
29+
30+
# Note that when a line has one vertex outside the view limits, the entire
31+
# line is hidden. The same is true for 3D patches (not shown).
32+
axs[1].plot(ps[:, 0], ps[:, 1], ps[:, 2], c='k', alpha=0.5, axlim_clip=True)
33+
axs[1].scatter(ps[:, 0], ps[:, 1], ps[:, 2], axlim_clip=True)
34+
axs[1].set(xlim=(0.25, 0.75), ylim=(0, 1), zlim=(0, 1),
35+
title='axlim_clip=True')

lib/matplotlib/collections.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ def _prepare_points(self):
336336
# This might have changed an ndarray into a masked array.
337337
offset_trf = offset_trf.get_affine()
338338

339-
if isinstance(offsets, np.ma.MaskedArray):
339+
if np.ma.isMaskedArray(offsets):
340340
offsets = offsets.filled(np.nan)
341341
# Changing from a masked array to nan-filled ndarray
342342
# is probably most efficient at this point.

lib/matplotlib/text.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -750,9 +750,16 @@ def draw(self, renderer):
750750

751751
# don't use self.get_position here, which refers to text
752752
# position in Text:
753-
posx = float(self.convert_xunits(self._x))
754-
posy = float(self.convert_yunits(self._y))
753+
x, y = self._x, self._y
754+
if np.ma.is_masked(x):
755+
x = np.nan
756+
if np.ma.is_masked(y):
757+
y = np.nan
758+
posx = float(self.convert_xunits(x))
759+
posy = float(self.convert_yunits(y))
755760
posx, posy = trans.transform((posx, posy))
761+
if np.isnan(posx) or np.isnan(posy):
762+
return # don't throw a warning here
756763
if not np.isfinite(posx) or not np.isfinite(posy):
757764
_log.warning("posx and posy should be finite values")
758765
return

0 commit comments

Comments
 (0)