Skip to content Skip to sidebar Skip to footer

My Matplotlib Title Gets Cropped

SOLVED - see comment below on combining wraptext.wrap and plt.tightlayout. PROBLEM: Here's the code: import matplotlib.pyplot as plt plt.bar([1,2],[5,4]) plt.title('this is a very

Solution 1:

You can try the solution found here.

It's quite a bit of code, but it seems to handle text wrapping for any sort of text on the plot.

Here's the code from the solution, modified to fit your example:

import matplotlib.pyplot as plt

defmain():
    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')

defon_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 figurefor ax in fig.axes:
        for artist in ax.get_children():
            # If it's a text artist, wrap it...ifisinstance(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

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

defmin_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.0001if 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))
    returnmin(distances)

if __name__ == '__main__':
    main()

This produces the following plot: enter image description here

UPDATE:

Use the following line to create more space between the top of the figure and the top of the actual plot:

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

For example, if you use:

plt.subplots_adjust(top=0.5)

The output will look like: enter image description here

Solution 2:

You could wrap the text with newline characters (\n) automatically using 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 this case, 100 is the number of characters per line (to the nearest whitespace - textwrap tries not to break up words)


Another option is to reduce the size of the font:

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

Solution 3:

You can include newline characters \n in the title to a break a title over a number of lines.

Post a Comment for "My Matplotlib Title Gets Cropped"