2017-03-24 12 views
5

Tôi đang cố gắng tạo một số số liệu cho một bài báo khoa học, vì vậy tôi muốn các số liệu của tôi có kích thước cụ thể. Tôi cũng thấy rằng Matplotlib theo mặc định cho biết thêm rất nhiều đệm trên biên giới của các con số, mà tôi không cần (vì các con số sẽ được trên một nền trắng anyway).Tạo hình có kích thước chính xác và không có đệm (và chú thích bên ngoài trục)

Để đặt kích thước hình cụ thể, tôi chỉ cần sử dụng plt.figure(figsize = [w, h]) và tôi thêm đối số tight_layout = {'pad': 0} để loại bỏ phần đệm. Điều này hoạt động hoàn hảo và thậm chí hoạt động nếu tôi thêm tiêu đề, y/x-labels, v.v. Ví dụ:

fig = plt.figure(
    figsize = [3,2], 
    tight_layout = {'pad': 0} 
) 
ax = fig.add_subplot(111) 
plt.title('title') 
ax.set_ylabel('y label') 
ax.set_xlabel('x label') 
plt.savefig('figure01.pdf') 

Điều này tạo ra tệp pdf có kích thước chính xác 3x2 (inch).

figure01.png

Vấn đề là tôi có khi tôi ví dụ thêm một hộp văn bản bên ngoài trục (thường là một hộp huyền thoại), Matplotlib không nhường chỗ cho hộp văn bản như nó khi thêm tiêu đề/trục nhãn. Thông thường, hộp văn bản bị cắt hoặc không hiển thị trong hình đã lưu. Ví dụ:

plt.close('all') 
fig = plt.figure(
    figsize = [3,2], 
    tight_layout = {'pad': 0} 
) 
ax = fig.add_subplot(111) 
plt.title('title') 
ax.set_ylabel('y label') 
ax.set_xlabel('x label') 
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round')) 
plt.savefig('figure02.pdf') 

figure02.png

Một giải pháp tôi đã tìm thấy ở những nơi khác trên SO là để thêm đối số bbox_inches = 'tight' để lệnh savefig. Hộp văn bản bây giờ được bao gồm như tôi muốn, nhưng pdf bây giờ là kích thước sai. Có vẻ như Matplotlib chỉ làm cho hình lớn hơn, thay vì giảm kích thước của các trục giống như khi thêm tiêu đề và x/y-label.

Ví dụ:

plt.close('all') 
fig = plt.figure(
    figsize = [3,2], 
    tight_layout = {'pad': 0} 
) 
ax = fig.add_subplot(111) 
plt.title('title') 
ax.set_ylabel('y label') 
ax.set_xlabel('x label') 
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round')) 
plt.savefig('figure03.pdf', bbox_inches = 'tight') 

figure03.png

(Con số này là 3.307x2.248)

Có bất kỳ giải pháp này bao gồm hầu hết trường hợp với một huyền thoại ngay bên ngoài các trục?

Trả lời

5

Vì vậy, yêu cầu là:

  1. Có cố định, được xác định trước con số kích thước
  2. Thêm một nhãn văn bản hoặc truyền thuyết bên ngoài các trục
  3. Axes và văn bản có thể không chồng chéo
  4. Các trục, cùng với nhãn tiêu đề và trục, ngồi chặt lại đường viền hình.

Vì vậy tight_layout với pad = 0, giải quyết 1. và 4. nhưng mâu thuẫn 2.

Người ta có thể nghĩ về thiết pad đến một giá trị lớn hơn. Điều này sẽ giải quyết 2. Tuy nhiên, vì nó là đối xứng trong tất cả các hướng, nó sẽ mâu thuẫn 4.

Sử dụng bbox_inches = 'tight' thay đổi kích thước hình. Mâu thuẫn 1.

Vì vậy, tôi nghĩ rằng không có giải pháp chung cho vấn đề này.

Điều tôi có thể đưa ra như sau: Nó đặt văn bản trong tọa độ hình và sau đó thay đổi kích thước trục theo chiều ngang hoặc theo chiều dọc sao cho không có sự chồng chéo giữa các trục và văn bản.

import matplotlib.pyplot as plt 
import matplotlib.transforms 

fig = plt.figure(figsize = [3,2]) 
ax = fig.add_subplot(111) 
plt.title('title') 
ax.set_ylabel('y label') 
ax.set_xlabel('x label') 

