Временная шкала событий — установка местоположения аннотации

#python #matplotlib #annotations #overlap

#python #matplotlib #аннотации #перекрытие

Вопрос:

Я пытаюсь адаптировать представленный здесь код, чтобы он лучше соответствовал тому, что мне нужно. Мне нужно отображать длинные аннотации, поэтому я хотел бы показать их, начиная с «края» основной строки, которая находится дальше по отношению к оси y, добавив немного места, если текст попадает поверх вертикальной линии. Я думаю, что могу объяснить это, сказав, что вместо определения того, где начинается текст, я хотел бы установить, когда он должен заканчиваться. Я думал о том, чтобы просто изменить [‘top’, ‘bottom’] часть кода на [‘bottom’, ‘top’], но это не совсем сработало.

Это воспроизводимый пример кода:

 import matplotlib.pyplot as plt
import numpy as np
import matplotlib.dates as mdates
from datetime import datetime


names = ['v2.2.4', 'v3.0.3', 'v3.0.2', 'v3.0.1', 'v3.0.0', 'v2.2.3',
        'v2.2.2', 'v2.2.1', 'v2.2.0', 'v2.1.2', 'v2.1.1', 'v2.1.0',
        'v2.0.2', 'v2.0.1', 'v2.0.0', 'v1.5.3', 'v1.5.2', 'v1.5.1',
        'v1.5.0', 'v1.4.3', 'v1.4.2', 'v1.4.1', 'v1.4.0']

dates = ['2019-02-26', '2019-02-26', '2018-11-10', '2018-11-10',
            '2018-09-18', '2018-08-10', '2018-03-17', '2018-03-16',
            '2018-03-06', '2018-01-18', '2017-12-10', '2017-10-07',
            '2017-05-10', '2017-05-02', '2017-01-17', '2016-09-09',
            '2016-07-03', '2016-01-10', '2015-10-29', '2015-02-16',
            '2014-10-26', '2014-10-18', '2014-08-26']

names = [name "very_very_long_long" for name in names]

# Convert date strings (e.g. 2014-10-18) to datetime
dates = [datetime.strptime(d, "%Y-%m-%d") for d in dates]


# Choose some nice levels
levels = np.tile([-5, 5, -3, 3, -1, 1],
                 int(np.ceil(len(dates)/6)))[:len(dates)]

# Create figure and plot a stem plot with the date
fig, ax = plt.subplots(figsize=(8.8, 4), constrained_layout=True)
ax.set(title="Matplotlib release dates")

markerline, stemline, baseline = ax.stem(dates, levels,
                                         linefmt="C3-", basefmt="k-",
                                         use_line_collection=True)

plt.setp(markerline, mec="k", mfc="w", zorder=3)

# Shift the markers to the baseline by replacing the y-data by zeros.
markerline.set_ydata(np.zeros(len(dates)))

# annotate lines
vert = np.array(['bottom', 'top'])[(levels > 0).astype(int)]
for d, l, r, va in zip(dates, levels, names, vert):
    ax.annotate(r, xy=(d, l), xytext=(-3, np.sign(l)*3),
                textcoords="offset points", va=va, ha="right", rotation=90)

# format xaxis with 4 month intervals
# ax.get_xaxis().set_major_locator(mdates.MonthLocator(interval=4))
# ax.get_xaxis().set_major_formatter(mdates.DateFormatter("%b %Y"))
# plt.setp(ax.get_xticklabels(), rotation=30, ha="right")

# remove y axis and spines
ax.get_yaxis().set_visible(False)
for spine in ["left", "top", "right"]:
    ax.spines[spine].set_visible(False)

# ax.margins(y=0.1)
plt.show()
 

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

Я пытался использовать эту библиотеку, но пока это просто создает новый беспорядок, потому что я, вероятно, неправильно ее использую.

Ответ №1:

У меня нет большого опыта работы с такого рода графами, но я пришел к двум идеям: первая — разместить строку горизонтально и расположить ее в верхней части уровня. Другой способ — радикально переключить оси и сделать его вертикальным графиком.

1) Горизонтальная и центрированная строка

 for d, l, r, va in zip(dates, levels, names, vert):
    ax.annotate(r, xy=(d, l), xytext=(60, np.sign(l)*15), 
                textcoords="offset points", va=va, ha="right")
 

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

  1. Вертикальный тип с заменой осей XY
 fig, ax = plt.subplots(figsize=(6, 12), constrained_layout=True)
# define bounding box
boxdic={'facecolor':'0.9',
        'edgecolor':'0.6',
        'boxstyle':'round',
        'linewidth':1}

arrowprops = dict(
    arrowstyle="->",
    connectionstyle="angle,angleA=0,angleB=45,rad=10")

arrowprops1 = dict(
    arrowstyle="->",
    connectionstyle="angle,angleA=0,angleB=-45,rad=10")

ax = plt.axes(xlim=(-5,5), ylim=(datetime(2020,1,1,0,0), datetime(2014,1,1,0,0)))
ax.set(title="Matplotlib release dates")
ax.scatter([0]*len(dates), dates, marker='o', color='w', ec='k')

ax.spines['left'].set_position('zero')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.axes.xaxis.set_visible(False)

for i, (d, n) in enumerate(zip(dates, names)):
    if i % 2 == 0:
        ax.annotate(str("{}".format(n)), xy=(0.1, d),
                    xycoords='data', xytext=(15, 25), textcoords='offset points',
                    color="k", fontsize=10, bbox=boxdic, arrowprops=arrowprops)
    else:
        ax.annotate(str("{}".format(n)), xy=(-0.1, d),
                    xycoords='data', xytext=(-15, 25), textcoords='offset points',
                    color="k", fontsize=10, bbox=boxdic, ha='right', arrowprops=arrowprops1)        

months = mdates.MonthLocator(interval = 6)
months_fmt = mdates.DateFormatter('%Y-%m')
ax.yaxis.set_major_locator(months)
ax.yaxis.set_major_formatter(months_fmt)

plt.show()
 

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