Skip to content

Commit 2af2044

Browse files
author
Tom Augspurger
committed
Merge pull request #9812 from sinhrks/sec_legend
BUG: secondary_y may not show legend properly
2 parents a2a5cec + 7496890 commit 2af2044

File tree

3 files changed

+125
-19
lines changed

3 files changed

+125
-19
lines changed

doc/source/whatsnew/v0.16.1.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ Bug Fixes
7575
- Bug in ``DataFrame`` slicing may not retain metadata (:issue:`9776`)
7676
- Bug where ``TimdeltaIndex`` were not properly serialized in fixed ``HDFStore`` (:issue:`9635`)
7777

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

8080

8181
- Bug in ``Series.quantile`` on empty Series of type ``Datetime`` or ``Timedelta`` (:issue:`9675`)

pandas/tests/test_graphics.py

+95
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,101 @@ def test_hist_no_overlap(self):
755755
axes = fig.get_axes()
756756
self.assertEqual(len(axes), 2)
757757

758+
@slow
759+
def test_hist_secondary_legend(self):
760+
# GH 9610
761+
df = DataFrame(np.random.randn(30, 4), columns=list('abcd'))
762+
763+
# primary -> secondary
764+
ax = df['a'].plot(kind='hist', legend=True)
765+
df['b'].plot(kind='hist', ax=ax, legend=True, secondary_y=True)
766+
# both legends are dran on left ax
767+
# left and right axis must be visible
768+
self._check_legend_labels(ax, labels=['a', 'b (right)'])
769+
self.assertTrue(ax.get_yaxis().get_visible())
770+
self.assertTrue(ax.right_ax.get_yaxis().get_visible())
771+
tm.close()
772+
773+
# secondary -> secondary
774+
ax = df['a'].plot(kind='hist', legend=True, secondary_y=True)
775+
df['b'].plot(kind='hist', ax=ax, legend=True, secondary_y=True)
776+
# both legends are draw on left ax
777+
# left axis must be invisible, right axis must be visible
778+
self._check_legend_labels(ax.left_ax, labels=['a (right)', 'b (right)'])
779+
self.assertFalse(ax.left_ax.get_yaxis().get_visible())
780+
self.assertTrue(ax.get_yaxis().get_visible())
781+
tm.close()
782+
783+
# secondary -> primary
784+
ax = df['a'].plot(kind='hist', legend=True, secondary_y=True)
785+
# right axes is returned
786+
df['b'].plot(kind='hist', ax=ax, legend=True)
787+
# both legends are draw on left ax
788+
# left and right axis must be visible
789+
self._check_legend_labels(ax.left_ax, labels=['a (right)', 'b'])
790+
self.assertTrue(ax.left_ax.get_yaxis().get_visible())
791+
self.assertTrue(ax.get_yaxis().get_visible())
792+
tm.close()
793+
794+
@slow
795+
def test_df_series_secondary_legend(self):
796+
# GH 9779
797+
df = DataFrame(np.random.randn(30, 3), columns=list('abc'))
798+
s = Series(np.random.randn(30), name='x')
799+
800+
# primary -> secondary (without passing ax)
801+
ax = df.plot()
802+
s.plot(legend=True, secondary_y=True)
803+
# both legends are dran on left ax
804+
# left and right axis must be visible
805+
self._check_legend_labels(ax, labels=['a', 'b', 'c', 'x (right)'])
806+
self.assertTrue(ax.get_yaxis().get_visible())
807+
self.assertTrue(ax.right_ax.get_yaxis().get_visible())
808+
tm.close()
809+
810+
# primary -> secondary (with passing ax)
811+
ax = df.plot()
812+
s.plot(ax=ax, legend=True, secondary_y=True)
813+
# both legends are dran on left ax
814+
# left and right axis must be visible
815+
self._check_legend_labels(ax, labels=['a', 'b', 'c', 'x (right)'])
816+
self.assertTrue(ax.get_yaxis().get_visible())
817+
self.assertTrue(ax.right_ax.get_yaxis().get_visible())
818+
tm.close()
819+
820+
# seconcary -> secondary (without passing ax)
821+
ax = df.plot(secondary_y=True)
822+
s.plot(legend=True, secondary_y=True)
823+
# both legends are dran on left ax
824+
# left axis must be invisible and right axis must be visible
825+
expected = ['a (right)', 'b (right)', 'c (right)', 'x (right)']
826+
self._check_legend_labels(ax.left_ax, labels=expected)
827+
self.assertFalse(ax.left_ax.get_yaxis().get_visible())
828+
self.assertTrue(ax.get_yaxis().get_visible())
829+
tm.close()
830+
831+
# secondary -> secondary (with passing ax)
832+
ax = df.plot(secondary_y=True)
833+
s.plot(ax=ax, legend=True, secondary_y=True)
834+
# both legends are dran on left ax
835+
# left axis must be invisible and right axis must be visible
836+
expected = ['a (right)', 'b (right)', 'c (right)', 'x (right)']
837+
self._check_legend_labels(ax.left_ax, expected)
838+
self.assertFalse(ax.left_ax.get_yaxis().get_visible())
839+
self.assertTrue(ax.get_yaxis().get_visible())
840+
tm.close()
841+
842+
# secondary -> secondary (with passing ax)
843+
ax = df.plot(secondary_y=True, mark_right=False)
844+
s.plot(ax=ax, legend=True, secondary_y=True)
845+
# both legends are dran on left ax
846+
# left axis must be invisible and right axis must be visible
847+
expected = ['a', 'b', 'c', 'x (right)']
848+
self._check_legend_labels(ax.left_ax, expected)
849+
self.assertFalse(ax.left_ax.get_yaxis().get_visible())
850+
self.assertTrue(ax.get_yaxis().get_visible())
851+
tm.close()
852+
758853
@slow
759854
def test_plot_fails_with_dupe_color_and_style(self):
760855
x = Series(randn(2))

