diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index e812550c46fd..e5b02701df96 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1149,16 +1149,21 @@ def _mesh(self): return (Y, X, extendlen) def _forward_boundaries(self, x): + # map boundaries equally between 0 and 1... b = self._boundaries - y = np.interp(x, b, np.linspace(0, b[-1], len(b))) + y = np.interp(x, b, np.linspace(0, 1, len(b))) + # the following avoids ticks in the extends: eps = (b[-1] - b[0]) * 1e-6 + # map these _well_ out of bounds to keep any ticks out + # of the extends region... y[x < b[0]-eps] = -1 y[x > b[-1]+eps] = 2 return y def _inverse_boundaries(self, x): + # invert the above... b = self._boundaries - return np.interp(x, np.linspace(0, b[-1], len(b)), b) + return np.interp(x, np.linspace(0, 1, len(b)), b) def _reset_locator_formatter_scale(self): """ diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index b85726d9146b..8ca161284cb0 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -873,3 +873,32 @@ def test_proportional_colorbars(): CS3 = axs[i, j].contourf(X, Y, Z, levels, cmap=cmap, norm=norm, extend=extends[i]) fig.colorbar(CS3, spacing=spacings[j], ax=axs[i, j]) + + +def test_negative_boundarynorm(): + fig, ax = plt.subplots(figsize=(1, 3)) + cmap = plt.get_cmap("viridis") + + clevs = np.arange(-94, -85) + norm = BoundaryNorm(clevs, cmap.N) + cb = fig.colorbar(cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax) + np.testing.assert_allclose(cb.ax.get_ylim(), [clevs[0], clevs[-1]]) + np.testing.assert_allclose(cb.ax.get_yticks(), clevs) + + clevs = np.arange(85, 94) + norm = BoundaryNorm(clevs, cmap.N) + cb = fig.colorbar(cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax) + np.testing.assert_allclose(cb.ax.get_ylim(), [clevs[0], clevs[-1]]) + np.testing.assert_allclose(cb.ax.get_yticks(), clevs) + + clevs = np.arange(-3, 3) + norm = BoundaryNorm(clevs, cmap.N) + cb = fig.colorbar(cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax) + np.testing.assert_allclose(cb.ax.get_ylim(), [clevs[0], clevs[-1]]) + np.testing.assert_allclose(cb.ax.get_yticks(), clevs) + + clevs = np.arange(-8, 1) + norm = BoundaryNorm(clevs, cmap.N) + cb = fig.colorbar(cm.ScalarMappable(cmap=cmap, norm=norm), cax=ax) + np.testing.assert_allclose(cb.ax.get_ylim(), [clevs[0], clevs[-1]]) + np.testing.assert_allclose(cb.ax.get_yticks(), clevs) diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index 6396105f12ec..5d887416a459 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -305,8 +305,12 @@ def test_clabel_zorder(use_clabeltext, contour_zorder, clabel_zorder): assert clabel.get_zorder() == expected_clabel_zorder +# tol because ticks happen to fall on pixel boundaries so small +# floating point changes in tick location flip which pixel gets +# the tick. @image_comparison(['contour_log_extension.png'], - remove_text=True, style='mpl20') + remove_text=True, style='mpl20', + tol=1.444) def test_contourf_log_extension(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False