Matplotlib göstergesini eksenin dışına taşımak, şekil kutusuyla kesilmesini sağlar


222

Aşağıdaki sorulara aşinayım:

Arsa dışında bir açıklama ile Matplotlib savefig

Efsaneyi arsadan nasıl çıkarabilirim?

Bu soruların cevaplarının, efsanenin uyması için eksenin tam olarak daralmasıyla uğraşma lüksüne sahip olduğu anlaşılıyor.

Bununla birlikte, eksenleri küçültmek ideal bir çözüm değildir, çünkü verileri küçülterek yorumlanmasını zorlaştırır; özellikle karmaşık ve çok şey oluyor ... dolayısıyla büyük bir efsaneye ihtiyaç duyulduğunda

Belgelerdeki karmaşık bir efsane örneği buna ihtiyaç olduğunu gösterir, çünkü arsalarındaki efsane aslında birden fazla veri noktasını tamamen gizler.

http://matplotlib.sourceforge.net/users/legend_guide.html#legend-of-complex-plots

Yapabileceğim şey, genişleyen şekil göstergesini karşılamak için şekil kutusunun boyutunu dinamik olarak genişletmektir.

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

Son etiketin 'Ters bronzluğun' aslında şekil kutusunun dışında olduğuna dikkat edin (ve yayın kalitesinde değil, kötü bir şekilde kesilmiş görünüyor!) resim açıklamasını buraya girin

Sonunda, bunun R ve LaTeX'te normal bir davranış olduğu söylendi, bu yüzden python'da bunun neden bu kadar zor olduğunu biraz kafam karıştı ... Tarihsel bir sebep var mı? Matlab bu konuda eşit derecede zayıf mı?

Bu kodun pastebin üzerinde (sadece biraz) daha uzun versiyonuna sahibim http://pastebin.com/grVjc007


10
Neden olduğu kadarıyla, matplotlib etkileşimli çizimlere yönelik olduğu için, R, vb. (Ve evet, Matlab bu durumda "eşit derecede zayıf".) Düzgün yapabilmek için, şekil her yeniden boyutlandırıldığında, yakınlaştırıldığında veya efsanenin konumu her güncellendiğinde eksenleri yeniden boyutlandırmak konusunda endişelenmeniz gerekir. (Etkili olarak, bu, çizimin her çizilişini kontrol etmek anlamına gelir, bu da yavaşlamalara neden olur.) Ggplot, vb. Statiktir, bu yüzden bunu varsayılan olarak yapma eğilimindedirler, ancak matplotlib ve matlab bunu yapmaz. Söylenenler, tight_layout()efsaneleri dikkate alacak şekilde değiştirilmelidir.
Joe Kington

3
Ben de bu soruyu matplotlib kullanıcılarının posta listesinde tartışıyorum. Bu yüzden savefig satırını ayarlama önerileri var: fig.savefig ('samplefigure', bbox_extra_artists = (lgd,), bbox = 'sıkı')
jbbiomed 13:12

3
Matplotlib'in her şeyin kullanıcının kontrolü altında olduğunu sevdiğini biliyorum, ancak efsanelerle ilgili tüm bu şey çok iyi bir şey. Efsaneyi dışarıya koyarsam, açıkça görünmesini istiyorum. Pencere, bu büyük kireç çözme güçlüğü yaratmak yerine kendisini sığacak şekilde ölçeklendirmelidir. En azından bu otomatik ölçekleme davranışını denetlemek için varsayılan bir True seçeneği olmalıdır. Kullanıcıları, kontrol adındaki ölçek numaralarını elde etmeye çalışmak için gülünç sayıda yeniden oluşturucudan geçmeye zorlamak tam tersini başarır.
Elliot

Yanıtlar:


300

