2012-01-13 15 views
35

मैं कुछ पाठ के साथ एक ग्राफ़ के किसी बार टिप्पणी करने के लिए चाहते हैं, लेकिन अगर सलाखों एक साथ करीब हैं और तुलनीय ऊंचाई है, एनोटेशन ईए से ऊपर हैं। अन्य और इस प्रकार पढ़ना मुश्किल है (एनोटेशन के लिए निर्देशांक बार की स्थिति और ऊंचाई से लिया गया था)।matplotlib ओवरलैपिंग एनोटेशन

वहाँ उन में से एक शिफ्ट करने के लिए एक टक्कर होती है यदि कोई तरीका है?

संपादित करें: सलाखों बहुत पतली और बहुत करीब हैं कभी कभी तो बस खड़ी संरेखित समस्या का समाधान नहीं करता है ...

एक तस्वीर चीजों को स्पष्ट कर सकता है: bar pattern

उत्तर

43

मैं एक त्वरित समाधान है, जो सभी अन्य एनोटेशन के लिए डिफ़ॉल्ट बाउंडिंग बॉक्स के खिलाफ प्रत्येक एनोटेशन स्थिति की जाँच करता है लिखा है। यदि कोई टकराव होता है तो यह अपनी स्थिति को अगले उपलब्ध टकराव मुक्त स्थान पर बदल देता है। यह भी अच्छे तीरों में डालता है। enter image description here

इसके बजाय इस बात का

:

एक काफी चरम उदाहरण के लिए, यह इस (संख्या में से कोई भी ओवरलैप) का उत्पादन करेगा

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) 

: enter image description here

यहाँ कोड है यहाँ कोड इन भूखंडों का निर्माण किया जाता है, उपयोग दिखा रहा है:

#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

काफी अच्छा है। क्या गैर-बार ग्राफिक्स पर इसे सामान्यीकृत करने का कोई तरीका है? मैं एक स्कैटरप्लॉट को एनोटेट करने की कोशिश कर रहा हूं, और स्वाभाविक रूप से यह अच्छा होगा अगर तीरों की दूरी को भी कम किया जाए। संख्याओं के माध्यम से जाने वाले तीरों की मात्रा को कम करना भी संभव है? – tarrasch

+0

@tarrasch - इस सिद्धांत को किसी भी प्रकार की साजिश के लिए ठीक काम करना चाहिए। उम्मीद है कि मेरे पास अगले कुछ दिनों में कोड को अधिक आकर्षक आकार में दस्तक देने का समय होगा (जैसा कि मैंने उल्लेख किया है, इसे सामान्यीकृत करने की आवश्यकता है)। तीरों की दूरी को थोड़ा कम किया जा सकता है ('2 * एल' से 'एल' में बदलें), लेकिन तीरों को कभी-कभी संख्याओं के माध्यम से जाना पड़ता है (हालांकि इससे बचने के लिए यह बहुत जटिल हो जाएगा), हालांकि यदि आप तीर 'अल्फा' सेटिंग को 'अल्फा = 0.3' में बदलते हैं, और पाठ' रंग 'को नीले रंग में बदलते हैं, तो साजिश और भी बेहतर दिखने लगती है। – fraxel

+0

अच्छा! मैं आज दोपहर इसे आजमाउंगा :) – tarrasch

7

एक विकल्प है टेक्स्ट/एनोटेशन घुमाएं, जो rotation कीवर्ड/प्रॉपर्टी द्वारा निर्धारित है। निम्नलिखित उदाहरण में, मैं 90 डिग्री पाठ को घुमाने के लिए गारंटी देता हूं कि यह पड़ोसी पाठ के साथ टकराएगा।

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

परिणाम निम्न चित्र है:

मैं भी, ताकि पाठ (मुद्दा यह है कि मैं पाठ को परिभाषित करने के लिए उपयोग ऊपर) पट्टी के ऊपर प्रस्तुत किया है va कीवर्ड (के लिए verticalalignment कम) सेट

enter image description here

विभिन्न एनोटेशन के बीच टकराव होने पर प्रोग्रामेटिक रूप से निर्धारित करना एक कठिन प्रक्रिया है। यह एक अलग प्रश्न के लायक हो सकता है: Matplotlib text dimensions

+0

यह कुछ हद तक व्यापक सलाखों के लिए सवाल का जवाब लेकिन मेरे मामले में वे बहुत पतली और बहुत करीब तो भी खड़ी संरेखण हैं (मैं निश्चित रूप से, यहाँ कुछ पैरामीटर्स को समायोजित करने के लिए किया था) नहीं करेगा मैंने कुछ बाध्यकारी बॉक्स टकराव परीक्षण के बारे में भी सोचा लेकिन इससे जटिलता में वृद्धि होगी जब तक मैं इस पर खर्च करने के इच्छुक हूं :) – BandGap

+0

@ बैंडगैप, तो मैं इसे मैन्युअल रूप से करूँगा, प्रत्येक बार के शीर्ष पर एनोटेशन स्थिति सेट करूँगा और जब तक वे टकराव नहीं करते हैं तब तक टेक्स्ट स्थिति को समायोजित करते हैं (केवल वाई घटक को समायोजित करके), और मैं एक तीरस्टाइल को परिभाषित करता हूं, जैसे वे उपयोगकर्ता मार्गदर्शिका के एनोटेटिंग अक्ष अनुभाग में करते हैं: http://matplotlib.sourceforge.net/ उपयोगकर्ता/annotations_guide.html # एनोटेटिंग-अक्ष यह एक तीर को आपके लेबल से आपकी बार में इंगित करने की अनुमति देता है और टेक्स्ट की पंक्तियों को एक दूसरे से अलग किया जा सकता है। मुझे बताएं कि यह सुझाव स्पष्ट नहीं है। – Yann

+3

अगर मुझे इसे मैन्युअल रूप से करना पड़ा तो मैं इसे आसानी से प्रिंट कर सकता था और हाथ से टेक्स्ट जोड़ सकता था। चूंकि मुझे बार बार और ऊंचाई बदलने के साथ कई भूखंडों की आवश्यकता है, इसलिए यह संभव नहीं है (इस तथ्य के अलावा कि कई बार बार हैं) – BandGap

6

मेरे पुस्तकालय adjustText, इस उद्देश्य (https://github.com/Phlya/adjustText) के लिए विशेष रूप से लिखा का उपयोग कर एक अन्य विकल्प। मुझे लगता है कि यह शायद काफी धीमी है कि स्वीकार किए जाते हैं जवाब (इसे नीचे काफी सलाखों के एक बहुत कुछ के साथ धीमा कर देती है), लेकिन और अधिक सामान्य और विन्यास है।

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

अगर हम एक्स अक्ष के साथ autoalignment, तो यह और भी बेहतर हो जाता है की अनुमति देते हैं (मैं तो बस एक छोटी सी समस्या को हल करने के लिए है कि यह अंक ऊपर लेबल डाल पसंद नहीं है की जरूरत है और करने के लिए एक सा नहीं पक्ष ...)।

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

+0

रीडमे कहता है कि यह ग्रंथों को एनोटेशन में बदल देता है। क्या इसका मतलब यह है कि अगर ओवरलैपिंग की टिप्पणियां शुरू हो रही हैं तो यह काम नहीं करेगा? –

+0

@ जोसेफ गारविन नो, वर्तमान में यह एनोटेशन का समर्थन नहीं करता है, इसे टेक्स्ट ऑब्जेक्ट्स से शुरू करना होगा। – Phlya