def text_legend(ax, x0, y0, text, direction = "v", padpoints = 3, margin=1.,**kwargs): 
    ha = kwargs.pop("ha", "right") 
    va = kwargs.pop("va", "top") 
    t = ax.figure.text(x0, y0, text, ha=ha, va=va, **kwargs) 
    otrans = ax.figure.transFigure 

    plt.tight_layout(pad=0) 
    ax.figure.canvas.draw() 
    plt.tight_layout(pad=0) 
    offs = t._bbox_patch.get_boxstyle().pad * t.get_size() + margin # adding 1pt 
    trans = otrans + \ 
      matplotlib.transforms.ScaledTranslation(-offs/72.,-offs/72.,fig.dpi_scale_trans) 
    t.set_transform(trans) 
    ax.figure.canvas.draw() 

    ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0] 
    trans2 = matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans) + \ 
      ax.figure.transFigure.inverted() 
    tbox = trans2.transform(t._bbox_patch.get_window_extent()) 
    bbox = ax.get_position() 
    if direction=="v": 
     ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox[0][1]-bbox.y0]) 
    else: 
     ax.set_position([bbox.x0, bbox.y0,tbox[0][0]-bbox.x0, bbox.height]) 

# case 1: place text label at top right corner of figure (1,1). Adjust axes height. 
#text_legend(ax, 1,1, 'my text here', bbox = dict(boxstyle = 'round'),) 

# case 2: place text left of axes, (1, y), direction=="v" 
text_legend(ax, 1., 0.8, 'my text here', margin=2., direction="h", bbox = dict(boxstyle = 'round')) 

plt.savefig(__file__+'.pdf') 
plt.show() 

trường hợp 1 (trái) và trường hợp 2 (bên phải):
enter image description here enter image description here


Doin cùng với một huyền thoại là hơi dễ dàng hơn, vì chúng ta có thể trực tiếp sử dụng các bbox_to_anchor luận và don 't cần phải kiểm soát hộp ưa thích xung quanh huyền thoại.

import matplotlib.pyplot as plt 
import matplotlib.transforms 

fig = plt.figure(figsize = [3.5,2]) 
ax = fig.add_subplot(111) 
ax.set_title('title') 
ax.set_ylabel('y label') 
ax.set_xlabel('x label') 
ax.plot([1,2,3], marker="o", label="quantity 1") 
ax.plot([2,1.7,1.2], marker="s", label="quantity 2") 

def legend(ax, x0=1,y0=1, direction = "v", padpoints = 3,**kwargs): 
    otrans = ax.figure.transFigure 
    t = ax.legend(bbox_to_anchor=(x0,y0), loc=1, bbox_transform=otrans,**kwargs) 
    plt.tight_layout(pad=0) 
    ax.figure.canvas.draw() 
    plt.tight_layout(pad=0) 
    ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0] 
    trans2=matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans)+\ 
      ax.figure.transFigure.inverted() 
    tbox = t.get_window_extent().transformed(trans2) 
    bbox = ax.get_position() 
    if direction=="v": 
     ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox.y0-bbox.y0]) 
    else: 
     ax.set_position([bbox.x0, bbox.y0,tbox.x0-bbox.x0, bbox.height]) 

# case 1: place text label at top right corner of figure (1,1). Adjust axes height. 
#legend(ax, borderaxespad=0) 
# case 2: place text left of axes, (1, y), direction=="h" 
legend(ax,y0=0.8, direction="h", borderaxespad=0.2) 

plt.savefig(__file__+'.pdf') 
plt.show() 

enter image description here enter image description here


Tại sao 72? Số 72 là số điểm trên mỗi inch (ppi). Đây là một đơn vị typographic cố định, ví dụ: fontsizes luôn được đưa ra trong các điểm (như 12pt). Bởi vì matplotlib định nghĩa phần đệm của hộp văn bản trong các đơn vị liên quan đến phông chữ, là điểm, chúng ta cần sử dụng 72 để chuyển đổi thành inch (và sau đó hiển thị tọa độ). Các chấm mặc định trên mỗi inch (dpi) không được chạm vào đây, nhưng được tính trong fig.dpi_scale_trans. Nếu bạn muốn thay đổi dpi, bạn cần phải chắc chắn rằng hình dpi được thiết lập khi tạo hình cũng như khi lưu nó (sử dụng dpi=.. trong cuộc gọi đến plt.figure() cũng như plt.savefig()).

+0

Cảm ơn, điều này thật tuyệt vời! Bạn có nhớ giải thích những gì mã cứng '72' xuất hiện ở một số nơi không? Tôi đoán đây là dpi mặc định, nhưng tôi không chắc chắn. –

+0

Tôi đã thêm một số giải thích cho câu trả lời. – ImportanceOfBeingErnest

+0

Xin cảm ơn một lần nữa. Có cách nào dễ dàng để thực hiện công việc này với truyền thuyết không? I E. khi đặt một huyền thoại bên ngoài các trục. –

Các vấn đề liên quan