-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Open
Labels
Description
Problem
Following my recent erroneous bug report, I think it would be great to have more customization options for the FancyArrow
class. More specifically, it would be nice to be able to change the shape of the tail via a simple kwarg, similar to capstyle
e.g.
I was also surprised to find that you have much more customization options for the annotation
arrow than for a class called FancyArrow
. It would be great to also have these options for the FancyArrow
class, and e.g. introduce a similar notation like .-|>
to get a round tail.
Proposed solution
As suggested by @jklymak, I tried to use a custom Polygon
to simulate the effect. Here is my attempt of modifying the FandyArrow
class:
class FancierArrow(FancyArrow):
"""
Like FancyArrow, but lets you choose a round butt.
"""
def __str__(self):
return "FancierArrow()"
def __init__(
self, x, y, dx, dy, width=0.001, length_includes_head=False,
head_width=None, head_length=None, shape='full', overhang=0,
head_starts_at_zero=False, round_tail=False, **kwargs
):
self._round_tail = round_tail
super().__init__(
x, y, dx, dy, width=width,
length_includes_head=length_includes_head,
head_width=head_width, head_length=head_length, shape=shape,
overhang=overhang, head_starts_at_zero=head_starts_at_zero,
**kwargs
)
def set_data(
self, *, x=None, y=None, dx=None, dy=None, width=None,
head_width=None, head_length=None, round_tail=None,
):
if round_tail is not None:
self._round_tail = round_tail
super().__init__(
x=x, y=y, dx=dx, dy=dy, width=width, head_width=head_width,
head_length=head_length
)
def _make_verts(self):
super()._make_verts()
if self._round_tail:
self._make_tail_round()
def _make_tail_round(self):
nvert = 25
add_rot = np.pi/2
radius = width/2
x, y = self._x, self._y
dx, dy = self._dx, self._dy
if self._head_starts_at_zero:
x, y = (self.verts[4] + self.verts[3])/2
dx, dy = (self.verts[4] - self.verts[3])/2
add_rot += np.pi/2
if self._shape in ['left', 'right']:
radius /= 2
x, y = (self.verts[4] + self.verts[3])/2
dx, dy = (self.verts[4] - self.verts[3])/2
if self._shape == 'left' and not self._head_starts_at_zero:
add_rot += np.pi/2
elif self._shape == 'right':
if self._head_starts_at_zero:
add_rot += np.pi
else:
add_rot -= np.pi/2
angle = np.arctan2(dx, dy) + add_rot
circ_segs = np.linspace(0, np.pi, nvert+1) + angle
tail_verts = np.zeros((nvert, 2), float)
tail_verts[:, 0] = np.sin(circ_segs)[:-1]*radius + x
tail_verts[:, 1] = np.cos(circ_segs)[:-1]*radius + y
if self._shape == 'right':
tail_verts = tail_verts[::-1]
self.verts = np.array([*self.verts[:4], *tail_verts, *self.verts[4:]])
fig, ax = plt.subplots(dpi=300)
fig.tight_layout()
width = 0.3
xs = np.sin(np.linspace(0, 2*np.pi, 9))[:-1]
ys = np.cos(np.linspace(0, 2*np.pi, 9))[:-1]
for x, y in zip(xs, ys):
polygon = FancierArrow(
x, y, x, y, width=width, edgecolor='black',
round_tail=True,
head_starts_at_zero=True,
shape='left'
)
ax.add_patch(polygon)
ax.relim()
ax.autoscale_view()
fig.show()