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

Skip to content

Commit 3341124

Browse files
OceanWolfjklymak
authored andcommitted
PiecewiseLinearNorm
1 parent 9328880 commit 3341124

File tree

2 files changed

+150
-70
lines changed

2 files changed

+150
-70
lines changed

lib/matplotlib/colors.py

Lines changed: 127 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -998,80 +998,160 @@ class PiecewiseLinearNorm(Normalize):
998998
999999
Normalizes data into the ``[0.0, 1.0]`` interval.
10001000
"""
1001-
def __init__(self, vmin=None, vcenter=None, vmax=None):
1002-
"""Normalize data with an offset midpoint
1003-
1004-
Useful when mapping data unequally centered around a conceptual
1005-
center, e.g., data that range from -2 to 4, with 0 as the midpoint.
1001+
# TODO rewrite the internals of this class once we support OrderedDicts
1002+
# i.e. after we drop support for python 2.6
1003+
def __init__(self, stops=None):
1004+
"""Normalize data linearly between the defined stop points.
1005+
Use this as more generic form of ``DivergingNorm``
10061006
10071007
Parameters
10081008
----------
1009-
vmin : float, optional
1010-
The data value that defines ``0.0`` in the normalized data.
1011-
Defaults to the min value of the dataset.
1012-
1013-
vcenter : float, optional
1014-
The data value that defines ``0.5`` in the normalized data.
1015-
Defaults to halfway between *vmin* and *vmax*.
1016-
1017-
vmax : float, optional
1018-
The data value that defines ``1.0`` in the normalized data.
1019-
Defaults to the the max value of the dataset.
1009+
stops : dict-like, optional
1010+
Accepts a dictionary or anything that can get converted to a
1011+
dictionary which maps the space [0.0, 1.0] to data point, i.e. key
1012+
value pairs.
10201013
10211014
Examples
10221015
--------
1016+
Note this example is equivalent to the DivergingNorm example.
10231017
>>> import matplotlib.colors as mcolors
1024-
>>> offset = mcolors.PiecewiseLinearNorm(vmin=-2., vcenter=0., vmax=4.)
1018+
>>> offset = mcolors.PiecewiseLinearNorm({0.: -2., 0.5: 0., 1.=4.})
10251019
>>> data = [-2., -1., 0., 1., 2., 3., 4.]
10261020
>>> offset(data)
10271021
array([0., 0.25, 0.5, 0.625, 0.75, 0.875, 1.0])
10281022
10291023
"""
1024+
self._set_stops(stops)
1025+
1026+
@property
1027+
def vmin(self):
1028+
try:
1029+
if self._stops[0][0] == 0:
1030+
return self._stops[0][1]
1031+
except IndexError:
1032+
return None
1033+
1034+
@vmin.setter
1035+
def vmin(self, vmin):
1036+
try:
1037+
if self._stops[0][0] == 0:
1038+
self._stops[0] = (self._stops[0][0], vmin)
1039+
return
1040+
except IndexError:
1041+
pass
1042+
self.append_stop(0., vmin)
1043+
1044+
@property
1045+
def vmax(self):
1046+
try:
1047+
if self._stops[-1][0] == 1:
1048+
return self._stops[-1][1]
1049+
except IndexError:
1050+
return None
1051+
1052+
@vmax.setter
1053+
def vmax(self, vmax):
1054+
try:
1055+
if self._stops[-1][0] == 1:
1056+
self._stops[-1] = (self._stops[-1][0], vmax)
1057+
return
1058+
except IndexError:
1059+
pass
1060+
self.append_stop(1., vmax)
1061+
1062+
# TODO Change this to a property when we drop 2.6 and use Ordered Dicts
1063+
def _set_stops(self, stops):
1064+
if not stops:
1065+
self._stops = []
1066+
return
1067+
1068+
stops = dict(stops)
1069+
self._stops = sorted(stops.items(), key=operator.itemgetter(0))
1070+
map_points, data_points = zip(*self._stops)
1071+
if not np.all(np.diff(data_points) > 0):
1072+
raise ValueError("stops must increase monotonically")
1073+
1074+
def append_stop(self, cmap_fraction, data_value):
1075+
i = -1
1076+
for i, (map_point, data_point) in enumerate(self._stops):
1077+
if map_point >= cmap_fraction:
1078+
d1 = data_point # the current index
1079+
break
1080+
else:
1081+
i += 1 # the index to insert before
1082+
d1 = np.inf
1083+
1084+
if i > 0:
1085+
d0 = self._stops[i-1][1]
1086+
else:
1087+
d0 = -np.inf
10301088

