frixhax
frixhax

Reputation: 1395

matplotlib legend location default

I use legend(loc='best') a lot. I noticed that it seems to default to the upper right corner, if a lot of space is available. However, I would like loc='best' to default to the upper left corner. (How) is it possible to change that behavior?

Upvotes: 4

Views: 465

Answers (2)

will
will

Reputation: 10650

You might be able to redefine the method in matplotlib which finds the best location,

It's in here.

As @BrenBarn comments below, and mentions in his answer, the magic happens in _get_anchored_bbox, on line 875.

Add this:

from matplotlib.legend import Legend

def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer):
  assert loc in range(1, 11)  # called only internally
  BEST, UL, UR, LL, LR, R, CL, CR, LC, UC, C = list(xrange(11))
  anchor_coefs = {UR: "NE", UL: "NW", LL: "SW", LR: "SE", R: "E",
                  CL: "W", CR: "E", LC: "S", UC: "N", C: "C"}

  c = anchor_coefs[loc]

  fontsize = renderer.points_to_pixels(self._fontsize)
  container = parentbbox.padded(-(self.borderaxespad) * fontsize)
  anchored_box = bbox.anchored(c, container=container)
  return anchored_box.x0, anchored_box.y0         

Legend._get_anchored_bbox = _get_anchored_bbox

To the top of your code, and it changes the behaviour. I've removed the comments and changed the spacing a little to make it take up less space. Alternatively you could just dump it in another file, call it legendLocationHack.py and call it whenever you want the behaviour.

Upvotes: 3

BrenBarn
BrenBarn

Reputation: 251568

You could monkeypatch matplotlib.legend.Legend._get_anchored_bbox. This function contains this code:

BEST, UR, UL, LL, LR, R, CL, CR, LC, UC, C = list(xrange(11))

anchor_coefs = {UR: "NE",
                UL: "NW",
                LL: "SW",
                LR: "SE",
                R: "E",
                CL: "W",
                CR: "E",
                LC: "S",
                UC: "N",
                C: "C"}

This maps the sequential "position codes" to the spatial locations. The badness-calculating code in _find_best_position breaks badness ties by taking the earliest position in the sequence, so upper-right will win unless another position is actually better. I think you can safely swap the codes by writing a new version of the function changing the first line to:

 BEST, UL, UR, LL, LR, R, CL, CR, LC, UC, C = list(xrange(11))

Note that I switched UL and UR. When I try this on a simple V-shaped graph, it puts the legend in the upper right before the change, but upper left after the change.

To monkeypatch in the change, you should redefine your own version of _get_anchored_bbox and then do matplotlib.legend.Legend._get_anchored_bbox = my_get_anchored_bbox.

Any such monkeypatched solution should be treated as fragile. It may break if something changes in a later matplotlib version, since it involves overriding internal functions that you're not supposed to mess with. It might be a better idea to ask on the matplotlib mailing list, or raise an issue in the bug tracker, asking for a configurable paramater to set the tie-breaking order.

Upvotes: 3

Related Questions