matplotlibの凡例を軸の外に移動すると、Figureボックスによってカットオフされます


222

次の質問に精通しています。

プロットの外側に凡例がある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')

最終的なラベル 'Inverse tan'が実際にFigureボックスの外側にあることに注意してください(そして、カットオフがひどく見える-出版物の品質ではありません!) ここに画像の説明を入力してください

最後に、これはRとLaTeXの通常の動作であると言われました。Pythonでこれが非常に難しいのはなぜか混乱しています...歴史的な理由はありますか?この問題についてMatlabも同様に貧弱ですか?

私はこのコードの(ほんの少しだけ)より長いバージョンをペーストビンhttp://pastebin.com/grVjc007に持っています


10
その理由は、matplotlibがインタラクティブなプロットを対象としているのに対し、Rなどはそうではないためです。(そうです、この特定の場合、Matlabは「同等に貧弱」です。)これを適切に行うには、Figureのサイズ変更、ズーム、または凡例の位置が更新されるたびに軸のサイズ変更を考慮する必要があります。(効果的には、これはプロットが描画されるたびにチェックすることを意味します。これにより、速度が低下します。)Ggplotなどは静的であるため、デフォルトでこれを行う傾向がありますが、matplotlibおよびmatlabはそうしません。言われていることは、tight_layout()伝説を考慮に入れるために変更されるべきです。
Joe Kington

3
この質問については、matplotlibユーザーのメーリングリストでも説明しています。したがって、savefig行を次のように調整することをお勧めします。fig.savefig( 'samplefigure'、bbox_extra_artists =(lgd、)、bbox = 'tight')
jbbiomed

3
私はmatplotlibがすべてがユーザーの制御下にあると宣伝するのが好きだと知っていますが、伝説を含むこの全体はあまりにも良いことです。伝説を外に置いた場合、明らかにそれを表示したままにしておきたい。ウィンドウは、この巨大な再スケーリングの煩わしさを作成する代わりに、自分自身に合うようにスケーリングする必要があります。少なくとも、この自動スケーリング動作を制御するデフォルトのTrueオプションが必要です。ユーザーに、とんでもない数の再レンダリングを実行して、コントロールの名前でスケール数を正しく取得するように強制することは、その逆を実現します。
Elliot 2013年

回答:


300

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が計算で追加のアーティストを考慮することを許可します。これにより、実際にFigureボックスのサイズが変更されました。

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アルゴリズム内でどのように考慮されるかを示しました。


1
/!\ matplotlib> = 1.0以降(Debian squeezeには0.99があり、これは機能しない)でのみ機能するようです
Julien Palard

1
仕事にこれを取得することはできません:(私はLGDにsavefigに渡すが、それはまだサイズは変更されません。問題は、私はサブプロットを使用していないかもしれません。。
6005

8
ああ!私はあなたがしたようにbbox_inches = "tight"を使う必要があるだけです。ありがとう!
6005 2015年

7
これはいいですが、試してみてもまだ体型が切れてplt.show()います。そのための修正はありますか?
Agostino


23

追加:私はすぐにトリックを実行するはずの何かを見つけましたが、以下のコードの残りの部分も代替を提供しています。

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)は、FigureウィンドウをMインチx Nインチに設定します。それがあなたに合うようになるまで、これで遊んでください。よりスケーラブルな画像形式に変換し、必要に応じてGIMPを使用して編集するか、viewportグラフィックスを含める場合はLaTeX オプションを使用して切り抜いてください。


オートレポートジェネレーターには適さない「見栄えが良くなるまで再生する」ことが依然として必要ですが、これは現時点で最良のソリューションであると思われます。私は実際にこのソリューションを既に使用していますが、実際の問題は、matplotlibが軸のbboxの外側にある凡例を動的に補正しないことです。@Joeが言ったように、tight_layout 軸、タイトル、ラベルだけでなく、より多くの機能を考慮に入れるべきです。これをmatplotlibの機能リクエストとして追加するかもしれません。
jbbiomed 2012

以前に切り取られていたxlabelsに収まる十分な大きさの画像を取得するためにも機能します
Frederick Nord

1
これは、matplotlib.orgからのサンプルコードを含むドキュメントです
Yojimbo

14

これは別の非常に手動のソリューションです。軸のサイズを定義でき、パディングはそれに応じて考慮されます(凡例と目盛りを含む)。それが誰かに役立つことを願っています。

例(軸のサイズは同じです!):

ここに画像の説明を入力してください

コード:

#==================================================
# 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()
#--------------------------------------------------
#==================================================

これは、最初のplt.draw()をに変更するまで機能しませんでしたax.figure.canvas.draw()。理由はわかりませんが、この変更前は凡例のサイズが更新されていませんでした。
ws_e_c421 2016年

これをGUIウィンドウで使用する場合は、に変更fig.set_size_inches(widthTot,heightTot)する必要がありますfig.set_size_inches(widthTot,heightTot, forward=True)
ws_e_c421 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.