Matplotlib 创建具有精确大小且无填充的图形(以及轴外的图例)
我正试图为一篇科学文章制作一些数字,所以我希望我的数字有一个特定的大小。我还看到Matplotlib在默认情况下在图形的边框上添加了很多填充,这是我不需要的(因为图形将在白色背景上) 要设置特定的图形大小,我只需使用Matplotlib 创建具有精确大小且无填充的图形(以及轴外的图例),matplotlib,Matplotlib,我正试图为一篇科学文章制作一些数字,所以我希望我的数字有一个特定的大小。我还看到Matplotlib在默认情况下在图形的边框上添加了很多填充,这是我不需要的(因为图形将在白色背景上) 要设置特定的图形大小,我只需使用plt.figure(figsize=[w,h]),然后添加参数tight_layout={'pad':0},以删除填充。这非常有效,甚至在我添加标题、y/x标签等时也有效。示例: fig = plt.figure( figsize = [3,2], tight_la
plt.figure(figsize=[w,h])
,然后添加参数tight_layout={'pad':0}
,以删除填充。这非常有效,甚至在我添加标题、y/x标签等时也有效。示例:
fig = plt.figure(
figsize = [3,2],
tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
plt.savefig('figure01.pdf')
plt.close('all')
fig = plt.figure(
figsize = [3,2],
tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round'))
plt.savefig('figure02.pdf')
这将创建一个精确大小为3x2(英寸)的pdf文件
我遇到的问题是,例如,当我在轴外添加文本框(通常是图例框)时,Matplotlib不会像添加标题/轴标签时那样为文本框腾出空间。通常,文本框会被切断,或者根本不显示在保存的图形中。例如:
fig = plt.figure(
figsize = [3,2],
tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
plt.savefig('figure01.pdf')
plt.close('all')
fig = plt.figure(
figsize = [3,2],
tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round'))
plt.savefig('figure02.pdf')
我在SO的其他地方找到的一个解决方案是在savefig命令中添加参数bbox\u inches='tight'
。文本框现在包含在我想要的内容中,但pdf现在的大小不正确。Matplotlib似乎只是使图形变大,而不是像添加标题和x/y标签时那样减小轴的大小
例如:
plt.close('all')
fig = plt.figure(
figsize = [3,2],
tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round'))
plt.savefig('figure03.pdf', bbox_inches = 'tight')
(该图为3.307x2.248)
是否有任何解决方案可以覆盖大多数轴外有图例的情况?因此要求如下:
pad=0的tight_布局
,解决了1。四,。但与第2条相矛盾
可以考虑将pad
设置为更大的值。这将解决2。然而,因为它在所有方向上都是对称的,所以它与4相矛盾
使用bbox\u 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\u to\u 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),但在图dpi\U scale\U trans中进行了说明。如果要更改dpi,则需要确保在创建图形以及保存图形时设置图形dpi(在调用plt.figure()
以及plt.savefig()
)时使用dpi=..
。自matplotlib==3.1.3
起,您可以使用constrated\u layout=True
来实现所需的结果。这目前是实验性的,但请参阅,以获取非常有用的指南(特别是图例)。请注意,图例将从情节中窃取空间,但这是不可避免的。我发现,只要图例相对于绘图的大小不占用太多空间,那么图形就可以在不裁剪任何内容的情况下保存
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(3, 2), constrained_layout=True)
ax.set_title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
ax.plot([0,1], [0,1], label='my text here')
ax.legend(loc='center left', bbox_to_anchor=(1.1, 0.5))
fig.savefig('figure03.pdf')
谢谢,这太棒了!您介意解释一下在某些地方出现的硬编码72
是什么吗?我猜这是默认的dpi,但我不确定。我在答案中添加了一些解释。再次感谢。有没有什么简单的方法可以让这部作品成为传奇?例如,在轴外放置图例时,我在答案中添加了图例的大小写。