Matplotlib: сохранение графика с легендой вне области построения
Я столкнулся с проблемой при попытке сохранить график с легендой, расположенной вне его области. Я прочитал статью и смог разместить легенду за пределами графика, используя следующий код:
import matplotlib.pyplot as pyplot
x = [0, 1, 2, 3, 4]
y = [xx*xx for xx in x]
fig = pyplot.figure()
ax = fig.add_subplot(111)
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width*0.8, box.height])
ax.plot(x, y)
leg = ax.legend(['abc'], loc = 'center left', bbox_to_anchor = (1.0, 0.5))
#pyplot.show()
fig.savefig('aaa.png', bbox_inches='tight')
Когда я использую pyplot.show()
, график отображается правильно, и легенда находится вне области графика. Однако, когда я пытаюсь сохранить график с помощью fig.savefig()
, легенда обрезается.
Некоторый поиск в интернете показал мне обходные пути, такие как добавление bbox_extra_artists=[leg.legendPatch]
или bbox_extra_artists=[leg]
в savefig()
, но ни один из этих методов не сработал.
Каков правильный способ решения этой проблемы? Версия Matplotlib - 0.99.3.
Спасибо.
3 ответ(ов)
Проблема заключается в том, что при динамической визуализации matplotlib
автоматически определяет границы, чтобы вписать все ваши объекты. Однако при сохранении файла эта автоматизация не срабатывает, поэтому вам нужно самостоятельно указать размер вашей фигуры, а также границы вашего объекта осей. Вот как можно исправить ваш код:
import matplotlib.pyplot as pyplot
x = [0, 1, 2, 3, 4]
y = [xx*xx for xx in x]
fig = pyplot.figure(figsize=(3, 3))
ax = fig.add_subplot(111)
#box = ax.get_position()
#ax.set_position([0.3, 0.4, box.width*0.3, box.height])
# Вы можете установить положение вручную, задав левую, нижнюю, ширину и высоту объекта осей.
ax.set_position([0.1, 0.1, 0.5, 0.8])
ax.plot(x, y)
leg = ax.legend(['abc'], loc='center left', bbox_to_anchor=(1.0, 0.5))
fig.savefig('aaa.png')
Используя метод set_position
, вы можете вручную настроить положение и размеры осей для более точного размещения элементов на графике перед его сохранением.
Вы правы, использование figlegend
вместе с tight_layout
может привести к проблемам, когда легенда оказывается обрезанной при сохранении фигуры. Это происходит из-за того, что bbox_inches='tight'
обрезает белое пространство вокруг всего изображения, и, как следствие, часть легенды может не поместиться в сохранённый файл.
Ваш временный обходной путь с использованием метода legend
, а не figlegend
, вполне разумен. Вы можете точно управлять позицией легенды, указывая координаты bbox_to_anchor
, которые позволяют размещать легенду в произвольном месте в пределах фигуры.
Вот ваш код с небольшими поправками на стиль и читаемость:
import matplotlib.pyplot as plt
# Установка параметров расположения подграфиков в фигуре
para = {
'figure.subplot.top': 0.5 # Указывает верхнюю границу подграфиков
}
# plt.rcParams.update(para) # Раскомментируйте, если нужно применить параметры
fig = plt.figure()
ax = fig.add_subplot(221)
ax.plot([1, 1, 1]) # Первый подграфик
ax = fig.add_subplot(222)
ax.plot([2, 2, 2]) # Второй подграфик
ax = fig.add_subplot(223)
ax.plot([3, 3, 3]) # Третий подграфик
ax = fig.add_subplot(224)
p1, = ax.plot([4, 4, 4]) # Четвёртый подграфик
p2, = ax.plot([2, 3, 2]) # Вторая линия на четвёртом подграфике
# Используем legend вместо figlegend для лучшего контроля
lgd = plt.legend(
[p1, p2],
['a', 'b'],
bbox_to_anchor=(-0.1, 2.5), # Позиция легенды
loc='center', # Локация относительно bbox_to_anchor
ncol=2 # Число колонок в легенде
)
# Сохранение фигуры с корректным учетом области
plt.savefig('temp.png', bbox_inches='tight') # Обратите внимание на bbox_inches
Если у вас есть возможность поэкспериментировать с bbox_inches
и bbox_extra_artist
при сохранении, вы можете попробовать использовать bbox_extra_artist=[lgd]
, чтобы избежать обрезки легенды. Однако, как вы заметили, это может не всегда сработать, поэтому использование стандартного legend
с ручной настройкой позиции — хорошее временное решение.
Если ничего не помогает, я использую функции вычисления рамки (bounding box) в Inkscape, чтобы справиться с тем, что я бы назвал стойкими багами в выводе matplotlib. Если вы работаете в GNU/Linux, просто сохраните то, что предоставляет Matplotlib, в формате PDF, а затем отправьте это на следующий скрипт:
def tightBoundingBoxInkscape(pdffile,use_xvfb=True):
"""Выполняет системные вызовы, специфичные для POSIX. Предпочтительно иметь установленный xvfb, чтобы избежать всплывающих окон графического интерфейса в фоновом режиме. Если это всё равно не сработает, можно использовать use_xvfb=False, что позволит некоторым графическим интерфейсам появляться во время выполнения задачи.
pdffile: путь к PDF-файлу без расширения
"""
usexvfb='xvfb-run ' * use_xvfb
import os
assert not pdffile.endswith('.pdf')
os.system("""
inkscape -f %(FN)s.pdf -l %(FN)s_tmp.svg
inkscape -f %(FN)s_tmp.svg --verb=FitCanvasToDrawing \
--verb=FileSave \
--verb=FileQuit
inkscape -f %(FN)s_tmp.svg -A %(FN)s-tightbb.pdf
""" % {'FN': pdffile})
Этот код помогает автоматизировать процесс использования Inkscape для корректировки выходных данных, получаемых из matplotlib, следуя описанным шагам. Не забудьте установить Inkscape и, при необходимости, xvfb.
Как вынести легенду за пределы графика
Не найдены элементы с подписями для добавления в легенду
Как сделать графики Matplotlib отображаемыми внутри IPython-ноутбука?
Построение графиков с логарифмическими осями
Цветовой график 2D массива в matplotlib