From 9ef1caa0d5adcc7d6274d4b530037f6cd265e7f3 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 6 Jul 2021 08:29:11 -0700 Subject: [PATCH 1/2] FIX: fix twoslope norm for colorbars --- lib/matplotlib/colors.py | 13 +++++++---- .../test_colorbar/colorbar_twoslope.png | Bin 5886 -> 5932 bytes lib/matplotlib/tests/test_colorbar.py | 5 +++-- tutorials/colors/colormapnorms.py | 21 ++++++++++++++---- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index e0c42c5b69d5..53ac34dfa3db 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1300,9 +1300,13 @@ def __call__(self, value, clip=None): if not self.vmin <= self.vcenter <= self.vmax: raise ValueError("vmin, vcenter, vmax must increase monotonically") + # must linearly extrapolate for ticks on colorbars that go + # beyond vmin and vmax: + min = self.vmin - (self.vcenter - self.vmin) + max = self.vmax + (self.vmax - self.vcenter) result = np.ma.masked_array( - np.interp(result, [self.vmin, self.vcenter, self.vmax], - [0, 0.5, 1.]), mask=np.ma.getmask(result)) + np.interp(result, [min, self.vcenter, max], + [-0.5, 0.5, 1.5]), mask=np.ma.getmask(result)) if is_scalar: result = np.atleast_1d(result)[0] return result @@ -1313,8 +1317,9 @@ def inverse(self, value): (vmin,), _ = self.process_value(self.vmin) (vmax,), _ = self.process_value(self.vmax) (vcenter,), _ = self.process_value(self.vcenter) - - result = np.interp(value, [0, 0.5, 1.], [vmin, vcenter, vmax]) + min = vmin - (vcenter - vmin) + max = vmax + (vmax - vcenter) + result = np.interp(value, [-0.5, 0.5, 1.5], [min, vcenter, max]) return result diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_twoslope.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_twoslope.png index 27d9227dcfe00c31b6ab1cb05484412a646662b1..b92103ae134756ab315f9c36ff1ac4434031da39 100644 GIT binary patch literal 5932 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 XFiMMfoLl5;0RFf-d+cC+8+hVh9%L>L literal 5886 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 a*@fgacZ&*=ZD#@ex6{ePk@5F{fBrAgYcjV0 diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index bb4781f131ed..75e12d3b44cc 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -776,11 +776,12 @@ def test_inset_colorbar_layout(): @image_comparison(['colorbar_twoslope.png'], remove_text=True, style='mpl20') def test_twoslope_colorbar(): - # Note that the first tick = 20, and should be in the middle + # Note that the second tick = 20, and should be in the middle # of the colorbar (white) + # There should be no tick right at the bottom, nor at the top. fig, ax = plt.subplots() - norm = mcolors.TwoSlopeNorm(20, 0, 100) + norm = mcolors.TwoSlopeNorm(20, 5, 95) pc = ax.pcolormesh(np.arange(1, 11), np.arange(1, 11), np.arange(100).reshape(10, 10), norm=norm, cmap='RdBu_r') diff --git a/tutorials/colors/colormapnorms.py b/tutorials/colors/colormapnorms.py index 41aa6310949a..56ad0d948715 100644 --- a/tutorials/colors/colormapnorms.py +++ b/tutorials/colors/colormapnorms.py @@ -277,7 +277,8 @@ # longitude depends on latitude. ax.set_aspect(1 / np.cos(np.deg2rad(49))) ax.set_title('TwoSlopeNorm(x)') -fig.colorbar(pcm, shrink=0.6) +cb = fig.colorbar(pcm, shrink=0.6) +cb.set_ticks([-500, 0, 1000, 2000, 3000, 4000]) plt.show() @@ -312,7 +313,8 @@ def _inverse(x): # ---------------------------------------------------------- # # The `.TwoSlopeNorm` described above makes a useful example for -# defining your own norm. +# defining your own norm. Note for the colorbar to work, you must +# define an inverse for your norm: class MidpointNormalize(colors.Normalize): @@ -323,9 +325,18 @@ def __init__(self, vmin=None, vmax=None, vcenter=None, clip=False): def __call__(self, value, clip=None): # I'm ignoring masked values and all kinds of edge cases to make a # simple example... - x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1] + # Note also that we must extrapolate linearly beyond vmin/vmax + min = self.vmin - (self.vcenter - self.vmin) + max = self.vmax + (self.vmax - self.vcenter) + x, y = [min, self.vcenter, max], [-0.5, 0.5, 1.5] return np.ma.masked_array(np.interp(value, x, y)) + def inverse(self, value): + min = self.vmin - (self.vcenter - self.vmin) + max = self.vmax + (self.vmax - self.vcenter) + y, x = [min, self.vcenter, max], [-0.5, 0.5, 1.5] + return np.interp(value, x, y) + fig, ax = plt.subplots() midnorm = MidpointNormalize(vmin=-500., vcenter=0, vmax=4000) @@ -334,5 +345,7 @@ def __call__(self, value, clip=None): cmap=terrain_map, shading='auto') ax.set_aspect(1 / np.cos(np.deg2rad(49))) ax.set_title('Custom norm') -fig.colorbar(pcm, shrink=0.6, extend='both') +cb = fig.colorbar(pcm, shrink=0.6, extend='both') +cb.set_ticks([-500, 0, 1000, 2000, 3000, 4000]) + plt.show() From 38ad08e50c086844bf4e1f829cd2ab694a9c77e5 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 7 Jul 2021 14:14:03 -0700 Subject: [PATCH 2/2] FIX: use left and right --- lib/matplotlib/colors.py | 15 ++++++--------- tutorials/colors/colormapnorms.py | 15 ++++++--------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 53ac34dfa3db..01005aabf282 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1300,13 +1300,11 @@ def __call__(self, value, clip=None): if not self.vmin <= self.vcenter <= self.vmax: raise ValueError("vmin, vcenter, vmax must increase monotonically") - # must linearly extrapolate for ticks on colorbars that go - # beyond vmin and vmax: - min = self.vmin - (self.vcenter - self.vmin) - max = self.vmax + (self.vmax - self.vcenter) + # note that we must extrapolate for tick locators: result = np.ma.masked_array( - np.interp(result, [min, self.vcenter, max], - [-0.5, 0.5, 1.5]), mask=np.ma.getmask(result)) + np.interp(result, [self.vmin, self.vcenter, self.vmax], + [0, 0.5, 1], left=-np.inf, right=np.inf), + mask=np.ma.getmask(result)) if is_scalar: result = np.atleast_1d(result)[0] return result @@ -1317,9 +1315,8 @@ def inverse(self, value): (vmin,), _ = self.process_value(self.vmin) (vmax,), _ = self.process_value(self.vmax) (vcenter,), _ = self.process_value(self.vcenter) - min = vmin - (vcenter - vmin) - max = vmax + (vmax - vcenter) - result = np.interp(value, [-0.5, 0.5, 1.5], [min, vcenter, max]) + result = np.interp(value, [0, 0.5, 1], [vmin, vcenter, vmax], + left=-np.inf, right=np.inf) return result diff --git a/tutorials/colors/colormapnorms.py b/tutorials/colors/colormapnorms.py index 56ad0d948715..ae3ac8825602 100644 --- a/tutorials/colors/colormapnorms.py +++ b/tutorials/colors/colormapnorms.py @@ -325,17 +325,14 @@ def __init__(self, vmin=None, vmax=None, vcenter=None, clip=False): def __call__(self, value, clip=None): # I'm ignoring masked values and all kinds of edge cases to make a # simple example... - # Note also that we must extrapolate linearly beyond vmin/vmax - min = self.vmin - (self.vcenter - self.vmin) - max = self.vmax + (self.vmax - self.vcenter) - x, y = [min, self.vcenter, max], [-0.5, 0.5, 1.5] - return np.ma.masked_array(np.interp(value, x, y)) + # Note also that we must extrapolate beyond vmin/vmax + x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1.] + return np.ma.masked_array(np.interp(value, x, y, + left=-np.inf, right=np.inf)) def inverse(self, value): - min = self.vmin - (self.vcenter - self.vmin) - max = self.vmax + (self.vmax - self.vcenter) - y, x = [min, self.vcenter, max], [-0.5, 0.5, 1.5] - return np.interp(value, x, y) + y, x = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1] + return np.interp(value, x, y, left=-np.inf, right=np.inf) fig, ax = plt.subplots()