Üzgünüm EMS, ama aslında matplotlib posta listesinden başka bir yanıt aldım (Teşekkürler Benjamin Root'a gider).

Aradığım kod savefig çağrısını şu şekilde ayarlıyor:

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

Bu görünüşe göre tight_layout çağrısına benzer, ancak bunun yerine savefig dosyasındaki hesaplamada fazladan sanatçı görmesini sağlarsınız. Bu aslında şekil kutusunu istendiği gibi yeniden boyutlandırdı.

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

Bu şunu üretir:

[değiştir] Bu sorunun amacı, bu sorunlara geleneksel çözüm olarak keyfi metin keyfi keyfi koordinat yerleşimlerini kullanmaktan tamamen kaçınmaktı. Buna rağmen, son zamanlarda çok sayıda düzenleme, bunları kodun bir hata yaratmasına yol açan şekillerde koymakta ısrar etti. Şimdi sorunları düzelttim ve bunların da bbox_extra_artists algoritması içinde nasıl değerlendirildiğini göstermek için rastgele metin tidied.


1
/! \ Yalnızca matplotlib> = 1.0'dan beri çalışıyor gibi görünüyor (Debian sıkması 0,99 var ve bu çalışmıyor)
Julien Palard

1
Çalışmak için bu alınamıyor :( lgd kaydetmek savefig geçmek ama yine de yeniden boyutlandırmıyor. Sorun bir alt parsel kullanmıyorum olabilir.
6005

8
Ah! Sadece senin gibi bbox_inches = "sıkı" kullanmak gerekiyordu. Teşekkürler!
6005

7
Bu güzel, ama denediğimde figürümü hala kesiyorum plt.show(). Bunun için herhangi bir düzeltme var mı?
Agostino


23

Eklendi: Hemen hile yapması gereken bir şey buldum, ancak aşağıdaki kodun geri kalanı da bir alternatif sunuyor.

subplots_adjust()Alt grafiğin altını yukarı taşımak için işlevi kullanın :

fig.subplots_adjust(bottom=0.2) # <-- Change the 0.02 to work for your plot.

Ardından bbox_to_anchor, açıklama kutusunu istediğiniz yere götürmek için açıklama komutunun açıklama bölümünde yer alan ofset ile oynayın . Ayarlama figsizeve kullanma yöntemlerinden bazılarının subplots_adjust(bottom=...)sizin için kaliteli bir çizim oluşturması gerekir.

Alternatif: Sadece çizgiyi değiştirdim:

fig = plt.figure(1)

için:

fig = plt.figure(num=1, figsize=(13, 13), dpi=80, facecolor='w', edgecolor='k')

ve değişti

lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))

için

lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,-0.02))

ve ekranımda iyi görünüyor (24 inç CRT monitör).

Burada figsize=(M,N)şekil penceresini M inç x N inç olarak ayarlar. Sizin için doğru görünene kadar bununla oynayın. Daha ölçeklenebilir bir görüntü formatına dönüştürün ve gerekirse düzenlemek için GIMP kullanın veya viewportgrafik eklerken LaTeX seçeneğiyle kırpın .


Görünüşe göre bu, o zamanki en iyi çözümdür, yine de 'iyi görünene kadar oynamak' gerektirir, ki bu bir autoreport jeneratörü için iyi bir çözüm değildir. Aslında bu çözümü zaten kullanıyorum, asıl sorun, matplotlib'in efsanenin ekseninin bbox'ı dışında olduğu için dinamik olarak telafi etmemesidir. @Joe dediği gibi, tight_layout gerektiğini daha dikkate almak sadece eksen, başlıklar ve lables daha bulunmaktadır. Bunu matplotlib'de bir özellik isteği olarak ekleyebilirim.
jbbiomed

Ayrıca daha önce kesilmiş xlabels uyacak kadar büyük bir resim elde etmek için çalışıyor
Frederick Nord

1
İşte matplotlib.org örnek kodlu belgeler
Yojimbo

14

İşte başka, çok manuel bir çözüm. Eksenin boyutunu tanımlayabilirsiniz ve dolgular buna göre dikkate alınır (gösterge ve işaret işaretleri dahil). Umarım birisi için yararlıdır.

Örnek (eksen boyutu aynıdır!):

resim açıklamasını buraya girin

Kod:

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

İlk değişti kadar bu iş için beni vermedi plt.draw()için ax.figure.canvas.draw(). Neden olduğundan emin değilim, ancak bu değişiklikten önce efsane boyutu güncellenmiyordu.
ws_e_c421

Bunu bir GUI penceresinde kullanmaya çalışıyorsanız, olarak değiştirmeniz fig.set_size_inches(widthTot,heightTot)gerekir fig.set_size_inches(widthTot,heightTot, forward=True).
ws_e_c421
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.