2012-07-18 2 views
3

Кто-нибудь знает, как легко расширить область сюжета, чтобы включить аннотации? У меня есть цифра, где некоторые метки длинны и/или многострочные строки, и вместо того, чтобы обрезать их по осям, я хочу расширить оси, включив аннотации.автозапчасти matplotlib для включения аннотаций

Autoscale_view этого не делает, и ax.relim не поднимает позицию аннотаций, так что это не похоже на вариант.

Я попытался сделать что-то наподобие приведенного ниже кода, который перебирает все аннотации (при условии, что они находятся в координатах данных), чтобы получить их экстенты, а затем обновляет оси соответственно, но в идеале я не хочу, чтобы мои аннотации в координатах данных (они смещены от фактических точек данных).

xmin, xmax = plt.xlim() 
ymin, ymax = plt.ylim() 
# expand figure to include labels 
for l in my_labels: 
    # get box surrounding text, in data coordinates 
    bbox = l.get_window_extent(renderer=plt.gcf().canvas.get_renderer()) 
    l_xmin, l_ymin, l_xmax, l_ymax = bbox.extents 
    xmin = min(xmin, l_xmin); xmax = max(xmax, l_xmax); ymin = min(ymin, l_ymin); ymax = max(ymax, l_ymax) 
plt.xlim(xmin, xmax) 
plt.ylim(ymin, ymax) 

ответ

1

В matplotlib1.1tight_layout вводится, чтобы решить некоторые из проблем компоновки. Есть хороший учебник here.

+0

К сожалению, tight_layout также не подбирает аннотации, насколько я могу судить. – Tango

1

Для меня tight_layout обычно решить эту проблему, но в некоторых случаях мне пришлось использовать «ручной» корректировки с subplots_adjust, как это:

fig = plt.figure() 
fig.subplots_adjust(bottom=0.2, top=0.12, left=0.12, right=0.1) 

Цифры обычно не сильно изменится, так что вы можете их исправить а затем попытайтесь рассчитать по фактическому сюжету.

BTW, установка xlim, как и в вашем примере, изменяет только диапазон данных, которые вы рисуете, а не белую область вокруг всех ваших меток.

+0

Спасибо. Беда в том, что у меня есть некоторые аннотации, которые выполняются над многими строками и должны быть большим блоком текста на рисунке, поэтому это не просто проблема слишком жестких границ ... – Tango

2

Я тоже боролся с этим. Ключевым моментом является то, что 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) 

plot with annotation clipped

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

fixed plot

Примечание это больше не нужно явно передавать plt.gcf().canvas.get_renderer() к get_window_extent после того, как участок был составлен один раз. Кроме того, я напрямую использую update_datalim вместо xlim и ylim, так что автосканирование может автоматически вырезать до круглого номера.

Я разместил этот ответ в формате ноутбука here.

Смежные вопросы