Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a44db64

Browse files
committed
Set norms using scale names.
1 parent b57506e commit a44db64

File tree

4 files changed

+75
-19
lines changed

4 files changed

+75
-19
lines changed

lib/matplotlib/cm.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
"""
1717

1818
from collections.abc import Mapping, MutableMapping
19+
import functools
1920

2021
import numpy as np
2122
from numpy import ma
2223

2324
import matplotlib as mpl
24-
from matplotlib import _api, colors, cbook
25+
from matplotlib import _api, colors, cbook, scale
2526
from matplotlib._cm import datad
2627
from matplotlib._cm_listed import cmaps as cmaps_listed
2728

@@ -331,6 +332,35 @@ def unregister_cmap(name):
331332
return _cmap_registry.pop(name)
332333

333334

335+
@functools.lru_cache(None)
336+
def _auto_norm_from_scale(scale_cls):
337+
"""
338+
Automatically generate a norm class from *scale_cls*.
339+
340+
This differs from `.colors.make_norm_from_scale` in the following points:
341+
342+
- This function is not a class decorator, but directly returns a norm class
343+
(as if decorating `.Normalize`).
344+
- The scale is automatically constructed with ``nonpositive="mask"``, if it
345+
supports such a parameter, to work around the difference in defaults
346+
between standard scales (which use "clip") and norms (which use "mask").
347+
- The returned norm class is memoized and reused for later calls.
348+
(`.colors.make_norm_from_scale` also memoizes, but we call it with a
349+
`functools.partial` instances which always compare unequal, so the cache
350+
doesn't get hit.)
351+
"""
352+
# Actually try to construct an instance, to verify whether
353+
# ``nonpositive="mask"`` is supported.
354+
try:
355+
norm = colors.make_norm_from_scale(
356+
functools.partial(scale_cls, nonpositive="mask"))(
357+
colors.Normalize)()
358+
except TypeError:
359+
norm = colors.make_norm_from_scale(scale_cls)(
360+
colors.Normalize)()
361+
return type(norm)
362+
363+
334364
class ScalarMappable:
335365
"""
336366
A mixin class to map scalar data to RGBA.
@@ -341,12 +371,13 @@ class ScalarMappable:
341371

342372
def __init__(self, norm=None, cmap=None):
343373
"""
344-
345374
Parameters
346375
----------
347-
norm : `matplotlib.colors.Normalize` (or subclass thereof)
376+
norm : `.Normalize` (or subclass thereof) or str or None
348377
The normalizing object which scales data, typically into the
349378
interval ``[0, 1]``.
379+
If a `str`, a `.Normalize` subclass is dynamically generated based
380+
on the scale with the corresponding name.
350381
If *None*, *norm* defaults to a *colors.Normalize* object which
351382
initializes its scaling based on the first data processed.
352383
cmap : str or `~matplotlib.colors.Colormap`
@@ -376,11 +407,11 @@ def _scale_norm(self, norm, vmin, vmax):
376407
"""
377408
if vmin is not None or vmax is not None:
378409
self.set_clim(vmin, vmax)
379-
if norm is not None:
410+
if isinstance(norm, colors.Normalize):
380411
raise ValueError(
381-
"Passing parameters norm and vmin/vmax simultaneously is "
382-
"not supported. Please pass vmin/vmax directly to the "
383-
"norm when creating it.")
412+
"Passing a Normalize instance simultaneously with "
413+
"vmin/vmax is not supported. Please pass vmin/vmax "
414+
"directly to the norm when creating it.")
384415

385416
# always resolve the autoscaling so we have concrete limits
386417
# rather than deferring to draw time.
@@ -554,9 +585,13 @@ def norm(self):
554585

555586
@norm.setter
556587
def norm(self, norm):
557-
_api.check_isinstance((colors.Normalize, None), norm=norm)
588+
_api.check_isinstance((colors.Normalize, str, None), norm=norm)
558589
if norm is None:
559590
norm = colors.Normalize()
591+
elif isinstance(norm, str):
592+
# case-insensitive, consistently with scale_factory.
593+
scale_cls = scale._scale_mapping[norm.lower()]
594+
norm = _auto_norm_from_scale(scale_cls)()
560595

