Перемещение легенды matplotlib за пределами оси заставляет ее обрезать блок фигуры
Мне знакомы следующие вопросы:
Matplotlib savefig с легендой вне сюжета
Как вывести легенду из сюжета
Похоже, что ответы на эти вопросы имеют роскошь быть способным возиться с точным сокращением оси, чтобы легенда соответствовала.
Сокращение осей, однако, не является идеальным решением, поскольку оно уменьшает количество данных, что делает его более трудным для интерпретации; особенно когда его сложность и есть много вещей, происходящих... следовательно, требуется большая легенда
Пример сложной легенды в документации демонстрирует необходимость этого, потому что легенда в их сюжете фактически полностью закрывает несколько точек данных.
http://matplotlib.sourceforge.net/users/legend_guide.html#legend-of-complex-plots
То, что я хотел бы сделать, - это динамически расширить размер окна с фигурами, чтобы приспособить расширяющуюся фигуру.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))
ax.grid('on')
Обратите внимание, как окончательная метка "Обратный загар" фактически находится за рамкой фигуры (и выглядит плохо срезанной, а не качеством публикации!)
![enter image description here]()
Наконец, мне сказали, что это нормальное поведение в R и LaTeX, поэтому я немного смущен, почему это так сложно в python... Есть ли историческая причина? Является ли Matlab одинаково бедным по этому поводу?
У меня есть (только немного) более длинная версия этого кода на pastebin http://pastebin.com/grVjc007
Ответы
Ответ 1
Извините, EMS, но я только что получил еще один ответ из списка рассылки matplotlib (Спасибо Бенджамину Руту).
Код, который я ищу, настраивает вызов savefig на:
fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight')
#Note that the bbox_extra_artists must be an iterable
Похоже, это похоже на вызов туго-слоистых, но вместо этого вы позволяете savefig учитывать дополнительных исполнителей в расчете. Это действительно изменило размер окна рисунка по желанию.
import matplotlib.pyplot as plt
import numpy as np
plt.gcf().clear()
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
handles, labels = ax.get_legend_handles_labels()
lgd = ax.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5,-0.1))
text = ax.text(-0.2,1.05, "Aribitrary text", transform=ax.transAxes)
ax.set_title("Trigonometry")
ax.grid('on')
fig.savefig('samplefigure', bbox_extra_artists=(lgd,text), bbox_inches='tight')
Это производит:
![]()
[править] Цель этого вопроса состояла в том, чтобы полностью избежать использования произвольных координатных размещений произвольного текста, что было традиционным решением этих проблем. Несмотря на это, в последнее время многочисленные исправления настаивали на их внесении, часто так, чтобы код вызывал ошибку. Теперь я исправил проблемы и исправил произвольный текст, чтобы показать, как они также учитываются в алгоритме bbox_extra_artists.
Ответ 2
Добавлено: Я нашел кое-что, что должно было сделать трюк сразу, но остальная часть приведенного ниже кода также предлагает альтернативу.
Используйте функцию subplots_adjust()
, чтобы переместить нижнюю часть подстроки вверх:
fig.subplots_adjust(bottom=0.2) # <-- Change the 0.02 to work for your plot.
Затем сыграйте со смещением в легенде bbox_to_anchor
часть команды легенды, чтобы получить окно легенды, в котором вы хотите. Некоторая комбинация настройки figsize
и использования subplots_adjust(bottom=...)
должна создать для вас качественный график.
Альтернатива:
Я просто изменил строку:
fig = plt.figure(1)
в
fig = plt.figure(num=1, figsize=(13, 13), dpi=80, facecolor='w', edgecolor='k')
и изменил
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))
к
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,-0.02))
и он отображается на моем экране (24-дюймовый ЭЛТ-монитор).
Здесь figsize=(M,N)
устанавливает, чтобы окно фигуры составляло M дюймов на дюйм. Просто играйте с этим, пока он не будет выглядеть правильно для вас. Преобразуйте его в более масштабируемый формат изображения и используйте GIMP для редактирования, если необходимо, или просто обрезайте с помощью параметра LaTeX viewport
при включении графики.
Ответ 3
Вот еще одно, очень ручное решение. Вы можете определить размер оси, и соответственно учитываются прокладки (включая легенду и отметки). Надеюсь, что это полезно кому-то.
Пример (размер осей одинаковый!):
![enter image description here]()
код:
#==================================================
# Plot table
colmap = [(0,0,1) #blue
,(1,0,0) #red
,(0,1,0) #green
,(1,1,0) #yellow
,(1,0,1) #magenta
,(1,0.5,0.5) #pink
,(0.5,0.5,0.5) #gray
,(0.5,0,0) #brown
,(1,0.5,0) #orange
]
import matplotlib.pyplot as plt
import numpy as np
import collections
df = collections.OrderedDict()
df['labels'] = ['GWP100a\n[kgCO2eq]\n\nasedf\nasdf\nadfs','human\n[pts]','ressource\n[pts]']
df['all-petroleum long name'] = [3,5,2]
df['all-electric'] = [5.5, 1, 3]
df['HEV'] = [3.5, 2, 1]
df['PHEV'] = [3.5, 2, 1]
numLabels = len(df.values()[0])
numItems = len(df)-1
posX = np.arange(numLabels)+1
width = 1.0/(numItems+1)
fig = plt.figure(figsize=(2,2))
ax = fig.add_subplot(111)
for iiItem in range(1,numItems+1):
ax.bar(posX+(iiItem-1)*width, df.values()[iiItem], width, color=colmap[iiItem-1], label=df.keys()[iiItem])
ax.set(xticks=posX+width*(0.5*numItems), xticklabels=df['labels'])
#--------------------------------------------------
# Change padding and margins, insert legend
fig.tight_layout() #tight margins
leg = ax.legend(loc='upper left', bbox_to_anchor=(1.02, 1), borderaxespad=0)
plt.draw() #to know size of legend
padLeft = ax.get_position().x0 * fig.get_size_inches()[0]
padBottom = ax.get_position().y0 * fig.get_size_inches()[1]
padTop = ( 1 - ax.get_position().y0 - ax.get_position().height ) * fig.get_size_inches()[1]
padRight = ( 1 - ax.get_position().x0 - ax.get_position().width ) * fig.get_size_inches()[0]
dpi = fig.get_dpi()
padLegend = ax.get_legend().get_frame().get_width() / dpi
widthAx = 3 #inches
heightAx = 3 #inches
widthTot = widthAx+padLeft+padRight+padLegend
heightTot = heightAx+padTop+padBottom
# resize ipython window (optional)
posScreenX = 1366/2-10 #pixel
posScreenY = 0 #pixel
canvasPadding = 6 #pixel
canvasBottom = 40 #pixel
ipythonWindowSize = '{0}x{1}+{2}+{3}'.format(int(round(widthTot*dpi))+2*canvasPadding
,int(round(heightTot*dpi))+2*canvasPadding+canvasBottom
,posScreenX,posScreenY)
fig.canvas._tkcanvas.master.geometry(ipythonWindowSize)
plt.draw() #to resize ipython window. Has to be done BEFORE figure resizing!
# set figure size and ax position
fig.set_size_inches(widthTot,heightTot)
ax.set_position([padLeft/widthTot, padBottom/heightTot, widthAx/widthTot, heightAx/heightTot])
plt.draw()
plt.show()
#--------------------------------------------------
#==================================================