@@ -2291,10 +2291,9 @@ def _interpolate_extrapolate(xq, x, y):
22912291 return yq
22922292
22932293
2294- def _sanitize_levels (levels , allow_descending = True ):
2294+ def _sanitize_levels (levels ):
22952295 """
2296- Ensure the levels are monotonic. If they are descending, either
2297- reverse them or raise an error.
2296+ Ensure the levels are monotonic. If they are descending, reverse them.
22982297 """
22992298 levels = np .atleast_1d (levels )
23002299 if levels .ndim != 1 or levels .size < 2 :
@@ -2304,14 +2303,11 @@ def _sanitize_levels(levels, allow_descending=True):
23042303 if not np .all (np .isfinite (levels )):
23052304 raise ValueError (f'Levels { levels } contain invalid values.' )
23062305 diffs = np .sign (np .diff (levels ))
2307- if all (diffs == 1 ):
2306+ if np . all (diffs == 1 ):
23082307 descending = False
2309- elif all (diffs == - 1 ):
2308+ elif np . all (diffs == - 1 ):
23102309 descending = True
2311- if allow_descending :
2312- levels = levels [::- 1 ]
2313- else :
2314- raise ValueError (f'Levels { levels } must be monotonically increasing.' )
2310+ levels = levels [::- 1 ]
23152311 else :
23162312 raise ValueError (f'Levels { levels } must be monotonic.' )
23172313 return levels , descending
@@ -2326,15 +2322,15 @@ class DiscreteNorm(mcolors.BoundaryNorm):
23262322 # WARNING: Must be child of BoundaryNorm. Many methods in ColorBarBase
23272323 # test for class membership, crucially including _process_values(), which
23282324 # if it doesn't detect BoundaryNorm will try to use DiscreteNorm.inverse().
2329- @warnings ._rename_kwargs ('0.7' , extend = 'unique' )
2330- def __init__ (
2331- self , levels , norm = None , unique = None , step = None , clip = False , descending = False ,
2332- ):
2325+ @warnings ._rename_kwargs (
2326+ '0.7' , extend = 'unique' , descending = 'DiscreteNorm(descending_levels)'
2327+ )
2328+ def __init__ ( self , levels , norm = None , unique = None , step = None , clip = False ):
23332329 """
23342330 Parameters
23352331 ----------
23362332 levels : sequence of float
2337- The level boundaries.
2333+ The level boundaries. Must be monotonically increasing or decreasing.
23382334 norm : `~matplotlib.colors.Normalize`, optional
23392335 The normalizer used to transform `levels` and data values passed to
23402336 `~DiscreteNorm.__call__` before discretization. The ``vmin`` and ``vmax``
@@ -2355,10 +2351,6 @@ def __init__(
23552351 Whether to clip values falling outside of the level bins. This only
23562352 has an effect on lower colors when unique is ``'min'`` or ``'both'``,
23572353 and on upper colors when unique is ``'max'`` or ``'both'``.
2358- descending : bool, optional
2359- Whether the levels are meant to be descending. This will cause
2360- the colorbar axis to be reversed when it is drawn with a
2361- `~matplotlib.cm.ScalarMappable` that uses this normalizer.
23622354
23632355 Note
23642356 ----
@@ -2395,11 +2387,11 @@ def __init__(
23952387 )
23962388
23972389 # Ensure monotonicaly increasing levels and add built-in attributes
2398- levels , _ = _sanitize_levels (levels , allow_descending = False )
2399- norm .vmin = vmin = np .min (levels )
2400- norm .vmax = vmax = np .max (levels )
2390+ levels , descending_levels = _sanitize_levels (levels )
2391+ bins , descending_bins = _sanitize_levels (norm (levels )) # e.g. SegmentedNorm
24012392 vcenter = getattr (norm , 'vcenter' , None )
2402- bins , _ = _sanitize_levels (norm (levels ), allow_descending = False )
2393+ vmin = norm .vmin = np .min (levels )
2394+ vmax = norm .vmax = np .max (levels )
24032395
24042396 # Get color coordinates for each bin, plus two extra for out-of-bounds
24052397 # For same out-of-bounds colors, looks like [0 - eps, 0, ..., 1, 1 + eps]
@@ -2420,21 +2412,16 @@ def __init__(
24202412 if unique in ('max' , 'both' ):
24212413 mids [- 1 ] += step * (mids [- 2 ] - mids [- 3 ])
24222414 if vcenter is None :
2423- mids = _interpolate_basic (
2424- mids , np .min (mids ), np .max (mids ), vmin , vmax
2425- )
2415+ mids = _interpolate_basic (mids , np .min (mids ), np .max (mids ), vmin , vmax )
24262416 else :
24272417 mids = mids .copy ()
2428- mids [mids < vcenter ] = _interpolate_basic (
2429- mids [mids < vcenter ], np .min (mids ), vcenter , vmin , vcenter ,
2430- )
2431- mids [mids >= vcenter ] = _interpolate_basic (
2432- mids [mids >= vcenter ], vcenter , np .max (mids ), vcenter , vmax ,
2433- )
2434- eps = 1e-10 # mids and dest are numpy.float64
2418+ ipts = (np .min (mids ), vcenter , vmin , vcenter )
2419+ mids [mids < vcenter ] = _interpolate_basic (mids [mids < vcenter ], * ipts )
2420+ ipts = (vcenter , np .max (mids ), vcenter , vmax )
2421+ mids [mids >= vcenter ] = _interpolate_basic (mids [mids >= vcenter ], * ipts )
24352422 dest = norm (mids )
2436- dest [0 ] -= eps
2437- dest [- 1 ] += eps
2423+ dest [0 ] -= 1e-10 # dest guaranteed to be numpy.float64
2424+ dest [- 1 ] += 1e-10
24382425
24392426 # Attributes
24402427 # NOTE: If clip is True, we clip values to the centers of the end bins
@@ -2443,12 +2430,12 @@ def __init__(
24432430 # NOTE: With unique='min' the minimimum in-bounds and out-of-bounds
24442431 # colors are the same so clip=True will have no effect. Same goes
24452432 # for unique='max' with maximum colors.
2433+ self ._descending = descending_levels or descending_bins
24462434 self ._bmin = np .min (mids )
24472435 self ._bmax = np .max (mids )
24482436 self ._bins = bins
24492437 self ._dest = dest
24502438 self ._norm = norm
2451- self ._descending = descending
24522439 self .vmin = vmin
24532440 self .vmax = vmax
24542441 self .boundaries = levels
@@ -2489,6 +2476,8 @@ def __call__(self, value, clip=None):
24892476 yq = ma .array (yq , mask = ma .getmask (xq ))
24902477 if is_scalar :
24912478 yq = np .atleast_1d (yq )[0 ]
2479+ if self .descending :
2480+ yq = 1 - yq
24922481 return yq
24932482
24942483 def inverse (self , value ): # noqa: U100
@@ -2500,7 +2489,7 @@ def inverse(self, value): # noqa: U100
25002489 @property
25012490 def descending (self ):
25022491 """
2503- Whether the colormap levels are descending.
2492+ Whether the normalizer levels are descending.
25042493 """
25052494 return self ._descending
25062495
@@ -2515,7 +2504,7 @@ def __init__(self, levels, vmin=None, vmax=None, clip=False):
25152504 Parameters
25162505 ----------
25172506 levels : sequence of float
2518- The level boundaries. Must be monotonically increasing.
2507+ The level boundaries. Must be monotonically increasing or decreasing .
25192508 vmin, vmax : None
25202509 Ignored. These are set to the minimum and maximum of `levels`.
25212510 clip : bool, optional
@@ -2541,11 +2530,11 @@ def __init__(self, levels, vmin=None, vmax=None, clip=False):
25412530 >>> fig, ax = pplt.subplots()
25422531 >>> ax.contourf(data, levels=levels)
25432532 """
2544- levels = np .asarray (levels )
2545- levels , _ = _sanitize_levels (levels , allow_descending = False )
2533+ levels , descending = _sanitize_levels (levels )
25462534 dest = np .linspace (0 , 1 , len (levels ))
25472535 vmin , vmax = np .min (levels ), np .max (levels )
25482536 super ().__init__ (vmin = vmin , vmax = vmax , clip = clip )
2537+ self ._descending = descending
25492538 self ._x = self .boundaries = levels # we use 'boundaries' in plot wrapper
25502539 self ._y = dest
25512540
@@ -2570,6 +2559,8 @@ def __call__(self, value, clip=None):
25702559 yq = _interpolate_extrapolate (xq , self ._x , self ._y )
25712560 if is_scalar :
25722561 yq = np .atleast_1d (yq )[0 ]
2562+ if self .descending :
2563+ yq = 1 - yq
25732564 return yq
25742565
25752566 def inverse (self , value ):
@@ -2587,6 +2578,13 @@ def inverse(self, value):
25872578 xq = np .atleast_1d (xq )[0 ]
25882579 return xq
25892580
2581+ @property
2582+ def descending (self ):
2583+ """
2584+ Whether the normalizer levels are descending.
2585+ """
2586+ return self ._descending
2587+
25902588
25912589class DivergingNorm (mcolors .Normalize ):
25922590 """
0 commit comments