Thanks to visit codestin.com
Credit goes to github.com

Skip to content

3D plots shared view angles #25821

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 1 commit into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/toolkits/mplot3d/axes3d.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ Sharing
:nosignatures:

sharez
shareview


Interactive
Expand Down
7 changes: 7 additions & 0 deletions doc/users/next_whats_new/3d_plots_shareview.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
3D plots can share view angles
------------------------------

3D plots can now share the same view angles, so that when you rotate one plot
the other plots also rotate. This can be done with the *shareview* keyword
argument when adding an axes, or by using the *ax1.shareview(ax2)* method of
existing 3D axes.
66 changes: 49 additions & 17 deletions lib/mpl_toolkits/mplot3d/axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Axes3D(Axes):

_axis_names = ("x", "y", "z")
Axes._shared_axes["z"] = cbook.Grouper()
Axes._shared_axes["view"] = cbook.Grouper()

vvec = _api.deprecate_privatize_attribute("3.7")
eye = _api.deprecate_privatize_attribute("3.7")
Expand All @@ -66,6 +67,7 @@ def __init__(
self, fig, rect=None, *args,
elev=30, azim=-60, roll=0, sharez=None, proj_type='persp',
box_aspect=None, computed_zorder=True, focal_length=None,
shareview=None,
**kwargs):
"""
Parameters
Expand Down Expand Up @@ -111,6 +113,8 @@ def __init__(
or infinity (numpy.inf). If None, defaults to infinity.
The focal length can be computed from a desired Field Of View via
the equation: focal_length = 1/tan(FOV/2)
shareview : Axes3D, optional
Other Axes to share view angles with.

**kwargs
Other optional keyword arguments:
Expand Down Expand Up @@ -142,6 +146,10 @@ def __init__(
self._shared_axes["z"].join(self, sharez)
self._adjustable = 'datalim'

self._shareview = shareview
if shareview is not None:
self._shared_axes["view"].join(self, shareview)

if kwargs.pop('auto_add_to_figure', False):
raise AttributeError(
'auto_add_to_figure is no longer supported for Axes3D. '
Expand Down Expand Up @@ -757,7 +765,8 @@ def clabel(self, *args, **kwargs):
"""Currently not implemented for 3D axes, and returns *None*."""
return None

def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z"):
def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z",
share=False):
"""
Set the elevation and azimuth of the axes in degrees (not radians).

Expand Down Expand Up @@ -804,29 +813,34 @@ def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z"):
constructor is used.
vertical_axis : {"z", "x", "y"}, default: "z"
The axis to align vertically. *azim* rotates about this axis.
share : bool, default: False
If ``True``, apply the settings to all Axes with shared views.
"""

self._dist = 10 # The camera distance from origin. Behaves like zoom

if elev is None:
self.elev = self.initial_elev
else:
self.elev = elev

elev = self.initial_elev
if azim is None:
self.azim = self.initial_azim
else:
self.azim = azim

azim = self.initial_azim
if roll is None:
self.roll = self.initial_roll
else:
self.roll = roll

self._vertical_axis = _api.check_getitem(
roll = self.initial_roll
vertical_axis = _api.check_getitem(
dict(x=0, y=1, z=2), vertical_axis=vertical_axis
)

if share:
axes = {sibling for sibling
in self._shared_axes['view'].get_siblings(self)}
else:
axes = [self]

for ax in axes:
ax.elev = elev
ax.azim = azim
ax.roll = roll
ax._vertical_axis = vertical_axis

def set_proj_type(self, proj_type, focal_length=None):
"""
Set the projection type.
Expand Down Expand Up @@ -964,7 +978,7 @@ def sharez(self, other):
Axes, and cannot be used if the z-axis is already being shared with
another Axes.
"""
_api.check_isinstance(maxes._base._AxesBase, other=other)
_api.check_isinstance(Axes3D, other=other)
if self._sharez is not None and other is not self._sharez:
raise ValueError("z-axis is already shared")
self._shared_axes["z"].join(self, other)
Expand All @@ -975,6 +989,23 @@ def sharez(self, other):
self.set_zlim(z0, z1, emit=False, auto=other.get_autoscalez_on())
self.zaxis._scale = other.zaxis._scale

def shareview(self, other):
"""
Share the view angles with *other*.

This is equivalent to passing ``shareview=other`` when
constructing the Axes, and cannot be used if the view angles are
already being shared with another Axes.
"""
_api.check_isinstance(Axes3D, other=other)
if self._shareview is not None and other is not self._shareview:
raise ValueError("view angles are already shared")
self._shared_axes["view"].join(self, other)
self._shareview = other
vertical_axis = {0: "x", 1: "y", 2: "z"}[other._vertical_axis]
self.view_init(elev=other.elev, azim=other.azim, roll=other.roll,
vertical_axis=vertical_axis, share=True)

def clear(self):
# docstring inherited.
super().clear()
Expand Down Expand Up @@ -1107,8 +1138,9 @@ def _on_move(self, event):
roll = np.deg2rad(self.roll)
delev = -(dy/h)*180*np.cos(roll) + (dx/w)*180*np.sin(roll)
dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll)
self.elev = self.elev + delev
self.azim = self.azim + dazim
elev = self.elev + delev
azim = self.azim + dazim
self.view_init(elev=elev, azim=azim, roll=roll, share=True)
self.stale = True

elif self.button_pressed in self._pan_btn:
Expand Down
14 changes: 14 additions & 0 deletions lib/mpl_toolkits/mplot3d/tests/test_axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -1689,6 +1689,20 @@ def test_set_zlim():
ax.set_zlim(top=0, zmax=1)


@check_figures_equal(extensions=["png"])
def test_shared_view(fig_test, fig_ref):
elev, azim, roll = 5, 20, 30
ax1 = fig_test.add_subplot(131, projection="3d")
ax2 = fig_test.add_subplot(132, projection="3d", shareview=ax1)
ax3 = fig_test.add_subplot(133, projection="3d")
ax3.shareview(ax1)
ax2.view_init(elev=elev, azim=azim, roll=roll, share=True)

for subplot_num in (131, 132, 133):
ax = fig_ref.add_subplot(subplot_num, projection="3d")
ax.view_init(elev=elev, azim=azim, roll=roll)


def test_shared_axes_retick():
fig = plt.figure()
ax1 = fig.add_subplot(211, projection="3d")
Expand Down