Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5aca651

Browse files
committed
Add public method set_loc() for Legend
1 parent dbc906a commit 5aca651

File tree

4 files changed

+119
-55
lines changed

4 files changed

+119
-55
lines changed

doc/users/next_whats_new/set_loc.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Add a public method to modify the location of ``Legend``
2+
--------------------------------------------------------
3+
4+
`~matplotlib.legend.Legend` locations now can be tweaked after they've been defined.
5+
6+
.. plot::
7+
:include-source: true
8+
9+
from matplotlib import pyplot as plt
10+
11+
fig = plt.figure()
12+
ax = fig.add_subplot(1, 1, 1)
13+
14+
x = list(range(-100, 101))
15+
y = [i**2 for i in x]
16+
17+
ax.plot(x, y, label="f(x)")
18+
ax.legend()
19+
ax.get_legend().set_loc("right")
20+
# Or
21+
# ax.get_legend().set(loc="right")
22+
23+
plt.show()

lib/matplotlib/legend.py

Lines changed: 75 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,12 @@ def _update_bbox_to_anchor(self, loc_in_canvas):
330330
_legend_kw_doc_base)
331331
_docstring.interpd.update(_legend_kw_doc=_legend_kw_both_st)
332332

333+
_legend_kw_set_loc_st = (
334+
_loc_doc_base.format(parent='axes/figure',
335+
default=":rc:`legend.loc` for Axes, 'upper right' for Figure",
336+
best=_loc_doc_best, outside=_outside_doc))
337+
_docstring.interpd.update(_legend_kw_set_loc_doc=_legend_kw_set_loc_st)
338+
333339

