Skip to content

BUG: secondary_y may not show legend properly #9812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 5, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v0.16.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Bug Fixes
- Bug in ``DataFrame`` slicing may not retain metadata (:issue:`9776`)
- Bug where ``TimdeltaIndex`` were not properly serialized in fixed ``HDFStore`` (:issue:`9635`)


- Bug in plotting continuously using ``secondary_y`` may not show legend properly. (:issue:`9610`, :issue:`9779`)


- Bug in ``Series.quantile`` on empty Series of type ``Datetime`` or ``Timedelta`` (:issue:`9675`)
Expand Down
95 changes: 95 additions & 0 deletions pandas/tests/test_graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,101 @@ def test_hist_no_overlap(self):
axes = fig.get_axes()
self.assertEqual(len(axes), 2)

@slow
def test_hist_secondary_legend(self):
# GH 9610
df = DataFrame(np.random.randn(30, 4), columns=list('abcd'))

# primary -> secondary
ax = df['a'].plot(kind='hist', legend=True)
df['b'].plot(kind='hist', ax=ax, legend=True, secondary_y=True)
# both legends are dran on left ax
# left and right axis must be visible
self._check_legend_labels(ax, labels=['a', 'b (right)'])
self.assertTrue(ax.get_yaxis().get_visible())
self.assertTrue(ax.right_ax.get_yaxis().get_visible())
tm.close()

# secondary -> secondary
ax = df['a'].plot(kind='hist', legend=True, secondary_y=True)
df['b'].plot(kind='hist', ax=ax, legend=True, secondary_y=True)
# both legends are draw on left ax
# left axis must be invisible, right axis must be visible
self._check_legend_labels(ax.left_ax, labels=['a (right)', 'b (right)'])
self.assertFalse(ax.left_ax.get_yaxis().get_visible())
self.assertTrue(ax.get_yaxis().get_visible())
tm.close()

# secondary -> primary
ax = df['a'].plot(kind='hist', legend=True, secondary_y=True)
# right axes is returned
df['b'].plot(kind='hist', ax=ax, legend=True)
# both legends are draw on left ax
# left and right axis must be visible
self._check_legend_labels(ax.left_ax, labels=['a (right)', 'b'])
self.assertTrue(ax.left_ax.get_yaxis().get_visible())
self.assertTrue(ax.get_yaxis().get_visible())
tm.close()

@slow
def test_df_series_secondary_legend(self):
# GH 9779
df = DataFrame(np.random.randn(30, 3), columns=list('abc'))
s = Series(np.random.randn(30), name='x')

# primary -> secondary (without passing ax)
ax = df.plot()
s.plot(legend=True, secondary_y=True)
# both legends are dran on left ax
# left and right axis must be visible
self._check_legend_labels(ax, labels=['a', 'b', 'c', 'x (right)'])
self.assertTrue(ax.get_yaxis().get_visible())
self.assertTrue(ax.right_ax.get_yaxis().get_visible())
tm.close()

# primary -> secondary (with passing ax)
ax = df.plot()
s.plot(ax=ax, legend=True, secondary_y=True)
# both legends are dran on left ax
# left and right axis must be visible
self._check_legend_labels(ax, labels=['a', 'b', 'c', 'x (right)'])
self.assertTrue(ax.get_yaxis().get_visible())
self.assertTrue(ax.right_ax.get_yaxis().get_visible())
tm.close()

# seconcary -> secondary (without passing ax)
ax = df.plot(secondary_y=True)
s.plot(legend=True, secondary_y=True)
# both legends are dran on left ax
# left axis must be invisible and right axis must be visible
expected = ['a (right)', 'b (right)', 'c (right)', 'x (right)']
self._check_legend_labels(ax.left_ax, labels=expected)
self.assertFalse(ax.left_ax.get_yaxis().get_visible())
self.assertTrue(ax.get_yaxis().get_visible())
tm.close()

