2012-01-13 84 views
35

Tôi muốn chú thích các thanh trong biểu đồ bằng một số văn bản nhưng nếu các thanh nằm gần nhau và có chiều cao tương đương, các chú thích ở trên ea. khác và do đó khó đọc (các tọa độ cho các chú thích được lấy từ vị trí và chiều cao của thanh).Chú thích chồng chéo Matplotlib

Có cách nào để thay đổi một trong số chúng nếu có va chạm không?

Edit: Các thanh rất mỏng và rất gần đôi khi vì vậy chỉ cần sắp xếp theo chiều dọc không giải quyết được vấn đề ...

Một bức tranh có thể làm sáng tỏ điều: bar pattern

Trả lời

43

Tôi đã viết giải pháp nhanh, kiểm tra từng vị trí chú thích đối với các hộp giới hạn mặc định cho tất cả các chú thích khác. Nếu có va chạm, nó sẽ thay đổi vị trí của nó sang vị trí xung đột có sẵn miễn phí tiếp theo. Nó cũng đặt trong mũi tên đẹp.

Đối với một ví dụ khá cực đoan, nó sẽ sản xuất này (không ai trong số những con số trùng nhau): enter image description here

Thay vì điều này: enter image description here

Đây là mã:

import numpy as np 
import matplotlib.pyplot as plt 
from numpy.random import * 

def get_text_positions(x_data, y_data, txt_width, txt_height): 
    a = zip(y_data, x_data) 
    text_positions = y_data.copy() 
    for index, (y, x) in enumerate(a): 
     local_text_positions = [i for i in a if i[0] > (y - txt_height) 
          and (abs(i[1] - x) < txt_width * 2) and i != (y,x)] 
     if local_text_positions: 
      sorted_ltp = sorted(local_text_positions) 
      if abs(sorted_ltp[0][0] - y) < txt_height: #True == collision 
       differ = np.diff(sorted_ltp, axis=0) 
       a[index] = (sorted_ltp[-1][0] + txt_height, a[index][1]) 
       text_positions[index] = sorted_ltp[-1][0] + txt_height 
       for k, (j, m) in enumerate(differ): 
        #j is the vertical distance between words 
        if j > txt_height * 2: #if True then room to fit a word in 
         a[index] = (sorted_ltp[k][0] + txt_height, a[index][1]) 
         text_positions[index] = sorted_ltp[k][0] + txt_height 
         break 
    return text_positions 

def text_plotter(x_data, y_data, text_positions, axis,txt_width,txt_height): 
    for x,y,t in zip(x_data, y_data, text_positions): 
     axis.text(x - txt_width, 1.01*t, '%d'%int(y),rotation=0, color='blue') 
     if y != t: 
      axis.arrow(x, t,0,y-t, color='red',alpha=0.3, width=txt_width*0.1, 
         head_width=txt_width, head_length=txt_height*0.5, 
         zorder=0,length_includes_head=True) 

Dưới đây là mã sản xuất các ô này, cho biết cách sử dụng:

#random test data: 
x_data = random_sample(100) 
y_data = random_integers(10,50,(100)) 

#GOOD PLOT: 
fig2 = plt.figure() 
ax2 = fig2.add_subplot(111) 
ax2.bar(x_data, y_data,width=0.00001) 
#set the bbox for the text. Increase txt_width for wider text. 
txt_height = 0.04*(plt.ylim()[1] - plt.ylim()[0]) 
txt_width = 0.02*(plt.xlim()[1] - plt.xlim()[0]) 
#Get the corrected text positions, then write the text. 
text_positions = get_text_positions(x_data, y_data, txt_width, txt_height) 
text_plotter(x_data, y_data, text_positions, ax2, txt_width, txt_height) 

plt.ylim(0,max(text_positions)+2*txt_height) 
plt.xlim(-0.1,1.1) 

#BAD PLOT: 
fig = plt.figure() 
ax = fig.add_subplot(111) 
ax.bar(x_data, y_data, width=0.0001) 
#write the text: 
for x,y in zip(x_data, y_data): 
    ax.text(x - txt_width, 1.01*y, '%d'%int(y),rotation=0) 
plt.ylim(0,max(text_positions)+2*txt_height) 
plt.xlim(-0.1,1.1) 

plt.show() 
+0

khá tốt đẹp. Có cách nào để khái quát hóa điều này lên grafics không phải thanh không? Tôi đang cố gắng chú thích một scatterplot, và tự nhiên nó sẽ là tốt đẹp nếu khoảng cách của các mũi tên đã được giảm thiểu, quá. Cũng có thể giảm thiểu số lượng mũi tên đi qua các con số không? – tarrasch

+0

@tarrasch - Nguyên tắc này sẽ hoạt động tốt cho bất kỳ loại âm mưu nào. Hy vọng rằng tôi sẽ có thời gian để gõ mã vào hình dạng hấp dẫn hơn trong vài ngày tới (nó cần phải được khái quát hóa, như tôi đã đề cập). Khoảng cách của các mũi tên có thể được giảm một chút (thay đổi '2 * L' thành' L'), nhưng các loại mũi tên phải đi qua các con số đôi khi (nó sẽ bắt đầu nhận được rất nhiều phức tạp hơn để tránh điều đó), tuy nhiên nếu bạn thay đổi cài đặt mũi tên 'alpha' thành' alpha = 0.3' và văn bản 'màu' thành màu xanh dương, ô sẽ bắt đầu trông đẹp hơn. – fraxel

