2013-09-29 9 views
20

Sto cercando di fermare il testo delle annotazioni che si sovrappongono nei miei grafici. Il metodo suggerito nella risposta accettata a Matplotlib overlapping annotations sembra estremamente promettente, tuttavia è per i grafici a barre. Ho difficoltà a convertire i metodi "asse" su quello che voglio fare, e non capisco come si allinea il testo.Annotazioni/testo sovrapposti matlockli

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

Immagini (se funziona) possono essere trovati here (il codice):

image1

e here (più complicato):

image2

+0

vedi anche http://stackoverflow.com/questions/14938541/how-to-improve-the-label-placement-for-matplotlib-scatter-chart-code- Algoritmo/15859652 # 15859652 – tacaswell

risposta

40

Volevo solo postare qui un'altra soluzione, una piccola biblioteca ho scritto per implementare questo tipo di cose: https://github.com/Phlya/adjustText Un esempio del processo può essere visto qui: enter image description here

Ecco l'immagine di esempio :

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

Se si desidera una figura perfetta, si può smanettare un po. Innanzitutto, facciamo in modo che il testo respinga le linee: per questo creiamo solo un sacco di punti virtuali su di loro usando scipy.interpolate.interp1d.

Vogliamo evitare di spostare le etichette lungo l'asse x, perché, beh, perché non farlo per scopi illustrativi. Per questo usiamo il parametro only_move={'points':'y', 'text':'y'}. Se vogliamo spostarli lungo l'asse x solo nel caso in cui si sovrappongano al testo, utilizzare move_only={'points':'y', 'text':'xy'}. Inoltre, all'inizio la funzione sceglie l'allineamento ottimale dei testi rispetto ai loro punti originali, quindi vogliamo che ciò avvenga anche lungo l'asse y, quindi autoalign='y'. Riduciamo anche la forza repulsiva dai punti per evitare che il testo voli troppo lontano a causa della nostra artificiale evasione delle linee. Tutti insieme:

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

+0

Bel lavoro Phlya! Probabilmente potresti anche aggiungere questa risposta o qualcosa di simile a https://stackoverflow.com/questions/9074996/matplotlib-how-to-annotate-point-on-a-scatter-automatically-placed-arrow – naught101

+0

Grazie, I ' sono contento che ti piaccia! Ci sono un paio di altre domande su SO che sono rilevanti, ma non l'ho visto ... Cercherò di trovare il tempo per scrivere una risposta, ma sentiti libero di farlo tu stesso, se vuoi! – Phlya

+0

questo è davvero fantastico, cambia-gioco – dataflow

3

Con un sacco di giocherellare, l'ho capito. Ancora una volta il credito per la soluzione originale va alla risposta per Matplotlib overlapping annotations.

Non so tuttavia come trovare la larghezza e l'altezza esatte del testo. Se qualcuno lo sa, per favore pubblica un miglioramento (o aggiungi un commento con il metodo).

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 enter image description here

Il grafico più complicato è ora http://i.stack.imgur.com/KJeYW.png, ancora un po 'incerto ma molto meglio! enter image description here

+0

Verificare di non aver eliminato la modifica. – tacaswell

+0

e ricorda di accettare la tua risposta quando ti lascerà. – tacaswell

+0

e 'get_window_extent()' è la funzione artista che si desidera annotazione – tacaswell