Di chuyển huyền thoại matplotlib bên ngoài trục làm cho nó bị cắt bởi hộp hình


222

Tôi quen thuộc với các câu hỏi sau:

Matplotlib savefig với một huyền thoại bên ngoài cốt truyện

Làm thế nào để đưa huyền thoại ra khỏi cốt truyện

Dường như các câu trả lời trong những câu hỏi này có sự xa xỉ trong việc có thể mân mê với sự thu hẹp chính xác của trục để truyền thuyết phù hợp.

Tuy nhiên, thu hẹp các trục không phải là một giải pháp lý tưởng vì nó làm cho dữ liệu nhỏ hơn khiến nó thực sự khó diễn giải hơn; đặc biệt là khi nó phức tạp và có rất nhiều thứ đang diễn ra ... do đó cần một huyền thoại lớn

Ví dụ về một huyền thoại phức tạp trong tài liệu chứng minh sự cần thiết của điều này bởi vì huyền thoại trong cốt truyện của họ thực sự che khuất hoàn toàn nhiều điểm dữ liệu.

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

Những gì tôi muốn có thể làm là tự động mở rộng kích thước của hộp hình để phù hợp với huyền thoại hình mở rộng.

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

Lưu ý rằng nhãn cuối cùng 'Đảo ngược tan' thực sự nằm ngoài hộp hình (và trông có vẻ bị cắt giảm - không phải là chất lượng xuất bản!) nhập mô tả hình ảnh ở đây

Cuối cùng, tôi đã được thông báo rằng đây là hành vi bình thường trong R và LaTeX, vì vậy tôi hơi bối rối tại sao điều này lại khó khăn như vậy ở trăn ... Có lý do lịch sử nào không? Là Matlab nghèo như nhau về vấn đề này?

Tôi có phiên bản dài hơn (chỉ một chút) của mã này trên pastebin http://pastebin.com/grVjc007


10
Theo như lý do là vì matplotlib hướng đến các âm mưu tương tác, trong khi R, v.v., thì không. (Và vâng, Matlab "nghèo như nhau" trong trường hợp cụ thể này.) Để thực hiện đúng cách, bạn cần lo lắng về việc thay đổi kích thước các trục mỗi khi hình được thay đổi kích thước, phóng to hoặc vị trí của chú giải được cập nhật. (Thực tế, điều này có nghĩa là kiểm tra mỗi khi cốt truyện được vẽ, dẫn đến chậm lại.) Ggplot, v.v., là tĩnh, vì vậy đó là lý do tại sao họ có xu hướng làm điều này theo mặc định, trong khi matplotlib và matlab thì không. Điều đó đã được nói, tight_layout()nên được thay đổi để đưa vào truyền thuyết.
Joe Kington

3
Tôi cũng đang thảo luận về câu hỏi này trong danh sách gửi thư của người dùng matplotlib. Vì vậy, tôi có đề xuất điều chỉnh dòng savefig thành: fig.savefig ('samplefigure', bbox_extra_artists = (lgd,), bbox = 'chặt chẽ')
jbbiomed

3
Tôi biết matplotlib thích quảng cáo rằng mọi thứ đều nằm trong tầm kiểm soát của người dùng, nhưng toàn bộ điều này với các huyền thoại là quá nhiều điều tốt. Nếu tôi đặt huyền thoại bên ngoài, rõ ràng tôi muốn nó vẫn được nhìn thấy. Cửa sổ chỉ nên tự điều chỉnh cho phù hợp thay vì tạo ra rắc rối thay đổi kích thước khổng lồ này. Ít nhất nên có một tùy chọn True mặc định để kiểm soát hành vi tự động hóa này. Buộc người dùng phải trải qua một số lần tái xuất vô lý để thử và lấy số tỷ lệ ngay trong tên của điều khiển hoàn thành ngược lại.
Elliot

Câu trả lời:


300

Xin lỗi EMS, nhưng tôi thực sự vừa nhận được phản hồi khác từ danh sách giết người matplotlib (Cảm ơn đi ra ngoài với Root Root).

Mã tôi đang tìm là điều chỉnh lệnh gọi savefig thành:

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

Điều này rõ ràng tương tự như cách gọi chặt chẽ_layout, nhưng thay vào đó, bạn cho phép savefig xem xét các nghệ sĩ bổ sung trong phép tính. Điều này thực tế đã thay đổi kích thước hộp hình như mong muốn.

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

