matplotlib/matplotlib · error · ValueError
Automatic legend placement (loc='best') not implemented for
Error message
Automatic legend placement (loc='best') not implemented for figure legend
What it means
loc='best' (or code 0) computes the position of least overlap with the plotted artists, which requires a single parent Axes. Figure legends aggregate multiple Axes, so _set_loc raises ValueError('Automatic legend placement ... not implemented for figure legend') when a non-Axes legend gets loc 0.
Source
Thrown at lib/matplotlib/legend.py:722
loc = tuple(loc)
# validate the tuple represents Real coordinates
if len(loc) != 2 or not all(isinstance(e, numbers.Real) for e in loc):
raise ValueError(type_err_message)
elif isinstance(loc, int):
# validate the integer represents a string numeric value
if loc < 0 or loc > 10:
raise ValueError(type_err_message)
else:
# all other cases are invalid values of loc
raise ValueError(type_err_message)
if self.isaxes and self._outside_loc:
raise ValueError(
f"'outside' option for loc='{loc0}' keyword argument only "
"works for figure legends")
if not self.isaxes and loc == 0:
raise ValueError(
"Automatic legend placement (loc='best') not implemented for "
"figure legend")
tmp = self._loc_used_default
self._set_loc(loc)
self._loc_used_default = tmp # ignore changes done by _set_loc
def _set_loc(self, loc):
# find_offset function will be provided to _legend_box and
# _legend_box will draw itself at the location of the return
# value of the find_offset.
self._loc_used_default = False
self._loc_real = loc
self.stale = True
self._legend_box.set_offset(self._findoffset)
def set_ncols(self, ncols):
"""Set the number of columns."""View on GitHub (pinned to b379c1b69e)
Solutions
- Pick an explicit location: fig.legend(loc='upper right') (or codes 1-10).
- In shared helpers, default loc='best' only for Axes legends and e.g. 'upper center' for figure legends.
- If you truly want minimal overlap, place the legend on a specific axes: axs[0].legend(loc='best').
Example fix
# before fig.legend(loc='best') # after fig.legend(loc='upper center')
Defensive patterns
Strategy: fallback
Validate before calling
def figure_legend_loc(loc):
# 'best'/0 is unsupported on figure legends
return 'upper center' if loc in (None, 'best', 0) else loc
fig.legend(handles=hs, loc=figure_legend_loc(requested_loc)) Type guard
def needs_axes_for_loc(loc) -> bool:
return loc == 'best' or loc == 0 Try / catch
try:
leg = fig.legend(loc='best')
except ValueError:
leg = fig.legend(loc='upper right') Prevention
- Default figure legends to an explicit loc ('upper center' is typical for multi-axes figures).
- Only allow 'best' through code paths that call ax.legend().
When it happens
Trigger: fig.legend(loc='best'); fig.legend(handles, labels, loc=0); a helper that defaults loc='best' for both ax.legend and fig.legend.
Common situations: Refactoring ax.legend calls into fig.legend for multi-axes (twin axes, subplots) layouts; passing loc=0 from a shared constants file; mpl versions where behavior/messages around figure-legend loc defaults changed.
Related errors
- 'outside' option for loc='{loc0}' keyword argument only work
- loc must be string, coordinate tuple, or an integer 0-10, no
- Failed to determine the version of {args[0]} from {' '.join(
- plot() with multiple groups of data (i.e., pairs of x and y)
- label must be scalar or have the same length as the input da
AI-assisted analysis of matplotlib/matplotlib@b379c1b69e (2026-08-21).
Data as JSON: /api/errors/1bfefd181c0da4c1.
Report an issue: GitHub.