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

Skip to content

Commit efd66d4

Browse files
authored
Merge pull request #25821 from scottshambaugh/3d_shared_view_axes
3D plots shared view angles
2 parents 4754161 + 933d9b9 commit efd66d4

File tree

4 files changed

+71
-17
lines changed

4 files changed

+71
-17
lines changed

doc/api/toolkits/mplot3d/axes3d.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ Sharing
209209
:nosignatures:
210210

211211
sharez
212+
shareview
212213

213214

214215
Interactive
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
3D plots can share view angles
2+
------------------------------
3+
4+
3D plots can now share the same view angles, so that when you rotate one plot
5+
the other plots also rotate. This can be done with the *shareview* keyword
6+
argument when adding an axes, or by using the *ax1.shareview(ax2)* method of
7+
existing 3D axes.

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class Axes3D(Axes):
5656

5757
_axis_names = ("x", "y", "z")
5858
Axes._shared_axes["z"] = cbook.Grouper()
59+
Axes._shared_axes["view"] = cbook.Grouper()
5960

6061
vvec = _api.deprecate_privatize_attribute("3.7")
6162
eye = _api.deprecate_privatize_attribute("3.7")
@@ -66,6 +67,7 @@ def __init__(
6667
self, fig, rect=None, *args,
6768
elev=30, azim=-60, roll=0, sharez=None, proj_type='persp',
6869
box_aspect=None, computed_zorder=True, focal_length=None,
70+
shareview=None,
6971
**kwargs):
7072
"""
7173
Parameters
@@ -111,6 +113,8 @@ def __init__(
111113
or infinity (numpy.inf). If None, defaults to infinity.
112114
The focal length can be computed from a desired Field Of View via
113115
the equation: focal_length = 1/tan(FOV/2)
116+
shareview : Axes3D, optional
117+
Other Axes to share view angles with.
114118
115119
**kwargs
116120
Other optional keyword arguments:
@@ -142,6 +146,10 @@ def __init__(
142146
self._shared_axes["z"].join(self, sharez)
143147
self._adjustable = 'datalim'
144148

149+
self._shareview = shareview
150+
if shareview is not None:
151+
self._shared_axes["view"].join(self, shareview)
152+
145153
if kwargs.pop('auto_add_to_figure', False):
146154
raise AttributeError(
147155
'auto_add_to_figure is no longer supported for Axes3D. '
@@ -757,7 +765,8 @@ def clabel(self, *args, **kwargs):
757765
"""Currently not implemented for 3D axes, and returns *None*."""
758766
return None
759767

760-
def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z"):
768+
def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z",
769+
share=False):
761770
"""
762771
Set the elevation and azimuth of the axes in degrees (not radians).
763772
@@ -804,29 +813,34 @@ def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z"):
804813
constructor is used.
805814
vertical_axis : {"z", "x", "y"}, default: "z"
806815
The axis to align vertically. *azim* rotates about this axis.
816+
share : bool, default: False
817+
If ``True``, apply the settings to all Axes with shared views.
807818
"""
808819

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

811822
if elev is None:
812-
self.elev = self.initial_elev
813-
else:
814-
self.elev = elev
815-
823+
elev = self.initial_elev
816824
if azim is None:
817-
self.azim = self.initial_azim
818-
else:
819-
self.azim = azim
820-
825+
azim = self.initial_azim
821826
if roll is None:
822-
self.roll = self.initial_roll
823-
else:
824-
self.roll = roll
825-
826-
self._vertical_axis = _api.check_getitem(
827+
roll = self.initial_roll
828+
vertical_axis = _api.check_getitem(
827829
dict(x=0, y=1, z=2), vertical_axis=vertical_axis
828830
)
829831

832+
if share:
833+
axes = {sibling for sibling
834+
in self._shared_axes['view'].get_siblings(self)}
835+
else:
836+
axes = [self]
837+
838+
for ax in axes:
839+
ax.elev = elev
840+
ax.azim = azim
841+
ax.roll = roll
842+
ax._vertical_axis = vertical_axis
843+
830844
def set_proj_type(self, proj_type, focal_length=None):
831845
"""
832846
Set the projection type.
@@ -964,7 +978,7 @@ def sharez(self, other):
964978
Axes, and cannot be used if the z-axis is already being shared with
965979
another Axes.
966980
"""
967-
_api.check_isinstance(maxes._base._AxesBase, other=other)
981+
_api.check_isinstance(Axes3D, other=other)
968982
if self._sharez is not None and other is not self._sharez:
969983
raise ValueError("z-axis is already shared")
970984
self._shared_axes["z"].join(self, other)
@@ -975,6 +989,23 @@ def sharez(self, other):
975989
self.set_zlim(z0, z1, emit=False, auto=other.get_autoscalez_on())
976990
self.zaxis._scale = other.zaxis._scale
977991

992+
def shareview(self, other):
993+
"""
994+
Share the view angles with *other*.
995+
996+
This is equivalent to passing ``shareview=other`` when
997+
constructing the Axes, and cannot be used if the view angles are
998+
already being shared with another Axes.
999+
"""
1000+
_api.check_isinstance(Axes3D, other=other)
1001+
if self._shareview is not None and other is not self._shareview:
1002+
raise ValueError("view angles are already shared")
1003+
self._shared_axes["view"].join(self, other)
1004+
self._shareview = other
1005+
vertical_axis = {0: "x", 1: "y", 2: "z"}[other._vertical_axis]
1006+
self.view_init(elev=other.elev, azim=other.azim, roll=other.roll,
1007+
vertical_axis=vertical_axis, share=True)
1008+
9781009
def clear(self):
9791010
# docstring inherited.
9801011
super().clear()
@@ -1107,8 +1138,9 @@ def _on_move(self, event):
11071138
roll = np.deg2rad(self.roll)
11081139
delev = -(dy/h)*180*np.cos(roll) + (dx/w)*180*np.sin(roll)
11091140
dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll)
1110-
self.elev = self.elev + delev
1111-
self.azim = self.azim + dazim
1141+
elev = self.elev + delev
1142+
azim = self.azim + dazim
1143+
self.view_init(elev=elev, azim=azim, roll=roll, share=True)
11121144
self.stale = True
11131145

11141146
elif self.button_pressed in self._pan_btn:

lib/mpl_toolkits/mplot3d/tests/test_axes3d.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,6 +1689,20 @@ def test_set_zlim():
16891689
ax.set_zlim(top=0, zmax=1)
16901690

16911691

1692+
@check_figures_equal(extensions=["png"])
1693+
def test_shared_view(fig_test, fig_ref):
1694+
elev, azim, roll = 5, 20, 30
1695+
ax1 = fig_test.add_subplot(131, projection="3d")
1696+
ax2 = fig_test.add_subplot(132, projection="3d", shareview=ax1)
1697+
ax3 = fig_test.add_subplot(133, projection="3d")
1698+
ax3.shareview(ax1)
1699+
ax2.view_init(elev=elev, azim=azim, roll=roll, share=True)
1700+
1701+
for subplot_num in (131, 132, 133):
1702+
ax = fig_ref.add_subplot(subplot_num, projection="3d")
1703+
ax.view_init(elev=elev, azim=azim, roll=roll)
1704+
1705+
16921706
def test_shared_axes_retick():
16931707
fig = plt.figure()
16941708
ax1 = fig.add_subplot(211, projection="3d")

0 commit comments

Comments
 (0)