Der Inhalt dieser Antwort wurde unter https://github.com/matplotlib/matplotlib/pull/4342 in mpl master zusammengeführt und wird in der nächsten Feature-Version enthalten sein.
Wow ... Dies ist ein heikles Problem ... (Und es weist viele Einschränkungen bei der Textwiedergabe von matplotlib auf ...)
Dies sollte (imo) etwas sein, das in matplotlib integriert ist, aber es ist nicht so. Es gibt ein paar gewesen Fäden darüber auf der Mailing - Liste, aber keine Lösung , dass ich in dem automatischen Textumbruch finden kann.
Zunächst einmal gibt es keine Möglichkeit, die Größe (in Pixel) der gerenderten Textzeichenfolge zu bestimmen, bevor sie in matplotlib gezeichnet wird. Dies ist kein allzu großes Problem, da wir es einfach zeichnen, die Größe ermitteln und dann den umbrochenen Text neu zeichnen können. (Es ist teuer, aber nicht zu schlecht)
Das nächste Problem besteht darin, dass Zeichen keine feste Breite in Pixel haben, sodass das Umbrechen einer Textzeichenfolge mit einer bestimmten Anzahl von Zeichen beim Rendern nicht unbedingt eine bestimmte Breite widerspiegelt. Dies ist jedoch kein großes Problem.
Darüber hinaus können wir dies nicht nur einmal tun ... Andernfalls wird es beim ersten Zeichnen (z. B. auf dem Bildschirm) korrekt verpackt, jedoch nicht beim erneuten Zeichnen (wenn die Größe der Figur geändert oder als gespeichert wird) Bild mit einer anderen DPI als der Bildschirm). Dies ist kein großes Problem, da wir einfach eine Rückruffunktion mit dem matplotlib-Zeichenereignis verbinden können.
In jedem Fall ist diese Lösung nicht perfekt, sollte aber in den meisten Situationen funktionieren. Ich versuche nicht, tex-gerenderte Zeichenfolgen, gestreckte Schriftarten oder Schriftarten mit einem ungewöhnlichen Seitenverhältnis zu berücksichtigen. Es sollte jetzt jedoch richtig mit gedrehtem Text umgehen.
Es sollte jedoch versucht werden, Textobjekte automatisch in mehrere Unterzeichnungen einzubinden, unabhängig davon, mit welchen Zahlen Sie den on_draw
Rückruf verbinden ... In vielen Fällen ist dies nicht perfekt, aber es leistet gute Arbeit.
import matplotlib.pyplot as plt
def main():
fig = plt.figure()
plt.axis([0, 10, 0, 10])
t = "This is a really long string that I'd rather have wrapped so that it"\
" doesn't go outside of the figure, but if it's long enough it will go"\
" off the top or bottom!"
plt.text(4, 1, t, ha='left', rotation=15)
plt.text(5, 3.5, t, ha='right', rotation=-15)
plt.text(5, 10, t, fontsize=18, ha='center', va='top')
plt.text(3, 0, t, family='serif', style='italic', ha='right')
plt.title("This is a really long title that I want to have wrapped so it"\
" does not go outside the figure boundaries", ha='center')
fig.canvas.mpl_connect('draw_event', on_draw)
plt.show()
def on_draw(event):
"""Auto-wraps all text objects in a figure at draw-time"""
import matplotlib as mpl
fig = event.canvas.figure
for ax in fig.axes:
for artist in ax.get_children():
if isinstance(artist, mpl.text.Text):
autowrap_text(artist, event.renderer)
func_handles = fig.canvas.callbacks.callbacks[event.name]
fig.canvas.callbacks.callbacks[event.name] = {}
fig.canvas.draw()
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
x0, y0 = textobj.get_transform().transform(textobj.get_position())
clip = textobj.get_axes().get_window_extent()
textobj.set_rotation_mode('anchor')
rotation = textobj.get_rotation()
right_space = min_dist_inside((x0, y0), rotation, clip)
left_space = min_dist_inside((x0, y0), rotation - 180, clip)
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)
aspect_ratio = 0.5
fontsize = textobj.get_size()
pixels_per_char = aspect_ratio * renderer.points_to_pixels(fontsize)
wrap_width = max(1, new_width // pixels_per_char)
try:
wrapped_text = textwrap.fill(textobj.get_text(), wrap_width)
except TypeError:
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:
distances.append((box.x1 - x0) / cos(rotation))
if cos(rotation) < -threshold:
distances.append((box.x0 - x0) / cos(rotation))
if sin(rotation) > threshold:
distances.append((box.y1 - y0) / sin(rotation))
if sin(rotation) < -threshold:
distances.append((box.y0 - y0) / sin(rotation))
return min(distances)
if __name__ == '__main__':
main()