diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index c23aa5cf4fd5..0bdb8ce5a854 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -422,6 +422,7 @@ def __init__(self, ax, cmap=None, extend=None, spacing='uniform', # uniform or proportional ticks=None, + ticklabels=None, format=None, drawedges=False, filled=True, @@ -463,6 +464,8 @@ def __init__(self, ax, cmap=None, 'min': slice(1, None), 'max': slice(0, -1)}, extend=extend) self.spacing = spacing + self._ticks = ticks + self._ticklabels = ticklabels self.orientation = orientation self.drawedges = drawedges self.filled = filled @@ -543,6 +546,8 @@ def draw_all(self): self.outline.set_xy(xy) self.patch.set_xy(xy) self.update_ticks() + if self._ticklabels: + self.set_ticklabels(self._ticklabels) if self.filled: self._add_solids(X, Y, self._values[:, np.newaxis]) @@ -688,14 +693,19 @@ def set_ticks(self, ticks, update_ticks=True): user has to call `update_ticks` later to update the ticks. """ - if np.iterable(ticks): - self.locator = ticker.FixedLocator(ticks, nbins=len(ticks)) + if np.iterable(ticks) or isinstance(ticks, ticker.Locator): + self._ticks = ticks # self._ticks always holds a valid value + if np.iterable(ticks): + self.locator = ticker.FixedLocator(ticks, nbins=len(ticks)) + else: + self.locator = ticks + if update_ticks: + self.update_ticks() + self._ticks = self.get_ticks() + self.stale = True else: - self.locator = ticks - - if update_ticks: - self.update_ticks() - self.stale = True + cbook._warn_external('The ticks need to be an array of values ' + 'or an instance of ticker.Locator') def get_ticks(self, minor=False): """Return the x ticks as a list of locations.""" @@ -716,12 +726,28 @@ def set_ticklabels(self, ticklabels, update_ticks=True): Tick labels are updated immediately unless *update_ticks* is *False*, in which case one should call `.update_ticks` explicitly. """ - if isinstance(self.locator, ticker.FixedLocator): + # check if explitic ticks have been supplied + if self._ticks is None: + cbook._warn_external('To set explicit ticklabels, call colorbar() ' + 'with ticks keyword or use set_ticks() ' + 'method first.') + # check if length of ticks and ticklabels match + elif len(self._ticks) != len(self._ticklabels): + cbook._warn_external('The ticklabels need to be of the same ' + 'length as the ticks.') + # check if objects in list have valid type + elif not all(type(item) in [str, float, int] for item in ticklabels): + cbook._warn_external('ticklabels need to be a list of str') + # this check was in the code previously, not sure if needed + elif isinstance(self.locator, ticker.FixedLocator): self.formatter = ticker.FixedFormatter(ticklabels) if update_ticks: self.update_ticks() else: - cbook._warn_external("set_ticks() must have been called.") + cbook._warn_external('To set ticklabels, ticks of the same ' + 'length need to be set explicitly first. ' + 'This can be done through ' + 'plt.colorbar(ticks) or set_ticks().') self.stale = True def minorticks_on(self): diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index bbd1e9c5f590..f5a3d0fb4ec7 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -705,3 +705,36 @@ def test_anchored_cbar_position_using_specgrid(): np.testing.assert_allclose( [cx1, cx0], [x1 * shrink + (1 - shrink) * p0, p0 * (1 - shrink) + x0 * shrink]) + + +def test_colorbar_ticklabels_2(): + fig = plt.figure() + plt.imshow(np.arange(100).reshape((10, 10))) + ticklabels = ['cat', 'dog'] + cbar = plt.colorbar(ticks=[10, 90], ticklabels=ticklabels) + fig.canvas.draw() + for i, item in enumerate(cbar.ax.yaxis.get_ticklabels()): + assert ticklabels[i] == item.get_text() + + +def test_colorbar_ticklabels_3(): + fig = plt.figure() + plt.imshow(np.arange(100).reshape((10, 10))) + ticklabels = ['cat', 'dog', 'elephant'] + with pytest.warns( + UserWarning, match='The ticklabels need to be of the same ' + 'length as the ticks.'): + plt.colorbar(ticks=[10, 90], ticklabels=ticklabels) + fig.canvas.draw() + + +def test_colorbar_ticklabels_no_ticks(): + fig = plt.figure() + plt.imshow(np.arange(100).reshape((10, 10))) + ticklabels = ['cat', 'dog', 'dog'] + with pytest.warns( + UserWarning, match='To set explicit ticklabels, call colorbar' + '\\(\\) with ticks keyword or use set_ticks\\(\\) ' + 'method first.'): + plt.colorbar(ticks=None, ticklabels=ticklabels) + fig.canvas.draw() diff --git a/lib/mpl_toolkits/axes_grid1/colorbar.py b/lib/mpl_toolkits/axes_grid1/colorbar.py index 77af5a1ab7fc..182dcaf9a6d1 100644 --- a/lib/mpl_toolkits/axes_grid1/colorbar.py +++ b/lib/mpl_toolkits/axes_grid1/colorbar.py @@ -55,30 +55,33 @@ colormap_kw_doc = ''' - =========== ==================================================== - Property Description - =========== ==================================================== - *extend* [ 'neither' | 'both' | 'min' | 'max' ] - If not 'neither', make pointed end(s) for out-of- - range values. These are set for a given colormap - using the colormap set_under and set_over methods. - *spacing* [ 'uniform' | 'proportional' ] - Uniform spacing gives each discrete color the same - space; proportional makes the space proportional to - the data interval. - *ticks* [ None | list of ticks | Locator object ] - If None, ticks are determined automatically from the - input. - *format* [ None | format string | Formatter object ] - If None, the - :class:`~matplotlib.ticker.ScalarFormatter` is used. - If a format string is given, e.g., '%.3f', that is - used. An alternative - :class:`~matplotlib.ticker.Formatter` object may be - given instead. - *drawedges* bool - Whether to draw lines at color boundaries. - =========== ==================================================== + ============ ==================================================== + Property Description + ============ ==================================================== + *extend* [ 'neither' | 'both' | 'min' | 'max' ] + If not 'neither', make pointed end(s) for out-of- + range values. These are set for a given colormap + using the colormap set_under and set_over methods. + *spacing* [ 'uniform' | 'proportional' ] + Uniform spacing gives each discrete color the same + space; proportional makes the space proportional to + the data interval. + *ticks* [ None | list of ticks | Locator object ] + If None, ticks are determined automatically from the + input. + *ticklabels* sequence of str + List of texts for tick labels; must include values + for non-visible labels. + *format* [ None | format string | Formatter object ] + If None, the + :class:`~matplotlib.ticker.ScalarFormatter` is used. + If a format string is given, e.g., '%.3f', that is + used. An alternative + :class:`~matplotlib.ticker.Formatter` object may be + given instead. + *drawedges* bool + Whether to draw lines at color boundaries. + ============ ==================================================== The following will probably be useful only in the context of indexed colors (that is, when the mappable has norm=NoNorm()),