diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index b8f59363b5107..3416f4c757888 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -173,6 +173,7 @@ def __init__( stacked: bool = False, label: Hashable | None = None, style=None, + legend_loc: str | tuple[float, float] = "best", **kwds, ) -> None: # if users assign an empty list or tuple, raise `ValueError` @@ -241,6 +242,7 @@ def __init__( self.legend = legend self.legend_handles: list[Artist] = [] self.legend_labels: list[Hashable] = [] + self.legend_loc = legend_loc self.logx = type(self)._validate_log_kwd("logx", logx) self.logy = type(self)._validate_log_kwd("logy", logy) @@ -909,7 +911,7 @@ def _make_legend(self) -> None: title = self.legend_title if len(handles) > 0: - ax.legend(handles, labels, loc="best", title=title) + ax.legend(handles, labels, loc=self.legend_loc, title=title) elif self.subplots and self.legend: for ax in self.axes: @@ -920,7 +922,7 @@ def _make_legend(self) -> None: "No artists with labels found to put in legend.", UserWarning, ) - ax.legend(loc="best") + ax.legend(loc=self.legend_loc) @final @staticmethod diff --git a/pandas/tests/plotting/frame/test_frame_legend.py b/pandas/tests/plotting/frame/test_frame_legend.py index 755293e0bf6d7..84354914fd6ba 100644 --- a/pandas/tests/plotting/frame/test_frame_legend.py +++ b/pandas/tests/plotting/frame/test_frame_legend.py @@ -10,6 +10,7 @@ from pandas.tests.plotting.common import ( _check_legend_labels, _check_legend_marker, + _check_plot_works, _check_text_labels, ) @@ -260,3 +261,55 @@ def test_missing_markers_legend_using_style(self): _check_legend_labels(ax, labels=["A", "B", "C"]) _check_legend_marker(ax, expected_markers=[".", ".", "."]) + + def test_no_legend_when_legend_false_legend_loc(self): + df = DataFrame({"a": [1, 2, 3]}) + ax = _check_plot_works(df.plot, legend=False, legend_loc="upper right") + assert ax.get_legend() is None + + def test_df_legend_labels_secondary_y_legend_loc(self): + pytest.importorskip("scipy") + df = DataFrame(np.random.default_rng(2).random((3, 3)), columns=["a", "b", "c"]) + df2 = DataFrame( + np.random.default_rng(2).random((3, 3)), columns=["d", "e", "f"] + ) + df3 = DataFrame( + np.random.default_rng(2).random((3, 3)), columns=["g", "h", "i"] + ) + + # preserves "best" behaviour if no legend_loc specified + ax = df.plot(legend=True, secondary_y="b", legend_loc=None) + _check_legend_labels(ax, labels=["a", "b (right)", "c"]) + ax = df2.plot(legend=False, ax=ax) + _check_legend_labels(ax, labels=["a", "b (right)", "c"]) + ax = df3.plot( + kind="bar", legend=True, secondary_y="h", legend_loc="upper right", ax=ax + ) + _check_legend_labels(ax, labels=["a", "b (right)", "c", "g", "h (right)", "i"]) + + @pytest.mark.parametrize( + "kind, labels", + [ + ("line", ["a", "b"]), + ("bar", ["a", "b"]), + ("scatter", ["b"]), + ], + ) + def test_across_kinds_legend_loc(self, kind, labels): + df = DataFrame({"a": [1, 2, 3], "b": [3, 2, 1]}) + if kind == "scatter": + ax = _check_plot_works( + df.plot, + kind="scatter", + x="a", + y="b", + label="b", + legend=True, + legend_loc="upper right", + ) + else: + ax = _check_plot_works( + df.plot, kind=kind, legend=True, legend_loc="upper right" + ) + _check_legend_labels(ax, labels=labels) + assert ax.get_legend() is not None