561596
if norm is self.norm:
562597
# We aren't updating anything
@@ -578,7 +613,7 @@ def set_norm(self, norm):
578613
579614
Parameters
580615
----------
581-
norm : `.Normalize` or None
616+
norm : `.Normalize` or str or None
582617
583618
Notes
584619
-----

lib/matplotlib/colors.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,12 +1587,15 @@ def autoscale_None(self, A):
15871587
in_trf_domain = np.extract(np.isfinite(self._trf.transform(A)), A)
15881588
return super().autoscale_None(in_trf_domain)
15891589

1590-
Norm.__name__ = (
1591-
f"{scale_cls.__name__}Norm" if base_norm_cls is Normalize
1592-
else base_norm_cls.__name__)
1593-
Norm.__qualname__ = (
1594-
f"{scale_cls.__qualname__}Norm" if base_norm_cls is Normalize
1595-
else base_norm_cls.__qualname__)
1590+
if base_norm_cls is Normalize:
1591+
name_source = (scale_cls.func
1592+
if isinstance(scale_cls, functools.partial)
1593+
else scale_cls)
1594+
Norm.__name__ = f"{name_source.__name__}Norm"
1595+
Norm.__qualname__ = f"{name_source.__qualname__}Norm"
1596+
else:
1597+
Norm.__name__ = base_norm_cls.__name__
1598+
Norm.__qualname__ = base_norm_cls.__qualname__
15961599
Norm.__module__ = base_norm_cls.__module__
15971600
Norm.__doc__ = base_norm_cls.__doc__
15981601

lib/matplotlib/tests/test_axes.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -923,8 +923,8 @@ def test_imshow_norm_vminvmax():
923923
a = [[1, 2], [3, 4]]
924924
ax = plt.axes()
925925
with pytest.raises(ValueError,
926-
match="Passing parameters norm and vmin/vmax "
927-
"simultaneously is not supported."):
926+
match="Passing a Normalize instance simultaneously "
927+
"with vmin/vmax is not supported."):
928928
ax.imshow(a, norm=mcolors.Normalize(-10, 10), vmin=0, vmax=5)
929929

930930

@@ -2285,8 +2285,8 @@ def test_scatter_norm_vminvmax(self):
22852285
x = [1, 2, 3]
22862286
ax = plt.axes()
22872287
with pytest.raises(ValueError,
2288-
match="Passing parameters norm and vmin/vmax "
2289-
"simultaneously is not supported."):
2288+
match="Passing a Normalize instance simultaneously "
2289+
"with vmin/vmax is not supported."):
22902290
ax.scatter(x, x, c=x, norm=mcolors.Normalize(-10, 10),
22912291
vmin=0, vmax=5)
22922292

lib/matplotlib/tests/test_image.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,3 +1376,21 @@ def test_rgba_antialias():
13761376
# alternating red and blue stripes become purple
13771377
axs[3].imshow(aa, interpolation='antialiased', interpolation_stage='rgba',
13781378
cmap=cmap, vmin=-1.2, vmax=1.2)
1379+
1380+
1381+
@check_figures_equal(extensions=["png"])
1382+
def test_str_norms(fig_test, fig_ref):
1383+
t = np.random.rand(10, 10) * .8 + .1 # between 0 and 1
1384+
axs = fig_test.subplots(1, 5)
1385+
axs[0].imshow(t, norm="log")
1386+
axs[1].imshow(t, norm="log", vmin=.2)
1387+
axs[2].imshow(t, norm="symlog")
1388+
axs[3].imshow(t, norm="symlog", vmin=.3, vmax=.7)
1389+
axs[4].imshow(t, norm="logit", vmin=.3, vmax=.7)
1390+
axs = fig_ref.subplots(1, 5)
1391+
axs[0].imshow(t, norm=colors.LogNorm())
1392+
axs[1].imshow(t, norm=colors.LogNorm(vmin=.2))
1393+
# same linthresh as SymmetricalLogScale's default.
1394+
axs[2].imshow(t, norm=colors.SymLogNorm(linthresh=2))
1395+
axs[3].imshow(t, norm=colors.SymLogNorm(linthresh=2, vmin=.3, vmax=.7))
1396+
axs[4].imshow(t, norm="logit", clim=(.3, .7))

0 commit comments

Comments
 (0)