Я тоже боролся с этим. Ключевым моментом является то, что matplotlib не определяет, насколько большой будет текст, пока он его не нарисовал. Поэтому вам нужно явно позвонить plt.draw()
, а затем скорректировать свои границы, а затем снова нарисовать.
Метод get_window_extent
должен давать ответ в координатах отображения, а не координатах данных, по documentation. Но если холст еще не нарисован, он, похоже, отвечает на любую систему координат, указанную в аргументе ключевого слова textcoords
, до annotate
. Вот почему ваш код выше работает с использованием textcoords='data'
, но не 'offset points'
.
Вот пример:
x = np.linspace(0,360,101)
y = np.sin(np.radians(x))
line, = plt.plot(x, y)
label = plt.annotate('finish', (360,0),
xytext=(12, 0), textcoords='offset points',
ha='left', va='center')
bbox = label.get_window_extent(plt.gcf().canvas.get_renderer())
print(bbox.extents)
array([ 12. , -5. , 42.84375, 5. ])
Мы хотим изменить пределы, так что текстовая метка находится в пределах осей. Значение bbox
не очень помогает: поскольку оно находится в точках относительно помеченной точки: смещено на 12 точек в x, строка, которая, очевидно, будет чуть более 30 пунктов, с 10-точечным шрифтом (от -5 до 5 в y). Нетрудно выяснить, как добраться оттуда до нового набора границ осей.
Однако, если мы вызываем метод снова теперь, когда мы нарисовали его, мы получаем совершенно другое BBox:
bbox = label.get_window_extent(plt.gcf().canvas.get_renderer())
print(bbox.extents)
Теперь мы получаем
array([ 578.36666667, 216.66666667, 609.21041667, 226.66666667])
Это в координатах дисплея, которые мы можем преобразовать с ax.transData
, как мы привыкли. Таким образом, чтобы получить наши этикетки в рамки, мы можем сделать:
x = np.linspace(0,360,101)
y = np.sin(np.radians(x))
line, = plt.plot(x, y)
label = plt.annotate('finish', (360,0),
xytext=(8, 0), textcoords='offset points',
ha='left', va='center')
plt.draw()
bbox = label.get_window_extent()
ax = plt.gca()
bbox_data = bbox.transformed(ax.transData.inverted())
ax.update_datalim(bbox_data.corners())
ax.autoscale_view()
Примечание это больше не нужно явно передавать plt.gcf().canvas.get_renderer()
к get_window_extent
после того, как участок был составлен один раз. Кроме того, я напрямую использую update_datalim
вместо xlim
и ylim
, так что автосканирование может автоматически вырезать до круглого номера.
Я разместил этот ответ в формате ноутбука here.
К сожалению, tight_layout также не подбирает аннотации, насколько я могу судить. – Tango