8000 Arrow in polar plot broken · Issue #5344 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Arrow in polar plot broken #5344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
michaelaye opened this issue Oct 29, 2015 · 13 comments
Closed

Arrow in polar plot broken #5344

michaelaye opened this issue Oct 29, 2015 · 13 comments

Comments

@michaelaye
Copy link

It works fine only at angle 0, otherwise the arrow head is corrupted.
Using this code:

fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.arrow(0, 0, 3.14/2, 0.8, linewidth=3, width=0.005 )
ax.yaxis.set_ticklabels([])
ax.tick_params(axis='both', which='major', labelsize=20)

Here's how it looks:
unknown

At zero for 3rd parameter of above arrow call, it looks okay, but only there:
unknown-1

Version: master on OSX, in the notebook, seen both with inline and nbagg backend.

@mdboom
Copy link
Member
mdboom commented Oct 29, 2015

Good catch, and indeed a problem. The fix may be tricky, however...

@QuLogic
Copy link
Member
QuLogic commented Oct 29, 2015

I concur; FancyArrow essentially does "nudge a bit left/right" in data space to produce the arrow head. Unfortunately, on a polar plot, "a bit left/right" means a rotation. This is somewhat noted in the docstring with a possible workaround:

The resulting arrow is affected by the axes aspect ratio and limits.
This may produce an arrow whose head is not square with its stem. To
create an arrow whose head is square with its stem, use
:meth:annotate for example::

           ax.annotate("", xy=(0.5, 0.5), xytext=(0, 0),
               arrowprops=dict(arrowstyle="->"))

I can confirm that the ax.annotate method does work on polar plots, though I'm not sure whether it provides exactly the same styling options as ax.arrow would.

@QuLogic
Copy link
Member
QuLogic commented Oct 29, 2015

Another workaround is to create the arrow at 0-angle, since you've found that it works, then provide a transform to the location you want:

fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.arrow(0, 0, 0, 0.8, linewidth=3, width=0.005,
         # Remember that you're working in (theta,r) space before the data transform.
         transform=mtransforms.Affine2D().translate(np.pi/2, 0) + ax.transData)
ax.yaxis.set_ticklabels([])
ax.tick_params(axis='both', which='major', labelsize=20)

but this becomes a bit troublesome if you want to place it in an arbitrary location.

@michaelaye
Copy link
Author

Nice work-arounds, thanks! For me though, a new requirement popped up, in having to draw the arrow inside an ellipse instead of a circle, so I guess I will have to go to free-drawing both elements, and then I might as well do it in a normal plot and switch the axes off.

@pizzathief
Copy link
Contributor

If you replace the code in patches.py

                elif shape == 'full':
                    # The half-arrows contain the midpoint of the stem,
                    # which we can omit from the full arrow. Including it
                    # twice caused a problem with xpdf.
                    coords = np.concatenate([left_half_arrow[:-1], 
                                             right_half_arrow[-2::-1]])
                else:
                    raise ValueError("Got unknown shape: %s" % shape)

with

                    coords = np.concatenate([left_half_arrow[:-2],
                                             right_half_arrow[-2::-1]])

Then the arrows have both left and right parts drawn fully, they're just pointing in the same wrong directions
foo

8000

@michaelaye
Copy link
Author

The wrongness of the arrow heads are not the same for the different angles. It looks like the error on its directions is 45 degrees at 45 and ending up at 90 degrees after going a full turn of 360 degrees around the circle. How fascinating. There seems to be some kind of function on the problem. ;)

@pizzathief
Copy link
Contributor

Its not the list of points generated by ax.arrow() thats the root of the problem, I've tried a few of the lines, they seem to generate passable arrows (when I stick them in gnuplot and see what happens). Must be just the polar plot itself thats rotating points too much.

@pizzathief
Copy link
Contributor

polartransform.transform_non_affine() is converting this:

xy

into this:

xy2

@QuLogic
Copy link
Member
QuLogic commented Nov 1, 2015

Yes, that's expected. You are essentially plotting data-space and axes-space (modulo the affine part of the transform) as Cartesian, but they aren't really the same coordinate system. For a polar plot, the data space is (theta,r), so the first plot really shouldn't look anything like the "correct" arrow you see. An addition to the theta component (or "x" in the first plot) results in greater rotation of the arrow head, which is clearly evident in the second plot.

The problem with arrow is that it calculates the sides and tip of the arrow as offsets in data-space assuming it is Cartesian. That produces issues not only in polar plots, but probably any other non-standard projection, like anything from Cartopy or Basemap.

@mdboom
Copy link
Member
mdboom commented Nov 2, 2015

@QuLogic: I agree. I think we need a new version of arrow that calculates the tip entirely in Cartesian regardless of the underlying projection (though it probably needs to get its orientation from the underlying projection).

@pizzathief
Copy link
Contributor

well, here's the patch to fix the arrowhead, if you want it. CR #5392

@afvincent
Copy link
Contributor

Just a status update.

Linux, Python 2.7, on master (matplotlib.__version__ is "2.0.0.post3567+g1d45c08").

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(figsize=(6.4, 6.4))

for sub, lw, w in [(221, 3, 0.005), (222, 1, 0.005),
                   (223, 3, 0.05), (224, 1, 0.05)]:
    ax = fig.add_subplot(sub, polar=True)
    ax.set_yticklabels([])
    ax.set_title("linewidth={lw}, width={w}".format(lw=lw, w=w))

    for rad in np.pi*np.arange(0.0, 2.0, 0.25):
        ax.arrow(0, 0, rad, 0.8, linewidth=lw,  width=w)

plt.tight_layout()

produces
current_state_on_master.pdf:

  • the directions of the arrow heads are still wrong, except for a null angle;
  • the origins of the arrows are not exactly (0, 0). It is especially noticeable in the case of a small line width (see for example with linewidth=1 in the picture above).

@anntzer
Copy link
Contributor
anntzer commented Jun 29, 2021

I'm going to close this as "it's basically behaving as expected (for an arrow drawn fully in data space); you should anyways use Axes.annotate" (and see the arrow_guide.py example as well).
We could consider deprecating Axes.arrow, but that's tracked by #20387.

As always feel free to reopen if you disagree.

@anntzer anntzer closed this as completed Jun 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants
0