1

I would like to calculate the size (height and width) of a text label in matplotlib in data coordinates so I can move it in a certain direction by its own size. Ideally I would like to know the size before I draw it.

import matplotlib.pylab as pylab
pylab.figure()
x, y = 5, 7
text = 'label'
pylab.plot(x, y, 'k.')
pylab.text(x, y, text, color='red')
pylab.show()

In the above example the label appears over the point, I'd like to move it in a direction by it's size in data coordinates.

EDIT: Please read the comments in the answer regarding different operating systems.

EDIT:

Attempted:

import matplotlib.pylab as pylab
fig = pylab.figure()
x, y = 5, 7
text = 'label'
pylab.plot(x, y, 'k.')
t = pylab.text(x, y, text, color='red')
fig.canvas.draw()
bbox = t.get_window_extent(renderer = pylab.gcf().canvas.get_renderer())
pylab.plot([bbox.x0, bbox.x1, bbox.x1, bbox.x0, bbox.x0], [bbox.y0, bbox.y0, bbox.y1, bbox.y1, bbox.y0], 'r-')
pylab.show()

enter image description here

If I use:

pylab.plot([bbox.x0, bbox.x1, bbox.x1, bbox.x0, bbox.x0], [bbox.y0, bbox.y0, bbox.y1, bbox.y1, bbox.y0], 'g-', transform=None)

I don't see the bounding box

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
nickponline
  • 25,354
  • 32
  • 99
  • 167
  • I think you instead want to change your alignment settings `ha` and `va`? – Julien Oct 06 '21 at 23:52
  • @Julien actually want to keep them as I want to move the labels in arbitrary directions. So they don't overlap with other parts of the plot too. – nickponline Oct 07 '21 at 00:24
  • Quite not sure about getting height and width, but how about getting the length of the text? i.e., len(text) – personaltest25 Oct 07 '21 at 04:26
  • 2
    FYI: don't use `pylab`, which is **disapproved** by `matplotlib` because it may result in **unexpected behavior**. See [Which is the recommended way to plot: matplotlib or pylab?](https://stackoverflow.com/a/51011921/7758804) – Trenton McKinney Oct 10 '21 at 01:05
  • Thanks @TrentonMcKinney I've changed to `matplotlib.pyplot` athought doesn't solve the problem for me. – nickponline Oct 10 '21 at 05:45
  • I didn't think it would resolve the issue. Just providing information about the API usage. – Trenton McKinney Oct 10 '21 at 14:46

2 Answers2

3

You can get the text size by get_window_extent of the text object. This will return the bounding box of the text in display coordinates (i.e. pixels), but to get the pixel position you obviously first need to draw the text. Doing so by canvas.draw caches the renderer, so you don't need to provide a renderer to get_window_extent (valid for Windows and Linux, for macOS see update 3 below).

import matplotlib.pylab as pylab

fig = pylab.figure()
x, y = 5, 7
text = 'label'
pylab.plot(x, y, 'k.')

t = pylab.text(x, y, text, color='red')
fig.canvas.draw()
print(t.get_window_extent())

pylab.show()

On my setup this results in

Bbox(x0=328.0, y0=234.60000000000036, x1=361.75, y1=248.60000000000036)

Update (see comments below):

import matplotlib.pylab as pylab
fig = pylab.figure()
x, y = 5, 7
text = 'label'
pylab.plot(x, y, 'k.')
t = pylab.text(x, y, text, color='red')
fig.canvas.draw()
bbox = t.get_window_extent()
pylab.plot([bbox.x0, bbox.x1, bbox.x1, bbox.x0, bbox.x0], 
           [bbox.y0, bbox.y0, bbox.y1, bbox.y1, bbox.y0],
           'r-',
           transform=None)
pylab.show()

Update 2: In order to get the bbox in data coords, you can use the inverted axes tranform, but you'll have to fix the x and y axes limits before adding the box, otherwise it'll trigger a rescaling of the axes.

import matplotlib.pylab as pylab

