Подтвердить что ты не робот

Matplotlib перекрывающиеся аннотации/текст

Я пытаюсь остановить текст аннотации, перекрывающийся на моих графиках. Метод, предложенный в принятом ответе на перекрывающие аннотации Matplotlib, выглядит чрезвычайно перспективным, однако для гистограмм. У меня возникли проблемы с преобразованием методов "оси" в то, что я хочу сделать, и я не понимаю, как выравниваются текстовые строки.

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

Изображения (если это работает) можно найти здесь (этот код):

image1

и здесь (более сложный):

image2

4b9b3361

Ответ 1

Я просто хотел опубликовать здесь другое решение, небольшую библиотеку, которую я написал для реализации таких вещей: https://github.com/Phlya/adjustText Пример процесса (из старой версии библиотеки - я слишком ленив, чтобы создать новый) можно увидеть здесь: введите описание изображения здесь

Вот пример изображения:

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

введите описание изображения здесь

Если вы хотите идеальную фигуру, вы можете немного поиграть. Во-первых, пусть также текст отталкивает линии - для этого мы просто создаем много виртуальных точек вдоль них, используя scipy.interpolate.interp1d.

Мы хотим избежать перемещения меток вдоль оси х, потому что, ну, почему бы не сделать это для иллюстративных целей. Для этого мы используем параметр only_move={'points':'y', 'text':'y'}. Если мы хотим переместить их вдоль оси x только в том случае, если они перекрываются с текстом, используйте move_only={'points':'y', 'text':'xy'}. Также в начале функция выбирает оптимальное выравнивание текстов относительно их исходных точек, поэтому мы хотим, чтобы это происходило и вдоль оси y, поэтому autoalign='y'. Мы также уменьшаем силу отталкивания от очков, чтобы избежать слишком сильного пролета текста из-за нашего искусственного избегания линий. Все вместе:

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

введите описание изображения здесь

Ответ 2

С большим количеством возиться, я понял это. Снова кредит для оригинального решения идет на ответ для перекрывающих аннотации Matplotlib.

Я не знаю, как найти точную ширину и высоту текста. Если кто-то знает, напишите об улучшении (или добавьте комментарий с помощью метода).

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

Создает http://i.stack.imgur.com/xiTeU.png enter image description here

Более сложный график теперь http://i.stack.imgur.com/KJeYW.png, все еще немного, но намного лучше! enter image description here