Skip to content

[ENH]: Having more customization options for the FancyArrow class (specifically different tails) #22344

@mapfiable

Description

@mapfiable

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()

grafik

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions