2

Consider the following simple example:

import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot()
ax.plot([0],[0])
ax.grid()
ax.set_xlim([0,10])
ax.set_ylim([0,10])
ax.annotate("", (2, 1), (4, 1), arrowprops={'arrowstyle':'<->'})
plt.show()

So, with the ax.annotate, I want to draw an arrow from point (x=2, y=1) to point (x=4, y=1); however the output is this:

matplotlib plot

As visible on the screenshot, the arrowheads do not come to exactly the endpoints (x=2, y=1) and (x=4, y=1) - but instead, there is a small amount of whitespace "padding" or "margin".

How can I have the arrow endpoints (the tips of the arrowheads) to align exactly with the stated endpoints? To make it explicit, I tried editing the image above, and manually drawing the arrowheads (in read) where I'd want them to be:

arrowheads


Thanks to comment by @JodyKlymak, I looked into https://matplotlib.org/stable/tutorials/text/annotations.html - the only thing that I found obviously related to my problem here was the "shrink" parameter, however, this:

import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot()
ax.plot([0],[0])
ax.grid()
ax.set_xlim([0,10])
ax.set_ylim([0,10])
ax.annotate("", (2, 1), (4, 1), arrowprops={'arrowstyle':'<->', 'shrink': 0})
plt.show()

... fails with:

AttributeError: 'FancyArrowPatch' object has no property 'shrink'

I found finally this: Using arrowstyle causes "Unknown property shrink" · Issue #3697 · matplotlib/matplotlib:

Ah, the problem is that if you use the simple arrow style which, it turns out, completely changes the code path that is taken. If 'arrowstyle' is in the dictionary then the arrowprops dictionary is used to create a FancyArrow patch. If arrowstyle is not in arrowprops than the dictionary is used to create a YAArrow object.

OK, so trying this:

import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot()
ax.plot([0],[0])
ax.grid()
ax.set_xlim([0,10])
ax.set_ylim([0,10])
ax.annotate("", (2, 1), (4, 1), arrowprops={'shrink': 0})
plt.show()

... and this does indeed pass - however it draws a completely different, "leftwards" arrow (instead of a left-right arrow):

matplotlib leftward arrow

So, let me reformulate - is it possible to get "correct" full-length arrow, with the default left-right arrow from ax.annotate (the one that looks just like a line, and not as a thicker filled surface)?

sdbbs
  • 4,270
  • 5
  • 32
  • 87
  • 3
    This is explicitly discussed in the docs: https://matplotlib.org/stable/tutorials/text/annotations.html – Jody Klymak Dec 06 '21 at 10:06
  • Thanks @JodyKlymak - I just looked through that page, but I don't see where it is explicitly discussed; the only think that I could parse is that there is a "shrink" argument, but when I try it in the code above, `ax.annotate("", (2, 1), (4, 1), arrowprops={'arrowstyle':'<->', 'shrink': 0})`, it fails with "AttributeError: 'FancyArrowPatch' object has no property 'shrink'". – sdbbs Dec 06 '21 at 10:23
  • 2
    Did you check https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.FancyArrowPatch.html ? There, the parameter is called `shrinkA` and `shrinkB`. Probably to control start and end independently. – JohanC Dec 06 '21 at 10:52
  • Thanks @JohanC - indeed, `shrinkA` and `shrinkB` work. – sdbbs Dec 06 '21 at 10:58

2 Answers2

2

Ok, I finally got it; the piece of information I was missing from https://matplotlib.org/stable/tutorials/text/annotations.html, is that there are several classes that can be used for arrows; and I guess that the definitive list would be the list of Matplotlib patch classes:

https://matplotlib.org/stable/api/patches_api.html

So, we have:

  • matplotlib.patches.Arrow - "An arrow patch. Draws an arrow from (x, y) to (x + dx, y + dy). The width of the arrow is scaled by width."

Note that the doc text for Arrow, never explicitly states, this this is "one-way" arrow - either "leftwards" or "rightwards" arrow. However, that is hinted in a comment at https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Arrow.html :

See also

FancyArrow - Patch that allows independent control of the head and tail properties.

Well, I guess this is what I'd need - although, I'd say the documentation is inconsistent here, because https://matplotlib.org/stable/api/patches_api.html states:

  • Arrow(x, y, dx, dy[, width]) - An arrow patch.
  • FancyArrow(x, y, dx, dy[, width, ...]) - Like Arrow, but lets you set head width and head height independently.
  • FancyArrowPatch([posA, posB, path, ...]) - A fancy arrow patch.

So, is FancyArrow "Patch that allows independent control of the head and tail properties", or "Like Arrow, but lets you set head width and head height independently"?

Thankfully, I did not have to determine an answer to that, because I finally realized that FancyArrowPatch is the class that allows independent control of the head and tail properties - because it has shrinkA and shrinkB properties; and indeed this works:

import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot()
ax.plot([0],[0])
ax.grid()
ax.set_xlim([0,10])
ax.set_ylim([0,10])
ax.annotate("", (2, 1), (4, 1), arrowprops={'arrowstyle':'<->', 'shrinkA': 0, 'shrinkB': 0})
plt.show()

... and indeed - finally - this generates the arrow that I wanted:

Matplotlib leftright arrow

sdbbs
  • 4,270
  • 5
  • 32
  • 87
0

It's not possible to use shrink with 'arrowstyle':'<->' as you mentioned. However, you can use duplicate of ax.annotate("", (2, 1), (4, 1), arrowprops={'shrink': 0}) as in:

ax.annotate("", (2, 1), (4, 1), arrowprops={'width': 0.01,'facecolor':'black','shrink': 0})
ax.annotate("", (4, 1), (2, 1), arrowprops={'width': 0.01,'facecolor':'black','shrink': 0})

which results in: enter image description here

Fatemeh Sangin
  • 558
  • 1
  • 4
  • 19
  • Thanks - that in principle should work - however, I'd rather not draw two arrows, in order to get one arrow. – sdbbs Dec 06 '21 at 11:01