Skip to content

[ENH]: Add fancyarrow() as a function to draw single arrows #29826

Closed as not planned

Description

@timhoffm

Problem

Drawing arrows is currently a mess.

  1. We have ax.arrow() which uses a fundamentally broken transformation, typically resulting in skewed arrows.
  2. There has been ample discussion on replacing arrow() with a more general vector() method Vector #22435
  3. We have the recommendation to ax.annotate() with an empty text.

I feel (3.) is quite complicated if you just want to draw an arrow between two points. I therefore suggest to add a method ax.fancyarrow() to replace (3.). It's basically a wrapper for creating a FancyArrowPatch

def fancyarrow(self, ...):
    patch = FancyArrowPatch(...)
    self.add_patch(patch)
    return patch

Rationale:

  • The FancyArrowPatch API is capable and clear.
  • a wrapper function is justified since user code typically does not explicitly create and add Artists
  • The scope is reasonably different from what is discussed for vector() so that it does not overlap with a potential introduction of vector() later
  • the name fancyarrow is a bit ... fancy, but we cannot use arrow and since this returns a FancyArrowPatch, fancyarrow seems the best viable name.

Activity

  1. JosephBARBIERDARNAL commented on Mar 30, 2025

    @JosephBARBIERDARNAL
    Contributor

    Hi there,

    FYI I've created a package dedicated to this purpose: https://github.com/JosephBARBIERDARNAL/drawarrow

    You can find the most relevant function doc here: https://josephbarbierdarnal.github.io/drawarrow/reference/ax_arrow/

    In my opinion, as a heavy matplotlib user and someone who uses arrows a lot, I really feel that my API is close to what I would expect in "native" matplotlib (fig_arrow() for an arrow on a Figure and ax_arrow() for an arrow on an Axes).

    I'm not sure my code will help you in practice but TLDR: I've created the FancyArrowPatch wrapper and I have the impression that the user can make any type of arrow he wants intuitively.

    And since you're planning to work on this, here are a few things I particularly like about my package that I think would be nice to have by default in matplotlib, which might give you some ideas:

    • the double_headed argument: a boolean that defines whether the arrow should have 2 heads or not
    • the fill_head argument: a boolean that defines the head style (-> or -|>)
    • the ability to add an inflection point by specifying its position rather than the angles -> it's far too difficult to think about angles when you want the inflection point to be in a specific place. In practice, my package doesn't correct the problem perfectly because the position sometimes remains odd, I'm not sure why (see this issue if you're interested: inflection point are not well located y-sunflower/drawarrow#15).

    Please let me know if you have any question, I'm particularly interested in this topic.

  2. story645 commented on Mar 30, 2025

    @story645
    Member

    Something like 90% of the arrows I draw using annotation are using the relative positioning to object support from annotation, and those transforms are currently computed in annotation rather than FancyArrowPatch. There's a proposal to factor out the annotation coordinate handling and I think it'd have to be implemented for the fancyarrow function to be really useful : #22223 (comment)

    For simple data coordinate arrows, I think the vector proposal should be revived and we should come to some hard decisions there.

    the ability to add an inflection point by specifying its position rather than the angles

    I also really wanted to be able to set the midpoint on the bezier and the coordinate system 'cause this is messy when spanning 3 coordinate systems.

  3. timhoffm commented on Mar 31, 2025

    @timhoffm
    MemberAuthor

    @JosephBARBIERDARNAL thanks for the input. With fancyarrow() I plan to stick more closely to the FancyArrowPatch API than you do in drawarrow. It should be a lightweight wrapper without additional logic. I also want to support all arrowstyles and connectionsstyles, so I think I cannot make the opinionated choices you've done to provide the high-level interface double_headed/fill_head/inflection_point.

    For arrowstyle, I believe "->" / "-|>" / "<->" is as good as double_headed/fill_head. For the inflection point, one would have to see whether one can have an alternative parametrization of the "angle3" connection style. But that would have to be solved in that class.

    @story645 while reasonable, I believe we can add Artist support later. I believe this is already an improvement even if we don't support artists as reference points right away:

  4. story645 commented on Mar 31, 2025

    @story645
    Member

    which is an awkward workaround - and arrow() currently also only supports coordinates.

    That's sorta my point though - what's the advantage of adding a a fancyarrow method vs sorting out the vector API. Down the line, do we actually expect/want the examples to differentiate?

  5. timhoffm commented on Mar 31, 2025

    @timhoffm
    MemberAuthor

    I believe they are separate use cases:

    • fancyarrow(): "a directed visual connection as decoration" - only a single arrow, very detailed configuration (arrowstyle, connectionstyle), possibly using Artists as reference points
    • vector(): "a directed connection as data" - a collection of arrows - possibly many arrows with one call, possibly different colors etc, reference points are data coordinates (not Artists), only straight connections (possible, but to be discussed: participation in color cycle, possibility to add this to legend)
  6. story645 commented on Mar 31, 2025

    @story645
    Member

    fancyarrow(): a directed visual connection as decoration"

    I think of ConnectionPatch as the "connect things" artist, and I think it's already got the API for comprehensive coordinate support. (xref #26440)

  7. timhoffm commented on Mar 31, 2025

    @timhoffm
    MemberAuthor

    Ok so what is your proposal? Should fancyarrow() (or other name t.b.d.) rather return a ConnectionPatch?

  8. story645 commented on Mar 31, 2025

    @story645
    Member

    Should fancyarrow() (or other name t.b.d.) rather return a ConnectionPatch?

    I'm not sure it should exist, but if it does then yeah I think wrapping ConnectionPatch makes more sense if the semantics are connection oriented. Make fig.connect and ax.connect and that means all the examples of manually adding the artist can be swapped out w/ function calls.

  9. timhoffm commented on Apr 1, 2025

    @timhoffm
    MemberAuthor

    Ok I yield for now. My thought was basically "let's make FancyArrowPath available as a function, so that we don't have to recommend the awkward use annotate() without text to create an arrow".

    That in itself is reasonable. The question arises whether additional functionality should be included. #22435 suggests a bunch of these to better support data-like arrows. OTOH we have Artist references points (as defined in annotate()) and reference points in different coordinates and Axes as defined in ConnectionPatch.

    I still think the distinction between data-like and annotation/decoration-like is reasonable and we should have two separate functions on that because we cannot squeeze all the above "arrow" extensions into one API. I consider vector() the potential data-like arrow API. Whereas this here is intended for the decoration-like arrow API.

    However, I don't have the capacity to think about the extensions in that direction right now. It might be that connect() is the better approach rather than fancyarrow(), but it doesn't feel good. "arrow" is nicely concrete whereas "connect" is more abstract due to its generality. I'm also unsure whether ConnectionPatch and FancyArrowPatch are really separate concepts or whether they should actually be the same class.

  10. story645 commented on Apr 1, 2025

    @story645
    Member

    I'm also unsure whether ConnectionPatch and FancyArrowPatch are really separate concepts or whether they should actually be the same class

    ConnectionPatch is basically a wrapper on FancyArrowPatch that handles the coordinate transforms. Which, probably would be best to keep separate if FancyArrowPatch is also gonna act as the base artist for vector (I'm not sure if Arrow or FancyArrow was decided on as the base).

    class ConnectionPatch(FancyArrowPatch):

    I still think the distinction between data-like and annotation/decoration-like is reasonable and we should have two separate functions on that because we cannot squeeze all the above "arrow" extensions into one API

    I don't totally disagree, I just think ax.annotate("", ...) is not the worst API and there's a lot of functionality there (ax.connect would be ax.annotate w/o the text options) and that having a clean definition for the data arrow API will help in defining the functionality for the annotation arrows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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