Cập nhật: Người dùng cphyc đã vui lòng tạo một kho lưu trữ Github cho mã trong câu trả lời này (xem tại đây ) và gói mã vào một gói có thể được cài đặt bằng cách sử dụng pip install matplotlib-label-lines
.
Bức tranh đẹp:
Trong matplotlib
đó, khá dễ dàng để gắn nhãn các ô đường viền (tự động hoặc bằng cách đặt nhãn theo cách thủ công với các cú nhấp chuột). Dường như chưa (chưa) có bất kỳ khả năng tương đương nào để gắn nhãn chuỗi dữ liệu theo kiểu này! Có thể có một số lý do ngữ nghĩa cho việc không bao gồm tính năng này mà tôi đang thiếu.
Bất kể, tôi đã viết mô-đun sau đây có bất kỳ cho phép ghi nhãn ô bán tự động. Nó chỉ yêu cầu numpy
và một vài chức năng từ math
thư viện chuẩn .
Sự miêu tả
Hoạt động mặc định của labelLines
hàm là tạo khoảng cách cho các nhãn đồng đều dọc theo x
trục (tự động đặt đúngy
dĩ nhiên là trị ). Nếu bạn muốn, bạn có thể chỉ cần chuyển một mảng các tọa độ x của mỗi nhãn. Bạn thậm chí có thể điều chỉnh vị trí của một nhãn (như được hiển thị trong ô dưới cùng bên phải) và tạo khoảng trống cho phần còn lại một cách đồng đều nếu bạn muốn.
Ngoài ra, label_lines
chức năng này không tính đến các dòng chưa được gán nhãn trong plot
lệnh (hoặc chính xác hơn nếu nhãn có chứa '_line'
).
Các đối số từ khóa được chuyển đến labelLines
hoặc labelLine
được chuyển cho lệnh text
gọi hàm (một số đối số từ khóa được đặt nếu mã gọi chọn không chỉ định).
Vấn đề
- Các hộp giới hạn chú thích đôi khi can thiệp không mong muốn với các đường cong khác. Như được hiển thị bởi
1
và 10
chú thích trong biểu đồ trên cùng bên trái. Tôi thậm chí không chắc điều này có thể tránh được.
- Sẽ rất tốt nếu bạn chỉ định một
y
vị trí thay vì đôi khi.
- Nó vẫn là một quá trình lặp đi lặp lại để có được các chú thích ở đúng vị trí
- Nó chỉ hoạt động khi các
x
giá trị -axis là float
s
Gotchas
- Theo mặc định,
labelLines
hàm giả định rằng tất cả các chuỗi dữ liệu nằm trong phạm vi được chỉ định bởi các giới hạn trục. Hãy nhìn vào đường cong màu xanh lam ở ô trên cùng bên trái của bức tranh đẹp. Nếu chỉ có sẵn dữ liệu cho x
phạm vi 0.5
- 1
thì chúng tôi không thể đặt nhãn ở vị trí mong muốn (nhỏ hơn một chút 0.2
). Xem câu hỏi này cho một ví dụ đặc biệt khó chịu. Hiện tại, mã không xác định tình huống này một cách thông minh và sắp xếp lại các nhãn, tuy nhiên, có một cách giải quyết hợp lý. Hàm labelLines nhận xvals
đối số; danh sáchx
-giá trị do người dùng chỉ định thay vì phân phối tuyến tính mặc định theo chiều rộng. Vì vậy, người dùng có thể quyết địnhx
-giá trị để sử dụng cho vị trí nhãn của từng chuỗi dữ liệu.
Ngoài ra, tôi tin rằng đây là câu trả lời đầu tiên để hoàn thành mục tiêu thưởng là căn chỉnh các nhãn với đường cong mà chúng nằm trên. :)
label_lines.py:
from math import atan2,degrees
import numpy as np
#Label line with line2D label data
def labelLine(line,x,label=None,align=True,**kwargs):
ax = line.axes
xdata = line.get_xdata()
ydata = line.get_ydata()
if (x < xdata[0]) or (x > xdata[-1]):
print('x label location is outside data range!')
return
#Find corresponding y co-ordinate and angle of the line
ip = 1
for i in range(len(xdata)):
if x < xdata[i]:
ip = i
break
y = ydata[ip-1] + (ydata[ip]-ydata[ip-1])*(x-xdata[ip-1])/(xdata[ip]-xdata[ip-1])
if not label:
label = line.get_label()
if align:
#Compute the slope
dx = xdata[ip] - xdata[ip-1]
dy = ydata[ip] - ydata[ip-1]
ang = degrees(atan2(dy,dx))
#Transform to screen co-ordinates
pt = np.array([x,y]).reshape((1,2))
trans_angle = ax.transData.transform_angles(np.array((ang,)),pt)[0]
else:
trans_angle = 0
#Set a bunch of keyword arguments
if 'color' not in kwargs:
kwargs['color'] = line.get_color()
if ('horizontalalignment' not in kwargs) and ('ha' not in kwargs):
kwargs['ha'] = 'center'
if ('verticalalignment' not in kwargs) and ('va' not in kwargs):
kwargs['va'] = 'center'
if 'backgroundcolor' not in kwargs:
kwargs['backgroundcolor'] = ax.get_facecolor()
if 'clip_on' not in kwargs:
kwargs['clip_on'] = True
if 'zorder' not in kwargs:
kwargs['zorder'] = 2.5
ax.text(x,y,label,rotation=trans_angle,**kwargs)
def labelLines(lines,align=True,xvals=None,**kwargs):
ax = lines[0].axes
labLines = []
labels = []
#Take only the lines which have labels other than the default ones
for line in lines:
label = line.get_label()
if "_line" not in label:
labLines.append(line)
labels.append(label)
if xvals is None:
xmin,xmax = ax.get_xlim()
xvals = np.linspace(xmin,xmax,len(labLines)+2)[1:-1]
for line,x,label in zip(labLines,xvals,labels):
labelLine(line,x,label,align,**kwargs)
Mã thử nghiệm để tạo ra hình ảnh đẹp ở trên:
from matplotlib import pyplot as plt
from scipy.stats import loglaplace,chi2
from labellines import *
X = np.linspace(0,1,500)
A = [1,2,5,10,20]
funcs = [np.arctan,np.sin,loglaplace(4).pdf,chi2(5).pdf]
plt.subplot(221)
for a in A:
plt.plot(X,np.arctan(a*X),label=str(a))
labelLines(plt.gca().get_lines(),zorder=2.5)
plt.subplot(222)
for a in A:
plt.plot(X,np.sin(a*X),label=str(a))
labelLines(plt.gca().get_lines(),align=False,fontsize=14)
plt.subplot(223)
for a in A:
plt.plot(X,loglaplace(4).pdf(a*X),label=str(a))
xvals = [0.8,0.55,0.22,0.104,0.045]
labelLines(plt.gca().get_lines(),align=False,xvals=xvals,color='k')
plt.subplot(224)
for a in A:
plt.plot(X,chi2(5).pdf(a*X),label=str(a))
lines = plt.gca().get_lines()
l1=lines[-1]
labelLine(l1,0.6,label=r'$Re=${}'.format(l1.get_label()),ha='left',va='bottom',align = False)
labelLines(lines[:-1],align=False)
plt.show()
plt.plot(x2, 3*x2**2, label="3x*x"); plt.plot(x2, 2*x2**2, label="2x*x"); plt.plot(x2, 0.5*x2**2, label="0.5x*x"); plt.plot(x2, -1*x2**2, label="-x*x"); plt.plot(x2, -2.5*x2**2, label="-2.5*x*x"); my_legend();
Điều này đặt một trong các nhãn ở góc trên bên trái. có ý tưởng nào để sửa cái này không? Có vẻ như vấn đề có thể là các dòng quá gần nhau.