2012-01-10 13 views
12

RISOLTO - vedere il commento qui sotto sulla combinazione di wraptext.wrap e plt.tightlayout.il mio titolo matplotlib viene ritagliato

PROBLEMA: Ecco il codice:

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

Questo crea una figura che sembra image

Il titolo è ritagliata, come posso farlo per visualizzare l'intero titolo?

UPDATE: Sto cercando una soluzione che causa la dimensione cifra per abbinare il testo nel titolo e assi etichette, non per una soluzione che taglia il titolo con a capo, come questo tipo di soluzione non lo fa sempre aiutare:

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

vedere il risultato: cropped title

+0

Ho aggiornato la questione – yoavram

+0

aggiornato la mia risposta. – amillerrhodes

risposta

18

Puoi provare la soluzione trovata here.

È piuttosto un po 'di codice, ma sembra gestire l'avvolgimento del testo per qualsiasi tipo di testo sulla trama.

Ecco il codice dalla soluzione, modificato per adattarsi tuo esempio:

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

Questo produce il seguente grafico: enter image description here

UPDATE:

utilizzare la seguente riga di creare più spazio tra la parte superiore della figura e la parte superiore della trama effettiva:

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

Ad esempio, se si utilizza:

plt.subplots_adjust(top=0.5) 

L'output sarà simile: enter image description here

+0

Questa risposta in realtà mi porta alla soluzione completa e semplice, che è quella di usare 'plt.tight_layout()', vedere [Guida di Tight Layout] (http://matplotlib.sourceforge.net/users/tight_layout_guide.html # tramando-guida-tenuta-layout). Ho ancora bisogno di avvolgere il testo, usando 'wraptext.wrap' come mostrato in una delle risposte sopra, ma questa _tightlayout_ cosa regola anche la figura per mostrare la xlabel proprio ecc. – yoavram

+1

Potresti accettare la mia risposta allora? In genere è meglio che mettere SOLVED in cima alla domanda sui siti Web di stackexchange. – amillerrhodes

2

È possibile includere i caratteri di nuova riga \n nel titolo ad una rottura di un titolo su un numero di linee.

11

Si potrebbe avvolgere il testo con caratteri di nuova riga (\n) utilizzando automaticamente 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' 

In questo caso, 100 è il numero di caratteri per riga (per gli spazi più vicina - textwrap cerca di non rompere parole)


Un'altra opzione è quella di ridurre la dimensione del carattere:

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