Skip to content

Commit 5b835ee

Browse files
authored
Merge pull request #18525 from QuLogic/text3d-simplification
Add Text3D position getter/setter
2 parents 3f92f43 + 7034b70 commit 5b835ee

File tree

3 files changed

+73
-11
lines changed

3 files changed

+73
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``mplot3d.art3d.get_dir_vector`` always returns NumPy arrays
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
For consistency, `~.mplot3d.art3d.get_dir_vector` now always returns NumPy
5+
arrays, even if the input is a 3-element iterable.

lib/mpl_toolkits/mplot3d/art3d.py

+43-11
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import numpy as np
1313

1414
from matplotlib import (
15-
artist, colors as mcolors, lines, text as mtext, path as mpath)
15+
artist, cbook, colors as mcolors, lines, text as mtext, path as mpath)
1616
from matplotlib.collections import (
1717
LineCollection, PolyCollection, PatchCollection, PathCollection)
1818
from matplotlib.colors import Normalize
@@ -49,13 +49,12 @@ def get_dir_vector(zdir):
4949
- 'y': equivalent to (0, 1, 0)
5050
- 'z': equivalent to (0, 0, 1)
5151
- *None*: equivalent to (0, 0, 0)
52-
- an iterable (x, y, z) is returned unchanged.
52+
- an iterable (x, y, z) is converted to a NumPy array, if not already
5353
5454
Returns
5555
-------
5656
x, y, z : array-like
57-
The direction vector. This is either a numpy.array or *zdir* itself if
58-
*zdir* is already a length-3 iterable.
57+
The direction vector.
5958
"""
6059
if zdir == 'x':
6160
return np.array((1, 0, 0))
@@ -66,7 +65,7 @@ def get_dir_vector(zdir):
6665
elif zdir is None:
6766
return np.array((0, 0, 0))
6867
elif np.iterable(zdir) and len(zdir) == 3:
69-
return zdir
68+
return np.array(zdir)
7069
else:
7170
raise ValueError("'x', 'y', 'z', None or vector of length 3 expected")
7271

@@ -95,22 +94,55 @@ def __init__(self, x=0, y=0, z=0, text='', zdir='z', **kwargs):
9594
mtext.Text.__init__(self, x, y, text, **kwargs)
9695
self.set_3d_properties(z, zdir)
9796

97+
def get_position_3d(self):
98+
"""Return the (x, y, z) position of the text."""
99+
return self._x, self._y, self._z
100+
101+
def set_position_3d(self, xyz, zdir=None):
102+
"""
103+
Set the (*x*, *y*, *z*) position of the text.
104+
105+
Parameters
106+
----------
107+
xyz : (float, float, float)
108+
The position in 3D space.
109+
zdir : {'x', 'y', 'z', None, 3-tuple}
110+
The direction of the text. If unspecified, the zdir will not be
111+
changed.
112+
"""
113+
super().set_position(xyz[:2])
114+
self.set_z(xyz[2])
115+
if zdir is not None:
116+
self._dir_vec = get_dir_vector(zdir)
117+
118+
def set_z(self, z):
119+
"""
120+
Set the *z* position of the text.
121+
122+
Parameters
123+
----------
124+
z : float
125+
"""
126+
self._z = z
127+
self.stale = True
128+
98129
def set_3d_properties(self, z=0, zdir='z'):
99-
x, y = self.get_position()
100-
self._position3d = np.array((x, y, z))
130+
self._z = z
101131
self._dir_vec = get_dir_vector(zdir)
102132
self.stale = True
103133

104134
@artist.allow_rasterization
105135
def draw(self, renderer):
136+
position3d = np.array((self._x, self._y, self._z))
106137
proj = proj3d.proj_trans_points(
107-
[self._position3d, self._position3d + self._dir_vec], renderer.M)
138+
[position3d, position3d + self._dir_vec],
139+
renderer.M)
108140
dx = proj[0][1] - proj[0][0]
109141
dy = proj[1][1] - proj[1][0]
110142
angle = math.degrees(math.atan2(dy, dx))
111-
self.set_position((proj[0][0], proj[1][0]))
112-
self.set_rotation(_norm_text_angle(angle))
113-
mtext.Text.draw(self, renderer)
143+
with cbook._setattr_cm(self, _x=proj[0][0], _y=proj[1][0],
144+
_rotation=_norm_text_angle(angle)):
145+
mtext.Text.draw(self, renderer)
114146
self.stale = False
115147

116148
def get_tightbbox(self, renderer):

lib/mpl_toolkits/tests/test_mplot3d.py

+25
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,31 @@ def test_text3d():
416416
ax.set_zlabel('Z axis')
417417

418418

419+
@check_figures_equal(extensions=['png'])
420+
def test_text3d_modification(fig_ref, fig_test):
421+
# Modifying the Text position after the fact should work the same as
422+
# setting it directly.
423+
zdirs = (None, 'x', 'y', 'z', (1, 1, 0), (1, 1, 1))
424+
xs = (2, 6, 4, 9, 7, 2)
425+
ys = (6, 4, 8, 7, 2, 2)
426+
zs = (4, 2, 5, 6, 1, 7)
427+
428+
ax_test = fig_test.add_subplot(projection='3d')
429+
ax_test.set_xlim3d(0, 10)
430+
ax_test.set_ylim3d(0, 10)
431+
ax_test.set_zlim3d(0, 10)
432+
for zdir, x, y, z in zip(zdirs, xs, ys, zs):
433+
t = ax_test.text(0, 0, 0, f'({x}, {y}, {z}), dir={zdir}')
434+
t.set_position_3d((x, y, z), zdir=zdir)
435+
436+
ax_ref = fig_ref.add_subplot(projection='3d')
437+
ax_ref.set_xlim3d(0, 10)
438+
ax_ref.set_ylim3d(0, 10)
439+
ax_ref.set_zlim3d(0, 10)
440+
for zdir, x, y, z in zip(zdirs, xs, ys, zs):
441+
ax_ref.text(x, y, z, f'({x}, {y}, {z}), dir={zdir}', zdir=zdir)
442+
443+
419444
@mpl3d_image_comparison(['trisurf3d.png'], tol=0.03)
420445
def test_trisurf3d():
421446
n_angles = 36

0 commit comments

Comments
 (0)