diff --git a/doc/api/next_api_changes/behaviour.rst b/doc/api/next_api_changes/behaviour.rst index be394fbd4277..3cca24e94df7 100644 --- a/doc/api/next_api_changes/behaviour.rst +++ b/doc/api/next_api_changes/behaviour.rst @@ -85,3 +85,11 @@ Previously, rcParams entries whose values were color-like accepted "spurious" extra letters or characters in the "middle" of the string, e.g. ``"(0, 1a, '0.5')"`` would be interpreted as ``(0, 1, 0.5)``. These extra characters (including the internal quotes) now cause a ValueError to be raised. + +`.SymLogNorm` now has a *base* parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, `.SymLogNorm` had no *base* kwarg, and defaulted to ``base=np.e`` +whereas the documentation said it was ``base=10``. In preparation to make +the default 10, calling `.SymLogNorm` without the new *base* kwarg emits a +deprecation warning. diff --git a/examples/userdemo/colormap_normalizations.py b/examples/userdemo/colormap_normalizations.py index 419a4b5051aa..2844fd9f9646 100644 --- a/examples/userdemo/colormap_normalizations.py +++ b/examples/userdemo/colormap_normalizations.py @@ -69,7 +69,7 @@ pcm = ax[0].pcolormesh(X, Y, Z1, norm=colors.SymLogNorm(linthresh=0.03, linscale=0.03, - vmin=-1.0, vmax=1.0), + vmin=-1.0, vmax=1.0, base=10), cmap='RdBu_r') fig.colorbar(pcm, ax=ax[0], extend='both') diff --git a/examples/userdemo/colormap_normalizations_symlognorm.py b/examples/userdemo/colormap_normalizations_symlognorm.py index 780381e43da8..b0fbf0dc30ea 100644 --- a/examples/userdemo/colormap_normalizations_symlognorm.py +++ b/examples/userdemo/colormap_normalizations_symlognorm.py @@ -29,7 +29,7 @@ pcm = ax[0].pcolormesh(X, Y, Z, norm=colors.SymLogNorm(linthresh=0.03, linscale=0.03, - vmin=-1.0, vmax=1.0), + vmin=-1.0, vmax=1.0, base=10), cmap='RdBu_r') fig.colorbar(pcm, ax=ax[0], extend='both') diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 52d2b2438959..5b584cfac2b3 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1202,8 +1202,8 @@ class SymLogNorm(Normalize): *linthresh* allows the user to specify the size of this range (-*linthresh*, *linthresh*). """ - def __init__(self, linthresh, linscale=1.0, - vmin=None, vmax=None, clip=False): + def __init__(self, linthresh, linscale=1.0, vmin=None, vmax=None, + clip=False, *, base=None): """ Parameters ---------- @@ -1213,14 +1213,29 @@ def __init__(self, linthresh, linscale=1.0, linscale : float, default: 1 This allows the linear range (-*linthresh* to *linthresh*) to be stretched relative to the logarithmic range. Its value is the - number of decades to use for each half of the linear range. For - example, when *linscale* == 1.0 (the default), the space used for - the positive and negative halves of the linear range will be equal - to one decade in the logarithmic range. + number of powers of *base* (decades for base 10) to use for each + half of the linear range. For example, when *linscale* == 1.0 + (the default), the space used for the positive and negative halves + of the linear range will be equal to a decade in the logarithmic + range if ``base=10``. + base : float, default: None + For v3.2 the default is the old value of ``np.e``, but that is + deprecated for v3.3 when base will default to 10. During the + transition, specify the *base* kwarg to avoid a deprecation + warning. """ Normalize.__init__(self, vmin, vmax, clip) + if base is None: + self._base = np.e + cbook.warn_deprecated("3.3", message="default base will change " + "from np.e to 10. To suppress this warning specify the base " + "kwarg.") + else: + self._base = base + self._log_base = np.log(self._base) + self.linthresh = float(linthresh) - self._linscale_adj = (linscale / (1.0 - np.e ** -1)) + self._linscale_adj = (linscale / (1.0 - self._base ** -1)) if vmin is not None and vmax is not None: self._transform_vmin_vmax() @@ -1255,7 +1270,8 @@ def _transform(self, a): with np.errstate(invalid="ignore"): masked = np.abs(a) > self.linthresh sign = np.sign(a[masked]) - log = (self._linscale_adj + np.log(np.abs(a[masked]) / self.linthresh)) + log = (self._linscale_adj + + np.log(np.abs(a[masked]) / self.linthresh) / self._log_base) log *= sign * self.linthresh a[masked] = log a[~masked] *= self._linscale_adj @@ -1265,7 +1281,8 @@ def _inv_transform(self, a): """Inverse inplace Transformation.""" masked = np.abs(a) > (self.linthresh * self._linscale_adj) sign = np.sign(a[masked]) - exp = np.exp(sign * a[masked] / self.linthresh - self._linscale_adj) + exp = np.power(self._base, + sign * a[masked] / self.linthresh - self._linscale_adj) exp *= sign * self.linthresh a[masked] = exp a[~masked] /= self._linscale_adj diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 05a81e26def3..d1cedfeb141e 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -398,7 +398,7 @@ def test_SymLogNorm(): """ Test SymLogNorm behavior """ - norm = mcolors.SymLogNorm(3, vmax=5, linscale=1.2) + norm = mcolors.SymLogNorm(3, vmax=5, linscale=1.2, base=np.e) vals = np.array([-30, -1, 2, 6], dtype=float) normed_vals = norm(vals) expected = [0., 0.53980074, 0.826991, 1.02758204] @@ -408,16 +408,30 @@ def test_SymLogNorm(): _mask_tester(norm, vals) # Ensure that specifying vmin returns the same result as above - norm = mcolors.SymLogNorm(3, vmin=-30, vmax=5, linscale=1.2) + norm = mcolors.SymLogNorm(3, vmin=-30, vmax=5, linscale=1.2, base=np.e) normed_vals = norm(vals) assert_array_almost_equal(normed_vals, expected) + # test something more easily checked. + norm = mcolors.SymLogNorm(1, vmin=-np.e**3, vmax=np.e**3, base=np.e) + nn = norm([-np.e**3, -np.e**2, -np.e**1, -1, + 0, 1, np.e**1, np.e**2, np.e**3]) + xx = np.array([0., 0.109123, 0.218246, 0.32737, 0.5, 0.67263, + 0.781754, 0.890877, 1.]) + assert_array_almost_equal(nn, xx) + norm = mcolors.SymLogNorm(1, vmin=-10**3, vmax=10**3, base=10) + nn = norm([-10**3, -10**2, -10**1, -1, + 0, 1, 10**1, 10**2, 10**3]) + xx = np.array([0., 0.121622, 0.243243, 0.364865, 0.5, 0.635135, + 0.756757, 0.878378, 1.]) + assert_array_almost_equal(nn, xx) + def test_SymLogNorm_colorbar(): """ Test un-called SymLogNorm in a colorbar. """ - norm = mcolors.SymLogNorm(0.1, vmin=-1, vmax=1, linscale=1) + norm = mcolors.SymLogNorm(0.1, vmin=-1, vmax=1, linscale=1, base=np.e) fig = plt.figure() mcolorbar.ColorbarBase(fig.add_subplot(111), norm=norm) plt.close(fig) @@ -428,7 +442,7 @@ def test_SymLogNorm_single_zero(): Test SymLogNorm to ensure it is not adding sub-ticks to zero label """ fig = plt.figure() - norm = mcolors.SymLogNorm(1e-5, vmin=-1, vmax=1) + norm = mcolors.SymLogNorm(1e-5, vmin=-1, vmax=1, base=np.e) cbar = mcolorbar.ColorbarBase(fig.add_subplot(111), norm=norm) ticks = cbar.get_ticks() assert sum(ticks == 0) == 1 @@ -905,9 +919,10 @@ def __add__(self, other): mydata = data.view(MyArray) for norm in [mcolors.Normalize(), mcolors.LogNorm(), - mcolors.SymLogNorm(3, vmax=5, linscale=1), + mcolors.SymLogNorm(3, vmax=5, linscale=1, base=np.e), mcolors.Normalize(vmin=mydata.min(), vmax=mydata.max()), - mcolors.SymLogNorm(3, vmin=mydata.min(), vmax=mydata.max()), + mcolors.SymLogNorm(3, vmin=mydata.min(), vmax=mydata.max(), + base=np.e), mcolors.PowerNorm(1)]: assert_array_equal(norm(mydata), norm(data)) fig, ax = plt.subplots() diff --git a/tutorials/colors/colormapnorms.py b/tutorials/colors/colormapnorms.py index 412278ac45cb..ea982b243207 100644 --- a/tutorials/colors/colormapnorms.py +++ b/tutorials/colors/colormapnorms.py @@ -98,7 +98,7 @@ pcm = ax[0].pcolormesh(X, Y, Z, norm=colors.SymLogNorm(linthresh=0.03, linscale=0.03, - vmin=-1.0, vmax=1.0), + vmin=-1.0, vmax=1.0, base=10), cmap='RdBu_r') fig.colorbar(pcm, ax=ax[0], extend='both')