3

it is driving me nuts that the annotate arrow in the screenshot below is not straight. What am I missing? I'm suspecting it has to do with the way I specify the xy and xytext position, yet they both have the same y.

arrow example

This is how I'm specifying the annotate:

plt.annotate('Head Motion Cutoff',
             xy=[data.shape[0], motion_cutoff],
             xytext=[data.shape[0]+12, motion_cutoff],
             fontsize=14,
             arrowprops=dict(fc='#A9A9A9',
                             ec='#A9A9A9',
                             arrowstyle='simple',
                             shrinkA=4,
                             shrinkB=4))
1

2 Answers 2

4

The problem is that the arrow is by default connected to the center of the text.

You may however change the position to which the arrow connects using the relpos argument of the arrowproperties.

plt.annotate(...,  arrowprops=dict(..., relpos=(0, 0)) )

The relative position is specified in coordinates of the text's bounding box.

enter image description here

For bottom aligned text, one would choose relpos=(0,0).
For center aligned text, one would choose relpos=(0,0.5).
For top aligned text, one would choose relpos=(0,1).
A problem is, if the text does not contain any character (like the "g" here), which goes down to the bottom, then a relpos=(0,0.2) might make sense.

Example:

import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, dpi=200)
ax.grid(color='k', alpha=0.5, ls=':')

plt.annotate('Head Motion Cutoff',
         xy=[0.1, 0.8],
         xytext=[60, 0], 
         verticalalignment = "baseline",
         arrowprops=dict(arrowstyle='simple',
                         fc='#A9A9A9', ec='#A9A9A9',
                         shrinkA=4, shrinkB=4,
                         relpos=(0, 0.2)),             
         fontsize=11, 
         textcoords="offset points")



ap = dict(fc='#A9A9A9', ec='#A9A9A9', arrowstyle='simple',
          shrinkA=4, shrinkB=4)

fontsize = 11
aligns = ["bottom", "top", "center"]
positions =  [dict(relpos=(0, 0.)),dict(relpos=(0, 1)),dict(relpos=(0, 0.5))]
kw = dict(fontsize=fontsize, textcoords="offset points")


for i, (align,pos) in enumerate(zip(aligns,positions)):
    ap.update(pos)
    kw.update(dict(arrowprops=ap)) 
    plt.annotate('Head Motion Cutoff (va={})'.format(align),
             xy=[0.1, i*0.2+0.2],
             xytext=[60, 0],
             va=align, ha="left", **kw)

plt.show()

enter image description here

Sign up to request clarification or add additional context in comments.

4 Comments

Great thanks! Accepted this as the answer for showing the combined effects of 'relpos' and 'va'. Thanks again, my OCD is now slightly alleviated :)
why do you have horizontalalignment = "bottom", in the first annotate? I presume that's a typo?
OK, but that's not actually doing anything. Try changing that to top and you'll see it makes no difference. horizontalalignment can take [ 'center' | 'right' | 'left' ] as its value, not 'bottom'. Are you sure you didn't mean verticalalignment there?
Oh, now I see what you mean. Sorry for that. I updated the answer.
4

It's due to the vertical alignment of the text (in this case 'Head Motion Cutoff'). The arrow is aligned to the center of the text, but by default, the baseline of the text will be set to the coordinates you give in xytext.

The simplest fix to this is to set the verticalalignment (or va) of the text to 'center', so everything lines up nicely.

For example:

import matplotlib.pyplot as plt
fig, ax = plt.subplots(1)
ax.grid(color='k', alpha=0.5, ls=':')

plt.annotate('Head Motion Cutoff (va=bottom)',
             xy=[0.1, 0.2],
             xytext=[0.4, 0.2],
             fontsize=14,
             arrowprops=dict(fc='#A9A9A9',
                             ec='#A9A9A9',
                             arrowstyle='simple',
                             shrinkA=4,
                             shrinkB=4),
             va='bottom')

plt.annotate('Head Motion Cutoff (va=baseline)',
             xy=[0.1, 0.4],
             xytext=[0.4, 0.4],
             fontsize=14,
             arrowprops=dict(fc='#A9A9A9',
                             ec='#A9A9A9',
                             arrowstyle='simple',
                             shrinkA=4,
                             shrinkB=4),
             va='baseline')

plt.annotate('Head Motion Cutoff (va=top)',
             xy=[0.1, 0.6],
             xytext=[0.4, 0.6],
             fontsize=14,
             arrowprops=dict(fc='#A9A9A9',
                             ec='#A9A9A9',
                             arrowstyle='simple',
                             shrinkA=4,
                             shrinkB=4),
             va='top')

plt.annotate('Head Motion Cutoff (va=center)',
             xy=[0.1, 0.8],
             xytext=[0.4, 0.8],
             fontsize=14,
             arrowprops=dict(fc='#A9A9A9',
                             ec='#A9A9A9',
                             arrowstyle='simple',
                             shrinkA=4,
                             shrinkB=4),
             va='center')

plt.show()

enter image description here

1 Comment

excellent, thanks for pointing me to the 'va' option. Very useful! I would like to have accepted your answer too, but I can only accept one :(. Accepted the other one for bringing 'relpos' in the mix too and showing that I could have chosen another number than 0, 0.5 or 1 (go figure :D)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.