我的matplotlib标题被裁剪

时间:2012-01-10 11:59:30

标签: python matplotlib

已解决 - 请参阅下面有关合并wraptext.wrapplt.tightlayout的评论。

问题: 这是代码:

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

3 个答案:

答案 0 :(得分: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

<强>更新

使用以下行在图的顶部和实际绘图的顶部之间创建更多空间:

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

答案 1 :(得分:13)

您可以使用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})

答案 2 :(得分:2)

您可以在标题中添加换行符\n,以便在多行中打破标题。