8000 ENH: use box_aspect to support setting pb_aspect · matplotlib/matplotlib@fb91f71 · GitHub
[go: up one dir, main page]

Skip to content

Commit fb91f71

Browse files
committed
ENH: use box_aspect to support setting pb_aspect
closes #17172 Instead of adding a new method, reuse `box_aspect` expecting a 3 vector. The default value of `None` is slightly special handled to make sure we hit the old behavior without having to put long floating point numbers in the source.
1 parent a5a2455 commit fb91f71

File tree

4 files changed

+320
-237
lines changed

4 files changed

+320
-237
lines changed

doc/users/next_whats_new/2019-03-25-mplot3d-projection.rst

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44
Plots made with :class:`~mpl_toolkits.mplot3d.axes3d.Axes3D` were previously
55
stretched to fit a square bounding box. As this stretching was done after
66
the projection from 3D to 2D, it resulted in distorted images if non-square
7-
bounding boxes were used.
7+
bounding boxes were used. As of this release, this no longer occurs by default.
< 8000 /td>
88

9-
As of this release, this no longer occurs.
9+
We have extended the functionality of `.Axes3D.set_aspect` to accept
10+
the modes ``'auto_Pb'``, which uses a fixed ratios between the sizes,
11+
in display space, of the axis. To control these ratios use the
12+
`.Axes3D.set_box_aspect` method which accepts the ratios at as a
13+
3-tuple of X:Y:Z. The default aspect ratio is 4:4:3.
14+
15+
Currently modes of setting the aspect, in data space, are not
16+
supported for Axes3D but maybe in the future. If you want to simulate
17+
having equal aspect in data space, set the ratio of your data limits
18+
to match the value of `.Axes3D.get_box_aspect`.
19+
20+
To restore the old behavior (which may result in distorted images) use
21+
``ax.set_aspect('auto')``.

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class Axes3D(Axes):
5353
def __init__(
5454
self, fig, rect=None, *args,
5555
azim=-60, elev=30, sharez=None, proj_type='persp',
56+
box_aspect=None, aspect='auto_pb',
5657
**kwargs):
5758
"""
5859
Parameters
@@ -91,11 +92,7 @@ def __init__(
9192
self.zz_viewLim = Bbox.unit()
9293
self.xy_dataLim = Bbox.unit()
9394
self.zz_dataLim = Bbox.unit()
94-
if 'pb_aspect' in kwargs:
95-
self.pb_aspect = np.asarray(kwargs['pb_aspect'])
96-
else:
97-
# chosen for similarity with the previous initial view
98-
self.pb_aspect = np.array([4, 4, 3]) / 3.5
95+
9996
# inhibit autoscale_view until the axes are defined
10097
# they can't be defined until Axes.__init__ has been called
10198
self.view_init(self.initial_elev, self.initial_azim)
@@ -105,8 +102,9 @@ def __init__(
105102
self._shared_z_axes.join(self, sharez)
106103
self._adjustable = 'datalim'
107104

108-
kwargs.setdefault('aspect', 'auto_pb')
109-
super().__init__(fig, rect, frameon=True, *args, **kwargs)
105+
super().__init__(fig, rect, frameon=True,
106+
box_aspect=box_aspect, aspect=aspect,
107+
*args, **kwargs)
110108
# Disable drawing of axes by base class
111109
super().set_axis_off()
112110
# Enable drawing of axes by Axes3D class
@@ -353,8 +351,43 @@ def set_anchor(self, anchor, share=False):
353351
ax._anchor = anchor
354352
ax.stale = True
355353

356-
def set_pb_aspect(self, pb_aspect, zoom=1):
357-
self.pb_aspect = pb_aspect * 1.8 * zoom / proj3d.mod(pb_aspect)
354+
def set_box_aspect(self, aspect, zoom=1):
355+
"""
356+
Set the axes box aspect.
357+
358+
The box aspect is the ratio of the axes height to the axes width in
359+
physical units. This is not to be confused with the data
360+
aspect, set via `~.Axes.set_aspect`.
361+
362+
Parameters
363+
----------
364+
aspect : 3-tuple of floats on None
365+
Changes the physical dimensions of the Axes, such that the ratio
366+
of the size of the axis in physical units is x:y:z
367+
368+
The input will be normalized to a unit vector.
369+
370+
If None, it is approximately ::
371+
372+
ax.set_box_aspect(aspect=(4, 4, 3), zoom=1)
373+
374+
zoom : float
375+
Control the "zoom" of the
376+
377+
See Also
378+
--------
379+
matplotlib.axes.Axes3D.set_aspect
380+
for a description of aspect handling.
381+
"""
382+
if aspect is None:
383+
aspect = zoom * np.asarray((4, 4, 3)) / 3.5
384+
else:
385+
aspect = np.asarray(aspect, dtype=float)
386+
# default scale tuned to match the
387+
aspect *= 1.829 * zoom / np.linalg.norm(aspect)
388+
389+
self._box_aspect = aspect
390+
self.stale = True
358391

359392
def apply_aspect(self, position=None):
360393
if position is None:
@@ -434,6 +467,7 @@ def get_axis_position(self):
434467
return xhigh, yhigh, zhigh
435468

436469
def _on_units_changed(self, scalex=False, scaley=False, scalez=False):
470+
437471
"""
438472
Callback for processing changes to axis units.
439473
@@ -981,8 +1015,6 @@ def set_proj_type(self, proj_type):
9811015

9821016
def get_proj(self):
9831017
"""Create the projection matrix from the current viewing position."""
984-
# chosen for similarity with the initial view before gh-8896
985-
9861018
# elev stores the elevation angle in the z plane
9871019
# azim stores the azimuth angle in the x,y plane
9881020
#
@@ -998,10 +1030,11 @@ def get_proj(self):
9981030
# transform to uniform world coordinates 0-1, 0-1, 0-1
9991031
worldM = proj3d.world_transformation(xmin, xmax,
10001032
ymin, ymax,
1001-
zmin, zmax, pb_aspect=self.pb_aspect)
1033+
zmin, zmax,
1034+
pb_aspect=self._box_aspect)
10021035

10031036
# look into the middle of the new coordinates
1004-
R = self.pb_aspect / 2
1037+
R = self._box_aspect / 2
10051038

10061039
xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
10071040
yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
Loading

0 commit comments

Comments
 (0)
0