0

I'm trying to get the text annotations in this line graph in matplotlib to show up above the line. I'm using the adjustText library to repel text from the line, but as seen in the image, some of the text is below the line. I based my code on the accepted answer here.

How can I get each annotation to appear above the line, connected to its point by a gray line? Some of them already are like this, but others are not.

x = list(df_usa['date'][61:-1].str.slice(5,)) # strings
x_range = np.arange(0, len(x))
y = list(df_usa['growth_factor'][61:-1])

plt.figure(figsize=(20,6))
plt.plot(x, y, color="red", alpha=0.5)
plt.xticks(np.arange(0, len(x), 5))

texts = [plt.text(x_range[i], y[i], round(y[i], 2)) for i in range(len(x))]

f = interpolate.interp1d(x_range, y)
f_x = np.arange(min(x_range), max(x_range), 0.005)
f_y = f(f_x)

# Generate non-overlapping data point labels
adjust_text(texts, x=f_x, y=f_y,
            only_move={'points': 'y', 'text': 'y'},
            force_points=0.15,
            autoalign='y',
            arrowprops=dict(arrowstyle="-", color="gray", lw=0.5))

plt.show()

enter image description here

brienna
  • 1,415
  • 1
  • 18
  • 45

1 Answers1

1

The current approach "prohibits" the points along the curve. An extension could be to also prohibit a band of points below the curve.

As your post doesn't seem to contain test data, here is an adaption of the example of the linked post:

import matplotlib.pyplot as plt
from adjustText import adjust_text
import numpy as np
from scipy import interpolate

together = [(0, 1.0, 0.4), (25, 1.0127692669427917, 0.41), (50, 1.016404709797609, 0.41), (75, 1.1043426359673716, 0.42), (100, 1.1610446924342996, 0.44), (125, 1.1685687930691457, 0.43), (150, 1.3486407784550272, 0.45), (250, 1.4013999168008104, 0.45)]
together.sort()

text = [x for (x,y,z) in together]
eucs = [y for (x,y,z) in together]
covers = [z for (x,y,z) in together]

p1 = plt.plot(eucs,covers,color="black", alpha=0.5)
y0, y1 = plt.ylim()
plt.ylim(y0, y1+.1*(y1-y0))
texts = []
for x, y, s in zip(eucs, covers, text):
    texts.append(plt.text(x, y, s))

f = interpolate.interp1d(eucs, covers)
x = np.arange(min(eucs), max(eucs), 0.0005)
y = f(x)

# optionally show the band of "forbidden" points
# plt.scatter(x=np.tile(x, 5), y=np.concatenate([y-eps for eps in np.linspace(0, 0.01, 5) ]), alpha=.1)

plt.xlabel("Proportional Euclidean Distance")
plt.ylabel("Percentage Timewindows Attended")
plt.title("Test plot")
adjust_text(texts, x=np.tile(x, 5), y=np.concatenate([y-eps for eps in np.linspace(0, 0.01, 5) ]), autoalign='y',
            only_move={'points':'y', 'text':'y'}, force_points=.3,
            arrowprops=dict(arrowstyle="->", color='r', lw=0.5))
plt.show()

resulting plot

JohanC
  • 71,591
  • 8
  • 33
  • 66