@@ -1036,45 +1036,95 @@ def format_zdata(self, z):
1036
1036
val = func (z )
1037
1037
return val
1038
1038
1039
- def format_coord (self , xd , yd ):
1039
+ def format_coord (self , xv , yv , renderer = None ):
1040
1040
"""
1041
- Given the 2D view coordinates attempt to guess a 3D coordinate.
1042
- Looks for the nearest edge to the point and then assumes that
1043
- the point is at the same z location as the nearest point on the edge .
1041
+ Return a string giving the current view rotation angles, or the x, y, z
1042
+ coordinates of the point on the nearest axis pane underneath the mouse
1043
+ cursor, depending on the mouse button pressed .
1044
1044
"""
1045
-
1046
- if self .M is None :
1047
- return ''
1045
+ coords = ''
1048
1046
1049
1047
if self .button_pressed in self ._rotate_btn :
1050
- # ignore xd and yd and display angles instead
1051
- norm_elev = art3d ._norm_angle (self .elev )
1052
- norm_azim = art3d ._norm_angle (self .azim )
1053
- norm_roll = art3d ._norm_angle (self .roll )
1054
- return (f"elevation={ norm_elev :.0f} \N{DEGREE SIGN} , "
1055
- f"azimuth={ norm_azim :.0f} \N{DEGREE SIGN} , "
1056
- f"roll={ norm_roll :.0f} \N{DEGREE SIGN} "
1057
- ).replace ("-" , "\N{MINUS SIGN} " )
1058
-
1059
- # nearest edge
1060
- p0 , p1 = min (self ._tunit_edges (),
1061
- key = lambda edge : proj3d ._line2d_seg_dist (
1062
- (xd , yd ), edge [0 ][:2 ], edge [1 ][:2 ]))
1063
-
1064
- # scale the z value to match
1065
- x0 , y0 , z0 = p0
1066
- x1 , y1 , z1 = p1
1067
- d0 = np .hypot (x0 - xd , y0 - yd )
1068
- d1 = np .hypot (x1 - xd , y1 - yd )
1069
- dt = d0 + d1
1070
- z = d1 / dt * z0 + d0 / dt * z1
1071
-
1072
- x , y , z = proj3d .inv_transform (xd , yd , z , self .M )
1073
-
1074
- xs = self .format_xdata (x )
1075
- ys = self .format_ydata (y )
1076
- zs = self .format_zdata (z )
1077
- return f'x={ xs } , y={ ys } , z={ zs } '
1048
+ # ignore xv and yv and display angles instead
1049
+ coords = self ._rotation_coords ()
1050
+
1051
+ elif self .M is not None :
1052
+ coords = self ._location_coords (xv , yv , renderer )
1053
+
1054
+ return coords
1055
+
1056
+ def _rotation_coords (self ):
1057
+ """
1058
+ Return the rotation angles as a string.
1059
+ """
1060
+ norm_elev = art3d ._norm_angle (self .elev )
1061
+ norm_azim = art3d ._norm_angle (self .azim )
1062
+ norm_roll = art3d ._norm_angle (self .roll )
1063
+ coords = (f"elevation={ norm_elev :.0f} \N{DEGREE SIGN} , "
1064
+ f"azimuth={ norm_azim :.0f} \N{DEGREE SIGN} , "
1065
+ f"roll={ norm_roll :.0f} \N{DEGREE SIGN} "
1066
+ ).replace ("-" , "\N{MINUS SIGN} " )
1067
+ return coords
1068
+
1069
+ def _location_coords (self , xv , yv , renderer ):
1070
+ """
1071
+ Return the location on the axis pane underneath the cursor as a string.
1072
+ """
1073
+ p1 = self ._calc_coord (xv , yv , renderer )
1074
+ xs = self .format_xdata (p1 [0 ])
1075
+ ys = self .format_ydata (p1 [1 ])
1076
+ zs = self .format_zdata (p1 [2 ])
1077
+ coords = f'x={ xs } , y={ ys } , z={ zs } '
1078
+ return coords
1079
+
1080
+ def _get_camera_loc (self ):
1081
+ """
1082
+ Returns the current camera location in data coordinates.
1083
+ """
1084
+ cx , cy , cz , dx , dy , dz = self ._get_w_centers_ranges ()
1085
+ c = np .array ([cx , cy , cz ])
1086
+ r = np .array ([dx , dy , dz ])
1087
+
1088
+ if self ._focal_length == np .inf : # orthographic projection
1089
+ focal_length = max (abs (r )) * 1e9 # large enough to be effectively infinite
1090
+ else : # perspective projection
1091
+ focal_length = self ._focal_length
1092
+ eye = c + self ._view_w * self ._dist * r / self ._box_aspect * focal_length
1093
+ return eye
1094
+
1095
+ def _calc_coord (self , xv , yv , renderer = None ):
1096
+ """
1097
+ Given the 2D view coordinates, find the point on the nearest axis pane
1098
+ that lies directly below those coordinates. Returns a 3D point in data
1099
+ coordinates.
1100
+ """
1101
+ if self ._focal_length == np .inf : # orthographic projection
1102
+ zv = 1
1103
+ else : # perspective projection
1104
+ zv = - 1 / self ._focal_length
1105
+
1106
+ # Convert point on view plane to data coordinates
1107
+ p1 = np .array (proj3d .inv_transform (xv , yv , zv , self .M )).ravel ()
1108
+ vec = self ._get_camera_loc () - p1
1109
+
1110
+ # Get the pane locations for each of the axes
1111
+ pane_locs = []
1112
+ for axis in self ._axis_map .values ():
1113
+ xys , loc = axis .active_pane (renderer )
1114
+ pane_locs .append (loc )
1115
+
1116
+ # Find the distance to the nearest pane
1117
+ scales = np .zeros (3 )
1118
+ for i in range (3 ):
1119
+ if vec [i ] == 0 :
1120
+ scales [i ] = np .inf
1121
+ else :
1122
+ scales [i ] = (p1 [i ] - pane_locs [i ]) / vec [i ]
1123
+ scale = scales [np .argmin (abs (scales ))]
1124
+
1125
+ # Calculate the point on the closest pane
1126
+ p2 = p1 - scale * vec
1127
+ return p2
1078
1128
1079
1129
def _on_move (self , event ):
1080
1130
"""
@@ -1296,21 +1346,27 @@ def _scale_axis_limits(self, scale_x, scale_y, scale_z):
1296
1346
scale_z : float
1297
1347
Scale factor for the z data axis.
1298
1348
"""
1299
- # Get the axis limits and centers
1349
+ # Get the axis centers and ranges
1350
+ cx , cy , cz , dx , dy , dz = self ._get_w_centers_ranges ()
1351
+
1352
+ # Set the scaled axis limits
1353
+ self .set_xlim3d (cx - dx * scale_x / 2 , cx + dx * scale_x / 2 )
1354
+ self .set_ylim3d (cy - dy * scale_y / 2 , cy + dy * scale_y / 2 )
1355
+ self .set_zlim3d (cz - dz * scale_z / 2 , cz + dz * scale_z / 2 )
1356
+
1357
+ def _get_w_centers_ranges (self ):
1358
+ """Get 3D world centers and axis ranges."""
1359
+ # Calculate center of axis limits
1300
1360
minx , maxx , miny , maxy , minz , maxz = self .get_w_lims ()
1301
1361
cx = (maxx + minx )/ 2
1302
1362
cy = (maxy + miny )/ 2
1303
1363
cz = (maxz + minz )/ 2
1304
1364
1305
- # Scale the data range
1306
- dx = (maxx - minx )* scale_x
1307
- dy = (maxy - miny )* scale_y
1308
- dz = (maxz - minz )* scale_z
1309
-
1310
- # Set the scaled axis limits
1311
- self .set_xlim3d (cx - dx / 2 , cx + dx / 2 )
1312
- self .set_ylim3d (cy - dy / 2 , cy + dy / 2 )
1313
- self .set_zlim3d (cz - dz / 2 , cz + dz / 2 )
1365
+ # Calculate range of axis limits
1366
+ dx = (maxx - minx )
1367
+ dy = (maxy - miny )
1368
+ dz = (maxz - minz )
1369
+ return cx , cy , cz , dx , dy , dz
1314
1370
1315
1371
def set_zlabel (self , zlabel , fontdict = None , labelpad = None , ** kwargs ):
1316
1372
"""
0 commit comments