8000 Merge pull request #18653 from jklymak/enh-add-func-norm · matplotlib/matplotlib@6429e9d · GitHub
[go: up one dir, main page]

Skip to content

Commit 6429e9d

Browse files
authored
Merge pull request #18653 from jklymak/enh-add-func-norm
ENH: Add func norm
2 parents 491e2a7 + a3bfa93 commit 6429e9d

File tree

5 files changed

+89
-2
lines changed

5 files changed

+89
-2
lines changed

doc/api/colors_api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Classes
3030
PowerNorm
3131
SymLogNorm
3232
TwoSlopeNorm
33+
FuncNorm
3334

3435
Functions
3536
---------

examples/userdemo/colormap_normalizations.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
shading='nearest')
7878
fig.colorbar(pcm, ax=ax[1], extend='both')
7979

80-
8180
###############################################################################
8281
# Custom Norm: An example with a customized normalization. This one
8382
# uses the example above, and normalizes the negative data differently

lib/matplotlib/colors.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,39 @@ def inverse(self, value):
14671467
return Norm
14681468

14691469

1470+
@_make_norm_from_scale(
1471+
scale.FuncScale,
1472+
init=lambda functions, vmin=None, vmax=None, clip=False: None)
1473+
class FuncNorm(Normalize):
1474+
"""
1475+
Arbitrary normalization using functions for the forward and inverse.
1476+
1477+
Parameters
1478+
----------
1479+
functions : (callable, callable)
1480+
two-tuple of the forward and inverse functions for the normalization.
1481+
The forward function must be monotonic.
1482+
1483+
Both functions must have the signature ::
1484+
1485+
def forward(values: array-like) -> array-like
1486+
1487+
vmin, vmax : float or None
1488+
If *vmin* and/or *vmax* is not given, they are initialized from the
1489+
minimum and maximum value, respectively, of the first input
1490+
processed; i.e., ``__call__(A)`` calls ``autoscale_None(A)``.
1491+
1492+
clip : bool, default: False
1493+
If ``True`` values falling outside the range ``[vmin, vmax]``,
1494+
are mapped to 0 or 1, whichever is closer, and masked values are
1495+
set to 1. If ``False`` masked values remain masked.
1496+
1497+
Clipping silently defeats the purpose of setting the over, under,
1498+
and masked colors in a colormap, so it is likely to lead to
1499+
surprises; therefore the default is ``clip=False``.
1500+
"""
1501+
1502+
14701503
@_make_norm_from_scale(functools.partial(scale.LogScale, nonpositive="mask"))
14711504
class LogNorm(Normalize):
14721505
"""Normalize a given value to the 0-1 range on a log scale."""

lib/matplotlib/tests/test_colors.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,29 @@ def test_Normalize():
537537
assert 0 < norm(1 + 50 * eps) < 1
538538

539539

540+
def test_FuncNorm():
541+
def forward(x):
542+
return (x**2)
543+
def inverse(x):
544+
return np.sqrt(x)
545+
546+
norm = mcolors.FuncNorm((forward, inverse), vmin=0, vmax=10)
547+
expected = np.array([0, 0.25, 1])
548+
input = np.array([0, 5, 10])
549+
assert_array_almost_equal(norm(input), expected)
550+
assert_array_almost_equal(norm.inverse(expected), input)
551+
552+
def forward(x):
553+
return np.log10(x)
554+
def inverse(x):
555+
return 10**x
556+
norm = mcolors.FuncNorm((forward, inverse), vmin=0.1, vmax=10)
557+
lognorm = mcolors.LogNorm(vmin=0.1, vmax=10)
558+
assert_array_almost_equal(norm([0.2, 5, 10]), lognorm([0.2, 5, 10]))
559+
assert_array_almost_equal(norm.inverse([0.2, 5, 10]),
560+
lognorm.inverse([0.2, 5, 10]))
561+
562+
540563
def test_TwoSlopeNorm_autoscale():
541564
norm = mcolors.TwoSlopeNorm(vcenter=20)
542565
norm.autoscale([10, 20, 30, 40])

tutorials/colors/colormapnorms.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,16 @@
169169
X, Y = np.mgrid[0:3:complex(0, N), 0:2:complex(0, N)]
170170
Z1 = (1 + np.sin(Y * 10.)) * X**2
171171

172-
fig, ax = plt.subplots(2, 1)
172+
fig, ax = plt.subplots(2, 1, constrained_layout=True)
173173

174174
pcm = ax[0].pcolormesh(X, Y, Z1, norm=colors.PowerNorm(gamma=0.5),
175175
cmap='PuBu_r', shading='auto')
176176
fig.colorbar(pcm, ax=ax[0], extend='max')
177+
ax[0].set_title('PowerNorm()')
177178

178179
pcm = ax[1].pcolormesh(X, Y, Z1, cmap='PuBu_r', shading='auto')
179180
fig.colorbar(pcm, ax=ax[1], extend='max')
181+
ax[1].set_title('Normalize()')
180182
plt.show()
181183

182184
###############################################################################
@@ -274,17 +276,45 @@
274276
# Simple geographic plot, set aspect ratio beecause distance between lines of
275277
# longitude depends on latitude.
276278
ax.set_aspect(1 / np.cos(np.deg2rad(49)))
279+
ax.set_title('TwoSlopeNorm(x)')
277280
fig.colorbar(pcm, shrink=0.6)
278281
plt.show()
279282

280283

284+
###############################################################################
285+
# FuncNorm: Arbitrary function normalization
286+
# ------------------------------------------
287+
#
288+
# If the above norms do not provide the normalization you want, you can use
289+
# `~.colors.FuncNorm` to define your own. Note that this example is the same
290+
# as `~.colors.PowerNorm` with a power of 0.5:
291+
292+
def _forward(x):
293+
return np.sqrt(x)
294+
295+
296+
def _inverse(x):
297+
return x**2
298+
299+
N = 100
300+
X, Y = np.mgrid[0:3:complex(0, N), 0:2:complex(0, N)]
301+
Z1 = (1 + np.sin(Y * 10.)) * X**2
302+
fig, ax = plt.subplots()
303+
304+
norm = colors.FuncNorm((_forward, _inverse), vmin=0, vmax=20)
305+
pcm = ax.pcolormesh(X, Y, Z1, norm=norm, cmap='PuBu_r', shading='auto')
306+
ax.set_title('FuncNorm(x)')
307+
fig.colorbar(pcm, shrink=0.6)
308+
plt.show()
309+
281310
###############################################################################
282311
# Custom normalization: Manually implement two linear ranges
283312
# ----------------------------------------------------------
284313
#
285314
# The `.TwoSlopeNorm` described above makes a useful example for
286315
# defining your own norm.
287316

317+
288318
class MidpointNormalize(colors.Normalize):
289319
def __init__(self, vmin=None, vmax=None, vcenter=None, clip=False):
290320
self.vcenter = vcenter
@@ -303,5 +333,6 @@ def __call__(self, value, clip=None):
303333
pcm = ax.pcolormesh(longitude, latitude, topo, rasterized=True, norm=midnorm,
304334
cmap=terrain_map, shading='auto')
305335
ax.set_aspect(1 / np.cos(np.deg2rad(49)))
336+
ax.set_title('Custom norm')
306337
fig.colorbar(pcm, shrink=0.6, extend='both')
307338
plt.show()

0 commit comments

Comments
 (0)
0