2012-01-10 40 views
12

已解決 - 請參閱下面關於合併wraptext.wrapplt.tightlayout的註釋。我的matplotlib標題被裁剪

問題: 下面的代碼:

import matplotlib.pyplot as plt 
plt.bar([1,2],[5,4]) 
plt.title('this is a very long title and therefore it gets cropped which is an unthinkable behaviour as it loses the information in the title') 
plt.show() 

這將創建一個數字看起來像image

標題被裁剪,我怎樣才能得到它來顯示整個標題?

更新:我在尋找引起數字大小的文本在冠軍爭奪戰中和軸的標籤,不是爲切斷與新行標題的解決方案的解決方案,作爲一種解決方案不總是幫助:

from textwrap import wrap 
import matplotlib.pyplot as plt 
title = 'this is a very long title and therefore it gets cropped which is an unthinkable behaviour as it loses the information in the title'*5 
plt.bar([1,2],[5,4]) 
plt.title('\n'.join(wrap(title,60))) 
plt.show()` 

看到的結果是:cropped title

+0

我已經更新的問題 – yoavram 2012-01-11 13:22:27

+0

更新我的答案。 – amillerrhodes 2012-01-11 21:40:06

回答

18

您可以嘗試找到的解決方案here

這是相當多的代碼,但它似乎處理劇情上任何類型文本的文本換行。

下面是從解決方案的代碼,修改,以適應您的例子:

import matplotlib.pyplot as plt 

def main(): 
    fig = plt.figure() 
    plt.subplots_adjust(top=0.85) # use a lower number to make more vertical space 
    plt.bar([1,2],[5,4]) 
    fig.canvas.mpl_connect('draw_event', on_draw) 
    plt.title('this is a very long title and therefore it gets cropped which is an unthinkable behaviour as it loses the information in the title') 
    plt.savefig('./test.png') 

def on_draw(event): 
    """Auto-wraps all text objects in a figure at draw-time""" 
    import matplotlib as mpl 
    fig = event.canvas.figure 

    # Cycle through all artists in all the axes in the figure 
    for ax in fig.axes: 
     for artist in ax.get_children(): 
      # If it's a text artist, wrap it... 
      if isinstance(artist, mpl.text.Text): 
       autowrap_text(artist, event.renderer) 

    # Temporarily disconnect any callbacks to the draw event... 
    # (To avoid recursion) 
    func_handles = fig.canvas.callbacks.callbacks[event.name] 
    fig.canvas.callbacks.callbacks[event.name] = {} 
    # Re-draw the figure.. 
    fig.canvas.draw() 
    # Reset the draw event callbacks 
    fig.canvas.callbacks.callbacks[event.name] = func_handles 

def autowrap_text(textobj, renderer): 
    """Wraps the given matplotlib text object so that it exceed the boundaries 
    of the axis it is plotted in.""" 
    import textwrap 
    # Get the starting position of the text in pixels... 
    x0, y0 = textobj.get_transform().transform(textobj.get_position()) 
    # Get the extents of the current axis in pixels... 
    clip = textobj.get_axes().get_window_extent() 
    # Set the text to rotate about the left edge (doesn't make sense otherwise) 
    textobj.set_rotation_mode('anchor') 

    # Get the amount of space in the direction of rotation to the left and 
    # right of x0, y0 (left and right are relative to the rotation, as well) 
    rotation = textobj.get_rotation() 
    right_space = min_dist_inside((x0, y0), rotation, clip) 
    left_space = min_dist_inside((x0, y0), rotation - 180, clip) 

    # Use either the left or right distance depending on the horiz alignment. 
    alignment = textobj.get_horizontalalignment() 
    if alignment is 'left': 
     new_width = right_space 
    elif alignment is 'right': 
     new_width = left_space 
    else: 
     new_width = 2 * min(left_space, right_space) 

    # Estimate the width of the new size in characters... 
    aspect_ratio = 0.5 # This varies with the font!! 
    fontsize = textobj.get_size() 
    pixels_per_char = aspect_ratio * renderer.points_to_pixels(fontsize) 

    # If wrap_width is < 1, just make it 1 character 
    wrap_width = max(1, new_width // pixels_per_char) 
    try: 
     wrapped_text = textwrap.fill(textobj.get_text(), wrap_width) 
    except TypeError: 
     # This appears to be a single word 
     wrapped_text = textobj.get_text() 
    textobj.set_text(wrapped_text) 

def min_dist_inside(point, rotation, box): 
    """Gets the space in a given direction from "point" to the boundaries of 
    "box" (where box is an object with x0, y0, x1, & y1 attributes, point is a 
    tuple of x,y, and rotation is the angle in degrees)""" 
    from math import sin, cos, radians 
    x0, y0 = point 
    rotation = radians(rotation) 
    distances = [] 
    threshold = 0.0001 
    if cos(rotation) > threshold: 
     # Intersects the right axis 
     distances.append((box.x1 - x0)/cos(rotation)) 
    if cos(rotation) < -threshold: 
     # Intersects the left axis 
     distances.append((box.x0 - x0)/cos(rotation)) 
    if sin(rotation) > threshold: 
     # Intersects the top axis 
     distances.append((box.y1 - y0)/sin(rotation)) 
    if sin(rotation) < -threshold: 
     # Intersects the bottom axis 
     distances.append((box.y0 - y0)/sin(rotation)) 
    return min(distances) 

if __name__ == '__main__': 
    main() 

這將產生以下情節: enter image description here

UPDATE

使用下面的行創建圖的頂部和實際圖的頂部之間的更多空間:

plt.subplots_adjust(top=0.85) # use a lower number to make more vertical space 

例如,如果你使用:

plt.subplots_adjust(top=0.5) 

的輸出如下: enter image description here

+0

這個答案實際上使我得到了一個完整和簡單的解決方案,即使用'plt.tight_layout()',參見[Tight Layout guide](http://matplotlib.sourceforge.net/users/tight_layout_guide。HTML#繪製導緊包佈局)。我仍然需要使用'wraptext.wrap'來包裝文本,如上面的答案之一所示,但是這個_tightlayout_東西也會調整數字以顯示xlabel等等。 – yoavram 2012-01-13 07:20:08

+1

您能接受我的答案嗎?這通常比在Stackexchange網站上提出問題的頂部的解決方案好。 – amillerrhodes 2012-01-13 08:03:31

2

您可以包括換行符在標題\n中間休息了多條線路的稱號。

11

你可以包裝使用自動textwrap與換行字符的文本(\n):

>>> longstring = "this is a very long title and therefore it gets cropped which is an unthinkable behaviour as it loses the information in the title" 
>>> "\n".join(textwrap.wrap(longstring, 100)) 
'this is a very long title and therefore it gets cropped which is an unthinkable behaviour as it\nloses the information in the title' 

在這種情況下,100是一個字符,每行(精確到空白的數量 - textwrap儘量不分手字)


另一種選擇是減少字體的大小:

matplotlib.rcParams.update({'font.size': 12})