Skip to content

Commit

Permalink
VIS: Fix DataFrame.plot() produces incorrect legend markers (#27808)
Browse files Browse the repository at this point in the history
  • Loading branch information
charlesdong1991 authored and TomAugspurger committed Aug 16, 2019
1 parent 0e24468 commit 5d3b492
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 4 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 12 additions & 4 deletions pandas/plotting/_matplotlib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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:
Expand All @@ -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

Expand All @@ -592,16 +594,22 @@ 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:
other_leg = other_ax.get_legend()
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):
Expand Down
22 changes: 22 additions & 0 deletions pandas/tests/plotting/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions pandas/tests/plotting/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

0 comments on commit 5d3b492

Please sign in to comment.