将 matplotlib 图例移出轴会使其被图形框截断

2025-02-10 08:57:00
admin
原创
59
摘要:问题描述:我熟悉以下问题:Matplotlib savefig 在图表外带有图例如何将传说从故事情节中剔除这些问题的答案似乎能够通过摆弄轴的精确收缩来适应图例。然而,缩小轴并不是一个理想的解决方案,因为它会使数据变小,从而实际上更难以解释;特别是当它很复杂,有很多事情发生时……因此需要一个大的图例文档中复杂图...

问题描述:

我熟悉以下问题:

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')

请注意,最终标签“逆正切”实际上位于图形框之外(并且看起来很严重截断 - 不符合出版质量!)
在此处输入图片描述

最后,有人告诉我这是 R 和 LaTeX 中的正常行为,所以我有点困惑为什么这在 python 中如此困难... 有历史原因吗?Matlab 在这件事上也同样糟糕吗?

我在 pastebin 上有此代码的(仅稍微)较长的版本http://pastebin.com/grVjc007


解决方案 1:

抱歉 EMS,但实际上我刚刚从 matplotlib 邮件列表收到了另一个回复(感谢 Benjamin Root)。

我正在寻找的代码是将 savefig 调用调整为:

fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight')
#Note that the bbox_extra_artists must be an iterable

这显然类似于调用 tight_layout,但您允许 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')

得出的结果为:

IT科技

[编辑] 这个问题的目的是完全避免使用任意文本的任意坐标位置,这是这些问题的传统解决方案。尽管如此,最近还是有许多编辑坚持要把这些放进去,通常会导致代码出现错误。我现在已经修复了这些问题,并整理了任意文本,以展示如何在 bbox_extra_artists 算法中考虑这些。

[编辑] 下面的一些评论指出,自 2019 年以来,该命令已简化。
plt.savefig('x.png', bbox_inches='tight') 就足够了。 谢谢分享。 – mateuszb 2019 年 6 月 27 日

解决方案 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 英寸 CRT 显示器)上。

这里figsize=(M,N)将图形窗口设置为 M 英寸乘 N 英寸。只需尝试一下,直到它看起来适合您为止。将其转换为更可扩展的图像格式,并在必要时使用 GIMP 进行编辑,或者viewport在包含图形时使用 LaTeX 选项进行裁剪。

解决方案 3:

这是另一个非常手动的解决方案。您可以定义轴的大小,并相应地考虑填充(包括图例和刻度标记)。希望它对某些人有用。

示例(轴尺寸相同!):

在此处输入图片描述

代码:

#==================================================
# 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
[kgCO2eq]

asedf
asdf
adfs','human
[pts]','ressource
[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()
#--------------------------------------------------
#==================================================

解决方案 4:

我尝试了一个非常简单的方法,只是让图形稍微宽一点:

fig, ax = plt.subplots(1, 1, figsize=(a, b))

调整ab适当的值,以便图例包含在图中

前

后

解决方案 5:

由于谷歌引导我到这里回答类似的问题,所以值得注意的是,现在你只需要使用plt.savefig('myplot.png', bbox_inches='tight')扩展画布来适应图例。

这里的大多数答案都已过时。

解决方案 6:

我同意 John Thomas 的观点,如果你使用基于 plt 的图形,那么你可能需要的是:plt.savefig('myplot.png', bbox_inches='tight')

解决方案 7:

layout="constrained"选项(自 Matplotlib 3.7.0 起可用)允许相当直接的实现:

import matplotlib.pyplot as plt
import numpy as np

if __name__ == "__main__":
    x = np.arange(-2 * np.pi, 2 * np.pi, 0.1)
    fig, ax = plt.subplots(1, 1, layout="constrained")
    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()
    ax.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5, -0.1))
    text = ax.text(-0.2, 1.05, "Arbitrary text", transform=ax.transAxes)
    ax.set_title("Trigonometry")
    ax.grid('on')
    fig.savefig('samplefigure.png')
    fig.show()

在此处输入图片描述

其中一个好处(与jonathanbsyd的原始答案相比)是fig.show()效果同样好。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用