Skip to content

Commit 5120b19

Browse files
authored
Merge pull request #18807 from ianhi/FancyArrow-set-positions
make FancyArrow animatable
2 parents 5e3a0f3 + 630494c commit 5120b19

File tree

4 files changed

+121
-25
lines changed

4 files changed

+121
-25
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``set_data`` method for ``FancyArrow`` patch
2+
--------------------------------------------
3+
4+
`.FancyArrow`, the patch returned by ``ax.arrow``, now has a ``set_data``
5+
method that allows for animating the arrow.

lib/matplotlib/axes/_axes.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5032,12 +5032,6 @@ def arrow(self, x, y, dx, dy, **kwargs):
50325032
50335033
Parameters
50345034
----------
5035-
x, y : float
5036-
The x and y coordinates of the arrow base.
5037-
5038-
dx, dy : float
5039-
The length of the arrow along x and y direction.
5040-
50415035
%(FancyArrow)s
50425036
50435037
Returns

lib/matplotlib/patches.py

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,12 @@ def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False,
13221322
"""
13231323
Parameters
13241324
----------
1325+
x, y : float
1326+
The x and y coordinates of the arrow base.
1327+
1328+
dx, dy : float
1329+
The length of the arrow along x and y direction.
1330+
13251331
width : float, default: 0.001
13261332
Width of full arrow tail.
13271333
@@ -1350,22 +1356,82 @@ def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False,
13501356
13511357
%(Patch_kwdoc)s
13521358
"""
1353-
if head_width is None:
1354-
head_width = 3 * width
1355-
if head_length is None:
1359+
self._x = x
1360+
self._y = y
1361+
self._dx = dx
1362+
self._dy = dy
1363+
self._width = width
1364+
self._length_includes_head = length_includes_head
1365+
self._head_width = head_width
1366+
self._head_length = head_length
1367+
self._shape = shape
1368+
self._overhang = overhang
1369+
self._head_starts_at_zero = head_starts_at_zero
1370+
self._make_verts()
1371+
super().__init__(self.verts, closed=True, **kwargs)
1372+
1373+
def set_data(self, *, x=None, y=None, dx=None, dy=None, width=None,
1374+
head_width=None, head_length=None):
1375+
"""
1376+
Set `.FancyArrow` x, y, dx, dy, width, head_with, and head_length.
1377+
Values left as None will not be updated.
1378+
1379+
Parameters
1380+
----------
1381+
x, y : float or None, default: None
1382+
The x and y coordinates of the arrow base.
1383+
1384+
dx, dy : float or None, default: None
1385+
The length of the arrow along x and y direction.
1386+
1387+
width: float or None, default: None
1388+
Width of full arrow tail.
1389+
1390+
head_width: float or None, default: None
1391+
Total width of the full arrow head.
1392+
1393+
head_length: float or None, default: None
1394+
Length of arrow head.
1395+
"""
1396+
if x is not None:
1397+
self._x = x
1398+
if y is not None:
1399+
self._y = y
1400+
if dx is not None:
1401+
self._dx = dx
1402+
if dy is not None:
1403+
self._dy = dy
1404+
if width is not None:
1405+
self._width = width
1406+
if head_width is not None:
1407+
self._head_width = head_width
1408+
if head_length is not None:
1409+
self._head_length = head_length
1410+
self._make_verts()
1411+
self.set_xy(self.verts)
1412+
1413+
def _make_verts(self):
1414+
if self._head_width is None:
1415+
head_width = 3 * self._width
1416+
else:
1417+
head_width = self._head_width
1418+
if self._head_length is None:
13561419
head_length = 1.5 * head_width
1420+
else:
1421+
head_length = self._head_length
13571422

1358-
distance = np.hypot(dx, dy)
1423+
distance = np.hypot(self._dx, self._dy)
13591424

