web-dev-qa-db-fra.com

Matplotlib annotations / texte qui se chevauchent

J'essaie d'arrêter le chevauchement du texte d'annotation dans mes graphiques. La méthode suggérée dans la réponse acceptée à annotations se chevauchant Matplotlib semble extrêmement prometteuse, mais concerne les graphiques à barres. J'ai du mal à convertir les méthodes "axes" en ce que je veux faire, et je ne comprends pas comment le texte s'aligne.

import sys
import matplotlib.pyplot as plt


# start new plot
plt.clf()
plt.xlabel("Proportional Euclidean Distance")
plt.ylabel("Percentage Timewindows Attended")
plt.title("Test plot")

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()

for x,y,z in together:
    plt.annotate(str(x), xy=(y, z), size=8)

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)

plt.savefig("test.png")

Des images (si cela fonctionne) peuvent être trouvées ici (ce code):

image1

et ici (plus compliqué):

image2

40
homebrand

Je voulais juste poster ici une autre solution, une petite bibliothèque que j'ai écrite pour implémenter ce genre de choses: https://github.com/Phlya/adjustText Un exemple du processus peut être vu ici: - enter image description here

Voici l'exemple d'image:

import matplotlib.pyplot as plt
from adjustText import adjust_text
import numpy as np
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)
texts = []
for x, y, s in Zip(eucs, covers, text):
    texts.append(plt.text(x, y, s))

plt.xlabel("Proportional Euclidean Distance")
plt.ylabel("Percentage Timewindows Attended")
plt.title("Test plot")
adjust_text(texts, only_move='y', arrowprops=dict(arrowstyle="->", color='r', lw=0.5))
plt.show()

enter image description here

Si vous voulez une silhouette parfaite, vous pouvez jouer un peu. Tout d'abord, faisons également en sorte que le texte repousse les lignes - pour cela, nous créons simplement beaucoup de points virtuels le long d'eux en utilisant scipy.interpolate.interp1d.

Nous voulons éviter de déplacer les étiquettes le long de l'axe des x, car pourquoi ne pas le faire à des fins d'illustration. Pour cela nous utilisons le paramètre only_move={'points':'y', 'text':'y'}. Si nous voulons les déplacer le long de l'axe x uniquement dans le cas où ils se chevauchent avec du texte, utilisez move_only={'points':'y', 'text':'xy'}. Toujours au début, la fonction choisit l'alignement optimal des textes par rapport à leurs points d'origine, nous voulons donc que cela se produise également le long de l'axe y, donc autoalign='y'. Nous réduisons également la force de répulsion des points pour éviter que le texte ne vole trop loin en raison de notre évitement artificiel des lignes. Tous ensemble:

from scipy import interpolate
p1 = plt.plot(eucs,covers,color="black", alpha=0.5)
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)    

plt.xlabel("Proportional Euclidean Distance")
plt.ylabel("Percentage Timewindows Attended")
plt.title("Test plot")
adjust_text(texts, x=x, y=y, autoalign='y',
            only_move={'points':'y', 'text':'y'}, force_points=0.15,
            arrowprops=dict(arrowstyle="->", color='r', lw=0.5))
plt.show()

enter image description here

90
Phlya

Avec beaucoup de tripotage, je l'ai compris. Encore une fois, le mérite de la solution d'origine revient à la réponse pour annotations se chevauchant Matplotlib .

Je ne sais cependant pas comment trouver la largeur et la hauteur exactes du texte. Si quelqu'un le sait, veuillez publier une amélioration (ou ajouter un commentaire avec la méthode).

import sys
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

def get_text_positions(text, x_data, y_data, txt_width, txt_height):
    a = Zip(y_data, x_data)
    text_positions = list(y_data)
    for index, (y, x) in enumerate(a):
        local_text_positions = [i for i in a if i[0] > (y - txt_height) 
                            and (abs(i[1] - x) < txt_width * 2) and i != (y,x)]
        if local_text_positions:
            sorted_ltp = sorted(local_text_positions)
            if abs(sorted_ltp[0][0] - y) < txt_height: #True == collision
                differ = np.diff(sorted_ltp, axis=0)
                a[index] = (sorted_ltp[-1][0] + txt_height, a[index][1])
                text_positions[index] = sorted_ltp[-1][0] + txt_height*1.01
                for k, (j, m) in enumerate(differ):
                    #j is the vertical distance between words
                    if j > txt_height * 2: #if True then room to fit a Word in
                        a[index] = (sorted_ltp[k][0] + txt_height, a[index][1])
                        text_positions[index] = sorted_ltp[k][0] + txt_height
                        break
    return text_positions

def text_plotter(text, x_data, y_data, text_positions, txt_width,txt_height):
    for z,x,y,t in Zip(text, x_data, y_data, text_positions):
        plt.annotate(str(z), xy=(x-txt_width/2, t), size=12)
        if y != t:
            plt.arrow(x, t,0,y-t, color='red',alpha=0.3, width=txt_width*0.1, 
                head_width=txt_width, head_length=txt_height*0.5, 
                zorder=0,length_includes_head=True)

# start new plot
plt.clf()
plt.xlabel("Proportional Euclidean Distance")
plt.ylabel("Percentage Timewindows Attended")
plt.title("Test plot")

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)

txt_height = 0.0037*(plt.ylim()[1] - plt.ylim()[0])
txt_width = 0.018*(plt.xlim()[1] - plt.xlim()[0])

text_positions = get_text_positions(text, eucs, covers, txt_width, txt_height)

text_plotter(text, eucs, covers, text_positions, txt_width, txt_height)

plt.savefig("test.png")
plt.show()

Crée http://i.stack.imgur.com/xiTeU.png enter image description here

Le graphique le plus compliqué est maintenant http://i.stack.imgur.com/KJeYW.png , toujours un peu incertain mais bien meilleur! enter image description here

4
homebrand