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

Skip to content

Commit 12b38a3

Browse files
authored
Merge pull request #26089 from cocolato/feat/add_pub_set_loc4legend
Add public method to update `Legend` object's loc property .
2 parents 083b1a0 + 5aca651 commit 12b38a3

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
@@ -332,6 +332,12 @@ def _update_bbox_to_anchor(self, loc_in_canvas):
332332
_legend_kw_doc_base)
333333
_docstring.interpd.update(_legend_kw_doc=_legend_kw_both_st)
334334

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

336342
class Legend(Artist):
337343
"""
@@ -505,58 +511,6 @@ def val_or_rc(val, rc_name):
505511
)
506512
self.parent = parent
507513

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

@@ -616,9 +570,8 @@ def val_or_rc(val, rc_name):
616570
# init with null renderer
617571
self._init_legend_box(handles, labels, markerfirst)
618572

619-
tmp = self._loc_used_default
620-
self._set_loc(loc)
621-
self._loc_used_default = tmp # ignore changes done by _set_loc
573+
# Set legend location
574+
self.set_loc(loc)
622575

623576
# figure out title font properties:
624577
if title_fontsize is not None and title_fontproperties is not None:
@@ -704,6 +657,73 @@ def _set_artist_props(self, a):
704657

705658
a.set_transform(self.get_transform())
706659

660+
@_docstring.dedent_interpd
661+
def set_loc(self, loc=None):
662+
"""
663+
Set the location of the legend.
664+
665+
.. versionadded:: 3.8
666+
667+
Parameters
668+
----------
669+
%(_legend_kw_set_loc_doc)s
670+
"""
671+
loc0 = loc
672+
self._loc_used_default = loc is None
673+
if loc is None:
674+
loc = mpl.rcParams["legend.loc"]
675+
if not self.isaxes and loc in [0, 'best']:
676+
loc = 'upper right'
677+
678+
type_err_message = ("loc must be string, coordinate tuple, or"
679+
f" an integer 0-10, not {loc!r}")
680+
681+
# handle outside legends:
682+
self._outside_loc = None
683+
if isinstance(loc, str):
684+
if loc.split()[0] == 'outside':
685+
# strip outside:
686+
loc = loc.split('outside ')[1]
687+
# strip "center" at the beginning
688+
self._outside_loc = loc.replace('center ', '')
689+
# strip first
690+
self._outside_loc = self._outside_loc.split()[0]
691+
locs = loc.split()
692+
if len(locs) > 1 and locs[0] in ('right', 'left'):
693+
# locs doesn't accept "left upper", etc, so swap
694+
if locs[0] != 'center':
695+
locs = locs[::-1]
696+
loc = locs[0] + ' ' + locs[1]
697+
# check that loc is in acceptable strings
698+
loc = _api.check_getitem(self.codes, loc=loc)
699+
elif np.iterable(loc):
700+
# coerce iterable into tuple
701+
loc = tuple(loc)
702+
# validate the tuple represents Real coordinates
703+
if len(loc) != 2 or not all(isinstance(e, numbers.Real) for e in loc):
704+
raise ValueError(type_err_message)
705+
elif isinstance(loc, int):
706+
# validate the integer represents a string numeric value
707+
if loc < 0 or loc > 10:
708+
raise ValueError(type_err_message)
709+
else:
710+
# all other cases are invalid values of loc
711+
raise ValueError(type_err_message)
712+
713+
if self.isaxes and self._outside_loc:
714+
raise ValueError(
715+
f"'outside' option for loc='{loc0}' keyword argument only "
716+
"works for figure legends")
717+
718+
if not self.isaxes and loc == 0:
719+
raise ValueError(
720+
"Automatic legend placement (loc='best') not implemented for "
721+
"figure legend")
722+
723+
tmp = self._loc_used_default
724+
self._set_loc(loc)
725+
self._loc_used_default = tmp # ignore changes done by _set_loc
726+
707727
def _set_loc(self, loc):
708728
# find_offset function will be provided to _legend_box and
709729
# _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
@@ -787,6 +787,26 @@ def test_legend_alignment(alignment):
787787
assert leg.get_alignment() == alignment
788788

789789

790+
@pytest.mark.parametrize('loc', ('center', 'best',))
791+
def test_ax_legend_set_loc(loc):
792+
fig, ax = plt.subplots()
793+
ax.plot(range(10), label='test')
794+
leg = ax.legend()
795+
leg.set_loc(loc)
796+
assert leg._get_loc() == mlegend.Legend.codes[loc]
797+
798+
799+
@pytest.mark.parametrize('loc', ('outside right', 'right',))
800+
def test_fig_legend_set_loc(loc):
801+
fig, ax = plt.subplots()
802+
ax.plot(range(10), label='test')
803+
leg = fig.legend()
804+
leg.set_loc(loc)
805+
806+
loc = loc.split()[1] if loc.startswith("outside") else loc
807+
assert leg._get_loc() == mlegend.Legend.codes[loc]
808+
809+
790810
@pytest.mark.parametrize('alignment', ('center', 'left', 'right'))
791811
def test_legend_set_alignment(alignment):
792812
fig, ax = plt.subplots()

0 commit comments

Comments
 (0)