write matplot inside font bbox python matplotlib annotate

python - matplot - Matplatlib solapando anotaciones/texto



text matplot (2)

Estoy tratando de detener la superposición de texto de anotación en mis gráficos. El método sugerido en la respuesta aceptada a las anotaciones superpuestas de Matplotlib parece extremadamente prometedor, sin embargo, es para gráficos de barras. Tengo problemas para convertir los métodos del "eje" en lo que quiero hacer, y no entiendo cómo se alinea el texto.

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

Las imágenes (si esto funciona) se pueden encontrar here (este código):

y here (más complicado):


Con mucho toquetear, lo descubrí. De nuevo, el crédito para la solución original va a la respuesta para las anotaciones superpuestas de Matplotlib .

No obstante, no sé cómo encontrar el ancho y el alto exactos del texto. Si alguien sabe, publique una mejora (o agregue un comentario con el método).

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

Crea http://i.stack.imgur.com/xiTeU.png

¡El gráfico más complicado ahora es http://i.stack.imgur.com/KJeYW.png , todavía un poco dudoso pero mucho mejor!


Solo quería publicar aquí otra solución, una pequeña biblioteca que escribí para implementar este tipo de cosas: https://github.com/Phlya/adjustText Aquí se puede ver un ejemplo del proceso:

Aquí está la imagen de ejemplo:

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

Si quieres una figura perfecta, puedes juguetear un poco. Primero, hagamos que el texto rechace las líneas, para eso simplemente creamos muchos puntos virtuales a lo largo de ellas utilizando scipy.interpolate.interp1d.

Queremos evitar mover las etiquetas a lo largo del eje x, porque, bueno, ¿por qué no hacerlo con fines ilustrativos? Para eso usamos el parámetro only_move={''points'':''y'', ''text'':''y''} . Si queremos moverlos a lo largo del eje x solo en el caso de que se solapen con el texto, use move_only={''points'':''y'', ''text'':''xy''} . También al principio, la función elige la alineación óptima de los textos en relación con sus puntos originales, por lo que solo queremos que eso suceda también a lo largo del eje y, por autoalign=''y'' tanto, autoalign=''y'' . También reducimos la fuerza repelente de los puntos para evitar que el texto salga demasiado lejos debido a nuestra evitación artificial de líneas. Todos juntos:

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