fig = pylab.figure()
x, y = 5, 7
text = 'label'
pylab.plot(x, y, 'k.')

xl, yl = pylab.xlim(),pylab.ylim()
t = pylab.text(x, y, text, color='red')

fig.canvas.draw()
bbox = pylab.gca().transData.inverted().transform_bbox(t.get_window_extent())
pylab.plot([bbox.x0, bbox.x1, bbox.x1, bbox.x0, bbox.x0], 
           [bbox.y0, bbox.y0, bbox.y1, bbox.y1, bbox.y0],
           'r-')

pylab.xlim(xl)
pylab.ylim(yl)
pylab.show()

enter image description here


Update 3: For macOS you need to explicitly pass a renderer to get_windows_extent (see here), i.e. the above examples only work as is on Windows and Linux. The following will work on all systems:

import matplotlib.pylab as pylab

fig = pylab.figure()
x, y = 5, 7
text = 'label'
pylab.plot(x, y, 'k.')

xl, yl = pylab.xlim(),pylab.ylim()
t = pylab.text(x, y, text, color='red')

renderer = fig.canvas.get_renderer()
bbox = pylab.gca().transData.inverted().transform_bbox(t.get_window_extent(renderer))
pylab.plot([bbox.x0, bbox.x1, bbox.x1, bbox.x0, bbox.x0], 
           [bbox.y0, bbox.y0, bbox.y1, bbox.y1, bbox.y0],
           'r-')

pylab.xlim(xl)
pylab.ylim(yl)
pylab.show()
Stef
  • 28,728
  • 2
  • 24
  • 52
  • unfortunately that doesn't seem to work - when I plot the bounding box it's in a different location. I put the example in my question. – nickponline Oct 07 '21 at 22:26
  • As I wrote in my answer, the bbox is returned in **display coords**, in your example you use them as **data coords**. Add `, transform=None` to your `pylab.plot` command and everything is OK, see https://i.stack.imgur.com/OlGD3.png – Stef Oct 08 '21 at 07:02
  • Sorry if I'm being dumb here but when I update my code to `pylab.plot([bbox.x0, bbox.x1, bbox.x1, bbox.x0, bbox.x0], [bbox.y0, bbox.y0, bbox.y1, bbox.y1, bbox.y0], 'g-', transform=None)` I don't see the bounding box anywhere. Edited in the question too. – nickponline Oct 08 '21 at 16:25
  • I wonder if it has to do with OS. I am using Mac. What about you? – nickponline Oct 18 '21 at 17:25
  • For example when I run your Update 2 I receive: raise RuntimeError('Cannot get window extent w/o renderer') RuntimeError: Cannot get window extent w/o renderer – nickponline Oct 18 '21 at 17:30
  • I'm using Windows 10 and just now verified that everything also works with Linux (Arch). Unfortunately I don't have Mac to test. It seems that renderer caching works differently (or not at all) with Mac (see also https://github.com/matplotlib/matplotlib/issues/7550#issuecomment-264452800). What if you explicitly pass a renderer as in your example? – Stef Oct 18 '21 at 19:11
  • you example works with `renderer = fig.canvas.get_renderer()` - if you add that in I can accept it. Thanks for working through this with me. – nickponline Oct 20 '21 at 20:20
0

This appears to work well:

from matplotlib.figure import Figure as mpl_Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as mpl_Canvas

fig = mpl_Figure()
x, y, text = 5, 7, 'My label text'

fig.gca().plot(x, y, 'k.')
canvas = mpl_Canvas(fig)
t = fig.gca().text(x, y, text, color='red')
canvas.draw()

bbox = t.get_window_extent(renderer = canvas.get_renderer())
fig.gca().plot(
    [bbox.x0, bbox.x1, bbox.x1, bbox.x0, bbox.x0],
    [bbox.y0, bbox.y0, bbox.y1, bbox.y1, bbox.y0],
    'k:',
    transform=None)
canvas.print_figure("img.png")

Result:

enter image description here

nickponline
  • 25,354
  • 32
  • 99
  • 167