diff --git a/doc/source/whatsnew/v0.16.1.txt b/doc/source/whatsnew/v0.16.1.txt index 5e75d9ed011a2..05c762b91b925 100644 --- a/doc/source/whatsnew/v0.16.1.txt +++ b/doc/source/whatsnew/v0.16.1.txt @@ -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`) diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py index 04e43fabcc1cc..3ce4e150326a2 100644 --- a/pandas/tests/test_graphics.py +++ b/pandas/tests/test_graphics.py @@ -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)) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index c7130a144adea..0be030d7c2c8e 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -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: @@ -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 @@ -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() @@ -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() @@ -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 @@ -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':