From 7d5a2cdc8514694d271b16107b30a8482f3eea2f Mon Sep 17 00:00:00 2001 From: sinhrks Date: Sat, 26 Jul 2014 07:47:13 +0900 Subject: [PATCH] BUG/VIS: rot and fontsize are not applied to minor ticks --- doc/source/v0.15.0.txt | 2 +- pandas/tests/test_graphics.py | 53 ++++++++++++++++++++------ pandas/tools/plotting.py | 72 ++++++++++++++++++++++++----------- 3 files changed, 91 insertions(+), 36 deletions(-) diff --git a/doc/source/v0.15.0.txt b/doc/source/v0.15.0.txt index a30400322716c..bf9dfa266817b 100644 --- a/doc/source/v0.15.0.txt +++ b/doc/source/v0.15.0.txt @@ -305,7 +305,7 @@ Bug Fixes (except for the case of two DataFrames with ``pairwise=False``, where behavior is unchanged) (:issue:`7542`) - +- Bug in ``DataFrame.plot`` and ``Series.plot`` may ignore ``rot`` and ``fontsize`` keywords (:issue:`7844`) - Bug in ``DatetimeIndex.value_counts`` doesn't preserve tz (:issue:`7735`) diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py index f9ae058c065e3..5d9b43e48e3c1 100644 --- a/pandas/tests/test_graphics.py +++ b/pandas/tests/test_graphics.py @@ -240,21 +240,33 @@ def _check_ticks_props(self, axes, xlabelsize=None, xrot=None, yrot : number expected yticks rotation """ + from matplotlib.ticker import NullFormatter axes = self._flatten_visible(axes) for ax in axes: if xlabelsize or xrot: - xtick = ax.get_xticklabels()[0] - if xlabelsize is not None: - self.assertAlmostEqual(xtick.get_fontsize(), xlabelsize) - if xrot is not None: - self.assertAlmostEqual(xtick.get_rotation(), xrot) + if isinstance(ax.xaxis.get_minor_formatter(), NullFormatter): + # If minor ticks has NullFormatter, rot / fontsize are not retained + labels = ax.get_xticklabels() + else: + labels = ax.get_xticklabels() + ax.get_xticklabels(minor=True) + + for label in labels: + if xlabelsize is not None: + self.assertAlmostEqual(label.get_fontsize(), xlabelsize) + if xrot is not None: + self.assertAlmostEqual(label.get_rotation(), xrot) if ylabelsize or yrot: - ytick = ax.get_yticklabels()[0] - if ylabelsize is not None: - self.assertAlmostEqual(ytick.get_fontsize(), ylabelsize) - if yrot is not None: - self.assertAlmostEqual(ytick.get_rotation(), yrot) + if isinstance(ax.yaxis.get_minor_formatter(), NullFormatter): + labels = ax.get_yticklabels() + else: + labels = ax.get_yticklabels() + ax.get_yticklabels(minor=True) + + for label in labels: + if ylabelsize is not None: + self.assertAlmostEqual(label.get_fontsize(), ylabelsize) + if yrot is not None: + self.assertAlmostEqual(label.get_rotation(), yrot) def _check_ax_scales(self, axes, xaxis='linear', yaxis='linear'): """ @@ -872,6 +884,7 @@ def test_plot(self): self._check_visible(ax.xaxis) self._check_visible(ax.get_xticklabels()) self._check_visible([ax.xaxis.get_label()]) + self._check_ticks_props(ax, xrot=30) _check_plot_works(df.plot, title='blah') @@ -1069,14 +1082,16 @@ def test_subplots_timeseries(self): self._check_visible(axes[-1].get_xticklabels(minor=True)) self._check_visible(axes[-1].xaxis.get_label()) self._check_visible(axes[-1].get_yticklabels()) + self._check_ticks_props(axes, xrot=30) - axes = df.plot(kind=kind, subplots=True, sharex=False) + axes = df.plot(kind=kind, subplots=True, sharex=False, rot=45, fontsize=7) for ax in axes: self._check_visible(ax.xaxis) self._check_visible(ax.get_xticklabels()) self._check_visible(ax.get_xticklabels(minor=True)) self._check_visible(ax.xaxis.get_label()) self._check_visible(ax.get_yticklabels()) + self._check_ticks_props(ax, xlabelsize=7, xrot=45) def test_negative_log(self): df = - DataFrame(rand(6, 4), @@ -1363,7 +1378,17 @@ def test_plot_bar(self): _check_plot_works(df.plot, kind='bar') df = DataFrame({'a': [0, 1], 'b': [1, 0]}) - _check_plot_works(df.plot, kind='bar') + ax = _check_plot_works(df.plot, kind='bar') + self._check_ticks_props(ax, xrot=90) + + ax = df.plot(kind='bar', rot=35, fontsize=10) + self._check_ticks_props(ax, xrot=35, xlabelsize=10) + + ax = _check_plot_works(df.plot, kind='barh') + self._check_ticks_props(ax, yrot=0) + + ax = df.plot(kind='barh', rot=55, fontsize=11) + self._check_ticks_props(ax, yrot=55, ylabelsize=11) def _check_bar_alignment(self, df, kind='bar', stacked=False, subplots=False, align='center', @@ -1591,6 +1616,10 @@ def test_kde(self): ax = _check_plot_works(df.plot, kind='kde') expected = [com.pprint_thing(c) for c in df.columns] self._check_legend_labels(ax, labels=expected) + self._check_ticks_props(ax, xrot=0) + + ax = df.plot(kind='kde', rot=20, fontsize=5) + self._check_ticks_props(ax, xrot=20, xlabelsize=5) axes = _check_plot_works(df.plot, kind='kde', subplots=True) self._check_axes_shape(axes, axes_num=4, layout=(4, 1)) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index ea7f963f79f28..40d848a48d103 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -753,6 +753,7 @@ class MPLPlot(object): """ _default_rot = 0 + orientation = None _pop_attributes = ['label', 'style', 'logy', 'logx', 'loglog', 'mark_right', 'stacked'] @@ -788,7 +789,14 @@ def __init__(self, data, kind=None, by=None, subplots=False, sharex=True, self.use_index = use_index self.fontsize = fontsize - self.rot = rot + + if rot is not None: + self.rot = rot + else: + if isinstance(self._default_rot, dict): + self.rot = self._default_rot[self.kind] + else: + self.rot = self._default_rot if grid is None: grid = False if secondary_y else True @@ -1018,14 +1026,30 @@ def _adorn_subplots(self): else: self.axes[0].set_title(self.title) - if self._need_to_set_index: - labels = [com.pprint_thing(key) for key in self.data.index] - labels = dict(zip(range(len(self.data.index)), labels)) + labels = [com.pprint_thing(key) for key in self.data.index] + labels = dict(zip(range(len(self.data.index)), labels)) - for ax_ in self.axes: - # ax_.set_xticks(self.xticks) - xticklabels = [labels.get(x, '') for x in ax_.get_xticks()] - ax_.set_xticklabels(xticklabels, rotation=self.rot) + for ax in self.axes: + if self.orientation == 'vertical' or self.orientation is None: + if self._need_to_set_index: + xticklabels = [labels.get(x, '') for x in ax.get_xticks()] + ax.set_xticklabels(xticklabels) + self._apply_axis_properties(ax.xaxis, rot=self.rot, + fontsize=self.fontsize) + elif self.orientation == 'horizontal': + if self._need_to_set_index: + yticklabels = [labels.get(y, '') for y in ax.get_yticks()] + ax.set_yticklabels(yticklabels) + self._apply_axis_properties(ax.yaxis, rot=self.rot, + fontsize=self.fontsize) + + def _apply_axis_properties(self, axis, rot=None, fontsize=None): + labels = axis.get_majorticklabels() + axis.get_minorticklabels() + for label in labels: + if rot is not None: + label.set_rotation(rot) + if fontsize is not None: + label.set_fontsize(fontsize) @property def legend_title(self): @@ -1336,6 +1360,8 @@ def _get_errorbars(self, label=None, index=None, xerr=True, yerr=True): class KdePlot(MPLPlot): + orientation = 'vertical' + def __init__(self, data, bw_method=None, ind=None, **kwargs): MPLPlot.__init__(self, data, **kwargs) self.bw_method=bw_method @@ -1480,6 +1506,9 @@ def _post_plot_logic(self): class LinePlot(MPLPlot): + _default_rot = 30 + orientation = 'vertical' + def __init__(self, data, **kwargs): MPLPlot.__init__(self, data, **kwargs) if self.stacked: @@ -1657,16 +1686,9 @@ def _post_plot_logic(self): index_name = self._get_index_name() - rot = 30 - if self.rot is not None: - rot = self.rot - for ax in self.axes: if condition: - format_date_labels(ax, rot=rot) - elif self.rot is not None: - for l in ax.get_xticklabels(): - l.set_rotation(self.rot) + format_date_labels(ax, rot=self.rot) if index_name is not None: ax.set_xlabel(index_name) @@ -1767,9 +1789,6 @@ def __init__(self, data, **kwargs): self.ax_pos = self.tick_pos - self.tickoffset def _args_adjust(self): - if self.rot is None: - self.rot = self._default_rot[self.kind] - if com.is_list_like(self.bottom): self.bottom = np.array(self.bottom) if com.is_list_like(self.left): @@ -1859,8 +1878,7 @@ def _post_plot_logic(self): if self.kind == 'bar': ax.set_xlim((s_edge, e_edge)) ax.set_xticks(self.tick_pos) - ax.set_xticklabels(str_index, rotation=self.rot, - fontsize=self.fontsize) + ax.set_xticklabels(str_index) if not self.log: # GH3254+ ax.axhline(0, color='k', linestyle='--') if name is not None: @@ -1869,14 +1887,22 @@ def _post_plot_logic(self): # horizontal bars ax.set_ylim((s_edge, e_edge)) ax.set_yticks(self.tick_pos) - ax.set_yticklabels(str_index, rotation=self.rot, - fontsize=self.fontsize) + ax.set_yticklabels(str_index) ax.axvline(0, color='k', linestyle='--') if name is not None: ax.set_ylabel(name) else: raise NotImplementedError(self.kind) + @property + def orientation(self): + if self.kind == 'bar': + return 'vertical' + elif self.kind == 'barh': + return 'horizontal' + else: + raise NotImplementedError(self.kind) + class PiePlot(MPLPlot):