2017-01-04 7 views
7

मुझे धुरी निर्देशांक (इस मामले में केवल टिक और टिकलबेल) सहित एक साजिश की सीमा को खोजने की आवश्यकता है (जैसा कि matplotlib transformations tutorial में परिभाषित किया गया है)।धुरी निर्देशांक में एक matplotlib साजिश (टिकलबेल सहित) की सीमा को ढूँढना

इस पर पृष्ठभूमि यह है कि मैं बड़ी संख्या में चार्ट के लिए स्वचालित रूप से थंबनेल प्लॉट (में) बना रहा हूं, केवल तभी जब मैं थंबनेल की स्थिति बना सकता हूं ताकि यह मूल साजिश में डेटा अस्पष्ट न हो।

यह मेरा वर्तमान दृष्टिकोण है:

  1. उम्मीदवार आयतों के एक नंबर बनाने के लिए परीक्षण करने के लिए, मूल कथानक के ऊपरी-दाएं से शुरू होकर काम कर छोड़ दिया है, तो मूल कथानक और इस कदम के निचले दाएं बाएं।
  2. प्रत्येक उम्मीदवार आयत के लिए:
    1. this SO question से कोड का उपयोग करना (अक्ष में निर्देशांक) डेटा निर्देशांक में है, जो एक्स-डेटा आयत को कवर किया जाएगा की टुकड़ा खोजने के लिए छोड़ दिया और रेक्ट के दाहिने हाथ की ओर परिवर्तित।
    2. आयताकार कवर के डेटा के टुकड़े के लिए न्यूनतम/अधिकतम वाई-मान खोजें।
    3. डेटा निर्देशांक में आयत के ऊपर और नीचे खोजें।
    4. उपरोक्त का उपयोग करके, यह निर्धारित करें कि आयताकार किसी भी डेटा के साथ ओवरलैप करता है या नहीं। यदि नहीं, तो मौजूदा आयत में थंबनेल प्लॉट खींचें, अन्यथा जारी रखें।

इस दृष्टिकोण के साथ समस्या यह है कि अक्ष निर्देशांक आप (1,1) को (कुल्हाड़ियों के नीचे बाईं ओर) (0,0) से धुरी की हद तक देना (शीर्ष सही) और टिक और ticklabels (शामिल नहीं है थंबनेल प्लॉट में शीर्षक, अक्ष लेबल, किंवदंतियों या अन्य कलाकार नहीं हैं)।

सभी चार्ट एक ही फ़ॉन्ट आकार उपयोग करें, लेकिन चार्ट, अलग-अलग लंबाई (जैसे 1.5 या 1.2345 * 10^6) की ticklabels हैं, हालांकि इन से पहले इनसेट तैयार की है जाना जाता है। फ़ॉन्ट आकार/अंक से धुरी निर्देशांक में कनवर्ट करने का कोई तरीका है? वैकल्पिक रूप से, शायद उपर्युक्त (बाध्यकारी बक्से?) से बेहतर दृष्टिकोण हो सकता है।

निम्नलिखित कोड उपरोक्त एल्गोरिथ्म लागू करता है:

import math 

from matplotlib import pyplot, rcParams 
rcParams['xtick.direction'] = 'out' 
rcParams['ytick.direction'] = 'out' 

INSET_DEFAULT_WIDTH = 0.35 
INSET_DEFAULT_HEIGHT = 0.25 
INSET_PADDING = 0.05 
INSET_TICK_FONTSIZE = 8 


def axis_data_transform(axis, xin, yin, inverse=False): 
    """Translate between axis and data coordinates. 
    If 'inverse' is True, data coordinates are translated to axis coordinates, 
    otherwise the transformation is reversed. 
    Code by Covich, from: https://stackoverflow.com/questions/29107800/ 
    """ 
    xlim, ylim = axis.get_xlim(), axis.get_ylim() 
    xdelta, ydelta = xlim[1] - xlim[0], ylim[1] - ylim[0] 
    if not inverse: 
     xout, yout = xlim[0] + xin * xdelta, ylim[0] + yin * ydelta 
    else: 
     xdelta2, ydelta2 = xin - xlim[0], yin - ylim[0] 
     xout, yout = xdelta2/xdelta, ydelta2/ydelta 
    return xout, yout 


def add_inset_to_axis(fig, axis, rect): 
    left, bottom, width, height = rect 
    def transform(coord): 
     return fig.transFigure.inverted().transform(
      axis.transAxes.transform(coord)) 
    fig_left, fig_bottom = transform((left, bottom)) 
    fig_width, fig_height = transform([width, height]) - transform([0, 0]) 
    return fig.add_axes([fig_left, fig_bottom, fig_width, fig_height]) 


def collide_rect((left, bottom, width, height), fig, axis, data): 
    # Find the values on the x-axis of left and right edges of the rect. 
    x_left_float, _ = axis_data_transform(axis, left, 0, inverse=False) 
    x_right_float, _ = axis_data_transform(axis, left + width, 0, inverse=False) 
    x_left = int(math.floor(x_left_float)) 
    x_right = int(math.ceil(x_right_float)) 
    # Find the highest and lowest y-value in that segment of data. 
    minimum_y = min(data[int(x_left):int(x_right)]) 
    maximum_y = max(data[int(x_left):int(x_right)]) 
    # Convert the bottom and top of the rect to data coordinates. 
    _, inset_top = axis_data_transform(axis, 0, bottom + height, inverse=False) 
    _, inset_bottom = axis_data_transform(axis, 0, bottom, inverse=False) 
    # Detect collision. 
    if ((bottom > 0.5 and maximum_y > inset_bottom) or # inset at top of chart 
      (bottom < 0.5 and minimum_y < inset_top)): # inset at bottom 
     return True 
    return False 


if __name__ == '__main__': 
    x_data, y_data = range(0, 100), [-1.0] * 50 + [1.0] * 50 # Square wave. 
    y_min, y_max = min(y_data), max(y_data) 
    fig = pyplot.figure() 
    axis = fig.add_subplot(111) 
    axis.set_ylim(y_min - 0.1, y_max + 0.1) 
    axis.plot(x_data, y_data) 
    # Find a rectangle that does not collide with data. Start top-right 
    # and work left, then try bottom-right and work left. 
    inset_collides = False 
    left_offsets = [x/10.0 for x in xrange(6)] * 2 
    bottom_values = (([1.0 - INSET_DEFAULT_HEIGHT - INSET_PADDING] * (len(left_offsets)/2)) 
        + ([INSET_PADDING * 2] * (len(left_offsets)/2))) 
    for left_offset, bottom in zip(left_offsets, bottom_values): 
     # rect: (left, bottom, width, height) 
     rect = (1.0 - INSET_DEFAULT_WIDTH - left_offset - INSET_PADDING, 
       bottom, INSET_DEFAULT_WIDTH, INSET_DEFAULT_HEIGHT) 
     inset_collides = collide_rect(rect, fig, axis, y_data) 
     print 'TRYING:', rect, 'RESULT:', inset_collides 
     if not inset_collides: 
      break 
    if not inset_collides: 
     inset = add_inset_to_axis(fig, axis, rect) 
     inset.set_ylim(axis.get_ylim()) 
     inset.set_yticks([y_min, y_min + ((y_max - y_min)/2.0), y_max]) 
     inset.xaxis.set_tick_params(labelsize=INSET_TICK_FONTSIZE) 
     inset.yaxis.set_tick_params(labelsize=INSET_TICK_FONTSIZE) 
     inset_xlimit = (0, int(len(y_data)/100.0 * 2.5)) # First 2.5% of data. 
     inset.set_xlim(inset_xlimit[0], inset_xlimit[1], auto=False) 
     inset.plot(x_data[inset_xlimit[0]:inset_xlimit[1] + 1], 
        y_data[inset_xlimit[0]:inset_xlimit[1] + 1]) 
    fig.savefig('so_example.png') 

और इस के उत्पादन में है:

TRYING: (0.6, 0.7, 0.35, 0.25) RESULT: True 
TRYING: (0.5, 0.7, 0.35, 0.25) RESULT: True 
TRYING: (0.4, 0.7, 0.35, 0.25) RESULT: True 
TRYING: (0.30000000000000004, 0.7, 0.35, 0.25) RESULT: True 
TRYING: (0.2, 0.7, 0.35, 0.25) RESULT: True 
TRYING: (0.10000000000000002, 0.7, 0.35, 0.25) RESULT: False 

script output

उत्तर

3

मेरे समाधान टिक के निशान का पता लगाने प्रतीत नहीं होता है, लेकिन टिक लेबल, धुरी लेबल और आकृति शीर्षक का ख्याल रखता है। उम्मीद है कि यह पर्याप्त है, क्योंकि टिक अंक के लिए एक निश्चित पैड मूल्य खाते के लिए ठीक होना चाहिए।

अक्षरों को प्राप्त करने के लिए axes.get_tightbbox का उपयोग करें जो लेबल सहित धुरी के चारों ओर फिट बैठता है।

from matplotlib import tight_layout 
renderer = tight_layout.get_renderer(fig) 
inset_tight_bbox = inset.get_tightbbox(renderer) 
जबकि अपने मूल आयत अक्ष bbox, inset.bbox सेट

।आयतों का पता लगाएं अक्ष इन दो bboxes के लिए निर्देशांक में:

inv_transform = axis.transAxes.inverted() 

xmin, ymin = inv_transform.transform(inset.bbox.min) 
xmin_tight, ymin_tight = inv_transform.transform(inset_tight_bbox.min) 

xmax, ymax = inv_transform.transform(inset.bbox.max) 
xmax_tight, ymax_tight = inv_transform.transform(inset_tight_bbox.max) 

अब अक्ष ही है, ऐसा है कि बाहरी तंग bbox वर्ष अक्ष bbox के लिए आकार में कम हो जाएगा के लिए एक नया आयत की गणना:

xmin_new = xmin + (xmin - xmin_tight) 
ymin_new = ymin + (ymin - ymin_tight) 
xmax_new = xmax - (xmax_tight - xmax) 
ymax_new = ymax - (ymax_tight - ymax)  

अब, बस वापस स्विच निर्देशांक लगाने और इनसेट कुल्हाड़ियों का स्थान:

[x_fig,y_fig] = axis_to_figure_transform([xmin_new, ymin_new]) 
[x2_fig,y2_fig] = axis_to_figure_transform([xmax_new, ymax_new]) 

inset.set_position ([x_fig, y_fig, x2_fig - x_fig, y2_fig - y_fig]) 

समारोह axis_to_figure_transform अपने transform मज़ा पर आधारित है add_inset_to_axis से ction है:

def axis_to_figure_transform(coord, axis): 
    return fig.transFigure.inverted().transform(
     axis.transAxes.transform(coord)) 

नोट: इस, fig.show() साथ काम नहीं करता तो कम से कम अपने सिस्टम पर; tight_layout.get_renderer(fig) एक त्रुटि का कारण बनता है। हालांकि, यह ठीक काम करता है अगर आप केवल savefig() का उपयोग कर रहे हैं और साजिश को इंटरैक्टिव रूप से प्रदर्शित नहीं कर रहे हैं।

import math 

from matplotlib import pyplot, rcParams, tight_layout 
rcParams['xtick.direction'] = 'out' 
rcParams['ytick.direction'] = 'out' 

INSET_DEFAULT_WIDTH = 0.35 
INSET_DEFAULT_HEIGHT = 0.25 
INSET_PADDING = 0.05 
INSET_TICK_FONTSIZE = 8 

