naughty_waves
naughty_waves

Reputation: 275

Read height of legend in Python

I have some plots with a lot of information and lines, so sometimes I tend to put the legend outside the plot itself using bbox_to_anchor. I also prefer to have a title of the plot, but this will positionally coincide with the legend in that case. The following example below is just an illustration of the problem.

import numpy as np
import matplotlib.pyplot as plt

t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
r = 1 + np.sin(4 * np.pi * t)
q = 1 + np.sin(6 * np.pi * t)

fig, ax = plt.subplots()
ax.plot(t, s, label='S')
ax.plot(t, r, label='R')
ax.plot(t, q, label='Q')
leg = ax.legend(loc=3, ncol=3, bbox_to_anchor=(.0, 1.02, 1., .102), borderaxespad=0., mode='expand')
ax.set_title('SIMPLE PLOT', y=1.1)
plt.show()

To avoid this, I set some kind of y-value (e.g. y=1.1). I would like to automate this process because I keep updating the same plot with new data, so the legend grows in size, and I need to adjust the position of the title accordingly.

Upvotes: 2

Views: 651

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339775

The height of the legend is determined at draw time. You can get it after having drawn the figure via legend.get_window_extent(). The resulting bounding box is in units of pixels. In order to find the offset of the title, you will need to subtract the upper limit of the legend from the upper limit of the axes. So you need to get the axes position in pixels as well.

The title can be offset either in figure coordinates (y=1.1) or points (pad=20). I would suggest to use points here, to make it independent of the size of the axes. So you can calculate the difference in upper positions, convert from pixels to points (i.e. distance [pixels] * ppi / dpi) and add some constant offset in points (because usually you would not want the title to sit exactly on the border of the legend). Then use that number as pad.

import numpy as np
import matplotlib.pyplot as plt


fig, ax = plt.subplots(constrained_layout=True)

ax.plot([1,2,3], np.random.rand(3,5), label='Label')

leg = ax.legend(loc="lower center", ncol=3, bbox_to_anchor=(.0, 1.02, 1., 1.02),
                borderaxespad=0, mode='expand')

fig.canvas.draw()
leg_box = leg.get_window_extent()
ax_box = ax.get_position().transformed(fig.transFigure)
pad = (leg_box.y1 - ax_box.y1)*72./fig.dpi + 6
ax.set_title('SIMPLE PLOT', pad=pad)

plt.show()

enter image description here

Note that here I also used constrained_layout to have the title not cropped by the figure boundaries.

Upvotes: 1

Related Questions