1031-
self.vmin = vmin
1032-
self.vcenter = vcenter
1033-
self.vmax = vmax
1089+
if not (d0 < data_value < d1):
1090+
raise ValueError(('Stops must increase monotonically, due to the '
1091+
+ 'stops already set, the cmap_fraction specified'
1092+
+ ' (%f) means that the data_value must lie '
1093+
+ 'between %f and %f, but %f given') %
1094+
(cmap_fraction, d0, d1, data_value))
1095+
1096+
self._stops.insert(i, (cmap_fraction, data_value))
10341097

10351098
def __call__(self, value, clip=None):
10361099
"""Map value to the interval [0, 1]. The clip argument is unused."""
10371100

10381101
result, is_scalar = self.process_value(value)
1039-
10401102
self.autoscale_None(result)
1041-
vmin, vcenter, vmax = self.vmin, self.vcenter, self.vmax
1042-
if vmin == vmax == vcenter:
1043-
result.fill(0)
1044-
elif not vmin <= vcenter <= vmax:
1045-
raise ValueError("minvalue must be less than or equal to "
1046-
"centervalue which must be less than or "
1047-
"equal to maxvalue")
1048-
else:
1049-
vmin = float(vmin)
1050-
vcenter = float(vcenter)
1051-
vmax = float(vmax)
1052-
# in degenerate cases, prefer the center value to the extremes
1053-
degen = (result == vcenter) if vcenter == vmax else None
1054-
1055-
x, y = [vmin, vcenter, vmax], [0, 0.5, 1]
1056-
result = ma.masked_array(np.interp(result, x, y),
1057-
mask=ma.getmask(result))
1058-
if degen is not None:
1059-
result[degen] = 0.5
10601103

1104+
map_points, data_points = zip(*self._stops)
1105+
result = ma.masked_array(np.interp(result, data_points, map_points),
1106+
mask=ma.getmask(result))
10611107
if is_scalar:
10621108
result = np.atleast_1d(result)[0]
10631109
return result
10641110