def axis_data_transform(axis, xin, yin, inverse=False): 
    """Translate between axis and data coordinates. 
    If 'inverse' is True, data coordinates are translated to axis coordinates, 
    otherwise the transformation is reversed. 
    Code by Covich, from: http://stackoverflow.com/questions/29107800/ 
    """ 
    xlim, ylim = axis.get_xlim(), axis.get_ylim() 
    xdelta, ydelta = xlim[1] - xlim[0], ylim[1] - ylim[0] 
    if not inverse: 
     xout, yout = xlim[0] + xin * xdelta, ylim[0] + yin * ydelta 
    else: 
     xdelta2, ydelta2 = xin - xlim[0], yin - ylim[0] 
     xout, yout = xdelta2/xdelta, ydelta2/ydelta 
    return xout, yout 

def axis_to_figure_transform(coord, axis): 
    return fig.transFigure.inverted().transform(
     axis.transAxes.transform(coord)) 

def add_inset_to_axis(fig, axis, rect): 
    left, bottom, width, height = rect 

    fig_left, fig_bottom = axis_to_figure_transform((left, bottom), axis) 
    fig_width, fig_height = axis_to_figure_transform([width, height], axis) \ 
            - axis_to_figure_transform([0, 0], axis) 
    return fig.add_axes([fig_left, fig_bottom, fig_width, fig_height], frameon=True) 


def collide_rect((left, bottom, width, height), fig, axis, data): 
    # Find the values on the x-axis of left and right edges of the rect. 
    x_left_float, _ = axis_data_transform(axis, left, 0, inverse=False) 
    x_right_float, _ = axis_data_transform(axis, left + width, 0, inverse=False) 
    x_left = int(math.floor(x_left_float)) 
    x_right = int(math.ceil(x_right_float)) 
    # Find the highest and lowest y-value in that segment of data. 
    minimum_y = min(data[int(x_left):int(x_right)]) 
    maximum_y = max(data[int(x_left):int(x_right)]) 
    # Convert the bottom and top of the rect to data coordinates. 
    _, inset_top = axis_data_transform(axis, 0, bottom + height, inverse=False) 
    _, inset_bottom = axis_data_transform(axis, 0, bottom, inverse=False) 
    # Detect collision. 
    if ((bottom > 0.5 and maximum_y > inset_bottom) or # inset at top of chart 
      (bottom < 0.5 and minimum_y < inset_top)): # inset at bottom 
     return True 
    return False 


if __name__ == '__main__': 
    x_data, y_data = range(0, 100), [-1.0] * 50 + [1.0] * 50 # Square wave. 
    y_min, y_max = min(y_data), max(y_data) 
    fig = pyplot.figure() 
    axis = fig.add_subplot(111) 
    axis.set_ylim(y_min - 0.1, y_max + 0.1) 
    axis.plot(x_data, y_data) 
    # Find a rectangle that does not collide with data. Start top-right 
    # and work left, then try bottom-right and work left. 
    inset_collides = False 
    left_offsets = [x/10.0 for x in xrange(6)] * 2 
    bottom_values = (([1.0 - INSET_DEFAULT_HEIGHT - INSET_PADDING] * (len(left_offsets)/2)) 
        + ([INSET_PADDING * 2] * (len(left_offsets)/2))) 
    for left_offset, bottom in zip(left_offsets, bottom_values): 
     # rect: (left, bottom, width, height) 
     rect = (1.0 - INSET_DEFAULT_WIDTH - left_offset - INSET_PADDING, 
       bottom, INSET_DEFAULT_WIDTH, INSET_DEFAULT_HEIGHT) 
     inset_collides = collide_rect(rect, fig, axis, y_data) 
     print 'TRYING:', rect, 'RESULT:', inset_collides 
     if not inset_collides: 
      break 
    if not inset_collides: 
     inset = add_inset_to_axis(fig, axis, rect) 
     inset.set_ylim(axis.get_ylim()) 
     inset.set_yticks([y_min, y_min + ((y_max - y_min)/2.0), y_max]) 
     inset.xaxis.set_tick_params(labelsize=INSET_TICK_FONTSIZE) 
     inset.yaxis.set_tick_params(labelsize=INSET_TICK_FONTSIZE) 
     inset_xlimit = (0, int(len(y_data)/100.0 * 2.5)) # First 2.5% of data. 
     inset.set_xlim(inset_xlimit[0], inset_xlimit[1], auto=False) 
     inset.plot(x_data[inset_xlimit[0]:inset_xlimit[1] + 1], 
        y_data[inset_xlimit[0]:inset_xlimit[1] + 1]) 


    # borrow this function from tight_layout 
    renderer = tight_layout.get_renderer(fig) 
    inset_tight_bbox = inset.get_tightbbox(renderer) 

    # uncomment this to show where the two bboxes are 
