8000 Fix mplot3d projection by eric-wieser · Pull Request #8896 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Fix mplot3d projection #8896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 7, 2020
Merged
Next Next commit
BUG: Fix the aspect ratio of 3d plots
Fixes gh-8894, by always using a "position" that maintains a uniform coordinate system.

Test added - when viewed from above, the plot should be square not rhombic.
  • Loading branch information
eric-wieser committed Jun 24, 2019
commit 64f197b9269a141991e8370865231804d4aa19eb
5 changes: 5 additions & 0 deletions doc/users/next_whats_new/2019-03-25-mplot3d-projection.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
``Axes3D`` now preserves right angles when rotating
---------------------------------------------------

:class:`~mpl_toolkits.mplot3d.Axes3D` no longer stretches the plot in the x
axis after projecting.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit misleading. It isn't always a stretch in the x axis. Look at some of the image diffs, particularly the bar3d rotated one, and sometimes the stretching was in other directions. This is also misleading to some users because they may not be explicitly rotating the figure, but this still impacts them. This impacts just about anybody who has used mplot3d.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a stretch in the window x axis (ie, the axis used after projection). You're right though, the wording is bad.

6 changes: 3 additions & 3 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1467,9 +1467,9 @@ def apply_aspect(self, position=None):
"""
Adjust the Axes for a specified data aspect ratio.

Depending on `.get_adjustable` this will modify either the Axes box
(position) or the view limits. In the former case, `.get_anchor`
will affect the position.
Depending on `.get_adjustable` this will modify either the
Axes box (position) or the view limits. In the former case,
`~matplotlib.axes.Axes.get_anchor` will affect the position.

Notes
-----
Expand Down
14 changes: 14 additions & 0 deletions lib/mpl_toolkits/mplot3d/axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,20 @@ def tunit_edges(self, vals=None, M=None):
(tc[7], tc[4])]
return edges

def apply_aspect(self, position=None):
if position is None:
position = self.get_position(original=True)

# in the superclass, we would go through and actually deal with axis
# scales and box/datalim. Those are all irrelevant - all we need to do
# is make sure our coordinate system is square.
figW, figH = self.get_figure().get_size_inches()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use the figure's aspect ratio? What if the figure has 3 subplots all in a row?

Copy link
Contributor Author
@eric-wieser eric-wieser Aug 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because all sizes in matplotlib are relative to the figure. pb.shrunk_to_aspect seems to take the ratio of the top-level parent (the figure), and the desired ratio. This is because the end result is the size relative to the parent, as all objects in matplotlib seem to be sized - so to ensure a given aspect ratio, you need to account for that of the parent

Note that this is the same as the normal aspect=equal code, so is presumably correct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping @efiring, as I think you are the resident expert on all the ways this could possibly go wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense. I have no intention of spending the time required to understand mplot3d well enough to make a detailed review, but in the absence of any such review I am inclined to trust that this PR is a major improvement, as it appears to be fixing a basic design flaw.

fig_aspect = figH / figW
box_aspect = 1
pb = position.frozen()
pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect)
self.set_position(pb1.anchored(self.get_anchor(), pb), 'active')

@artist.allow_rasterization
def draw(self, renderer):
# draw the background patch
Expand Down
7 changes: 7 additions & 0 deletions lib/mpl_toolkits/tests/test_mplot3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,13 @@ def test_axes3d_cla():
ax.cla() # make sure the axis displayed is 3D (not 2D)


@image_comparison(['axes3d_rotated.png'])
def test_axes3d_rotated():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.view_init(90, 45) # look down, rotated. Should be square


def test_plotsurface_1d_raises():
x = np.linspace(0.5, 10, num=100)
y = np.linspace(0.5, 10, num=100)
Expand Down
0