Skip to content

Commit 5451b25

Browse files
committed
ENH/FIX: make "auto" aspect with Axes3D match Axes better
To make bbox_inches='tight' work correctly, we cache the positions of all of the axes, change their aspect to 'auto', adjust the transforms on the figure, render the output, and then restore the previous setting of aspect to each of the figures. This prevent the Axes from trying to adjust their size on draw. This matters because for the render that is emitted the figure transforms are out of sync with the figure size. As part of cleaning fixing the rendering of Axes3D in #8896 / #16472 we started to use apply_aspect to re-size the area the Axes3D takes up to maintain a fixed ratio between the axes when the aspect is "auto". However this conflicts with the expectation in tight_bbox.adjust_bbox as it assumes setting the aspect to "auto" will prevent any axes resizing. This commit addresses this by: - exiting the Axes3D.apply_aspect early if aspect is auto - adding a new aspect mode 'auto_pb' which is the default for Axes3D which maintains the current master branch behavior of maintaining a fixed ratio between the sizes of the x, y z axis independent of the data limits. - re implement several functions on Axes3D to make sure they handle sharez correctly. closes #16463.
1 parent 90200c6 commit 5451b25

File tree

2 files changed

+91
-4
lines changed

2 files changed

+91
-4
lines changed

lib/matplotlib/axes/_base.py

-4
Original file line numberDiff line numberDiff line change
@@ -1339,10 +1339,6 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
13391339
if cbook._str_equal(aspect, 'equal'):
13401340
aspect = 1
13411341
if not cbook._str_equal(aspect, 'auto'):
1342-
if self.name == '3d':
1343-
raise NotImplementedError(
1344-
'It is not currently possible to manually set the aspect '
1345-
'on 3D axes')
13461342
aspect = float(aspect) # raise ValueError if necessary
13471343

13481344
if share:

lib/mpl_toolkits/mplot3d/axes3d.py

+91
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import matplotlib.colors as mcolors
2525
import matplotlib.docstring as docstring
2626
import matplotlib.scale as mscale
27+
import matplotlib.transforms as mtransforms
2728
from matplotlib.axes import Axes, rcParams
2829
from matplotlib.axes._base import _axis_method_wrapper
2930
from matplotlib.transforms import Bbox
@@ -99,6 +100,7 @@ def __init__(
99100
self._shared_z_axes.join(self, sharez)
100101
self._adjustable = 'datalim'
101102

103+
kwargs.setdefault('aspect', 'auto_pb')
102104
super().__init__(fig, rect, frameon=True, *args, **kwargs)
103105
# Disable drawing of axes by base class
104106
super().set_axis_off()
@@ -261,10 +263,99 @@ def tunit_edges(self, vals=None, M=None):
261263
(tc[7], tc[4])]
262264
return edges
263265

266+
def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
267+
"""
268+
Set the aspect of the axis scaling.
269+
270+
Parameters
271+
----------
272+
aspect : {'auto_pb', 'auto'}
273+
Possible values:
274+
275+
======== =================================================
276+
value description
277+
======== =================================================
278+
'auto_pb' This will make the size of the axes have a fixed
279+
ratio
280+
'auto' automatic; fill the position rectangle with data.
281+
This will let the ratio between the size of the
282+
(pseudo-) bounding box be free and will not adjust
283+
anything at draw time.
284+
======== =================================================
285+
286+
adjustable : None or {'box', 'datalim'}, optional
287+
If not ``None``, this defines which parameter will be adjusted to
288+
meet the required aspect. See `.set_adjustable` for further
289+
details.
290+
291+
Currently ignored by Axes3D
292+
293+
anchor : None or str or 2-tuple of float, optional
294+
If not ``None``, this defines where the Axes will be drawn if there
295+
is extra space due to aspect constraints. The most common way to
296+
to specify the anchor are abbreviations of cardinal directions:
297+
298+
===== =====================
299+
value description
300+
===== =====================
301+
'C' centered
302+
'SW' lower left corner
303+
'S' middle of bottom edge
304+
'SE' lower right corner
305+
etc.
306+
===== =====================
307+
308+
See `.set_anchor` for further details.
309+
310+
share : bool, default: False
311+
If ``True``, apply the settings to all shared Axes.
312+
313+
"""
314+
if aspect not in {'auto', 'auto_pb'}:
315+
raise NotImplementedError(
316+
"Axes3D currently only support the aspect arguments "
317+
"{'auto', 'auto_pb'}. " + f"You passed in {aspect!r}."
318+
)
319+
320+
if share:
321+
axes = {*self._shared_x_axes.get_siblings(self),
322+
*self._shared_y_axes.get_siblings(self),
323+
*self._shared_z_axes.get_siblings(self),
324+
}
325+
else:
326+
axes = {self}
327+
328+
for ax in axes:
329+
ax._aspect = aspect
330+
ax.stale = True
331+
332+
if anchor is not None:
333+
self.set_anchor(anchor, share=share)
334+
335+
def set_anchor(self, anchor, share=False):
336+
# docstring inherited
337+
if not (anchor in mtransforms.Bbox.coefs or len(anchor) == 2):
338+
raise ValueError('argument must be among %s' %
339+
', '.join(mtransforms.Bbox.coefs))
340+
if share:
341+
axes = {*self._shared_x_axes.get_siblings(self),
342+
*self._shared_y_axes.get_siblings(self),
343+
*self._shared_z_axes.get_siblings(self),
344+
}
345+
else:
346+
axes = {self}
347+
for ax in axes:
348+
ax._anchor = anchor
349+
ax.stale = True
350+
264351
def apply_aspect(self, position=None):
265352
if position is None:
266353
position = self.get_position(original=True)
267354

355+
aspect = self.get_aspect()
356+
if aspect == 'auto':
357+
return
358+
268359
# in the superclass, we would go through and actually deal with axis
269360
# scales and box/datalim. Those are all irrelevant - all we need to do
270361
# is make sure our coordinate system is square.

0 commit comments

Comments
 (0)