# def show_bbox_on_plot(ax, bbox, color='b'): 
#  inv_transform = ax.transAxes.inverted() 
#  xmin, ymin = inv_transform.transform(bbox.min) 
#  xmax, ymax = inv_transform.transform(bbox.max) 
#  axis.add_patch(pyplot.Rectangle([xmin, ymin], xmax-xmin, ymax-ymin, transform=axis.transAxes, color = color)) 
#   
# show_bbox_on_plot(axis, inset_tight_bbox) 
# show_bbox_on_plot(axis, inset.bbox, color = 'g') 

    inv_transform = axis.transAxes.inverted() 

    xmin, ymin = inv_transform.transform(inset.bbox.min) 
    xmin_tight, ymin_tight = inv_transform.transform(inset_tight_bbox.min) 

    xmax, ymax = inv_transform.transform(inset.bbox.max) 
    xmax_tight, ymax_tight = inv_transform.transform(inset_tight_bbox.max) 

    # shift actual axis bounds inwards by "margin" so that new size + margin 
    # is original axis bounds 
    xmin_new = xmin + (xmin - xmin_tight) 
    ymin_new = ymin + (ymin - ymin_tight) 
    xmax_new = xmax - (xmax_tight - xmax) 
    ymax_new = ymax - (ymax_tight - ymax) 

    [x_fig,y_fig] = axis_to_figure_transform([xmin_new, ymin_new], axis) 
    [x2_fig,y2_fig] = axis_to_figure_transform([xmax_new, ymax_new], axis) 

    inset.set_position ([x_fig, y_fig, x2_fig - x_fig, y2_fig - y_fig]) 

    fig.savefig('so_example.png') 
+0

इस बात के लिए बहुत धन्यवाद:

अंत में, यहाँ मेरी परिवर्तन और परिवर्धन के साथ अपना पूरा कोड है। जब मैंने इसे हमारे मूल कोड पर पोर्ट किया (जिस पर यहां उदाहरण दिया गया है) 'शो()' ठीक काम करने लग रहा था। मुझे संदेह है क्योंकि हम एक पीडीएफ रेंडरर का उपयोग कर रहे हैं। संयोग से, मुझे लगता है कि लाइन 'inset_bbox = inset.bbox.inverse_transformed (axis.transAxes) 'अनावश्यक हो सकती है, क्योंकि' inset_bbox' कभी नहीं पढ़ा जाता है। – snim2

+0

आप सही हैं, वह रेखा पिछले पुनरावृत्ति से बचे हुए थे और अब मैंने इसे स्पष्टता के लिए हटा दिया है। यदि आप लगभग 'रेंडरर' सामान के आसपास खोज करते हैं, तो यह आपके बैकएंड के आधार पर हिट या मिस लगता है, मैक ओएस विशेष रूप से समस्याग्रस्त होने के साथ। कोई भी 'tighterer = fig.canvas.get_renderer()' का उपयोग करने का प्रयास कर सकता है, 'tight_layout' आयात करने या [इस प्रश्न] के उत्तर (http://stackoverflow.com/questions/22667224/matplotlib-get-text-bounding -Box स्वतंत्र के- बैकएंड)। वैसे भी, अगर यह पहले से ही काम करता है, तो यह बहुत अच्छा है। मैं खुशी से मदद कर सकता है! –

संबंधित मुद्दे