# secondary -> secondary (with passing ax)
ax = df.plot(secondary_y=True)
s.plot(ax=ax, legend=True, secondary_y=True)
# both legends are dran on left ax
# left axis must be invisible and right axis must be visible
expected = ['a (right)', 'b (right)', 'c (right)', 'x (right)']
self._check_legend_labels(ax.left_ax, expected)
self.assertFalse(ax.left_ax.get_yaxis().get_visible())
self.assertTrue(ax.get_yaxis().get_visible())
tm.close()

# secondary -> secondary (with passing ax)
ax = df.plot(secondary_y=True, mark_right=False)
s.plot(ax=ax, legend=True, secondary_y=True)
# both legends are dran on left ax
# left axis must be invisible and right axis must be visible
expected = ['a', 'b', 'c', 'x (right)']
self._check_legend_labels(ax.left_ax, expected)
self.assertFalse(ax.left_ax.get_yaxis().get_visible())
self.assertTrue(ax.get_yaxis().get_visible())
tm.close()

@slow
def test_plot_fails_with_dupe_color_and_style(self):
x = Series(randn(2))
Expand Down
47 changes: 29 additions & 18 deletions pandas/tools/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,19 @@ def generate(self):
def _args_adjust(self):
pass

def _maybe_right_yaxis(self, ax):
def _has_plotted_object(self, ax):
"""check whether ax has data"""
return (len(ax.lines) != 0 or
len(ax.artists) != 0 or
len(ax.containers) != 0)

def _maybe_right_yaxis(self, ax, axes_num):
if not self.on_right(axes_num):
if hasattr(ax, 'left_ax'):
# secondary axes may be passed as axes
return ax.left_ax
return ax

if hasattr(ax, 'right_ax'):
return ax.right_ax
else:
Expand All @@ -936,7 +948,7 @@ def _maybe_right_yaxis(self, ax):
orig_ax.right_ax, new_ax.left_ax = new_ax, orig_ax
new_ax.right_ax = new_ax

if len(orig_ax.get_lines()) == 0: # no data on left y
if not self._has_plotted_object(orig_ax): # no data on left y
orig_ax.get_yaxis().set_visible(False)
return new_ax

Expand Down Expand Up @@ -978,7 +990,15 @@ def result(self):
else:
return self.axes
else:
return self.axes[0]
sec_true = isinstance(self.secondary_y, bool) and self.secondary_y
all_sec = (com.is_list_like(self.secondary_y) and
len(self.secondary_y) == self.nseries)
if (sec_true or all_sec):
# if all data is plotted on secondary,
# return secondary axes
return self.axes[0].right_ax
else:
return self.axes[0]

def _compute_plot_data(self):
numeric_data = self.data.convert_objects()._get_numeric_data()
Expand Down Expand Up @@ -1128,8 +1148,8 @@ def _make_legend(self):

def _get_ax_legend(self, ax):
leg = ax.get_legend()
other_ax = (getattr(ax, 'right_ax', None) or
getattr(ax, 'left_ax', None))
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()
Expand Down Expand Up @@ -1221,20 +1241,11 @@ def _get_ax(self, i):
if self.subplots:
ax = self.axes[i]

if self.on_right(i):
ax = self._maybe_right_yaxis(ax)
self.axes[i] = ax
ax = self._maybe_right_yaxis(ax, i)
self.axes[i] = ax
else:
ax = self.axes[0]

if self.on_right(i):
ax = self._maybe_right_yaxis(ax)

sec_true = isinstance(self.secondary_y, bool) and self.secondary_y
all_sec = (com.is_list_like(self.secondary_y) and
len(self.secondary_y) == self.nseries)
if sec_true or all_sec:
self.axes[0] = ax
ax = self._maybe_right_yaxis(ax, i)

ax.get_yaxis().set_visible(True)
return ax
Expand Down Expand Up @@ -1971,7 +1982,7 @@ def _make_plot(self):
kwds['style'] = style

artists = plotf(ax, y, column_num=i, **kwds)
self._add_legend_handle(artists[0], label)
self._add_legend_handle(artists[0], label, index=i)

def _post_plot_logic(self):
if self.orientation == 'horizontal':
Expand Down