Matplotlib: расширять легенду вертикально

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

borderaxespad=0. будет расширяться по горизонтали, но я не смог бы найти эквивалент, чтобы его вертикально расширить.

Я использую matplotlib 2.0

Пример кода:

import numpy as np

x = np.linspace(0, 2*np.pi, 100)
data = [np.sin(x * np.pi/float(el)) for el in range(1, 5)]

fig, ax = plt.subplots(1)
for key, el in enumerate(data):
    ax.plot(x, el, label=str(key))
ax.legend(bbox_to_anchor=(1.04,1), loc="upper left", borderaxespad=0., mode='expand')
plt.tight_layout(rect=[0,0,0.8,1])

Что производит:

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

Ответ 1

Сначала объясним вывод из вопроса: при использовании 2-кортежной нотации для bbox_to_anchor создается ограничивающий прямоугольник без растягивания. mode="expand" будет расширять легенду горизонтально в эту ограничительную рамку, которая имеет нулевую длину, эффективно уменьшая ее до нулевого размера.

Проблема заключается в том, что mode="expand" расширяет легенду только по горизонтали. Из документация:

mode: { "expand", None}
Если для режима установлено значение "expand", легенда будет горизонтально расширена, чтобы заполнить область осей (или bbox_to_anchor, если определяет размер легенд).

Для решения вам нужно глубоко вникать в легендарные внутренние элементы. Во-первых, вам нужно установить привязку bbox-to-anchor с 4-кортежем, указав также ширину и высоту bbox, bbox_to_anchor=(x0,y0,width,height), где все числа находятся в координатах с координатами координат. Затем вам нужно рассчитать высоту легенды _legend_box. Поскольку есть определенное дополнение, вам нужно вычесть это заполнение из высоты ограничивающего прямоугольника. Чтобы рассчитать заполнение, необходимо знать текущий шрифт легенды. Все это должно произойти после последнего изменения положения осей.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2*np.pi, 100)
data = [np.sin(x * np.pi/float(el)) for el in range(1, 5)]

fig, ax = plt.subplots(1)
for key, el in enumerate(data):
    ax.plot(x, el, label=str(key))

# legend:    
leg = ax.legend(bbox_to_anchor=(1.04,0.0,0.2,1), loc="lower left",
                borderaxespad=0, mode='expand')

plt.tight_layout(rect=[0,0,0.8,1])

# do this after calling tight layout or changing axes positions in any way:
fontsize = fig.canvas.get_renderer().points_to_pixels(leg._fontsize)
pad = 2 * (leg.borderaxespad + leg.borderpad) * fontsize
leg._legend_box.set_height(leg.get_bbox_to_anchor().height-pad)

plt.show()

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

Ответ 2

labelspacing может быть то, что вы ищете?

fig, ax = plt.subplots(1)
for key, el in enumerate(data):
    ax.plot(x, el, label=str(key))
ax.legend(labelspacing=8, loc=6, bbox_to_anchor=(1, 0.5))
plt.tight_layout(rect=[0, 0, 0.9, 1])

Он не является автоматическим, но вы можете найти какое-либо отношение с figsize (здесь также 8).

loc=6, bbox_to_anchor=(1, 0.5) будет центрировать вашу легенду в правой части вашего сюжета.

Что дает: expand legend matplotib