14

I want to use single annotation text to annotate several data points with several arrows. I made a simple workaround:

ax = plt.gca()
ax.plot([1,2,3,4],[1,4,2,6])
an1 = ax.annotate('Test',
  xy=(2,4), xycoords='data',
  xytext=(30,-80), textcoords='offset points',
  arrowprops=dict(arrowstyle="-|>",
                  connectionstyle="arc3,rad=0.2",
                  fc="w"))
an2 = ax.annotate('Test',
  xy=(3,2), xycoords='data',
  xytext=(0,0), textcoords=an1,
  arrowprops=dict(arrowstyle="-|>",
                  connectionstyle="arc3,rad=0.2",
                  fc="w"))
plt.show()

Producing following result: enter image description here

But I don't really like this solution because it is... well, an ugly dirty hack.

Besides that, it affects the appearance of annotation (mainly if using semi-transparent bboxes etc).

So, if anyone got an actual solution or at least an idea how to implement it, please share.

2
  • It is solved here: stackoverflow.com/questions/17414010/… Commented Nov 13, 2021 at 20:18
  • That's exactly the "solution" that I used in my question. It affects the visual of the text because it dumps the same text in the same place. You can notice it the most if you use semitransparent elements there. Commented Nov 14, 2021 at 19:24

2 Answers 2

18

I guess the proper solution will require too much effort - subclassing _AnnotateBase and adding support for multiple arrows all by yourself. But I managed to eliminate that issue with second annotate affecting visual appearance simply by adding alpha=0.0. So the updated solution here if no one will provide anything better:

def my_annotate(ax, s, xy_arr=[], *args, **kwargs):
  ans = []
  an = ax.annotate(s, xy_arr[0], *args, **kwargs)
  ans.append(an)
  d = {}
  try:
    d['xycoords'] = kwargs['xycoords']
  except KeyError:
    pass
  try:
    d['arrowprops'] = kwargs['arrowprops']
  except KeyError:
    pass
  for xy in xy_arr[1:]:
    an = ax.annotate(s, xy, alpha=0.0, xytext=(0,0), textcoords=an, **d)
    ans.append(an)
  return ans

ax = plt.gca()
ax.plot([1,2,3,4],[1,4,2,6])
my_annotate(ax,
            'Test',
            xy_arr=[(2,4), (3,2), (4,6)], xycoords='data',
            xytext=(30, -80), textcoords='offset points',
            bbox=dict(boxstyle='round,pad=0.2', fc='yellow', alpha=0.3),
            arrowprops=dict(arrowstyle="-|>",
                            connectionstyle="arc3,rad=0.2",
                            fc="w"))
plt.show()

Resulting picture: enter image description here

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

2 Comments

You should accept this answer (you can answer your own questions, its okay).
I know, but stackoverflow doesn't let me do it :) I need to wait 2 days or something.
2

Personally, I would use set axes fraction coordinates to guarantee the placement of the text label, then make all but one label visible by playing with the color keyword argument.

ax = plt.gca()
ax.plot([1,2,3,4],[1,4,2,6])
label_frac_x = 0.35
label_frac_y = 0.2

#label first point
ax.annotate('Test', 
  xy=(2,4), xycoords='data', color='white',
  xytext=(label_frac_x,label_frac_y), textcoords='axes fraction',
  arrowprops=dict(arrowstyle="-|>",
                  connectionstyle="arc3,rad=0.2",
                  fc="w"))

#label second point    
ax.annotate('Test', 
      xy=(3,2), xycoords='data', color='black',
      xytext=(label_frac_x, label_frac_y), textcoords='axes fraction',
      arrowprops=dict(arrowstyle="-|>",
                      connectionstyle="arc3,rad=0.2",
                      fc="w"))
plt.show()

View Example Plot

Comments

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.