การย้ายคำอธิบายแผนภูมิ matplotlib นอกแกนทำให้ตัดออกโดยกล่องรูป


222

ฉันคุ้นเคยกับคำถามต่อไปนี้:

Matplotlib บันทึกรูปที่มีตำนานนอกพล็อต

วิธีการนำตำนานออกจากพล็อต

ดูเหมือนว่าคำตอบในคำถามเหล่านี้มีความสามารถในการเล่นซอกับการหดตัวของแกนที่แน่นอนเพื่อให้ตำนานพอดี

อย่างไรก็ตามการลดขนาดของแกนไม่ใช่วิธีแก้ปัญหาในอุดมคติเพราะทำให้ข้อมูลมีขนาดเล็กลงทำให้ยากต่อการตีความ โดยเฉพาะอย่างยิ่งเมื่อมีความซับซ้อนและมีหลายสิ่งที่เกิดขึ้น ... จึงต้องมีตำนานที่ยิ่งใหญ่

ตัวอย่างของตำนานที่ซับซ้อนในเอกสารแสดงให้เห็นถึงความจำเป็นในการนี้เพราะตำนานในพล็อตของพวกเขาปิดบังจุดข้อมูลหลายจุดอย่างสมบูรณ์

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 คือ "ไม่ดีเท่า ๆ กัน" ในกรณีนี้) ในการทำอย่างถูกต้องคุณต้องกังวลเกี่ยวกับการปรับขนาดแกนทุกครั้งที่มีการปรับขนาดตัวเลขซูมหรือตำแหน่งของตำนาน (อย่างมีประสิทธิภาพหมายถึงการตรวจสอบทุกครั้งที่มีการวาดพล็อตซึ่งนำไปสู่การชะลอตัว) Ggplot ฯลฯ เป็นแบบคงที่ดังนั้นจึงเป็นสาเหตุที่พวกเขามักจะทำสิ่งนี้ตามค่าเริ่มต้นในขณะที่ matplotlib และ matlab ที่ได้รับการกล่าวว่าtight_layout()ควรมีการเปลี่ยนแปลงเพื่อพิจารณาตำนาน
Joe Kington

3
ฉันยังพูดคุยคำถามนี้ในรายการส่งเมลของผู้ใช้ matplotlib ดังนั้นฉันมีคำแนะนำในการปรับบรรทัด savefig เป็น: fig.savefig ('samplefigure', bbox_extra_artists = (lgd,), bbox = 'tight')
jbbiomed

3
ฉันรู้ว่า matplotlib ชอบโน้มน้าวว่าทุกอย่างอยู่ภายใต้การควบคุมของผู้ใช้ แต่สิ่งทั้งหมดนี้กับตำนานนั้นเป็นสิ่งที่ดีเกินไป ถ้าฉันวางตำนานไว้ข้างนอกฉันก็ต้องการให้มันมองเห็นได้ชัด หน้าต่างควรปรับขนาดให้พอดีแทนที่จะสร้างความยุ่งยากในการลดขนาดใหญ่อีกครั้ง อย่างน้อยที่สุดควรมีตัวเลือก True ที่เป็นค่าเริ่มต้นเพื่อควบคุมลักษณะการทำงานอัตโนมัติ บังคับให้ผู้ใช้ผ่านจำนวนการแสดงผลซ้ำเพื่อลองและรับจำนวนสเกลในชื่อของตัวควบคุมสำเร็จตรงข้าม
Elliot

คำตอบ:


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 (บีบ Debian มี 0.99 และสิ่งนี้ไม่ทำงาน)
Julien Palard

1
ไม่สามารถทำงานนี้ได้ :( ฉันผ่านไปยัง lgd เพื่อ 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))

และมันก็แสดงผลได้ดีบนหน้าจอของฉัน (จอภาพ CRT ขนาด 24 นิ้ว)

ที่นี่figsize=(M,N)กำหนดหน้าต่างรูปเป็น M นิ้ว N นิ้ว เพียงแค่เล่นกับสิ่งนี้จนกว่ามันจะเหมาะกับคุณ แปลงเป็นรูปแบบภาพที่ปรับขนาดได้และใช้ GIMP เพื่อแก้ไขหากจำเป็นหรือครอบตัดด้วยviewportตัวเลือกLaTeX เมื่อรวมถึงกราฟิก


ดูเหมือนว่านี่จะเป็นทางออกที่ดีที่สุดในเวลาปัจจุบันแม้ว่าจะยังต้องการ 'การเล่นจนกว่าจะดูดี' ซึ่งไม่ใช่วิธีแก้ปัญหาที่ดีสำหรับเครื่องกำเนิดไฟฟ้าอัตโนมัติ ฉันใช้โซลูชันนี้ไปแล้วจริง ๆ แล้วปัญหาที่แท้จริงคือ matplotlib ไม่ชดเชยความเชื่อในตำนานที่อยู่นอก bbox ของแกนแบบไดนามิก ดังที่ @Joe กล่าวว่า tight_layout ควรคำนึงถึงคุณสมบัติมากกว่าแค่แค่แกนชื่อเรื่องและฉลาก ฉันอาจเพิ่มสิ่งนี้เป็นคำขอคุณลักษณะบน matplotlib
jbbiomed

ยังใช้งานได้สำหรับผมที่จะได้รับภาพที่ใหญ่พอที่จะพอดีกับ xlabels ก่อนหน้านี้ถูกตัดออก
เฟรเดอริ 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.