2

I have a scatterplot with multiple points, sometimes overlapping, so there is a hover box annotation on each point (used the solution here to create this). To deal with the multiple points and sometimes long descriptions, there is a line break in the annotation. The problem is with multiple lines, the annotation box builds up instead of down making it go outside the axis like seen below. Is there a way to have the text start at the line right below the axis then build down into the plot?

Relevant code:

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)

x = np.random.rand(15)
y = np.random.rand(15)
names = np.array(list("ABCDEFGHIJKLMNO"))
c = np.random.randint(1,5,size=15)

norm = plt.Normalize(1,4)
cmap = plt.cm.RdYlGn

fig,ax = plt.subplots()
sc = plt.scatter(x,y,c=c, s=100, cmap=cmap, norm=norm)

annot = ax.annotate("", xy=(0.05, 0.95), xycoords='axes fraction',
                    bbox=dict(boxstyle="round", fc="w"),
                    zorder=1000)
annot.set_visible(False)

def update_annot(ind):

    pos = sc.get_offsets()[ind["ind"][0]]
    annot.xy = pos
    text = "{}".format("\n".join([f"{n}: {names[n]}" for n in ind["ind"]]))
    annot.set_text(text)
    annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]])))
    annot.get_bbox_patch().set_alpha(0.4)


def hover(event):
    vis = annot.get_visible()
    if event.inaxes == ax:
        cont, ind = sc.contains(event)
        if cont:
            update_annot(ind)
            annot.set_visible(True)
            fig.canvas.draw_idle()
        else:
            if vis:
                annot.set_visible(False)
                fig.canvas.draw_idle()

fig.canvas.mpl_connect("motion_notify_event", hover)

plt.show()

How it looks with one line text in annotation

enter image description here

How it looks with two line text in annotation (hover point overlaps two points). Would ideally have the 0: A where 8: I is, then 8: I below it

enter image description here

TomNash
  • 3,147
  • 2
  • 21
  • 57

1 Answers1

3

I think the code is a bit overkill to ask the simple question how to align a text or annotation.

That is done via the verticalalignment or va argument (or the horizontalalignment / ha). Set it to "top" to align the text from the top.

Use the xytext and textcoords arguments (just as in the linked code) to give the text some offset in units of points (i.e. independent on how large the figure is).

import matplotlib.pyplot as plt

fig,ax = plt.subplots()

annot = ax.annotate("Three\nLine\nText", xy=(0, 1), xycoords='axes fraction',
                    xytext=(5,-5), textcoords="offset points",
                    bbox=dict(boxstyle="round", fc="w"), va="top")

plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712