Điều này tạo ra:

[sửa] Mục đích của câu hỏi này là tránh hoàn toàn việc sử dụng các vị trí tọa độ tùy ý của văn bản tùy ý như là giải pháp truyền thống cho những vấn đề này. Mặc dù vậy, rất nhiều chỉnh sửa gần đây đã khăng khăng đưa những thứ này vào, thường theo những cách dẫn đến mã gây ra lỗi. Bây giờ tôi đã sửa các vấn đề và dọn dẹp văn bản tùy ý để chỉ ra cách chúng cũng được xem xét trong thuật toán bbox_extra_artists.


1
/! \ Dường như chỉ hoạt động kể từ matplotlib> = 1.0 (nén Debian có 0,99 và điều này không hoạt động)
Julien Palard

1
Không thể làm điều này hoạt động :( Tôi chuyển qua lgd để lưu lại nhưng nó vẫn không thay đổi kích thước. Vấn đề có thể là tôi không sử dụng một subplot.
6005

8
Ah! Tôi chỉ cần sử dụng bbox_inches = "chặt chẽ" như bạn đã làm. Cảm ơn!
6005

7
Điều này là tốt, nhưng tôi vẫn bị cắt giảm hình khi tôi cố gắng plt.show(). Bất kỳ sửa chữa cho điều đó?
Agostino


23

Đã thêm: Tôi đã tìm thấy một cái gì đó nên thực hiện thủ thuật ngay lập tức, nhưng phần còn lại của mã dưới đây cũng cung cấp một giải pháp thay thế.

Sử dụng subplots_adjust()chức năng để di chuyển phần dưới của subplot lên:

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

Sau đó chơi với phần bù trong phần huyền thoại bbox_to_anchorcủa lệnh huyền thoại, để lấy hộp huyền thoại nơi bạn muốn. Một số kết hợp của việc thiết lập figsizevà sử dụng subplots_adjust(bottom=...)sẽ tạo ra một âm mưu chất lượng cho bạn.

Thay thế: Tôi chỉ đơn giản là thay đổi dòng:

fig = plt.figure(1)

đến:

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

và thay đổi

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

đến

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

và nó hiển thị tốt trên màn hình của tôi (màn hình CRT 24 inch).

Ở đây figsize=(M,N)đặt cửa sổ hình là M inch bằng N inch. Chỉ cần chơi với điều này cho đến khi nó phù hợp với bạn. Chuyển đổi nó thành định dạng hình ảnh có thể mở rộng hơn và sử dụng GIMP để chỉnh sửa nếu cần hoặc chỉ cắt với viewporttùy chọn LaTeX khi bao gồm đồ họa.


Có vẻ như đây là giải pháp tốt nhất ở thời điểm hiện tại, mặc dù nó vẫn yêu cầu 'chơi cho đến khi nó trông ổn', đó không phải là giải pháp tốt cho trình tạo tự động. Tôi thực sự đã sử dụng giải pháp này, vấn đề thực sự là matplotlib không tự động bù đắp cho truyền thuyết nằm ngoài hộp của trục. Như @Joe đã nói, chặt chẽ_layout nên tính đến nhiều tính năng hơn chỉ là trục, tiêu đề và lables. Tôi có thể thêm điều này như một yêu cầu tính năng trên matplotlib.
jbbiomed

cũng hoạt động để tôi có được một bức ảnh đủ lớn để phù hợp với các xlabels trước đây đã bị cắt
Frederick Nord

1
đây là tài liệu với mã ví dụ từ matplotlib.org
Yojimbo

14

Đây là một giải pháp rất thủ công. Bạn có thể xác định kích thước của trục và phần đệm được xem xét tương ứng (bao gồm cả chú thích và dấu tích). Hy vọng nó có ích cho ai đó.

Ví dụ (kích thước trục là như nhau!):

nhập mô tả hình ảnh ở đây

Mã số:

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

Điều này đã không làm việc cho tôi cho đến khi tôi thay đổi người đầu tiên plt.draw()đến ax.figure.canvas.draw(). Tôi không chắc tại sao, nhưng trước khi thay đổi, kích thước chú giải không được cập nhật.
ws_e_c421

Nếu bạn đang cố gắng sử dụng điều này trên cửa sổ GUI, bạn cần thay đổi fig.set_size_inches(widthTot,heightTot)thành fig.set_size_inches(widthTot,heightTot, forward=True).
ws_e_c421
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.