2012-01-10 12 views
12

SOLVED - patrz komentarz poniżej na łączeniu wraptext.wrap i plt.tightlayout.mój tytuł matplotlib zostanie przycięty

PROBLEM: Oto kod:

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

Stwarza to postać wygląda jak image

tytuł jest przycięty, w jaki sposób mogę je wyświetlić całą tytuł?

UPDATE: Szukam rozwiązanie, które powoduje, że rozmiar rysunku, aby dopasować tekst w tytule i osi etykiety, a nie rozwiązania, które przecina tytuł z nowej linii, jako że takie rozwiązanie nie robi zawsze pomaga:

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

Zobacz wynik: cropped title

+0

zaktualizowałem pytanie – yoavram

+0

Updated moją odpowiedź. – amillerrhodes

Odpowiedz

18

Możesz wypróbować znalezione rozwiązanie: here.

To trochę kodu, ale wydaje się obsługiwać zawijanie tekstu dla dowolnego tekstu na działce.

Oto kod z roztworu, zmodyfikowany, aby zmieścić się na przykład:

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

To daje następujące działki: enter image description here

UPDATE:

stosujemy poniższy wiersz do tworzenia więcej miejsca między górą rysunku a górą faktycznej działki:

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

Na przykład, jeśli używasz:

plt.subplots_adjust(top=0.5) 

Wyjście będzie wyglądać następująco: enter image description here

+0

Ta odpowiedź faktycznie doprowadziła mnie do pełnego i łatwego rozwiązania, które polega na użyciu 'plt.tight_layout()', zobacz [Tight Layout guide] (http://matplotlib.sourceforge.net/users/tight_layout_guide.html # plotting-guide-tight-layout). Nadal muszę owijać tekst, używając 'wraptext.wrap', jak pokazano w jednej z powyższych odpowiedzi, ale to _stightlayout_ również dopasowuje figurę tak, aby dobrze wyglądała xlabel itd. – yoavram

+1

Czy mógłbyś wtedy przyjąć moją odpowiedź? Zwykle jest to lepsze niż umieszczenie SOLVED na górze strony na stronach stackexchange. – amillerrhodes

2

można zawierać znaki nowej linii \n w tytule do przerwy tytuł nad wieloma liniami.

11

Można zawinąć tekst ze znakami nowej linii (\n) automatycznie przy użyciu textwrap:

>>> 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' 

W tym przypadku 100 to liczba znaków w linii (z dokładnością do spacji - odpychania tekstu nie próbuje zerwać słów)


Inną opcją jest zmniejszenie rozmiaru czcionki:

matplotlib.rcParams.update({'font.size': 12}) 
Powiązane problemy