10651111
def autoscale_None(self, A):
1066-
' autoscale only None-valued vmin or vmax'
1067-
if self.vmin is None and np.size(A) > 0:
1068-
self.vmin = ma.min(A)
1112+
"""Ensures we have the upper and lower bounds set, using the data A"""
1113+
if len(self._stops) == 0 or self._stops[0][0] != 0:
1114+
self.append_stop(0., ma.min(A))
1115+
if self._stops[-1][0] != 1:
1116+
self.append_stop(1., ma.max(A))
1117+
1118+
1119+
class DivergingNorm(PiecewiseLinearNorm):
1120+
def __init__(self, vmin=None, vcenter=None, vmax=None):
1121+
"""Normalize data with an offset midpoint
10691122
1070-
if self.vmax is None and np.size(A) > 0:
1071-
self.vmax = ma.max(A)
1123+
Useful when mapping data unequally centered around a conceptual
1124+
center, e.g., data that range from -2 to 4, with 0 as the midpoint.
1125+
1126+
Parameters
1127+
----------
1128+
vmin : float, optional
1129+
The data value that defines ``0.0`` in the normalized data.
1130+
Defaults to the min value of the dataset.
10721131
1073-
if self.vcenter is None:
1074-
self.vcenter = (self.vmax + self.vmin) * 0.5
1132+
vcenter : float, optional
1133+
The data value that defines ``0.5`` in the normalized data.
1134+
Defaults to halfway between *vmin* and *vmax*.
1135+
1136+
vmax : float, optional
1137+
The data value that defines ``1.0`` in the normalized data.
1138+
Defaults to the the max value of the dataset.
1139+
1140+
Examples
1141+
--------
1142+
>>> import matplotlib.colors as mcolors
1143+
>>> offset = mcolors.PiecewiseLinearNorm(vmin=-2., vcenter=0., vmax=4.)
1144+
>>> data = [-2., -1., 0., 1., 2., 3., 4.]
1145+
>>> offset(data)
1146+
array([0., 0.25, 0.5, 0.625, 0.75, 0.875, 1.0])
1147+
stops = {}
1148+
if vmin is not None:
1149+
stops[0.] = vmin
1150+
if vcenter is not None:
1151+
stops[0.5] = vcenter
1152+
if vmax is not None:
1153+
stops[1.] = vmax
1154+
super(DivergingNorm, self).__init__(stops)
10751155
10761156
10771157
class LogNorm(Normalize):

lib/matplotlib/tests/test_colors.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -283,45 +283,45 @@ def test_process_value_array(self):
283283
assert_array_equal(res, np.array([5., 10.]))
284284

285285

286-
class BasePiecewiseLinearNorm(BaseNormMixin):
287-
normclass = mcolors.PiecewiseLinearNorm
286+
class BaseDivergingNorm(BaseNormMixin):
287+
normclass = mcolors.DivergingNorm
288288
test_inverse = False
289289

290290

291-
class test_PiecewiseLinearNorm_Even(BasePiecewiseLinearNorm):
291+
class test_DivergingNorm_Even(BaseDivergingNorm):
292292
def setup(self):
293293
self.norm = self.normclass(vmin=-1, vcenter=0, vmax=4)
294294
self.vals = np.array([-1.0, -0.5, 0.0, 1.0, 2.0, 3.0, 4.0])
295295
self.expected = np.array([0.0, 0.25, 0.5, 0.625, 0.75, 0.875, 1.0])
296296

297297

298-
class test_PiecewiseLinearNorm_Odd(BasePiecewiseLinearNorm):
298+
class test_DivergingNorm_Odd(BaseDivergingNorm):
299299
def setup(self):
300-
self.normclass = mcolors.PiecewiseLinearNorm
300+
self.normclass = mcolors.DivergingNorm
301301
self.norm = self.normclass(vmin=-2, vcenter=0, vmax=5)
302302
self.vals = np.array([-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
303303
self.expected = np.array([0.0, 0.25, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
304304

305305

306-
class test_PiecewiseLinearNorm_AllNegative(BasePiecewiseLinearNorm):
306+
class test_DivergingNorm_AllNegative(BaseDivergingNorm):
307307
def setup(self):
308-
self.normclass = mcolors.PiecewiseLinearNorm
308+
self.normclass = mcolors.DivergingNorm
309309
self.norm = self.normclass(vmin=-10, vcenter=-8, vmax=-2)
310310
self.vals = np.array([-10., -9., -8., -6., -4., -2.])
311311
self.expected = np.array([0.0, 0.25, 0.5, 0.666667, 0.833333, 1.0])
312312

313313

314-
class test_PiecewiseLinearNorm_AllPositive(BasePiecewiseLinearNorm):
314+
class test_DivergingNorm_AllPositive(BaseDivergingNorm):
315315
def setup(self):
316-
self.normclass = mcolors.PiecewiseLinearNorm
316+
self.normclass = mcolors.DivergingNorm
317317
self.norm = self.normclass(vmin=0, vcenter=3, vmax=9)
318318
self.vals = np.array([0., 1.5, 3., 4.5, 6.0, 7.5, 9.])
319319
self.expected = np.array([0.0, 0.25, 0.5, 0.625, 0.75, 0.875, 1.0])
320320

321321

322-
class test_PiecewiseLinearNorm_NoVs(BasePiecewiseLinearNorm):
322+
class test_DivergingNorm_NoVs(BaseDivergingNorm):
323323
def setup(self):
324-
self.normclass = mcolors.PiecewiseLinearNorm
324+
self.normclass = mcolors.DivergingNorm
325325
self.norm = self.normclass(vmin=None, vcenter=None, vmax=None)
326326
self.vals = np.array([-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0])
327327
self.expected = np.array([0., 0.16666667, 0.33333333,
@@ -346,26 +346,26 @@ def test_vmax(self):
346346
assert_equal(self.norm.vmax, self.expected_vmax)
347347

348348

349-
class test_PiecewiseLinearNorm_VminEqualsVcenter(BasePiecewiseLinearNorm):
349+
class test_DivergingNorm_VminEqualsVcenter(BaseDivergingNorm):
350350
def setup(self):
351-
self.normclass = mcolors.PiecewiseLinearNorm
351+
self.normclass = mcolors.DivergingNorm
352352
self.norm = self.normclass(vmin=-2, vcenter=-2, vmax=2)
353353
self.vals = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])
354354
self.expected = np.array([0.5, 0.625, 0.75, 0.875, 1.0])
355355

356356

357-
class test_PiecewiseLinearNorm_VmaxEqualsVcenter(BasePiecewiseLinearNorm):
357+
class test_DivergingNorm_VmaxEqualsVcenter(BaseDivergingNorm):
358358
def setup(self):
359-
self.normclass = mcolors.PiecewiseLinearNorm
359+
self.normclass = mcolors.DivergingNorm
360360
self.norm = self.normclass(vmin=-2, vcenter=2, vmax=2)
361361
self.vals = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])
362362
self.expected = np.array([0.0, 0.125, 0.25, 0.375, 0.5])
363363

364364

365-
class test_PiecewiseLinearNorm_VsAllEqual(BasePiecewiseLinearNorm):
365+
class test_DivergingNorm_VsAllEqual(BaseDivergingNorm):
366366
def setup(self):
367367
self.v = 10
368-
self.normclass = mcolors.PiecewiseLinearNorm
368+
self.normclass = mcolors.DivergingNorm
369369
self.norm = self.normclass(vmin=self.v, vcenter=self.v, vmax=self.v)
370370
self.vals = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])
371371
self.expected = np.array([0.0, 0.0, 0.0, 0.0, 0.0])
@@ -378,28 +378,28 @@ def test_inverse(self):
378378
)
379379

380380

381-
class test_PiecewiseLinearNorm_Errors(object):
381+
class test_DivergingNorm_Errors(object):
382382
def setup(self):
383383
self.vals = np.arange(50)
384384

385385
@raises(ValueError)
386386
def test_VminGTVcenter(self):
387-
norm = mcolors.PiecewiseLinearNorm(vmin=10, vcenter=0, vmax=20)
387+
norm = mcolors.DivergingNorm(vmin=10, vcenter=0, vmax=20)
388388
norm(self.vals)
389389

390390
@raises(ValueError)
391391
def test_VminGTVmax(self):
392-
norm = mcolors.PiecewiseLinearNorm(vmin=10, vcenter=0, vmax=5)
392+
norm = mcolors.DivergingNorm(vmin=10, vcenter=0, vmax=5)
393393
norm(self.vals)
394394

395395
@raises(ValueError)
396396
def test_VcenterGTVmax(self):
397-
norm = mcolors.PiecewiseLinearNorm(vmin=10, vcenter=25, vmax=20)
397+
norm = mcolors.DivergingNorm(vmin=10, vcenter=25, vmax=20)
398398
norm(self.vals)
399399

400400
@raises(ValueError)
401401
def test_premature_scaling(self):
402-
norm = mcolors.PiecewiseLinearNorm()
402+
norm = mcolors.DivergingNorm()
403403
norm.inverse(np.array([0.1, 0.5, 0.9]))
404404

405405

@@ -412,7 +412,7 @@ def test_offset_norm_img():
412412

413413
fig, (ax1, ax2) = plt.subplots(ncols=2)
414414
cmap = plt.cm.coolwarm
415-
norm = mcolors.PiecewiseLinearNorm(vmin=-2, vcenter=0, vmax=7)
415+
norm = mcolors.DivergingNorm(vmin=-2, vcenter=0, vmax=7)
416416

417417
img1 = ax1.imshow(Z, cmap=cmap, norm=None)
418418
cbar1 = fig.colorbar(img1, ax=ax1)

0 commit comments

Comments
 (0)