@@ -1255,6 +1255,101 @@ def __call__(self, value, clip=None):
12551255 return result
12561256
12571257
1258+ class CenteredNorm (Normalize ):
1259+ def __init__ (self , vcenter = 0 , halfrange = None , clip = False ):
1260+ """
1261+ Normalize symmetrical data around a center (0 by default).
1262+
1263+ Unlike `TwoSlopeNorm`, `CenteredNorm` applies an equal rate of change
1264+ around the center.
1265+
1266+ Useful when mapping symmetrical data around a conceptual center
1267+ e.g., data that range from -2 to 4, with 0 as the midpoint, and
1268+ with equal rates of change around that midpoint.
1269+
1270+ Parameters
1271+ ----------
1272+ vcenter : float, default: 0
1273+ The data value that defines ``0.5`` in the normalization.
1274+ halfrange : float, optional
1275+ The range of data values that defines a range of ``0.5`` in the
1276+ normalization, so that *vcenter* - *halfrange* is ``0.0`` and
1277+ *vcenter* + *halfrange* is ``1.0`` in the normalization.
1278+ Defaults to the largest absolute difference to *vcenter* for
1279+ the values in the dataset.
1280+
1281+ Examples
1282+ --------
1283+ This maps data values -2 to 0.25, 0 to 0.5, and 4 to 1.0
1284+ (assuming equal rates of change above and below 0.0):
1285+
1286+ >>> import matplotlib.colors as mcolors
1287+ >>> norm = mcolors.CenteredNorm(halfrange=4.0)
1288+ >>> data = [-2., 0., 4.]
1289+ >>> norm(data)
1290+ array([0.25, 0.5 , 1. ])
1291+ """
1292+ self ._vcenter = vcenter
1293+ # calling the halfrange setter to set vmin and vmax
1294+ self .halfrange = halfrange
1295+ self .clip = clip
1296+
1297+ def _set_vmin_vmax (self ):
1298+ """
1299+ Set *vmin* and *vmax* based on *vcenter* and *halfrange*.
1300+ """
1301+ self .vmax = self ._vcenter + self ._halfrange
1302+ self .vmin = self ._vcenter - self ._halfrange
1303+
1304+ def autoscale (self , A ):
1305+ """
1306+ Set *halfrange* to ``max(abs(A-vcenter))``, then set *vmin* and *vmax*.
1307+ """
1308+ A = np .asanyarray (A )
1309+ self ._halfrange = max (self ._vcenter - A .min (),
1310+ A .max ()- self ._vcenter )
1311+ self ._set_vmin_vmax ()
1312+
1313+ def autoscale_None (self , A ):
1314+ """Set *vmin* and *vmax*."""
1315+ A = np .asanyarray (A )
1316+ if self .vmax is None and A .size :
1317+ self .autoscale (A )
1318+
1319+ @property
1320+ def vcenter (self ):
1321+ return self ._vcenter
1322+
1323+ @vcenter .setter
1324+ def vcenter (self , vcenter ):
1325+ self ._vcenter = vcenter
1326+ if self .vmax is not None :
1327+ # recompute halfrange assuming vmin and vmax represent
1328+ # min and max of data
1329+ self ._halfrange = max (self ._vcenter - self .vmin ,
1330+ self .vmax - self ._vcenter )
1331+ self ._set_vmin_vmax ()
1332+
1333+ @property
1334+ def halfrange (self ):
1335+ return self ._halfrange
1336+
1337+ @halfrange .setter
1338+ def halfrange (self , halfrange ):
1339+ if halfrange is None :
1340+ self ._halfrange = None
1341+ self .vmin = None
1342+ self .vmax = None
1343+ else :
1344+ self ._halfrange = abs (halfrange )
1345+
1346+ def __call__ (self , value , clip = None ):
1347+ if self ._halfrange is not None :
1348+ # enforce symmetry, reset vmin and vmax
1349+ self ._set_vmin_vmax ()
1350+ return super ().__call__ (value , clip = clip )
1351+
1352+
12581353def _make_norm_from_scale (scale_cls , base_norm_cls = None , * , init = None ):
12591354 """
12601355 Decorator for building a `.Normalize` subclass from a `.Scale` subclass.
0 commit comments