1616"""
1717
1818from collections .abc import Mapping , MutableMapping
19+ import functools
1920
2021import numpy as np
2122from numpy import ma
2223
2324import matplotlib as mpl
24- from matplotlib import _api , colors , cbook
25+ from matplotlib import _api , colors , cbook , scale
2526from matplotlib ._cm import datad
2627from 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+
334364class 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 -----
0 commit comments