diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index b35f230100f8d..82bba1ae22bf2 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -165,6 +165,7 @@ Plotting - Bug in :meth:`Series.plot` not able to plot boolean values (:issue:`23719`) - +- Bug in :meth:`DataFrame.plot` producing incorrect legend markers when plotting multiple series on the same axis (:issue:`18222`) - Bug in :meth:`DataFrame.plot` when ``kind='box'`` and data contains datetime or timedelta data. These types are now automatically dropped (:issue:`22799`) Groupby/resample/rolling diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index a262f89dcc79c..287cc2f4130f4 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -562,7 +562,7 @@ def _add_legend_handle(self, handle, label, index=None): self.legend_labels.append(label) def _make_legend(self): - ax, leg = self._get_ax_legend(self.axes[0]) + ax, leg, handle = self._get_ax_legend_handle(self.axes[0]) handles = [] labels = [] @@ -571,7 +571,8 @@ def _make_legend(self): if not self.subplots: if leg is not None: title = leg.get_title().get_text() - handles = leg.legendHandles + # Replace leg.LegendHandles because it misses marker info + handles.extend(handle) labels = [x.get_text() for x in leg.get_texts()] if self.legend: @@ -581,6 +582,7 @@ def _make_legend(self): handles += self.legend_handles labels += self.legend_labels + if self.legend_title is not None: title = self.legend_title @@ -592,8 +594,14 @@ def _make_legend(self): if ax.get_visible(): ax.legend(loc="best") - def _get_ax_legend(self, ax): + def _get_ax_legend_handle(self, ax): + """ + Take in axes and return ax, legend and handle under different scenarios + """ leg = ax.get_legend() + + # Get handle from axes + handle, _ = ax.get_legend_handles_labels() other_ax = getattr(ax, "left_ax", None) or getattr(ax, "right_ax", None) other_leg = None if other_ax is not None: @@ -601,7 +609,7 @@ def _get_ax_legend(self, ax): if leg is None and other_leg is not None: leg = other_leg ax = other_ax - return ax, leg + return ax, leg, handle @cache_readonly def plt(self): diff --git a/pandas/tests/plotting/common.py b/pandas/tests/plotting/common.py index 4929422d20e8a..5a591f72d7361 100644 --- a/pandas/tests/plotting/common.py +++ b/pandas/tests/plotting/common.py @@ -103,6 +103,28 @@ def _check_legend_labels(self, axes, labels=None, visible=True): else: assert ax.get_legend() is None + def _check_legend_marker(self, ax, expected_markers=None, visible=True): + """ + Check ax has expected legend markers + + Parameters + ---------- + ax : matplotlib Axes object + expected_markers : list-like + expected legend markers + visible : bool + expected legend visibility. labels are checked only when visible is + True + """ + if visible and (expected_markers is None): + raise ValueError("Markers must be specified when visible is True") + if visible: + handles, _ = ax.get_legend_handles_labels() + markers = [handle.get_marker() for handle in handles] + assert markers == expected_markers + else: + assert ax.get_legend() is None + def _check_data(self, xp, rs): """ Check each axes has identical lines diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 65815bcedebfc..7fdc0252b71e3 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -1881,6 +1881,31 @@ def test_df_legend_labels(self): self._check_legend_labels(ax, labels=["LABEL_b", "LABEL_c"]) assert df5.columns.tolist() == ["b", "c"] + def test_missing_marker_multi_plots_on_same_ax(self): + # GH 18222 + df = pd.DataFrame( + data=[[1, 1, 1, 1], [2, 2, 4, 8]], columns=["x", "r", "g", "b"] + ) + fig, ax = self.plt.subplots(nrows=1, ncols=3) + # Left plot + df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[0]) + df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[0]) + df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[0]) + self._check_legend_labels(ax[0], labels=["r", "g", "b"]) + self._check_legend_marker(ax[0], expected_markers=["o", "x", "o"]) + # Center plot + df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[1]) + df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[1]) + df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[1]) + self._check_legend_labels(ax[1], labels=["b", "r", "g"]) + self._check_legend_marker(ax[1], expected_markers=["o", "o", "x"]) + # Right plot + df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[2]) + df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[2]) + df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[2]) + self._check_legend_labels(ax[2], labels=["g", "b", "r"]) + self._check_legend_marker(ax[2], expected_markers=["x", "o", "o"]) + def test_legend_name(self): multi = DataFrame( randn(4, 4),