FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Easier vectorized line segments and arrows · Issue #19705 · matplotlib/matplotlib · GitHub

Repository navigation

Easier vectorized line segments and arrows  #19705

Description

Per this discussion on Twitter, the matplotlib account suggested we create a feature request for easier arrows. In thinking about how this could be addressed, I think it ties in with another frustration I have with matplotlib: drawing lots of line segments and both could be tackled with the same new function.

Problem

Visually, arrows can be used for a lot of different purposes. plt.annotate() is great for pointing to a feature and labeling it with text, but is not a solid solution for data-to-data pointing because the arrow tips don't fall in the center of the points, e.g.see this example.

plt.arrow() seems like the right solution. However, for general purposes, it has two main shortcomings. First, the scaling of the arrowhead is not automatic, so for certain scales of data the arrowheads look bizarre by default, and the arguments are not vectorized.

# it looks fine if we don't divide by 100, but if we do, the scaling is way off
x0 = np.random.uniform(0, 1, 5) / 100
y0 = np.repeat(0, 5)

dx = np.random.normal(0, 0.1, 5) / 100
dy = np.repeat(1, 5)

for i in range(5):
    plt.arrow(x0[i], y0[i], dx[i], dy[i])

Second, as the example shows, the arguments x, y, dx, and dy are not vectorized and explicit looping is required. This relates to another section of the API that's undesirably complex in my view: to draw lots of line segments, one needs to use a LineCollection and use ax.add_collection().

Proposed Solution

Matplotlib could solve both of these issues with a new ax.segments() method. This would take xto, xfrom, yto, and yfrom arguments, and ideally a vector of colors or labels too, and then plot the line segments. Then, one could pass an arrowstyle argument too, which would turn this into a collection of line segments with arrow ends. Also, I think in general it would be easier if the API used to and from coordinates rather than from and dx, dy coordinates. Finally, the default should be that the arrows are lines, rather than filled rectangles as with plt.arrow(), which create visually odd thin-width arrow bodies depending on the angle is steep between the two and from points.

Activity

  1. timhoffm commented on Mar 15, 2021

    Member

    I agree with you that plt.arrow() has a lot of shortcomings.

    plt.annotate() is not a solid solution for data-to-data pointing because the arrow tips don't fall in the center of the points.

    That could probably be made configurable.

    For vectorized arrows we have to solve two issues:

    1. Which Artist should represent these? annotate() uses FancyArrowPatch internally, which handles all the arrow style. One option would be to use LineCollection and extend it for arrow functionality. Similar for PathCollection, which would be a little more general. Alternatively one would need a completely new Collection type, e.g. ArrowCollection.
    2. How should the API look like? In the simplest case, we leave it with ax.add_collection(LineCollection(..., arrowstyle=...)). Having explicit functions like ax.segments() is convenient, but more work to develop and maintain. We don't do this for all functionality. Using *to and *from only makes sense for direct connections. That does not work if one wants arrow heads on more general LineCollection and PathCollection.

    Unfortunately, as simple as it sounds, doing this right needs careful consideration and a non-negligable amount of work.

  2. jklymak commented on Mar 15, 2021

    Member

    Indeed arrows are non-trivial, and there are lots of attempts already in the library. I'm not sure we should add another until we are sure we know we don't already have something that works. I think its well-understood that arrow is broken, and maybe needs a new backend.

    I'm not entirely clear what is being asked above, but it sounds like what FancyArrowPatch is for? https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.FancyArrowPatch.html#matplotlib.patches.FancyArrowPatch

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.patches as mpatches
    
    np.random.seed(19680808)
    x0 = np.random.uniform(0, 1, 5) 
    y0 = np.repeat(0, 5)
    
    x1 = np.random.uniform(0, 1, 5) 
    y1 = np.repeat(1, 5)
    
    fig, ax = plt.subplots()
    
    ax.plot(x0, y0, 'o', markersize=2)
    ax.plot(x1, y1, 'o', markersize=2)
    
    arrowstyle = mpatches.ArrowStyle("<->", head_length=6, head_width=6)
    for j in range(5):
        arrow = mpatches.FancyArrowPatch((x0[j], y0[j]), (x1[j], y1[j]),
                                         arrowstyle=arrowstyle, zorder=10)
        ax.add_patch(arrow)

    There is also quiver which takes vectors or matrices. So, for sure, the above uses a loop, but I'm not sure that is worth adding a new Collection for. How many arrows do you need on a plot?

  3. TAdeJong commented on Apr 17, 2021

    Seconding quiver() as a vector of arrows plotter, it does almost everything what @vsbuffalo listed, (if I understand everything correctly):

    x0 = np.random.uniform(0, 1, 5) / 100
    y0 = np.repeat(0, 5)
    
    dx = np.random.normal(0, 0.1, 5) / 100
    dy = np.repeat(1, 5)
    
    plt.quiver(x0, y0, dx, dy, angles='xy', scale_units='xy', scale=1)
    plt.ylim(-0.1,1.1)

    The need of the manual limit scaling is unfortunate, but even plain linesegments can be achieved by setting headlength and headaxislength:

    plt.quiver(x0, y0, dx, dy, angles='xy', scale_units='xy', scale=1, 
               headlength=0., headaxislength=0)
    plt.ylim(-0.1,1.1)

    However, both these applications of quiver do not seem to be documented that well (I had to search quiet a bit on how to disable the arrowheads when I needed it for my own work).
    An example showcasing these usages and "see also's" in the documentation of plt.arrow and plt.annotate might already go a long way in solving this?

  4. kmdalton commented on Feb 10, 2022

    For plotting line segments, can't you just use plt.plot? For example,

    x1x2 = np.random.random((2, 100))
    y1y2 = np.random.random((2, 100))
    plt.plot(x1x2, y1y2)

    Maybe there is some nuance I'm missing though.

  5. github-actions commented on Nov 29, 2023

    This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

  6. added
    status: closed as inactiveIssues closed by the "Stale" Github Action. Please comment on any you think should still be open.
    on Dec 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    New featurestatus: closed as inactiveIssues closed by the "Stale" Github Action. Please comment on any you think should still be open.status: inactiveMarked by the “Stale” Github Actiontopic: arrow

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL