From d1c5a6ab163f507e8b0f1b3871e0de8d72266098 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 28 May 2021 15:42:05 -0700 Subject: [PATCH] FIX: fix colorbars with no scales --- lib/matplotlib/colorbar.py | 36 +++++++++++------- lib/matplotlib/colors.py | 12 +++++- .../test_colorbar/colorbar_twoslope.png | Bin 0 -> 5886 bytes lib/matplotlib/tests/test_colorbar.py | 14 +++++++ lib/matplotlib/tests/test_colors.py | 3 +- 5 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_twoslope.png diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index b1cd08d955a7..16b0377a43bc 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1006,24 +1006,34 @@ def _reset_locator_formatter_scale(self): self.locator = None self.minorlocator = None self.formatter = None - if ((self.spacing == 'uniform') and - ((self.boundaries is not None) or - isinstance(self.norm, colors.BoundaryNorm))): - funcs = (self._forward_boundaries, self._inverse_boundaries) - self.ax.set_xscale('function', functions=funcs) - self.ax.set_yscale('function', functions=funcs) - self.__scale = 'function' - elif hasattr(self.norm, '_scale') and (self.norm._scale is not None): + if (self.boundaries is not None or + isinstance(self.norm, colors.BoundaryNorm)): + if self.spacing == 'uniform': + funcs = (self._forward_boundaries, self._inverse_boundaries) + self.ax.set_xscale('function', functions=funcs) + self.ax.set_yscale('function', functions=funcs) + self.__scale = 'function' + elif self.spacing == 'proportional': + self.__scale = 'linear' + self.ax.set_xscale('linear') + self.ax.set_yscale('linear') + elif hasattr(self.norm, '_scale') and self.norm._scale is not None: + # use the norm's scale: self.ax.set_xscale(self.norm._scale) self.ax.set_yscale(self.norm._scale) self.__scale = self.norm._scale.name - else: + elif type(self.norm) is colors.Normalize: + # plain Normalize: self.ax.set_xscale('linear') self.ax.set_yscale('linear') - if type(self.norm) is colors.Normalize: - self.__scale = 'linear' - else: - self.__scale = 'manual' + self.__scale = 'linear' + else: + # norm._scale is None or not an attr: derive the scale from + # the Norm: + funcs = (self.norm, self.norm.inverse) + self.ax.set_xscale('function', functions=funcs) + self.ax.set_yscale('function', functions=funcs) + self.__scale = 'function' def _locate(self, x): """ diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index f678a4ffefd5..3b7e5988eab7 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1152,7 +1152,7 @@ def __init__(self, vmin=None, vmax=None, clip=False): self.vmin = _sanitize_extrema(vmin) self.vmax = _sanitize_extrema(vmax) self.clip = clip - self._scale = scale.LinearScale(axis=None) + self._scale = None # will default to LinearScale for colorbar @staticmethod def process_value(value): @@ -1334,6 +1334,16 @@ def __call__(self, value, clip=None): result = np.atleast_1d(result)[0] return result + def inverse(self, value): + if not self.scaled(): + raise ValueError("Not invertible until both vmin and vmax are set") + (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]) + return result + class CenteredNorm(Normalize): def __init__(self, vcenter=0, halfrange=None, clip=False): diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_twoslope.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_twoslope.png new file mode 100644 index 0000000000000000000000000000000000000000..27d9227dcfe00c31b6ab1cb05484412a646662b1 GIT binary patch literal 5886 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 a*@fgacZ&*=ZD#@ex6{ePk@5F{fBrAgYcjV0 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index ac054755c4c1..6167618575dd 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -771,3 +771,17 @@ def test_inset_colorbar_layout(): np.testing.assert_allclose(cb.ax.get_position().bounds, [0.87, 0.342, 0.0237, 0.315], atol=0.01) assert cb.ax.outer_ax in ax.child_axes + + +@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 + # of the colorbar (white) + fig, ax = plt.subplots() + + norm = mcolors.TwoSlopeNorm(20, 0, 100) + pc = ax.pcolormesh(np.arange(1, 11), np.arange(1, 11), + np.arange(100).reshape(10, 10), + norm=norm, cmap='RdBu_r') + fig.colorbar(pc) diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 81dd65bab713..ae004e957591 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -1406,6 +1406,5 @@ def test_norm_deepcopy(): norm = mcolors.Normalize() norm.vmin = 0.0002 norm2 = copy.deepcopy(norm) - assert isinstance(norm2._scale, mscale.LinearScale) + assert norm2._scale is None assert norm2.vmin == norm.vmin - assert norm2._scale is not norm._scale pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy