Est-il possible d'afficher du texte dans une boîte via Matplotlib, avec sauts de ligne automatiques? En utilisant pyplot.text()
, je n'ai pu imprimer que du texte multi-lignes qui dépasse les limites de la fenêtre, ce qui est ennuyeux. La taille des lignes n'est pas connue à l'avance ... Toute idée serait très appréciée!Zone de texte avec retour à la ligne dans matplotlib?
Répondre
Le contenu de cette réponse a été fusionné dans mpl master dans https://github.com/matplotlib/matplotlib/pull/4342 et sera dans la prochaine version.
Wow ... C'est un problème épineux ... (Et il expose beaucoup de limitations dans le rendu de texte matplotlib ...)
Cela devrait (imo) quelque chose qui a construit Matplotlib -in, mais ce n'est pas le cas. Il y a eu quelques threads about it sur la liste de diffusion, mais aucune solution que je pourrais trouver à l'habillage automatique de texte. Donc, tout d'abord, il n'y a aucun moyen de déterminer la taille (en pixels) de la chaîne de texte rendue avant qu'elle ne soit dessinée dans matplotlib. Ce n'est pas un problème trop important, car nous pouvons simplement le dessiner, obtenir la taille, puis redessiner le texte encapsulé. Le problème suivant est que les caractères n'ont pas une largeur fixe en pixels, donc encapsuler une chaîne de texte à un nombre donné de caractères ne reflètera pas nécessairement une largeur donnée quand rendu. Ce n'est pas un gros problème, cependant. Au-delà, nous ne pouvons pas le faire une seule fois ... Sinon, il sera correctement enveloppé lors du premier affichage (sur l'écran, par exemple), mais pas s'il est dessiné à nouveau (lorsque la figure est redimensionnée) ou enregistré en tant qu'image avec un DPI différent de l'écran). Ce n'est pas un gros problème, car nous pouvons simplement connecter une fonction de rappel à l'événement de dessin matplotlib. En tout cas, cette solution est imparfaite, mais elle devrait fonctionner dans la plupart des cas. Je n'essaie pas de rendre compte des chaînes tex-rendues, des polices étendues ou des polices avec un rapport d'aspect inhabituel. Cependant, il devrait maintenant gérer correctement le texte pivoté. Toutefois, il devrait essayer d'envelopper automatiquement tous les objets texte dans plusieurs sous-placettes dans les chiffres que vous connectez le rappel on_draw
à ... Ce sera imparfait dans de nombreux cas, mais il fait un travail décent.
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')
# Now make the text auto-wrap...
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
# 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()
Son été environ cinq ans, mais il ne semble pas encore être une excellente façon de le faire. Voici ma version de la solution acceptée. Mon objectif était de permettre l'application sélective de pixels aux applications individuelles. J'ai également créé une fonction textBox() simple qui convertira tous les axes dans une zone de texte avec des marges et un alignement personnalisés. Au lieu de supposer un rapport d'aspect de police particulier ou une largeur moyenne, je dessine réellement la chaîne un mot à la fois et insère des nouvelles lignes une fois que le seuil est atteint. C'est horriblement lent par rapport aux approximations, mais il se sent tout de même assez accrocheur pour les chaînes de < 200 mots.
# Text Wrapping
# Defines wrapText which will attach an event to a given mpl.text object,
# wrapping it within the parent axes object. Also defines a the convenience
# function textBox() which effectively converts an axes to a text box.
def wrapText(text, margin=4):
""" Attaches an on-draw event to a given mpl.text object which will
automatically wrap its string wthin the parent axes object.
The margin argument controls the gap between the text and axes frame
in points.
"""
ax = text.get_axes()
margin = margin/72 * ax.figure.get_dpi()
def _wrap(event):
"""Wraps text within its parent axes."""
def _width(s):
"""Gets the length of a string in pixels."""
text.set_text(s)
return text.get_window_extent().width
# Find available space
clip = ax.get_window_extent()
x0, y0 = text.get_transform().transform(text.get_position())
if text.get_horizontalalignment() == 'left':
width = clip.x1 - x0 - margin
elif text.get_horizontalalignment() == 'right':
width = x0 - clip.x0 - margin
else:
width = (min(clip.x1 - x0, x0 - clip.x0) - margin) * 2
# Wrap the text string
words = [''] + _splitText(text.get_text())[::-1]
wrapped = []
line = words.pop()
while words:
line = line if line else words.pop()
lastLine = line
while _width(line) <= width:
if words:
lastLine = line
line += words.pop()
# Add in any whitespace since it will not affect redraw width
while words and (words[-1].strip() == ''):
line += words.pop()
else:
lastLine = line
break
wrapped.append(lastLine)
line = line[len(lastLine):]
if not words and line:
wrapped.append(line)
text.set_text('\n'.join(wrapped))
# Draw wrapped string after disabling events to prevent recursion
handles = ax.figure.canvas.callbacks.callbacks[event.name]
ax.figure.canvas.callbacks.callbacks[event.name] = {}
ax.figure.canvas.draw()
ax.figure.canvas.callbacks.callbacks[event.name] = handles
ax.figure.canvas.mpl_connect('draw_event', _wrap)
def _splitText(text):
""" Splits a string into its underlying chucks for wordwrapping. This
mostly relies on the textwrap library but has some additional logic to
avoid splitting latex/mathtext segments.
"""
import textwrap
import re
math_re = re.compile(r'(?<!\\)\$')
textWrapper = textwrap.TextWrapper()
if len(math_re.findall(text)) <= 1:
return textWrapper._split(text)
else:
chunks = []
for n, segment in enumerate(math_re.split(text)):
if segment and (n % 2):
# Mathtext
chunks.append('${}$'.format(segment))
else:
chunks += textWrapper._split(segment)
return chunks
def textBox(text, axes, ha='left', fontsize=12, margin=None, frame=True, **kwargs):
""" Converts an axes to a text box by removing its ticks and creating a
wrapped annotation.
"""
if margin is None:
margin = 6 if frame else 0
axes.set_xticks([])
axes.set_yticks([])
axes.set_frame_on(frame)
an = axes.annotate(text, fontsize=fontsize, xy=({'left':0, 'right':1, 'center':0.5}[ha], 1), ha=ha, va='top',
xytext=(margin, -margin), xycoords='axes fraction', textcoords='offset points', **kwargs)
wrapText(an, margin=margin)
return an
Utilisation:
ax = plot.plt.figure(figsize=(6, 6)).add_subplot(111)
an = ax.annotate(t, fontsize=12, xy=(0.5, 1), ha='center', va='top', xytext=(0, -6),
xycoords='axes fraction', textcoords='offset points')
wrapText(an)
j'ai laissé tomber quelques caractéristiques qui ne sont pas aussi importantes pour moi. Le redimensionnement échouera car chaque appel à _wrap() insère des retours à la ligne supplémentaires dans la chaîne mais n'a aucun moyen de les supprimer. Cela peut être résolu soit en supprimant tous les caractères \ n de la fonction _wrap, soit en stockant la chaîne d'origine quelque part et en "réinitialisant" l'instance de texte entre les wraps.
- 1. Le retour à la ligne automatique et le retour chariot ne fonctionnent pas à l'intérieur de la zone de texte
- 2. Comment insérer un retour à la ligne dans une zone de texte RDL?
- 3. intercepter le retour chariot dans une zone de texte
- 4. entrer la clé pour insérer le retour à la ligne dans le contrôle de zone de texte multiligne asp.net
- 5. Java replaceAll avec retour à la ligne
- 6. sIFR 3.0 - Retour de texte/retour à la ligne incohérent dans Firefox 3 sur PC uniquement
- 7. option de retour à la ligne/retour à la ligne dans Aptana Studio 3 Beta?
- 8. VB.NET Lire la ligne actuelle dans une zone de texte?
- 9. Etiquettes de zone de texte en ligne avec WPF
- 10. Expression régulière et retour à la ligne
- 11. Retour à la ligne dans Firefox 2.0
- 12. Excel 2007 - Étiquettes de l'axe X de retour à la ligne (saut de ligne, retour à la ligne)
- 13. Problème de mise à jour de ligne Gridview avec la zone de texte
- 14. Script travaillant avec mysql et php dans une zone de texte et retour
- 15. PHP Zone de texte pour chaque ligne
- 16. Conserver les sauts de ligne lors du collage dans la zone de texte
- 17. Modification du texte de clabel dans Matplotlib
- 18. Recherche de sauts de ligne dans la zone de texte avec jQuery
- 19. asp.net jquery ajouter ligne (ligne de clone) avec zone de texte et dérouler dynamiquement
- 20. Ajouter une ligne à DGV lié par zone de texte
- 21. Texte de zone de texte à fractionner avec les conditions
- 22. Problème avec IDataErrorInfo avec la zone de texte dans Expander
- 23. Remplir la zone de texte avec flex
- 24. La zone de texte affiche incorrectement les valeurs après retour à la page (ASP.NET 3.5)
- 25. Empêcher le retour à la ligne dans DataGrid
- 26. Comment obtenir le nombre de sauts de retour à la ligne dans richtextboxusing C#?
- 27. Panneau avec retour à la ligne et saut de ligne dans Java Swing
- 28. mx: Retour à la ligne de mx: Liste
- 29. Supprimer la ligne se terminant par un caractère de retour à la ligne dans le fichier texte
- 30. Comment assigner la valeur (valeur ajoutée totale de chaque zone de texte) à la zone de texte 'txtTotal' dans gridview
+1. Hou la la! Impressionnant maîtrise de Matplotlib. :) Avec le code que vous fournissez, quand je change la taille de la fenêtre, les largeurs deviennent de plus en plus petites, mais semblent ne jamais grossir (y compris atteindre leur taille d'origine quand la fenêtre est remise à sa taille d'origine) ... – EOL
@Joe: Le fil sur lequel vous pointez est également intéressant: le wrapping LaTeX pourrait être une option utile. – EOL
@EOL - Merci! J'ai ajouté une nouvelle version qui corrige les problèmes de redimensionnement (et gère également correctement le texte centré). Le texte devrait maintenant reflow à la fois lorsque la figure est faite de plus en plus petit. L'emballage LaTeX est une bonne option (et certainement plus simple!), Mais je n'arrive pas à trouver un moyen de l'adapter automatiquement à la taille des axes ... Peut-être qu'il me manque quelque chose d'évident? –