Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapping text not working in matplotlib

I'm attempting to wrap text using wrap=True but it doesn't seem to be working for me. Running the example from matplotlib below:

import matplotlib.pyplot as plt

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, wrap=True)
plt.text(6, 5, t, ha='left', rotation=15, wrap=True)
plt.text(5, 5, t, ha='right', rotation=-15, wrap=True)
plt.text(5, 10, t, fontsize=18, style='oblique', ha='center',
         va='top', wrap=True)
plt.text(3, 4, t, family='serif', style='italic', ha='right', wrap=True)
plt.text(-1, 0, t, ha='left', rotation=-15, wrap=True)

plt.show()

gets me this:

Text wrapping gone wrong

enter image description here

Any ideas on what's the issue?

like image 558
AToe Avatar asked Jan 03 '18 14:01

AToe


1 Answers

Matplotlib is hardwired to use the figure box as the wrapping width. To get around this, you have to override the _get_wrap_line_width method, which returns how long the line can be in screen pixels. For example:

text = ('Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
        'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ')
txt = ax.text(.2, .8, text, ha='left', va='top', wrap=True,
              bbox=dict(boxstyle='square', fc='w', ec='r'))
txt._get_wrap_line_width = lambda : 600.  #  wrap to 600 screen pixels

The lambda function is just an easy way to create a function/method without having to write a named one using def.

Obviously using a private method comes with risks, such as it being removed in future versions. And I haven't tested how well this works with rotations. If you want to make something more sophisticated, such as using data coordinates, you would have to subclass the Text class, and override the _get_wrap_line_width method explicitly.

import matplotlib.pyplot as plt
import matplotlib.text as mtext

class WrapText(mtext.Text):
    def __init__(self,
                 x=0, y=0, text='',
                 width=0,
                 **kwargs):
        mtext.Text.__init__(self,
                 x=x, y=y, text=text,
                 wrap=True,
                 **kwargs)
        self.width = width  # in screen pixels. You could do scaling first

    def _get_wrap_line_width(self):
        return self.width

fig = plt.figure(1, clear=True)
ax = fig.add_subplot(111)

text = ('Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
        'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ')

# Create artist object. Note clip_on is True by default
# The axes doesn't have this method, so the object is created separately
# and added afterwards.
wtxt = WrapText(.8, .4, text, width=500, va='top', clip_on=False,
                bbox=dict(boxstyle='square', fc='w', ec='b'))
# Add artist to the axes
ax.add_artist(wtxt)

plt.show()
like image 196
Dan Neuman Avatar answered Sep 27 '22 19:09

Dan Neuman