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

  1. Pick an explicit location: fig.legend(loc='upper right') (or codes 1-10).
  2. In shared helpers, default loc='best' only for Axes legends and e.g. 'upper center' for figure legends.
  3. 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

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


AI-assisted analysis of matplotlib/matplotlib@b379c1b69e (2026-08-21). Data as JSON: /api/errors/1bfefd181c0da4c1. Report an issue: GitHub.