python matplotlib: How can I place text in a given area?

Viewed 677

When placing text in a matplotlib figure, it is possible to specify a location in axes coordinates. However, as the text becomes longer, it will eventually take more space. As suggested here, given a text it is possible to draw a bounding box around it. Is there a way to do it the other way around, that is, given a user-defined bounding box, make sure that the text stays in that bounding box? I am aware that this might require automatic font size adjustment or line breaking. I'm just not sure if something like this already exists in matplotlib.

Use case: What I want to get is a table consisting of rectangular cells as shown below, where each cell should contain a small string in it. The strings should not leve the cells and overlap with each other. Optimally, they should be entered. Here is the code generating the table (without text so far). If you could think of an easier way to achieve my goal rather than manipulating text in matplotlib, please let me know as well.

import matplotlib.pyplot as plt
import numpy as np
import sys

np.random.seed(int(sys.argv[1]))

fig, ax = plt.subplots(1,1)
all_coordinates = np.array([(i,j) for i in range(1, 6) for j in range(1,6)])
for i, coordinates in enumerate(np.random.permutation(all_coordinates)):
    x, y = coordinates
    if i<9:
        color = 'r'
    elif i<17:
        color = 'b'
    elif i<24:
        color = 'y'
    else:
        color = 'k'
    ax.fill_between(np.arange(x-0.5, x+0.5, 0.01), y-0.5, y+0.5, color=color)
low, high = 0.5, 5.5
ax.set_xlim(low, high)
ax.set_ylim(low, high)

major_ticks = np.arange(1, 6, 1)
ax.set_xticks(major_ticks)
ax.set_yticks(major_ticks)

minor_ticks = [1.5, 2.5, 3.5, 4.5]
ax.set_xticks(minor_ticks, minor=True)
ax.set_yticks(minor_ticks, minor=True)
ax.grid(True, which='minor')
plt.show()

Resulting plot

1 Answers

Well, this answer is not complete, but I think it should be enough to get you started. I'm not going to care about too long strings which require text resizing. (Why would anyone ever want to write a novel on a plot?)

Matplotlib uses the figure box by default when you specify wrap=True. You need to overwrite it to return your box size:

t = ax.text(x, y, 'text', ha='center', va='center', wrap=True)
t._get_wrap_line_width = lambda : 50

It means the maximum length of the text on the screen is 50 pixels. As you can see it's not a too sophisticated solution, the method name starts with underscore, which indicates this is not really meant to be used in the public API.

I completed your example:

import matplotlib.pyplot as plt
import numpy as np

text = 'this is a long-long string'

fig, ax = plt.subplots(1,1)
all_coordinates = np.array([(i,j) for i in range(1, 6) for j in range(1,6)])

for i, coordinates in enumerate(np.random.permutation(all_coordinates)):
    x, y = coordinates
    if i<9:
        color = 'r'
    elif i<17:
        color = 'b'
    elif i<24:
        color = 'y'
    else:
        color = 'k'
    ax.fill_between(np.arange(x-0.5, x+0.5, 0.01), y-0.5, y+0.5, color=color)

    txt = ax.text(x, y, text, ha='center', va='center', wrap=True)
    txt._get_wrap_line_width = lambda : 50

low, high = 0.5, 5.5
ax.set_xlim(low, high)
ax.set_ylim(low, high)

major_ticks = np.arange(1, 6, 1)
ax.set_xticks(major_ticks)
ax.set_yticks(major_ticks)

minor_ticks = [1.5, 2.5, 3.5, 4.5]
ax.set_xticks(minor_ticks, minor=True)
ax.set_yticks(minor_ticks, minor=True)
ax.grid(True, which='minor')
plt.show()

which results in: fig

Related