@@ -139,7 +139,11 @@ def __init__(
139
139
140
140
# inhibit autoscale_view until the axes are defined
141
141
# they can't be defined until Axes.__init__ has been called
142
- self .view_init (self .initial_elev , self .initial_azim , self .initial_roll )
142
+ self .view_init (
143
+ elev = self .initial_elev ,
144
+ azim = self .initial_azim ,
145
+ roll = self .initial_roll ,
146
+ )
143
147
144
148
self ._sharez = sharez
145
149
if sharez is not None :
@@ -1094,25 +1098,66 @@ def clabel(self, *args, **kwargs):
1094
1098
def view_init (self , elev = None , azim = None , roll = None , vertical_axis = "z" ,
1095
1099
share = False ):
1096
1100
"""
1097
- Set the elevation and azimuth of the Axes in degrees (not radians).
1101
+ Set the azimuth, elevation, and roll of the Axes, in degrees (not radians).
1098
1102
1099
1103
This can be used to rotate the Axes programmatically.
1100
1104
1101
- To look normal to the primary planes, the following elevation and
1102
- azimuth angles can be used. A roll angle of 0, 90, 180, or 270 deg
1103
- will rotate these views while keeping the axes at right angles.
1105
+ To look normal to the primary planes, the following azimuth and
1106
+ elevation angles can be used:
1104
1107
1105
1108
========== ==== ====
1106
- view plane elev azim
1109
+ view plane azim elev
1107
1110
========== ==== ====
1108
- XY 90 - 90
1109
- XZ 0 -90
1110
- YZ 0 0
1111
- -XY - 90 90
1112
- -XZ 0 90
1113
- -YZ 0 180
1111
+ XY - 90 90
1112
+ XZ -90 0
1113
+ YZ 0 0
1114
+ -XY 90 - 90
1115
+ -XZ 90 0
1116
+ -YZ 180 0
1114
1117
========== ==== ====
1115
1118
1119
+ A roll angle of 0, 90, 180, or 270 degrees will rotate these views
1120
+ while keeping the axes at right angles.
1121
+
1122
+ The *azim*, *elev*, *roll* angles correspond to rotations of the scene
1123
+ observed by a stationary camera, as follows (assuming a default vertical
1124
+ axis of 'z'). First, a left-handed rotation about the z axis is applied
1125
+ (*azim*), then a right-handed rotation about the (camera) y axis (*elev*),
1126
+ then a right-handed rotation about the (camera) x axis (*roll*). Here,
1127
+ the z, y, and x axis are fixed axes (not the axes that rotate together
1128
+ with the original scene).
1129
+
1130
+ If you would like to make the connection with quaternions (because
1131
+ `Euler angles are horrible
1132
+ <https://github.com/moble/quaternion/wiki/Euler-angles-are-horrible>`_):
1133
+ the *azim*, *elev*, *roll* angles relate to the (intrinsic) rotation of
1134
+ the plot via:
1135
+
1136
+ *q* = exp(+roll **x̂** / 2) exp(+elev **ŷ** / 2) exp(−azim **ẑ** / 2)
1137
+
1138
+ (with angles given in radians instead of degrees). That is, the angles
1139
+ are a kind of `Tait-Bryan angles
1140
+ <https://en.wikipedia.org/wiki/Euler_angles#Tait%E2%80%93Bryan_angles>`_:
1141
+ −z, +y', +x", rather than classic `Euler angles
1142
+ <https://en.wikipedia.org/wiki/Euler_angles>`_.
1143
+
1144
+ To avoid confusion, it makes sense to provide the view angles as keyword
1145
+ arguments:
1146
+ ``.view_init(azim=-60, elev=30, roll=0, ...)``
1147
+ This specific order is consistent with the order in which the rotations
1148
+ actually are applied. Moreover, this particular order appears to be most
1149
+ common, see :ghissue:`28353`, and it is consistent with the ordering in
1150
+ `matplotlib.colors.LightSource`.
1151
+
1152
+ For backwards compatibility, positional arguments in the old sequence
1153
+ (first ``elev``, then ``azim``) will still be accepted; but preferably,
1154
+ use keyword arguments, to avoid confusion as to which angle is which.
1155
+ Unfortunately, the order of the positional arguments does not match
1156
+ the actual order of the applied rotations, and it differs from that
1157
+ used in other programs (``azim, elev``). It would be nice if the sensible
1158
+ (keyword) ordering could take over eventually.
1159
+
1160
+
1116
1161
Parameters
1117
1162
----------
1118
1163
elev : float, default: None
@@ -1145,10 +1190,10 @@ def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z",
1145
1190
1146
1191
self ._dist = 10 # The camera distance from origin. Behaves like zoom
1147
1192
1148
- if elev is None :
1149
- elev = self .initial_elev
1150
1193
if azim is None :
1151
1194
azim = self .initial_azim
1195
+ if elev is None :
1196
+ elev = self .initial_elev
1152
1197
if roll is None :
1153
1198
roll = self .initial_roll
1154
1199
vertical_axis = _api .check_getitem (
@@ -1163,8 +1208,8 @@ def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z",
1163
1208
axes = [self ]
1164
1209
1165
1210
for ax in axes :
1166
- ax .elev = elev
1167
1211
ax .azim = azim
1212
+ ax .elev = elev
1168
1213
ax .roll = roll
1169
1214
ax ._vertical_axis = vertical_axis
1170
1215
@@ -1229,15 +1274,15 @@ def get_proj(self):
1229
1274
# Look into the middle of the world coordinates:
1230
1275
R = 0.5 * box_aspect
1231
1276
1232
- # elev: elevation angle in the z plane.
1233
1277
# azim: azimuth angle in the xy plane.
1278
+ # elev: elevation angle in the z plane.
1234
1279
# Coordinates for a point that rotates around the box of data.
1235
1280
# p0, p1 corresponds to rotating the box only around the vertical axis.
1236
1281
# p2 corresponds to rotating the box only around the horizontal axis.
1237
- elev_rad = np .deg2rad (self .elev )
1238
1282
azim_rad = np .deg2rad (self .azim )
1239
- p0 = np .cos (elev_rad ) * np .cos (azim_rad )
1240
- p1 = np .cos (elev_rad ) * np .sin (azim_rad )
1283
+ elev_rad = np .deg2rad (self .elev )
1284
+ p0 = np .cos (azim_rad ) * np .cos (elev_rad )
1285
+ p1 = np .sin (azim_rad ) * np .cos (elev_rad )
1241
1286
p2 = np .sin (elev_rad )
1242
1287
1243
1288
# When changing vertical axis the coordinates changes as well.
@@ -1339,8 +1384,13 @@ def shareview(self, other):
1339
1384
self ._shared_axes ["view" ].join (self , other )
1340
1385
self ._shareview = other
1341
1386
vertical_axis = self ._axis_names [other ._vertical_axis ]
1342
- self .view_init (elev = other .elev , azim = other .azim , roll = other .roll ,
1343
- vertical_axis = vertical_axis , share = True )
1387
+ self .view_init (
1388
+ elev = other .elev ,
1389
+ azim = other .azim ,
1390
+ roll = other .roll ,
1391
+ vertical_axis = vertical_axis ,
1392
+ share = True ,
1393
+ )
1344
1394
1345
1395
def clear (self ):
1346
1396
# docstring inherited.
@@ -1392,8 +1442,8 @@ def _set_view(self, view):
1392
1442
# docstring inherited
1393
1443
props , (elev , azim , roll ) = view
1394
1444
self .set (** props )
1395
- self .elev = elev
1396
1445
self .azim = azim
1446
+ self .elev = elev
1397
1447
self .roll = roll
1398
1448
1399
1449
def format_zdata (self , z ):
@@ -1430,11 +1480,11 @@ def _rotation_coords(self):
1430
1480
"""
1431
1481
Return the rotation angles as a string.
1432
1482
"""
1433
- norm_elev = art3d ._norm_angle (self .elev )
1434
1483
norm_azim = art3d ._norm_angle (self .azim )
1484
+ norm_elev = art3d ._norm_angle (self .elev )
1435
1485
norm_roll = art3d ._norm_angle (self .roll )
1436
- coords = (f"elevation= { norm_elev :.0f} \N{DEGREE SIGN} , "
1437
- f"azimuth= { norm_azim :.0f} \N{DEGREE SIGN} , "
1486
+ coords = (f"azimuth= { norm_azim :.0f} \N{DEGREE SIGN} , "
1487
+ f"elevation= { norm_elev :.0f} \N{DEGREE SIGN} , "
1438
1488
f"roll={ norm_roll :.0f} \N{DEGREE SIGN} "
1439
1489
).replace ("-" , "\N{MINUS SIGN} " )
1440
1490
return coords
@@ -1561,10 +1611,10 @@ def _on_move(self, event):
1561
1611
return
1562
1612
1563
1613
# Convert to quaternion
1564
- elev = np .deg2rad (self .elev )
1565
1614
azim = np .deg2rad (self .azim )
1615
+ elev = np .deg2rad (self .elev )
1566
1616
roll = np .deg2rad (self .roll )
1567
- q = _Quaternion .from_cardan_angles (elev , azim , roll )
1617
+ q = _Quaternion .from_cardan_angles (azim , elev , roll )
1568
1618
1569
1619
# Update quaternion - a variation on Ken Shoemake's ARCBALL
1570
1620
current_vec = self ._arcball (self ._sx / w , self ._sy / h )
@@ -1573,18 +1623,13 @@ def _on_move(self, event):
1573
1623
q = dq * q
1574
1624
1575
1625
# Convert to elev, azim, roll
1576
- elev , azim , roll = q .as_cardan_angles ()
1626
+ azim , elev , roll = q .as_cardan_angles ()
1577
1627
azim = np .rad2deg (azim )
1578
1628
elev = np .rad2deg (elev )
1579
1629
roll = np .rad2deg (roll )
1580
1630
vertical_axis = self ._axis_names [self ._vertical_axis ]
1581
- self .view_init (
1582
- elev = elev ,
1583
- azim = azim ,
1584
- roll = roll ,
1585
- vertical_axis = vertical_axis ,
1586
- share = True ,
1587
- )
1631
+ self .view_init (elev , azim , roll , vertical_axis = vertical_axis ,
1632
+ share = True )
1588
1633
self .stale = True
1589
1634
1590
1635
# Pan
@@ -3662,10 +3707,10 @@ def _extract_errs(err, data, lomask, himask):
3662
3707
quiversize = np .mean (np .diff (quiversize , axis = 0 ))
3663
3708
# quiversize is now in Axes coordinates, and to convert back to data
3664
3709
# coordinates, we need to run it through the inverse 3D transform. For
3665
- # consistency, this uses a fixed elevation, azimuth , and roll.
3710
+ # consistency, this uses a fixed azimuth, elevation , and roll.
3666
3711
with cbook ._setattr_cm (self , elev = 0 , azim = 0 , roll = 0 ):
3667
3712
invM = np .linalg .inv (self .get_proj ())
3668
- # elev= azim=roll=0 produces the Y-Z plane, so quiversize in 2D 'x' is
3713
+ # azim=elev =roll=0 produces the Y-Z plane, so quiversize in 2D 'x' is
3669
3714
# 'y' in 3D, hence the 1 index.
3670
3715
quiversize = np .dot (invM , [quiversize , 0 , 0 , 0 ])[1 ]
3671
3716
# Quivers use a fixed 15-degree arrow head, so scale up the length so
@@ -4000,7 +4045,7 @@ def rotate_from_to(cls, r1, r2):
4000
4045
return q
4001
4046
4002
4047
@classmethod
4003
- def from_cardan_angles (cls , elev , azim , roll ):
4048
+ def from_cardan_angles (cls , azim , elev , roll ):
4004
4049
"""
4005
4050
Converts the angles to a quaternion
4006
4051
q = exp((roll/2)*e_x)*exp((elev/2)*e_y)*exp((-azim/2)*e_z)
@@ -4027,4 +4072,4 @@ def as_cardan_angles(self):
4027
4072
azim = np .arctan2 (2 * (- qw * qz + qx * qy ), qw * qw + qx * qx - qy * qy - qz * qz )
4028
4073
elev = np .arcsin ( 2 * ( qw * qy + qz * qx )/ (qw * qw + qx * qx + qy * qy + qz * qz )) # noqa E201
4029
4074
roll = np .arctan2 (2 * ( qw * qx - qy * qz ), qw * qw - qx * qx - qy * qy + qz * qz ) # noqa E201
4030
- return elev , azim , roll
4075
+ return azim , elev , roll
0 commit comments