Ответ 1
Таким образом, требования:
- Наличие фиксированного предопределенного размера фигуры
- Добавление текстовой метки или легенды вне осей
- Оси и текст не могут перекрываться
- Оси вместе с метками названия и оси сидят снова на границе рисунка.
So tight_layout
с pad = 0
, решает 1 и 4. но противоречит 2.
Можно подумать, что при установке pad
на большее значение. Это позволило бы решить 2. Однако, поскольку он симметричен во всех направлениях, это противоречило бы 4.
Использование bbox_inches = 'tight'
изменяет размер фигуры. Противоречит 1.
Итак, я думаю, что нет общего решения этой проблемы.
Что-то, что я могу придумать, следующее: он устанавливает текст в координатах фигур, а затем изменяет размеры осей либо в горизонтальном, либо в вертикальном направлении, так что между осями и текстом нет совпадения.
import matplotlib.pyplot as plt
import matplotlib.transforms
fig = plt.figure(figsize = [3,2])
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
def text_legend(ax, x0, y0, text, direction = "v", padpoints = 3, margin=1.,**kwargs):
ha = kwargs.pop("ha", "right")
va = kwargs.pop("va", "top")
t = ax.figure.text(x0, y0, text, ha=ha, va=va, **kwargs)
otrans = ax.figure.transFigure
plt.tight_layout(pad=0)
ax.figure.canvas.draw()
plt.tight_layout(pad=0)
offs = t._bbox_patch.get_boxstyle().pad * t.get_size() + margin # adding 1pt
trans = otrans + \
matplotlib.transforms.ScaledTranslation(-offs/72.,-offs/72.,fig.dpi_scale_trans)
t.set_transform(trans)
ax.figure.canvas.draw()
ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0]
trans2 = matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans) + \
ax.figure.transFigure.inverted()
tbox = trans2.transform(t._bbox_patch.get_window_extent())
bbox = ax.get_position()
if direction=="v":
ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox[0][1]-bbox.y0])
else:
ax.set_position([bbox.x0, bbox.y0,tbox[0][0]-bbox.x0, bbox.height])
# case 1: place text label at top right corner of figure (1,1). Adjust axes height.
#text_legend(ax, 1,1, 'my text here', bbox = dict(boxstyle = 'round'), )
# case 2: place text left of axes, (1, y), direction=="v"
text_legend(ax, 1., 0.8, 'my text here', margin=2., direction="h", bbox = dict(boxstyle = 'round') )
plt.savefig(__file__+'.pdf')
plt.show()
случай 1 (слева) и случай 2 (справа):
Делать то же самое с легендой немного легче, потому что мы можем напрямую использовать аргумент
bbox_to_anchor
и не нуждаемся в управлении причудливым полем вокруг легенды.
import matplotlib.pyplot as plt
import matplotlib.transforms
fig = plt.figure(figsize = [3.5,2])
ax = fig.add_subplot(111)
ax.set_title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
ax.plot([1,2,3], marker="o", label="quantity 1")
ax.plot([2,1.7,1.2], marker="s", label="quantity 2")
def legend(ax, x0=1,y0=1, direction = "v", padpoints = 3,**kwargs):
otrans = ax.figure.transFigure
t = ax.legend(bbox_to_anchor=(x0,y0), loc=1, bbox_transform=otrans,**kwargs)
plt.tight_layout(pad=0)
ax.figure.canvas.draw()
plt.tight_layout(pad=0)
ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0]
trans2=matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans)+\
ax.figure.transFigure.inverted()
tbox = t.get_window_extent().transformed(trans2 )
bbox = ax.get_position()
if direction=="v":
ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox.y0-bbox.y0])
else:
ax.set_position([bbox.x0, bbox.y0,tbox.x0-bbox.x0, bbox.height])
# case 1: place text label at top right corner of figure (1,1). Adjust axes height.
#legend(ax, borderaxespad=0)
# case 2: place text left of axes, (1, y), direction=="h"
legend(ax,y0=0.8, direction="h", borderaxespad=0.2)
plt.savefig(__file__+'.pdf')
plt.show()
Почему
72
? 72
- количество точек на дюйм (ppi). Это фиксированная типографская единица, например. шрифты всегда указываются в точках (например, 12pt). Поскольку matplotlib определяет заполнение текстового поля в единицах относительно fontsize, которое является точками, нам нужно использовать 72
для преобразования обратно в дюймы (а затем для отображения координат). Точки по умолчанию на дюйм (dpi) здесь не затрагиваются, но учитываются в fig.dpi_scale_trans
. Если вы хотите изменить dpi, вам нужно убедиться, что рисунок dpi задан при создании рисунка, а также при его сохранении (используйте dpi=..
в вызове plt.figure()
, а также plt.savefig()
).