pandas/tools/plotting.py

+29-18
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,19 @@ def generate(self):
926926
def _args_adjust(self):
927927
pass
928928

929-
def _maybe_right_yaxis(self, ax):
929+
def _has_plotted_object(self, ax):
930+
"""check whether ax has data"""
931+
return (len(ax.lines) != 0 or
932+
len(ax.artists) != 0 or
933+
len(ax.containers) != 0)
934+
935+
def _maybe_right_yaxis(self, ax, axes_num):
936+
if not self.on_right(axes_num):
937+
if hasattr(ax, 'left_ax'):
938+
# secondary axes may be passed as axes
939+
return ax.left_ax
940+
return ax
941+
930942
if hasattr(ax, 'right_ax'):
931943
return ax.right_ax
932944
else:
@@ -936,7 +948,7 @@ def _maybe_right_yaxis(self, ax):
936948
orig_ax.right_ax, new_ax.left_ax = new_ax, orig_ax
937949
new_ax.right_ax = new_ax
938950

939-
if len(orig_ax.get_lines()) == 0: # no data on left y
951+
if not self._has_plotted_object(orig_ax): # no data on left y
940952
orig_ax.get_yaxis().set_visible(False)
941953
return new_ax
942954

@@ -978,7 +990,15 @@ def result(self):
978990
else:
979991
return self.axes
980992
else:
981-
return self.axes[0]
993+
sec_true = isinstance(self.secondary_y, bool) and self.secondary_y
994+
all_sec = (com.is_list_like(self.secondary_y) and
995+
len(self.secondary_y) == self.nseries)
996+
if (sec_true or all_sec):
997+
# if all data is plotted on secondary,
998+
# return secondary axes
999+
return self.axes[0].right_ax
1000+
else:
1001+
return self.axes[0]
9821002

9831003
def _compute_plot_data(self):
9841004
numeric_data = self.data.convert_objects()._get_numeric_data()
@@ -1128,8 +1148,8 @@ def _make_legend(self):
11281148

11291149
def _get_ax_legend(self, ax):
11301150
leg = ax.get_legend()
1131-
other_ax = (getattr(ax, 'right_ax', None) or
1132-
getattr(ax, 'left_ax', None))
1151+
other_ax = (getattr(ax, 'left_ax', None) or
1152+
getattr(ax, 'right_ax', None))
11331153
other_leg = None
11341154
if other_ax is not None:
11351155
other_leg = other_ax.get_legend()
@@ -1221,20 +1241,11 @@ def _get_ax(self, i):
12211241
if self.subplots:
12221242
ax = self.axes[i]
12231243

1224-
if self.on_right(i):
1225-
ax = self._maybe_right_yaxis(ax)
1226-
self.axes[i] = ax
1244+
ax = self._maybe_right_yaxis(ax, i)
1245+
self.axes[i] = ax
12271246
else:
12281247
ax = self.axes[0]
1229-
1230-
if self.on_right(i):
1231-
ax = self._maybe_right_yaxis(ax)
1232-
1233-
sec_true = isinstance(self.secondary_y, bool) and self.secondary_y
1234-
all_sec = (com.is_list_like(self.secondary_y) and
1235-
len(self.secondary_y) == self.nseries)
1236-
if sec_true or all_sec:
1237-
self.axes[0] = ax
1248+
ax = self._maybe_right_yaxis(ax, i)
12381249

12391250
ax.get_yaxis().set_visible(True)
12401251
return ax
@@ -1971,7 +1982,7 @@ def _make_plot(self):
19711982
kwds['style'] = style
19721983

19731984
artists = plotf(ax, y, column_num=i, **kwds)
1974-
self._add_legend_handle(artists[0], label)
1985+
self._add_legend_handle(artists[0], label, index=i)
19751986

19761987
def _post_plot_logic(self):
19771988
if self.orientation == 'horizontal':

0 commit comments

Comments
 (0)