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

Skip to content

Commit ed62274

Browse files
Rework 3d coordinates mouse hover, now displays coordinate of underlying view pane
1 parent 7100e9e commit ed62274

File tree

5 files changed

+64
-100
lines changed

5 files changed

+64
-100
lines changed

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,45 +1036,61 @@ def format_zdata(self, z):
10361036
val = func(z)
10371037
return val
10381038

1039-
def format_coord(self, xd, yd):
1039+
def format_coord(self, xd, yd, renderer=None):
10401040
"""
10411041
Given the 2D view coordinates attempt to guess a 3D coordinate.
10421042
Looks for the nearest edge to the point and then assumes that
10431043
the point is at the same z location as the nearest point on the edge.
10441044
"""
1045-
10461045
if self.M is None:
1047-
return ''
1046+
coords = ''
10481047

1049-
if self.button_pressed in self._rotate_btn:
1048+
elif self.button_pressed in self._rotate_btn:
10501049
# ignore xd and yd and display angles instead
10511050
norm_elev = art3d._norm_angle(self.elev)
10521051
norm_azim = art3d._norm_angle(self.azim)
10531052
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}'
1053+
coords = (f"elevation={norm_elev:.0f}\N{DEGREE SIGN}, "
1054+
f"azimuth={norm_azim:.0f}\N{DEGREE SIGN}, "
1055+
f"roll={norm_roll:.0f}\N{DEGREE SIGN}"
1056+
).replace("-", "\N{MINUS SIGN}")
1057+
1058+
else:
1059+
p1 = self._calc_coord(xd, yd, renderer)
1060+
xs = self.format_xdata(p1[0])
1061+
ys = self.format_ydata(p1[1])
1062+
zs = self.format_zdata(p1[2])
1063+
coords = f'x={xs}, y={ys}, z={zs}'
1064+
1065+
return coords
1066+
1067+
def _calc_coord(self, xd, yd, renderer=None):
1068+
"""
1069+
Given the 2D view coordinates, find the point on the nearest axis pane
1070+
that lies directly below those coordinates.
1071+
"""
1072+
# convert to data coordinates
1073+
x, y, z = proj3d.inv_transform(xd, yd, -1, self.M)
1074+
p0 = np.array([x, y, z])
1075+
1076+
# Get the pane locations for each of the axes
1077+
pane_locs = []
1078+
for axis in self._axis_map.values():
1079+
xys, loc = axis.active_pane(renderer)
1080+
pane_locs.append(loc)
1081+
1082+
# Find the distance to the nearest pane
1083+
scales = np.zeros(3)
1084+
for i in range(3):
1085+
if self._view_w[i] == 0:
1086+
scales[i] = np.inf
1087+
else:
1088+
scales[i] = (p0[i] - pane_locs[i]) / self._view_w[i]
1089+
scale = scales[np.argmin(abs(scales))]
1090+
1091+
# Calculate the point on the closest pane
1092+
p1 = p0 - scale*self._view_w
1093+
return p1
10781094

10791095
def _on_move(self, event):
10801096
"""

lib/mpl_toolkits/mplot3d/axis3d.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,20 @@ def _get_tickdir(self):
321321
tickdir = np.roll(info_i, -j)[np.roll(tickdirs_base, j)][i]
322322
return tickdir
323323

324+
def active_pane(self, renderer):
325+
mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
326+
info = self._axinfo
327+
index = info['i']
328+
if not highs[index]:
329+
loc = mins[index]
330+
plane = self._PLANES[2 * index]
331+
else:
332+
loc = maxs[index]
333+
plane = self._PLANES[2 * index + 1]
334+
xys = [tc[p] for p in plane]
335+
self._set_pane_pos(xys)
336+
return xys, loc
337+
324338
def draw_pane(self, renderer):
325339
"""
326340
Draw pane.
@@ -330,19 +344,9 @@ def draw_pane(self, renderer):
330344
renderer : `~matplotlib.backend_bases.RendererBase` subclass
331345
"""
332346
renderer.open_group('pane3d', gid=self.get_gid())
333-
334-
mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
335-
336-
info = self._axinfo
337-
index = info['i']
338-
if not highs[index]:
339-
plane = self._PLANES[2 * index]
340-
else:
341-
plane = self._PLANES[2 * index + 1]
342-
xys = [tc[p] for p in plane]
347+
xys, loc = self.active_pane(renderer)
343348
self._set_pane_pos(xys)
344349
self.pane.draw(renderer)
345-
346350
renderer.close_group('pane3d')
347351

348352
@artist.allow_rasterization

lib/mpl_toolkits/mplot3d/proj3d.py

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,6 @@
77

88
from matplotlib import _api
99

10-
11-
def _line2d_seg_dist(p, s0, s1):
12-
"""
13-
Return the distance(s) from point(s) *p* to segment(s) (*s0*, *s1*).
14-
15-
Parameters
16-
----------
17-
p : (ndim,) or (N, ndim) array-like
18-
The points from which the distances are computed.
19-
s0, s1 : (ndim,) or (N, ndim) array-like
20-
The xy(z...) coordinates of the segment endpoints.
21-
"""
22-
s0 = np.asarray(s0)
23-
s01 = s1 - s0 # shape (ndim,) or (N, ndim)
24-
s0p = p - s0 # shape (ndim,) or (N, ndim)
25-
l2 = s01 @ s01 # squared segment length
26-
# Avoid div. by zero for degenerate segments (for them, s01 = (0, 0, ...)
27-
# so the value of l2 doesn't matter; this just replaces 0/0 by 0/1).
28-
l2 = np.where(l2, l2, 1)
29-
# Project onto segment, without going past segment ends.
30-
p1 = s0 + np.multiply.outer(np.clip(s0p @ s01 / l2, 0, 1), s01)
31-
return ((p - p1) ** 2).sum(axis=-1) ** (1/2)
32-
33-
3410
def world_transformation(xmin, xmax,
3511
ymin, ymax,
3612
zmin, zmax, pb_aspect=None):
@@ -220,10 +196,11 @@ def inv_transform(xs, ys, zs, M):
220196
iM = linalg.inv(M)
221197
vec = _vec_pad_ones(xs, ys, zs)
222198
vecr = np.dot(iM, vec)
223-
try:
224-
vecr = vecr / vecr[3]
225-
except OverflowError:
226-
pass
199+
if vecr[3] != 0:
200+
try:
201+
vecr = vecr / vecr[3]
202+
except OverflowError:
203+
pass
227204
return vecr[0], vecr[1], vecr[2]
228205

229206

Binary file not shown.

lib/mpl_toolkits/mplot3d/tests/test_axes3d.py

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,39 +1116,6 @@ def test_world():
11161116
[0, 0, 0, 1]])
11171117

11181118

1119-
@mpl3d_image_comparison(['proj3d_lines_dists.png'])
1120-
def test_lines_dists():
1121-
fig, ax = plt.subplots(figsize=(4, 6), subplot_kw=dict(aspect='equal'))
1122-
1123-
xs = (0, 30)
1124-
ys = (20, 150)
1125-
ax.plot(xs, ys)
1126-
p0, p1 = zip(xs, ys)
1127-
1128-
xs = (0, 0, 20, 30)
1129-
ys = (100, 150, 30, 200)
1130-
ax.scatter(xs, ys)
1131-
1132-
dist0 = proj3d._line2d_seg_dist((xs[0], ys[0]), p0, p1)
1133-
dist = proj3d._line2d_seg_dist(np.array((xs, ys)).T, p0, p1)
1134-
assert dist0 == dist[0]
1135-
1136-
for x, y, d in zip(xs, ys, dist):
1137-
c = Circle((x, y), d, fill=0)
1138-
ax.add_patch(c)
1139-
1140-
ax.set_xlim(-50, 150)
1141-
ax.set_ylim(0, 300)
1142-
1143-
1144-
def test_lines_dists_nowarning():
1145-
# No RuntimeWarning must be emitted for degenerate segments, see GH#22624.
1146-
s0 = (10, 30, 50)
1147-
p = (20, 150, 180)
1148-
proj3d._line2d_seg_dist(p, s0, s0)
1149-
proj3d._line2d_seg_dist(np.array(p), s0, s0)
1150-
1151-
11521119
def test_autoscale():
11531120
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
11541121
assert ax.get_zscale() == 'linear'

0 commit comments

Comments
 (0)