334340
class Legend(Artist):
335341
"""
@@ -503,58 +509,6 @@ def val_or_rc(val, rc_name):
503509
)
504510
self.parent = parent
505511

506-
loc0 = loc
507-
self._loc_used_default = loc is None
508-
if loc is None:
509-
loc = mpl.rcParams["legend.loc"]
510-
if not self.isaxes and loc in [0, 'best']:
511-
loc = 'upper right'
512-
513-
type_err_message = ("loc must be string, coordinate tuple, or"
514-
f" an integer 0-10, not {loc!r}")
515-
516-
# handle outside legends:
517-
self._outside_loc = None
518-
if isinstance(loc, str):
519-
if loc.split()[0] == 'outside':
520-
# strip outside:
521-
loc = loc.split('outside ')[1]
522-
# strip "center" at the beginning
523-
self._outside_loc = loc.replace('center ', '')
524-
# strip first
525-
self._outside_loc = self._outside_loc.split()[0]
526-
locs = loc.split()
527-
if len(locs) > 1 and locs[0] in ('right', 'left'):
528-
# locs doesn't accept "left upper", etc, so swap
529-
if locs[0] != 'center':
530-
locs = locs[::-1]
531-
loc = locs[0] + ' ' + locs[1]
532-
# check that loc is in acceptable strings
533-
loc = _api.check_getitem(self.codes, loc=loc)
534-
elif np.iterable(loc):
535-
# coerce iterable into tuple
536-
loc = tuple(loc)
537-
# validate the tuple represents Real coordinates
538-
if len(loc) != 2 or not all(isinstance(e, numbers.Real) for e in loc):
539-
raise ValueError(type_err_message)
540-
elif isinstance(loc, int):
541-
# validate the integer represents a string numeric value
542-
if loc < 0 or loc > 10:
543-
raise ValueError(type_err_message)
544-
else:
545-
# all other cases are invalid values of loc
546-
raise ValueError(type_err_message)
547-
548-
if self.isaxes and self._outside_loc:
549-
raise ValueError(
550-
f"'outside' option for loc='{loc0}' keyword argument only "
551-
"works for figure legends")
552-
553-
if not self.isaxes and loc == 0:
554-
raise ValueError(
555-
"Automatic legend placement (loc='best') not implemented for "
556-
"figure legend")
557-
558512
self._mode = mode
559513
self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
560514

@@ -598,9 +552,8 @@ def val_or_rc(val, rc_name):
598552
# init with null renderer
599553
self._init_legend_box(handles, labels, markerfirst)
600554

601-
tmp = self._loc_used_default
602-
self._set_loc(loc)
603-
self._loc_used_default = tmp # ignore changes done by _set_loc
555+
# Set legend location
556+
self.set_loc(loc)
604557

605558
# figure out title font properties:
606559
if title_fontsize is not None and title_fontproperties is not None:
@@ -686,6 +639,73 @@ def _set_artist_props(self, a):
686639

687640
a.set_transform(self.get_transform())
688641

642+
@_docstring.dedent_interpd
643+
def set_loc(self, loc=None):
644+
"""
645+
Set the location of the legend.
646+
647+
.. versionadded:: 3.8
648+
649+
Parameters
650+
----------
651+
%(_legend_kw_set_loc_doc)s
652+
"""
653+
loc0 = loc
654+
self._loc_used_default = loc is None
655+
if loc is None:
656+
loc = mpl.rcParams["legend.loc"]
657+
if not self.isaxes and loc in [0, 'best']:
658+
loc = 'upper right'
659+
660+
type_err_message = ("loc must be string, coordinate tuple, or"
661+
f" an integer 0-10, not {loc!r}")
662+
663+
# handle outside legends:
664+
self._outside_loc = None
665+
if isinstance(loc, str):
666+
if loc.split()[0] == 'outside':
667+
# strip outside:
668+
loc = loc.split('outside ')[1]
669+
# strip "center" at the beginning
670+
self._outside_loc = loc.replace('center ', '')
671+
# strip first
672+
self._outside_loc = self._outside_loc.split()[0]
673+
locs = loc.split()
674+
if len(locs) > 1 and locs[0] in ('right', 'left'):
675+
# locs doesn't accept "left upper", etc, so swap
676+
if locs[0] != 'center':
677+
locs = locs[::-1]
678+
loc = locs[0] + ' ' + locs[1]
679+
# check that loc is in acceptable strings
680+
loc = _api.check_getitem(self.codes, loc=loc)
681+
elif np.iterable(loc):
682+
# coerce iterable into tuple
683+
loc = tuple(loc)
684+
# validate the tuple represents Real coordinates
685+
if len(loc) != 2 or not all(isinstance(e, numbers.Real) for e in loc):
686+
raise ValueError(type_err_message)
687+
elif isinstance(loc, int):
688+
# validate the integer represents a string numeric value
689+
if loc < 0 or loc > 10:
690+
raise ValueError(type_err_message)
691+
else:
692+
# all other cases are invalid values of loc
693+
raise ValueError(type_err_message)
694+
695+
if self.isaxes and self._outside_loc:
696+
raise ValueError(
697+
f"'outside' option for loc='{loc0}' keyword argument only "
698+
"works for figure legends")
699+
700+
if not self.isaxes and loc == 0:
701+
raise ValueError(
702+
"Automatic legend placement (loc='best') not implemented for "
703+
"figure legend")
704+
705+
tmp = self._loc_used_default
706+
self._set_loc(loc)
707+
self._loc_used_default = tmp # ignore changes done by _set_loc
708+
689709
def _set_loc(self, loc):
690710
# find_offset function will be provided to _legend_box and
691711
# _legend_box will draw itself at the location of the return

lib/matplotlib/legend.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class Legend(Artist):
118118
def get_texts(self) -> list[Text]: ...
119119
def set_alignment(self, alignment: Literal["center", "left", "right"]) -> None: ...
120120
def get_alignment(self) -> Literal["center", "left", "right"]: ...
121+
def set_loc(self, loc: str | tuple[float, float] | int | None = ...) -> None: ...
121122
def set_title(
122123
self, title: str, prop: FontProperties | str | pathlib.Path | None = ...
123124
) -> None: ...

lib/matplotlib/tests/test_legend.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,26 @@ def test_legend_alignment(alignment):
755755
assert leg.get_alignment() == alignment
756756

757757

758+
@pytest.mark.parametrize('loc', ('center', 'best',))
759+
def test_ax_legend_set_loc(loc):
760+
fig, ax = plt.subplots()
761+
ax.plot(range(10), label='test')
762+
leg = ax.legend()
763+
leg.set_loc(loc)
764+
assert leg._get_loc() == mlegend.Legend.codes[loc]
765+
766+
767+
@pytest.mark.parametrize('loc', ('outside right', 'right',))
768+
def test_fig_legend_set_loc(loc):
769+
fig, ax = plt.subplots()
770+
ax.plot(range(10), label='test')
771+
leg = fig.legend()
772+
leg.set_loc(loc)
773+
774+
loc = loc.split()[1] if loc.startswith("outside") else loc
775+
assert leg._get_loc() == mlegend.Legend.codes[loc]
776+
777+
758778
@pytest.mark.parametrize('alignment', ('center', 'left', 'right'))
759779
def test_legend_set_alignment(alignment):
760780
fig, ax = plt.subplots()

0 commit comments

Comments
 (0)