23

I am trying to right-align the entries in a matplotlib axes legend (by default they are left-aligned), but can't seem to find any way of doing this. The setup I have is below:

(I have added data and labels to my_fig axes using the ax.plot() command)

ax = my_fig.get_axes()[0]
legend_font = FontProperties(size=10)
ax.legend(prop=legend_font, num_points=1, markerscale=0.5)

There is a list of legend keyword arguments in the docs for matplotlib Axes, but there doesn't seem to be any straighforward way to set the alignment of the legend entries there. Anybody know of a backdoor way of doing this? Thanks.

EDIT:

To clarify what I am trying to achieve, right now my legend looks like:

Maneuver: 12-OCT-2011 12:00 UTC 

Bias: 14-OCT-2011 06:00 UTC

I want it to look like:

Maneuver: 12-OCT-2011 12:00 UTC 

    Bias: 14-OCT-2011 06:00 UTC
jeremiahbuddha
  • 9,701
  • 5
  • 28
  • 34
  • 1
    Does your label text include multi-lines? If so you can use label_text.set_multialignment('right') – Gökhan Sever Oct 29 '11 at 02:22
  • Not sure I understand what you mean. For the labels, I have a brief description and date. So right now, the legend has the descriptions stacked and aligned on the left, what I would like is for all the dates to be stacked (aligned on the right). I've edited my main message with an illustration. – jeremiahbuddha Oct 30 '11 at 00:31
  • 1
    As a workaround, you could pad them with spaces (provided you're using monospace font) – George Karpenkov Oct 30 '11 at 06:59

3 Answers3

29

The backdoor you're looking for is the following:

# get the width of your widest label, since every label will need 
# to shift by this amount after we align to the right
shift = max([t.get_window_extent().width for t in legend.get_texts()])
for t in legend.get_texts():
    t.set_ha('right') # ha is alias for horizontalalignment
    t.set_position((shift,0))
Paul Ivanov
  • 1,984
  • 1
  • 16
  • 14
  • 4
    Shout out! Just realized that you are the Paul I met at the IPython sprint at PyCon this year. I'm finally getting around to coding up this answer, thanks! – jeremiahbuddha May 01 '12 at 17:25
  • 1
    For me, this leads to "RuntimeError: Cannot get window extent w/o renderer". EDIT: Alright, this can be fixed via "renderer = fig.canvas.get_renderer()", and then passing the renderer to "get_window_extent()" – pfincent Feb 25 '22 at 14:10
27

I tried to get the example work, but I couldn't.

At least since matplotlib version 1.1.1 (maybe earlier) we need a dedicated renderer instance. Take care of your backend which defines the renderer. Depending on backend the output may look fine on screen but dismal as PDF.

# get the width of your widest label, since every label will need 
#to shift by this amount after we align to the right
renderer = figure.canvas.get_renderer()
shift = max([t.get_window_extent(renderer).width for t in legend.get_texts()])
for t in legend.get_texts():
    t.set_ha('right') # ha is alias for horizontalalignment
    t.set_position((shift,0))
Peter
  • 271
  • 3
  • 2
2

The answer of @Paul Ivanov took me into the right direction. But it needed a slight adaptation for me:

max_shift = max([t.get_window_extent().width for t in legend_obj.get_texts()])
for t in legend_obj.get_texts():
    t.set_ha('right')  # ha is alias for horizontalalignment
    temp_shift = max_shift - t.get_window_extent().width
    t.set_position((temp_shift, 0))

The change means that we set a different shift for each object based on its own width and the max width of the legend text.

For those that get the Cannot get window extent w/o renderer error, add a plt.pause(0.1) :)

zwep
  • 1,207
  • 12
  • 26