1360-
if length_includes_head:
1425+
if self._length_includes_head:
13611426
length = distance
13621427
else:
13631428
length = distance + head_length
13641429
if not length:
1365-
verts = np.empty([0, 2]) # display nothing if empty
1430+
self.verts = np.empty([0, 2]) # display nothing if empty
13661431
else:
13671432
# start by drawing horizontal arrow, point at (0, 0)
1368-
hw, hl, hs, lw = head_width, head_length, overhang, width
1433+
hw, hl = head_width, head_length
1434+
hs, lw = self._overhang, self._width
13691435
left_half_arrow = np.array([
13701436
[0.0, 0.0], # tip
13711437
[-hl, -hw / 2], # leftmost
@@ -1374,36 +1440,37 @@ def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False,
13741440
[-length, 0],
13751441
])
13761442
# if we're not including the head, shift up by head length
1377-
if not length_includes_head:
1443+
if not self._length_includes_head:
13781444
left_half_arrow += [head_length, 0]
13791445
# if the head starts at 0, shift up by another head length
1380-
if head_starts_at_zero:
1446+
if self._head_starts_at_zero:
13811447
left_half_arrow += [head_length / 2, 0]
13821448
# figure out the shape, and complete accordingly
1383-
if shape == 'left':
1449+
if self._shape == 'left':
13841450
coords = left_half_arrow
13851451
else:
13861452
right_half_arrow = left_half_arrow * [1, -1]
1387-
if shape == 'right':
1453+
if self._shape == 'right':
13881454
coords = right_half_arrow
1389-
elif shape == 'full':
1455+
elif self._shape == 'full':
13901456
# The half-arrows contain the midpoint of the stem,
13911457
# which we can omit from the full arrow. Including it
13921458
# twice caused a problem with xpdf.
13931459
coords = np.concatenate([left_half_arrow[:-1],
13941460
right_half_arrow[-2::-1]])
13951461
else:
1396-
raise ValueError("Got unknown shape: %s" % shape)
1462+
raise ValueError("Got unknown shape: %s" % self.shape)
13971463
if distance != 0:
1398-
cx = dx / distance
1399-
sx = dy / distance
1464+
cx = self._dx / distance
1465+
sx = self._dy / distance
14001466
else:
14011467
# Account for division by zero
14021468
cx, sx = 0, 1
14031469
M = [[cx, sx], [-sx, cx]]
1404-
verts = np.dot(coords, M) + (x + dx, y + dy)
1405-
1406-
super().__init__(verts, closed=True, **kwargs)
1470+
self.verts = np.dot(coords, M) + [
1471+
self._x + self._dx,
1472+
self._y + self._dy,
1473+
]
14071474

14081475

14091476
docstring.interpd.update(

lib/matplotlib/tests/test_patches.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,37 @@ def test_fancyarrow_units():
548548
dtime = datetime(2000, 1, 1)
549549
fig, ax = plt.subplots()
550550
arrow = FancyArrowPatch((0, dtime), (0.01, dtime))
551-
ax.add_patch(arrow)
551+
552+
553+
def test_fancyarrow_setdata():
554+
fig, ax = plt.subplots()
555+
arrow = ax.arrow(0, 0, 10, 10, head_length=5, head_width=1, width=.5)
556+
expected1 = np.array(
557+
[[13.54, 13.54],
558+
[10.35, 9.65],
559+
[10.18, 9.82],
560+
[0.18, -0.18],
561+
[-0.18, 0.18],
562+
[9.82, 10.18],
563+
[9.65, 10.35],
564+
[13.54, 13.54]]
565+
)
566+
assert np.allclose(expected1, np.round(arrow.verts, 2))
567+
568+
expected2 = np.array(
569+
[[16.71, 16.71],
570+
[16.71, 15.29],
571+
[16.71, 15.29],
572+
[1.71, 0.29],
573+
[0.29, 1.71],
574+
[15.29, 16.71],
575+
[15.29, 16.71],
576+
[16.71, 16.71]]
577+
)
578+
arrow.set_data(
579+
x=1, y=1, dx=15, dy=15, width=2, head_width=2, head_length=1
580+
)
581+
assert np.allclose(expected2, np.round(arrow.verts, 2))
552582

553583

554584
@image_comparison(["large_arc.svg"], style="mpl20")

0 commit comments

Comments
 (0)