matplotlib 범례를 축 밖으로 이동하면 그림 상자로 잘립니다.


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'이 실제로 그림 상자 밖에 어떻게 표시되는지 확인하십시오 (게시 품질이 아니라 잘려 보이지 않음). 여기에 이미지 설명을 입력하십시오

마지막으로, 이것이 R과 LaTeX에서 정상적인 동작이라고 들었습니다. 그래서 파이썬에서 왜 그렇게 어려운지 조금 혼란 스럽습니다 ... 역사적인 이유가 있습니까? 이 문제에 대해 Matlab도 마찬가지로 열악합니까?

pastebin 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 옵션이 있어야합니다. 사용자가 우스운 수의 재 렌더링을 통해 제어 이름으로 스케일 번호를 얻으려고하면 반대의 결과가 나타납니다.
엘리엇

답변:


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가 계산에서 추가 아티스트를 고려하도록 허용합니다. 실제로 그림 상자의 크기를 원하는대로 조정했습니다.

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 만 이후 작동하는 것 같다 (데비안 스퀴즈가 0.99이 있고이없는 작업을 수행)
줄리앙 Palard

1
이 작업을 수행 할 수 없습니다. (lgfi에서 savefig로 전달했지만 여전히 크기가 조정되지 않습니다. 문제는 서브 플롯을 사용하지 않는 것일 수 있습니다.
6005

8
아! 방금했던 것처럼 bbox_inches = "tight"를 사용해야했습니다. 감사!
6005

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)그림 창을 M 인치 x N 인치로 설정합니다. 당신에게 잘 어울릴 때까지 이걸로 연주하십시오. 보다 확장 가능한 이미지 형식으로 변환하고 필요한 경우 김프를 사용하여 편집하거나 viewport그래픽을 포함 할 때 LaTeX 옵션으로 잘라내기만 하면됩니다.


자동 보고서 생성기에 적합한 솔루션이 아닌 '좋아 보일 때까지 재생'이 필요하지만 현재로서는 이것이 최상의 솔루션 인 것 같습니다. 나는 실제로이 솔루션을 이미 사용하고 있는데, 실제 문제는 matplotlib이 축의 bbox 외부에있는 범례를 동적으로 보상하지 않는다는 것입니다. @Joe 말했듯이 tight_layout 축, 제목 및 lables보다 더 많은 기능을 고려해야합니다. 이것을 matplotlib의 기능 요청으로 추가 할 수 있습니다.
jbbiomed

또한 이전에 잘려진 xlabel에 맞도록 충분히 큰 그림을 얻을 수 있도록 노력합니다
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

GUI 창에서이를 사용하려는 경우로 변경 fig.set_size_inches(widthTot,heightTot)해야 fig.set_size_inches(widthTot,heightTot, forward=True)합니다.
ws_e_c421
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.