+0

đẹp! Tôi sẽ thử nó chiều nay :) – tarrasch

7

Một lựa chọn là xoay văn bản/chú thích, được đặt bởi từ khóa/thuộc tính rotation. Trong ví dụ sau, tôi xoay văn bản 90 độ để đảm bảo rằng nó sẽ không va chạm với văn bản lân cận. Tôi cũng đặt va (viết tắt của verticalalignment) từ khóa, do đó văn bản được trình bày ở trên quầy bar (trên điểm mà tôi sử dụng để xác định văn bản):

import matplotlib.pyplot as plt 

data = [10, 8, 8, 5] 

fig = plt.figure() 
ax = fig.add_subplot(111) 
ax.bar(range(4),data) 
ax.set_ylim(0,12) 
# extra .4 is because it's half the default width (.8): 
ax.text(1.4,8,"2nd bar",rotation=90,va='bottom') 
ax.text(2.4,8,"3nd bar",rotation=90,va='bottom') 

plt.show() 

Kết quả là con số sau đây:

enter image description here

Xác định theo chương trình nếu có sự va chạm giữa các chú thích khác nhau là một quá trình phức tạp hơn. Đây có thể là một câu hỏi riêng biệt: Matplotlib text dimensions.

+0

này trả lời các câu hỏi cho các quán ăn hơi rộng hơn nhưng trong trường hợp của tôi họ rất mỏng và rất gần với sự liên kết vì vậy ngay cả dọc sẽ không làm. Tôi cũng nghĩ về một số thử nghiệm va chạm hộp bị chặn nhưng điều này sẽ làm tăng độ phức tạp vượt xa thời gian tôi sẵn sàng chi tiêu này :) – BandGap

+0

@BandGap, sau đó tôi sẽ thực hiện việc này theo cách thủ công, đặt vị trí chú thích ở đầu mỗi thanh và điều chỉnh vị trí văn bản cho đến khi chúng không va chạm (chỉ điều chỉnh thành phần y), và tôi sẽ định nghĩa một kiểu mũi tên, giống như chúng làm trong phần chú thích trục của hướng dẫn sử dụng: http://matplotlib.sourceforge.net/ users/annotations_guide.html # annotating-axes Điều này cho phép một mũi tên trỏ từ nhãn của bạn đến thanh của bạn và các dòng văn bản được tách biệt với nhau. Hãy cho tôi biết đề xuất này không rõ ràng. – Yann

+3

Nếu tôi phải thực hiện thủ công, tôi có thể in ra và thêm văn bản bằng tay. Vì tôi cần một số lô với thay đổi vị trí thanh và chiều cao này là không khả thi (ngoài thực tế là có hàng chục quán bar) – BandGap

6

Tùy chọn khác sử dụng thư viện của tôi adjustText, được viết riêng cho mục đích này (https://github.com/Phlya/adjustText). Tôi nghĩ rằng nó có thể chậm hơn đáng kể mà câu trả lời được chấp nhận (nó làm chậm đáng kể với rất nhiều thanh), nhưng nhiều hơn nữa chung chung và cấu hình.

from adjustText import adjust_text 
np.random.seed(2017) 
x_data = np.random.random_sample(100) 
y_data = np.random.random_integers(10,50,(100)) 

f, ax = plt.subplots(dpi=300) 
bars = ax.bar(x_data, y_data, width=0.001, facecolor='k') 
texts = [] 
for x, y in zip(x_data, y_data): 
    texts.append(plt.text(x, y, y, horizontalalignment='center', color='b')) 
adjust_text(texts, add_objects=bars, autoalign='y', expand_objects=(0.1, 1), 
      only_move={'points':'', 'text':'y', 'objects':'y'}, force_text=0.75, force_objects=0.1, 
      arrowprops=dict(arrowstyle="simple, head_width=0.25, tail_width=0.05", color='r', lw=0.5, alpha=0.5)) 
plt.show() 

enter image description here

Nếu chúng ta cho phép autoalignment cùng trục x, nó được thậm chí tốt hơn (tôi chỉ cần để giải quyết một vấn đề nhỏ mà nó không thích đặt nhãn trên các điểm và không một chút để các bên...).

np.random.seed(2017) 
x_data = np.random.random_sample(100) 
y_data = np.random.random_integers(10,50,(100)) 

f, ax = plt.subplots(dpi=300) 
bars = ax.bar(x_data, y_data, width=0.001, facecolor='k') 
texts = [] 
for x, y in zip(x_data, y_data): 
    texts.append(plt.text(x, y, y, horizontalalignment='center', size=7, color='b')) 
adjust_text(texts, add_objects=bars, autoalign='xy', expand_objects=(0.1, 1), 
      only_move={'points':'', 'text':'y', 'objects':'y'}, force_text=0.75, force_objects=0.1, 
      arrowprops=dict(arrowstyle="simple, head_width=0.25, tail_width=0.05", color='r', lw=0.5, alpha=0.5)) 
plt.show() 

enter image description here

(tôi đã phải điều chỉnh một số thông số ở đây, tất nhiên)

+0

Readme nói rằng nó biến văn bản thành chú thích. Điều đó có nghĩa là nó sẽ không hoạt động nếu các chú thích chồng chéo là những gì tôi phải bắt đầu? –

+0

@JosephGarvin no, hiện tại nó không hỗ trợ chú thích, nó phải bắt đầu với các đối tượng văn bản. – Phlya

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