Skip to content

Commit 5d3b492

Browse files
charlesdong1991TomAugspurger
authored andcommitted
VIS: Fix DataFrame.plot() produces incorrect legend markers (#27808)
1 parent 0e24468 commit 5d3b492

File tree

4 files changed

+60
-4
lines changed

4 files changed

+60
-4
lines changed

doc/source/whatsnew/v1.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ Plotting
165165

166166
- Bug in :meth:`Series.plot` not able to plot boolean values (:issue:`23719`)
167167
-
168+
- Bug in :meth:`DataFrame.plot` producing incorrect legend markers when plotting multiple series on the same axis (:issue:`18222`)
168169
- Bug in :meth:`DataFrame.plot` when ``kind='box'`` and data contains datetime or timedelta data. These types are now automatically dropped (:issue:`22799`)
169170

170171
Groupby/resample/rolling

pandas/plotting/_matplotlib/core.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ def _add_legend_handle(self, handle, label, index=None):
562562
self.legend_labels.append(label)
563563

564564
def _make_legend(self):
565-
ax, leg = self._get_ax_legend(self.axes[0])
565+
ax, leg, handle = self._get_ax_legend_handle(self.axes[0])
566566

567567
handles = []
568568
labels = []
@@ -571,7 +571,8 @@ def _make_legend(self):
571571
if not self.subplots:
572572
if leg is not None:
573573
title = leg.get_title().get_text()
574-
handles = leg.legendHandles
574+
# Replace leg.LegendHandles because it misses marker info
575+
handles.extend(handle)
575576
labels = [x.get_text() for x in leg.get_texts()]
576577

577578
if self.legend:
@@ -581,6 +582,7 @@ def _make_legend(self):
581582

582583
handles += self.legend_handles
583584
labels += self.legend_labels
585+
584586
if self.legend_title is not None:
585587
title = self.legend_title
586588

@@ -592,16 +594,22 @@ def _make_legend(self):
592594
if ax.get_visible():
593595
ax.legend(loc="best")
594596

595-
def _get_ax_legend(self, ax):
597+
def _get_ax_legend_handle(self, ax):
598+
"""
599+
Take in axes and return ax, legend and handle under different scenarios
600+
"""
596601
leg = ax.get_legend()
602+
603+
# Get handle from axes
604+
handle, _ = ax.get_legend_handles_labels()
597605
other_ax = getattr(ax, "left_ax", None) or getattr(ax, "right_ax", None)
598606
other_leg = None
599607
if other_ax is not None:
600608
other_leg = other_ax.get_legend()
601609
if leg is None and other_leg is not None:
602610
leg = other_leg
603611
ax = other_ax
604-
return ax, leg
612+
return ax, leg, handle
605613

606614
@cache_readonly
607615
def plt(self):

pandas/tests/plotting/common.py

+22
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,28 @@ def _check_legend_labels(self, axes, labels=None, visible=True):
103103
else:
104104
assert ax.get_legend() is None
105105

106+
def _check_legend_marker(self, ax, expected_markers=None, visible=True):
107+
"""
108+
Check ax has expected legend markers
109+
110+
Parameters
111+
----------
112+
ax : matplotlib Axes object
113+
expected_markers : list-like
114+
expected legend markers
115+
visible : bool
116+
expected legend visibility. labels are checked only when visible is
117+
True
118+
"""
119+
if visible and (expected_markers is None):
120+
raise ValueError("Markers must be specified when visible is True")
121+
if visible:
122+
handles, _ = ax.get_legend_handles_labels()
123+
markers = [handle.get_marker() for handle in handles]
124+
assert markers == expected_markers
125+
else:
126+
assert ax.get_legend() is None
127+
106128
def _check_data(self, xp, rs):
107129
"""
108130
Check each axes has identical lines

pandas/tests/plotting/test_frame.py

+25
Original file line numberDiff line numberDiff line change
@@ -1881,6 +1881,31 @@ def test_df_legend_labels(self):
18811881
self._check_legend_labels(ax, labels=["LABEL_b", "LABEL_c"])
18821882
assert df5.columns.tolist() == ["b", "c"]
18831883

1884+
def test_missing_marker_multi_plots_on_same_ax(self):
1885+
# GH 18222
1886+
df = pd.DataFrame(
1887+
data=[[1, 1, 1, 1], [2, 2, 4, 8]], columns=["x", "r", "g", "b"]
1888+
)
1889+
fig, ax = self.plt.subplots(nrows=1, ncols=3)
1890+
# Left plot
1891+
df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[0])
1892+
df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[0])
1893+
df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[0])
1894+
self._check_legend_labels(ax[0], labels=["r", "g", "b"])
1895+
self._check_legend_marker(ax[0], expected_markers=["o", "x", "o"])
1896+
# Center plot
1897+
df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[1])
1898+
df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[1])
1899+
df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[1])
1900+
self._check_legend_labels(ax[1], labels=["b", "r", "g"])
1901+
self._check_legend_marker(ax[1], expected_markers=["o", "o", "x"])
1902+
# Right plot
1903+
df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[2])
1904+
df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[2])
1905+
df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[2])
1906+
self._check_legend_labels(ax[2], labels=["g", "b", "r"])
1907+
self._check_legend_marker(ax[2], expected_markers=["x", "o", "o"])
1908+
18841909
def test_legend_name(self):
18851910
multi = DataFrame(
18861911
randn(4, 4),

0 commit comments

Comments
 (0)