From 538fb748ba9b2e6615a6c2b22b07603c81e7e233 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sun, 7 Jan 2018 15:31:34 -1000 Subject: [PATCH 1/3] Handle Tick gridline properties like other Tick properties This is a refactoring for internal and API consistency, so that Axis.set_tick_params can set all Tick properties. It also removes some redundant code involving attributes like isDefault_majloc. --- lib/matplotlib/axis.py | 121 ++++++++++++++++++++++------------------ lib/matplotlib/scale.py | 11 +++- 2 files changed, 74 insertions(+), 58 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 6e40f4138042..6c54af06d268 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -28,6 +28,14 @@ GRIDLINE_INTERPOLATION_STEPS = 180 +# This list is being used for compatibility with Axes.grid, which +# allows all Line2D kwargs. +_line_AI = artist.ArtistInspector(mlines.Line2D) +_line_param_names = _line_AI.get_setters() +_line_param_aliases = [list(d.keys())[0] for d in _line_AI.aliasd.values()] +_gridline_param_names = ['grid_' + name + for name in _line_param_names + _line_param_aliases] + class Tick(artist.Artist): """ @@ -86,6 +94,11 @@ def __init__(self, axes, loc, label, label2On=False, major=True, labelrotation=0, + grid_color=None, + grid_linestyle=None, + grid_linewidth=None, + grid_alpha=None, + **kw # Other Line2D kwargs applied to gridlines. ): """ bbox is the Bound2D bounding box in display coords of the Axes @@ -153,6 +166,17 @@ def __init__(self, axes, loc, label, zorder = mlines.Line2D.zorder self._zorder = zorder + self._grid_color = (rcParams['grid.color'] + if grid_color is None else grid_color) + self._grid_linestyle = (rcParams['grid.linestyle'] + if grid_linestyle is None else grid_linestyle) + self._grid_linewidth = (rcParams['grid.linewidth'] + if grid_linewidth is None else grid_linewidth) + self._grid_alpha = (rcParams['grid.alpha'] + if grid_alpha is None else grid_alpha) + + self._grid_kw = {k[5:]: v for k, v in kw.items()} + self.apply_tickdir(tickdir) self.tick1line = self._get_tick1line() @@ -368,6 +392,14 @@ def _apply_params(self, **kw): v = getattr(self.label1, 'get_' + k)() setattr(self, '_label' + k, v) + grid_list = [k for k in six.iteritems(kw) + if k[0] in _gridline_param_names] + if grid_list: + grid_kw = {k[5:]: v for k, v in grid_list} + self.gridline.set(**grid_kw) + for k, v in six.iteritems(grid_kw): + setattr(self, '_grid_' + k, v) + def update_position(self, loc): 'Set the location of tick in data coords with scalar *loc*' raise NotImplementedError('Derived must override') @@ -469,11 +501,12 @@ def _get_gridline(self): 'Get the default line2D instance' # x in data coords, y in axes coords l = mlines.Line2D(xdata=(0.0, 0.0), ydata=(0, 1.0), - color=rcParams['grid.color'], - linestyle=rcParams['grid.linestyle'], - linewidth=rcParams['grid.linewidth'], - alpha=rcParams['grid.alpha'], - markersize=0) + color=self._grid_color, + linestyle=self._grid_linestyle, + linewidth=self._grid_linewidth, + alpha=self._grid_alpha, + markersize=0, + **self._grid_kw) l.set_transform(self.axes.get_xaxis_transform(which='grid')) l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS self._set_artist_props(l) @@ -592,12 +625,12 @@ def _get_gridline(self): 'Get the default line2D instance' # x in axes coords, y in data coords l = mlines.Line2D(xdata=(0, 1), ydata=(0, 0), - color=rcParams['grid.color'], - linestyle=rcParams['grid.linestyle'], - linewidth=rcParams['grid.linewidth'], - alpha=rcParams['grid.alpha'], - markersize=0) - + color=self._grid_color, + linestyle=self._grid_linestyle, + linewidth=self._grid_linewidth, + alpha=self._grid_alpha, + markersize=0, + **self._grid_kw) l.set_transform(self.axes.get_yaxis_transform(which='grid')) l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS self._set_artist_props(l) @@ -650,13 +683,6 @@ def __init__(self, axes, pickradius=15): artist.Artist.__init__(self) self.set_figure(axes.figure) - # Keep track of setting to the default value, this allows use to know - # if any of the following values is explicitly set by the user, so as - # to not overwrite their settings with any of our 'auto' settings. - self.isDefault_majloc = True - self.isDefault_minloc = True - self.isDefault_majfmt = True - self.isDefault_minfmt = True self.isDefault_label = True self.axes = axes @@ -745,22 +771,10 @@ def get_children(self): def cla(self): 'clear the current axis' - self.set_major_locator(mticker.AutoLocator()) - self.set_major_formatter(mticker.ScalarFormatter()) - self.set_minor_locator(mticker.NullLocator()) - self.set_minor_formatter(mticker.NullFormatter()) - self.set_label_text('') - self._set_artist_props(self.label) + self.label.set_text('') # self.set_label_text would change isDefault_ - # Keep track of setting to the default value, this allows use to know - # if any of the following values is explicitly set by the user, so as - # to not overwrite their settings with any of our 'auto' settings. - self.isDefault_majloc = True - self.isDefault_minloc = True - self.isDefault_majfmt = True - self.isDefault_minfmt = True - self.isDefault_label = True + self._set_scale('linear') # Clear the callback registry for this axis, or it may "leak" self.callbacks = cbook.CallbackRegistry() @@ -771,9 +785,6 @@ def cla(self): self._gridOnMinor = (rcParams['axes.grid'] and rcParams['axes.grid.which'] in ('both', 'minor')) - self.label.set_text('') - self._set_artist_props(self.label) - self.reset_ticks() self.converter = None @@ -782,9 +793,11 @@ def cla(self): self.stale = True def reset_ticks(self): - # build a few default ticks; grow as necessary later; only - # define 1 so properties set on ticks will be copied as they - # grow + """ + Re-initialize the major and minor Tick lists. + + Each list starts with a single fresh Tick. + """ del self.majorTicks[:] del self.minorTicks[:] @@ -793,6 +806,11 @@ def reset_ticks(self): self._lastNumMajorTicks = 1 self._lastNumMinorTicks = 1 + try: + self.set_clip_path(self.axes.patch) + except AttributeError: + pass + def set_tick_params(self, which='major', reset=False, **kw): """ Set appearance parameters for ticks and ticklabels. @@ -810,6 +828,7 @@ def set_tick_params(self, which='major', reset=False, **kw): if reset: d.clear() d.update(kwtrans) + if reset: self.reset_ticks() else: @@ -833,7 +852,8 @@ def _translate_tick_kw(kw, to_init_kw=True): kwkeys1 = ['length', 'direction', 'left', 'bottom', 'right', 'top', 'labelleft', 'labelbottom', 'labelright', 'labeltop', 'labelrotation'] - kwkeys = kwkeys0 + kwkeys1 + kwkeys2 = _gridline_param_names + kwkeys = kwkeys0 + kwkeys1 + kwkeys2 kwtrans = dict() if to_init_kw: if 'length' in kw: @@ -975,7 +995,7 @@ def _update_ticks(self, renderer): """ interval = self.get_view_interval() - tick_tups = list(self.iter_ticks()) + tick_tups = list(self.iter_ticks()) # iter_ticks calls the locator if self._smart_bounds and tick_tups: # handle inverted limits view_low, view_high = sorted(interval) @@ -1401,30 +1421,21 @@ def grid(self, b=None, which='major', **kwargs): if len(kwargs): b = True which = which.lower() + gridkw = {'grid_' + item[0]: item[1] for item in kwargs.items()} if which in ['minor', 'both']: if b is None: self._gridOnMinor = not self._gridOnMinor else: self._gridOnMinor = b - for tick in self.minorTicks: # don't use get_ticks here! - if tick is None: - continue - tick.gridOn = self._gridOnMinor - if len(kwargs): - tick.gridline.update(kwargs) - self._minor_tick_kw['gridOn'] = self._gridOnMinor + self.set_tick_params(which='minor', gridOn=self._gridOnMinor, + **gridkw) if which in ['major', 'both']: if b is None: self._gridOnMajor = not self._gridOnMajor else: self._gridOnMajor = b - for tick in self.majorTicks: # don't use get_ticks here! - if tick is None: - continue - tick.gridOn = self._gridOnMajor - if len(kwargs): - tick.gridline.update(kwargs) - self._major_tick_kw['gridOn'] = self._gridOnMajor + self.set_tick_params(which='major', gridOn=self._gridOnMajor, + **gridkw) self.stale = True def update_units(self, data): @@ -1454,11 +1465,11 @@ def _update_axisinfo(self): check the axis converter for the stored units to see if the axis info needs to be updated """ - if self.converter is None: return info = self.converter.axisinfo(self.units, self) + if info is None: return if info.majloc is not None and \ diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 1fb0b00edd53..d9026a619212 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -6,10 +6,11 @@ import numpy as np from numpy import ma -from matplotlib import cbook, docstring +from matplotlib import cbook, docstring, rcParams from matplotlib.ticker import ( NullFormatter, ScalarFormatter, LogFormatterSciNotation, LogitFormatter, - NullLocator, LogLocator, AutoLocator, SymmetricalLogLocator, LogitLocator) + NullLocator, LogLocator, AutoLocator, AutoMinorLocator, + SymmetricalLogLocator, LogitLocator) from matplotlib.transforms import Transform, IdentityTransform @@ -71,8 +72,12 @@ def set_default_locators_and_formatters(self, axis): """ axis.set_major_locator(AutoLocator()) axis.set_major_formatter(ScalarFormatter()) - axis.set_minor_locator(NullLocator()) axis.set_minor_formatter(NullFormatter()) + # update the minor locator for x and y axis based on rcParams + if rcParams['xtick.minor.visible']: + axis.set_minor_locator(AutoMinorLocator()) + else: + axis.set_minor_locator(NullLocator()) def get_transform(self): """ From 0100cec5046eef5edc76fd62f6eff2bf34a16d21 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sun, 7 Jan 2018 16:18:37 -1000 Subject: [PATCH 2/3] Add test for using Axes.tick_params to set gridline properties --- lib/matplotlib/tests/test_axes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 6f3d513e4fbf..61377cb8f212 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5152,6 +5152,18 @@ def test_axis_set_tick_params_labelsize_labelcolor(): assert axis_1.yaxis.majorTicks[0]._labelcolor == 'red' +def test_axes_tick_params_gridlines(): + # Now treating grid params like other Tick params + ax = plt.subplot() + ax.tick_params(grid_color='b', grid_linewidth=5, grid_alpha=0.5, + grid_linestyle='dashdot') + for axis in ax.xaxis, ax.yaxis: + assert axis.majorTicks[0]._grid_color == 'b' + assert axis.majorTicks[0]._grid_linewidth == 5 + assert axis.majorTicks[0]._grid_alpha == 0.5 + assert axis.majorTicks[0]._grid_linestyle == 'dashdot' + + def test_none_kwargs(): fig, ax = plt.subplots() ln, = ax.plot(range(32), linestyle=None) From ee35e584a67aa1304b54af1a72954ecea04d8197 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Sun, 7 Jan 2018 16:52:51 -1000 Subject: [PATCH 3/3] Add a Whats_new entry, and augment docstrings --- .../2018_01_07_tick_params_gridlines.rst | 8 ++++++++ lib/matplotlib/axes/_base.py | 19 ++++++++++++++++--- lib/matplotlib/axis.py | 2 +- 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 doc/users/next_whats_new/2018_01_07_tick_params_gridlines.rst diff --git a/doc/users/next_whats_new/2018_01_07_tick_params_gridlines.rst b/doc/users/next_whats_new/2018_01_07_tick_params_gridlines.rst new file mode 100644 index 000000000000..b14327d3736b --- /dev/null +++ b/doc/users/next_whats_new/2018_01_07_tick_params_gridlines.rst @@ -0,0 +1,8 @@ +`Axes.tick_params` can set gridline properties +---------------------------------------------- + +`Tick` objects hold gridlines as well as the tick mark and its label. +`Axis.set_tick_params`, `Axes.tick_params` and `pyplot.tick_params` +now have keyword arguments 'grid_color', 'grid_alpha', 'grid_linewidth', +and 'grid_linestyle' for overriding the defaults in `rcParams`: +'grid.color', etc. diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index f7819aaf9fd8..614c943d73d1 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2790,7 +2790,7 @@ def locator_params(self, axis='both', tight=None, **kwargs): self.autoscale_view(tight=tight, scalex=_x, scaley=_y) def tick_params(self, axis='both', **kwargs): - """Change the appearance of ticks and tick labels. + """Change the appearance of ticks, tick labels, and gridlines. Parameters ---------- @@ -2848,16 +2848,29 @@ def tick_params(self, axis='both', **kwargs): labelrotation : float Tick label rotation + grid_color : color + Changes the gridline color to the given mpl color spec. + + grid_alpha : float + Transparency of gridlines: 0 (transparent) to 1 (opaque). + + grid_linewidth : float + Width of gridlines in points. + + grid_linestyle : string + Any valid :class:`~matplotlib.lines.Line2D` line style spec. + Examples -------- Usage :: - ax.tick_params(direction='out', length=6, width=2, colors='r') + ax.tick_params(direction='out', length=6, width=2, colors='r', + grid_color='r', grid_alpha=0.5) This will make all major ticks be red, pointing out of the box, and with dimensions 6 points by 2 points. Tick labels will - also be red. + also be red. Gridlines will be red and translucent. """ if axis in ['x', 'both']: diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 6c54af06d268..ac6bf3425c41 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -813,7 +813,7 @@ def reset_ticks(self): def set_tick_params(self, which='major', reset=False, **kw): """ - Set appearance parameters for ticks and ticklabels. + Set appearance parameters for ticks, ticklabels, and gridlines. For documentation of keyword arguments, see :meth:`matplotlib.axes.Axes.tick_params`.