diff --git a/doc/users/next_whats_new/colorbar_minor_ticks.rst b/doc/users/next_whats_new/colorbar_minor_ticks.rst new file mode 100644 index 000000000000..a8b47a940875 --- /dev/null +++ b/doc/users/next_whats_new/colorbar_minor_ticks.rst @@ -0,0 +1,10 @@ +Add ``minorticks_on()/off()`` methods for colorbar +-------------------------------------------------- + +A new method :meth:`.Colobar.minorticks_on` is +introduced to correctly display minor ticks on the colorbar. This method +doesn't allow the minor ticks to extend into the regions beyond vmin and vmax +when the extend `kwarg` (used while creating the colorbar) is set to 'both', +'max' or 'min'. +A complementary method :meth:`.Colobar.minorticks_off` +is introduced to remove the minor ticks on the colorbar. diff --git a/examples/color/colorbar_basics.py b/examples/color/colorbar_basics.py index 6ba4d00477b6..adc9733f6a67 100644 --- a/examples/color/colorbar_basics.py +++ b/examples/color/colorbar_basics.py @@ -20,7 +20,7 @@ Zpos = np.ma.masked_less(Z, 0) Zneg = np.ma.masked_greater(Z, 0) -fig, (ax1, ax2) = plt.subplots(figsize=(8, 3), ncols=2) +fig, (ax1, ax2, ax3) = plt.subplots(figsize=(13, 3), ncols=3) # plot just the positive data and save the # color "mappable" object returned by ax1.imshow @@ -35,6 +35,13 @@ neg = ax2.imshow(Zneg, cmap='Reds_r', interpolation='none') fig.colorbar(neg, ax=ax2) +# Plot both positive and negative values betwen +/- 1.2 +pos_neg_clipped = ax3.imshow(Z, cmap='RdBu', vmin=-1.2, vmax=1.2, + interpolation='none') +# Add minorticks on the colorbar to make it easy to read the +# values off the colorbar. +cbar = fig.colorbar(pos_neg_clipped, ax=ax3, extend='both') +cbar.minorticks_on() plt.show() ############################################################################# diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 702d7dba955d..0670383e5b2e 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -244,6 +244,32 @@ def tick_values(self, vmin, vmax): return ticks[(ticks >= vmin) & (ticks <= vmax)] +class _ColorbarAutoMinorLocator(ticker.AutoMinorLocator): + """ + AutoMinorLocator for Colorbar + + This locator is just a `.AutoMinorLocator` except the min and max are + clipped by the norm's min and max (i.e. vmin/vmax from the + image/pcolor/contour object). This is necessary so that the minorticks + don't extrude into the "extend regions". + """ + + def __init__(self, colorbar, n=None): + """ + This ticker needs to know the *colorbar* so that it can access + its *vmin* and *vmax*. + """ + self._colorbar = colorbar + self.ndivs = n + ticker.AutoMinorLocator.__init__(self, n=None) + + def __call__(self): + vmin = self._colorbar.norm.vmin + vmax = self._colorbar.norm.vmax + ticks = ticker.AutoMinorLocator.__call__(self) + return ticks[(ticks >= vmin) & (ticks <= vmax)] + + class _ColorbarLogLocator(ticker.LogLocator): """ LogLocator for Colorbarbar @@ -1164,6 +1190,33 @@ def remove(self): # use_gridspec was True ax.set_subplotspec(subplotspec) + def minorticks_on(self): + """ + Turns on the minor ticks on the colorbar without extruding + into the "extend regions". + """ + ax = self.ax + long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis + + if long_axis.get_scale() == 'log': + warnings.warn('minorticks_on() has no effect on a ' + 'logarithmic colorbar axis') + else: + long_axis.set_minor_locator(_ColorbarAutoMinorLocator(self)) + + def minorticks_off(self): + """ + Turns off the minor ticks on the colorbar. + """ + ax = self.ax + long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis + + if long_axis.get_scale() == 'log': + warnings.warn('minorticks_off() has no effect on a ' + 'logarithmic colorbar axis') + else: + long_axis.set_minor_locator(ticker.NullLocator()) + @docstring.Substitution(make_axes_kw_doc) def make_axes(parents, location=None, orientation=None, fraction=0.15, diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index d956209c599b..450d73e69697 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -270,6 +270,31 @@ def test_colorbar_ticks(): assert len(cbar.ax.xaxis.get_ticklocs()) == len(clevs) +def test_colorbar_minorticks_on_off(): + # test for github issue #11510 and PR #11584 + np.random.seed(seed=12345) + data = np.random.randn(20, 20) + with rc_context({'_internal.classic_mode': False}): + fig, ax = plt.subplots() + # purposefully setting vmin and vmax to odd fractions + # so as to check for the correct locations of the minor ticks + im = ax.pcolormesh(data, vmin=-2.3, vmax=3.3) + + cbar = fig.colorbar(im, extend='both') + cbar.minorticks_on() + correct_minorticklocs = np.array([-2.2, -1.8, -1.6, -1.4, -1.2, -0.8, + -0.6, -0.4, -0.2, 0.2, 0.4, 0.6, + 0.8, 1.2, 1.4, 1.6, 1.8, 2.2, 2.4, + 2.6, 2.8, 3.2]) + # testing after minorticks_on() + np.testing.assert_almost_equal(cbar.ax.yaxis.get_minorticklocs(), + correct_minorticklocs) + cbar.minorticks_off() + # testing after minorticks_off() + np.testing.assert_almost_equal(cbar.ax.yaxis.get_minorticklocs(), + np.array([])) + + def test_colorbar_autoticks(): # Test new autotick modes. Needs to be classic because # non-classic doesn't go this route.