Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bb3d7fc

Browse filesBrowse files
committed
Build lognorm/symlognorm from corresponding scales.
This fails test_colors.py::test_SymLogNorm because the old symlog norm and scale were not consistent. test_contour::test_contourf_log_extension looks better with the patch?
1 parent d311b9b commit bb3d7fc
Copy full SHA for bb3d7fc

File tree

Expand file treeCollapse file tree

1 file changed

+82
-143
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+82
-143
lines changed

‎lib/matplotlib/colors.py

Copy file name to clipboardExpand all lines: lib/matplotlib/colors.py
+82-143Lines changed: 82 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@
6767

6868
from collections.abc import Sized
6969
import functools
70+
import inspect
7071
import itertools
7172
from numbers import Number
7273
import re
7374

7475
import numpy as np
75-
import matplotlib.cbook as cbook
76-
from matplotlib import docstring
76+
from matplotlib import cbook, docstring, scale
7777
from ._color_data import BASE_COLORS, TABLEAU_COLORS, CSS4_COLORS, XKCD_COLORS
7878

7979

@@ -1139,61 +1139,67 @@ class DivergingNorm(TwoSlopeNorm):
11391139
...
11401140

11411141

1142+
def _make_norm_from_scale(scale_cls, base_cls=None, *, init=None):
1143+
if base_cls is None:
1144+
return functools.partial(_make_norm_from_scale, scale_cls, init=init)
1145+
1146+
if init is None:
1147+
init = lambda vmin=None, vmax=None, clip=False: None
1148+
init_signature = inspect.signature(init)
1149+
1150+
class Norm(base_cls):
1151+
1152+
def __init__(self, *args, **kwargs):
1153+
ba = init_signature.bind(*args, **kwargs)
1154+
ba.apply_defaults()
1155+
super().__init__(
1156+
**{k: ba.arguments.pop(k) for k in ["vmin", "vmax", "clip"]})
1157+
self._scale = scale_cls(axis=None, **ba.arguments)
1158+
self._trf = self._scale.get_transform()
1159+
self._inv_trf = self._trf.inverted()
1160+
1161+
def __call__(self, value, clip=None):
1162+
value, is_scalar = self.process_value(value)
1163+
self.autoscale_None(value)
1164+
if self.vmin > self.vmax:
1165+
raise ValueError("vmin must be less or equal to vmax")
1166+
if self.vmin == self.vmax:
1167+
return np.full_like(value, 0)
1168+
if clip is None:
1169+
clip = self.clip
1170+
if clip:
1171+
value = np.clip(value, self.vmin, self.vmax)
1172+
t_value = self._trf.transform(value).reshape(np.shape(value))
1173+
t_vmin, t_vmax = self._trf.transform([self.vmin, self.vmax])
1174+
if not np.isfinite([t_vmin, t_vmax]).all():
1175+
raise ValueError("Invalid vmin or vmax")
1176+
t_value -= t_vmin
1177+
t_value /= (t_vmax - t_vmin)
1178+
t_value = np.ma.masked_invalid(t_value, copy=False)
1179+
return t_value[0] if is_scalar else t_value
1180+
1181+
def inverse(self, value):
1182+
if not self.scaled():
1183+
raise ValueError("Not invertible until scaled")
1184+
if self.vmin > self.vmax:
1185+
raise ValueError("vmin must be less or equal to vmax")
1186+
t_vmin, t_vmax = self._trf.transform([self.vmin, self.vmax])
1187+
if not np.isfinite([t_vmin, t_vmax]).all():
1188+
raise ValueError("Invalid vmin or vmax")
1189+
rescaled = value * (t_vmax - t_vmin)
1190+
rescaled += t_vmin
1191+
return self._inv_trf.transform(rescaled).reshape(np.shape(value))
1192+
1193+
Norm.__name__ = base_cls.__name__
1194+
Norm.__qualname__ = base_cls.__qualname__
1195+
Norm.__module__ = base_cls.__module__
1196+
return Norm
1197+
1198+
1199+
@_make_norm_from_scale(functools.partial(scale.LogScale, nonpositive="mask"))
11421200
class LogNorm(Normalize):
11431201
"""Normalize a given value to the 0-1 range on a log scale."""
11441202

1145-
def _check_vmin_vmax(self):
1146-
if self.vmin > self.vmax:
1147-
raise ValueError("minvalue must be less than or equal to maxvalue")
1148-
elif self.vmin <= 0:
1149-
raise ValueError("minvalue must be positive")
1150-
1151-
def __call__(self, value, clip=None):
1152-
if clip is None:
1153-
clip = self.clip
1154-
1155-
result, is_scalar = self.process_value(value)
1156-
1157-
result = np.ma.masked_less_equal(result, 0, copy=False)
1158-
1159-
self.autoscale_None(result)
1160-
self._check_vmin_vmax()
1161-
vmin, vmax = self.vmin, self.vmax
1162-
if vmin == vmax:
1163-
result.fill(0)
1164-
else:
1165-
if clip:
1166-
mask = np.ma.getmask(result)
1167-
result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax),
1168-
mask=mask)
1169-
# in-place equivalent of above can be much faster
1170-
resdat = result.data
1171-
mask = result.mask
1172-
if mask is np.ma.nomask:
1173-
mask = (resdat <= 0)
1174-
else:
1175-
mask |= resdat <= 0
1176-
np.copyto(resdat, 1, where=mask)
1177-
np.log(resdat, resdat)
1178-
resdat -= np.log(vmin)
1179-
resdat /= (np.log(vmax) - np.log(vmin))
1180-
result = np.ma.array(resdat, mask=mask, copy=False)
1181-
if is_scalar:
1182-
result = result[0]
1183-
return result
1184-
1185-
def inverse(self, value):
1186-
if not self.scaled():
1187-
raise ValueError("Not invertible until scaled")
1188-
self._check_vmin_vmax()
1189-
vmin, vmax = self.vmin, self.vmax
1190-
1191-
if np.iterable(value):
1192-
val = np.ma.asarray(value)
1193-
return vmin * np.ma.power((vmax / vmin), val)
1194-
else:
1195-
return vmin * pow((vmax / vmin), value)
1196-
11971203
def autoscale(self, A):
11981204
# docstring inherited.
11991205
super().autoscale(np.ma.masked_less_equal(A, 0, copy=False))
@@ -1203,6 +1209,9 @@ def autoscale_None(self, A):
12031209
super().autoscale_None(np.ma.masked_less_equal(A, 0, copy=False))
12041210

12051211

1212+
@_make_norm_from_scale(
1213+
scale.SymmetricalLogScale,
1214+
init=lambda linthresh, linscale=1., vmin=None, vmax=None, clip=False: None)
12061215
class SymLogNorm(Normalize):
12071216
"""
12081217
The symmetrical logarithmic scale is logarithmic in both the
@@ -1212,98 +1221,28 @@ class SymLogNorm(Normalize):
12121221
need to have a range around zero that is linear. The parameter
12131222
*linthresh* allows the user to specify the size of this range
12141223
(-*linthresh*, *linthresh*).
1215-
"""
1216-
def __init__(self, linthresh, linscale=1.0,
1217-
vmin=None, vmax=None, clip=False):
1218-
"""
1219-
Parameters
1220-
----------
1221-
linthresh : float
1222-
The range within which the plot is linear (to avoid having the plot
1223-
go to infinity around zero).
1224-
linscale : float, default: 1
1225-
This allows the linear range (-*linthresh* to *linthresh*) to be
1226-
stretched relative to the logarithmic range. Its value is the
1227-
number of decades to use for each half of the linear range. For
1228-
example, when *linscale* == 1.0 (the default), the space used for
1229-
the positive and negative halves of the linear range will be equal
1230-
to one decade in the logarithmic range.
1231-
"""
1232-
Normalize.__init__(self, vmin, vmax, clip)
1233-
self.linthresh = float(linthresh)
1234-
self._linscale_adj = (linscale / (1.0 - np.e ** -1))
1235-
if vmin is not None and vmax is not None:
1236-
self._transform_vmin_vmax()
1237-
1238-
def __call__(self, value, clip=None):
1239-
if clip is None:
1240-
clip = self.clip
1241-
1242-
result, is_scalar = self.process_value(value)
1243-
self.autoscale_None(result)
1244-
vmin, vmax = self.vmin, self.vmax
1245-
1246-
if vmin > vmax:
1247-
raise ValueError("minvalue must be less than or equal to maxvalue")
1248-
elif vmin == vmax:
1249-
result.fill(0)
1250-
else:
1251-
if clip:
1252-
mask = np.ma.getmask(result)
1253-
result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax),
1254-
mask=mask)
1255-
# in-place equivalent of above can be much faster
1256-
resdat = self._transform(result.data)
1257-
resdat -= self._lower
1258-
resdat /= (self._upper - self._lower)
1259-
1260-
if is_scalar:
1261-
result = result[0]
1262-
return result
12631224
1264-
def _transform(self, a):
1265-
"""Inplace transformation."""
1266-
with np.errstate(invalid="ignore"):
1267-
masked = np.abs(a) > self.linthresh
1268-
sign = np.sign(a[masked])
1269-
log = (self._linscale_adj + np.log(np.abs(a[masked]) / self.linthresh))
1270-
log *= sign * self.linthresh
1271-
a[masked] = log
1272-
a[~masked] *= self._linscale_adj
1273-
return a
1274-
1275-
def _inv_transform(self, a):
1276-
"""Inverse inplace Transformation."""
1277-
masked = np.abs(a) > (self.linthresh * self._linscale_adj)
1278-
sign = np.sign(a[masked])
1279-
exp = np.exp(sign * a[masked] / self.linthresh - self._linscale_adj)
1280-
exp *= sign * self.linthresh
1281-
a[masked] = exp
1282-
a[~masked] /= self._linscale_adj
1283-
return a
1284-
1285-
def _transform_vmin_vmax(self):
1286-
"""Calculates vmin and vmax in the transformed system."""
1287-
vmin, vmax = self.vmin, self.vmax
1288-
arr = np.array([vmax, vmin]).astype(float)
1289-
self._upper, self._lower = self._transform(arr)
1290-
1291-
def inverse(self, value):
1292-
if not self.scaled():
1293-
raise ValueError("Not invertible until scaled")
1294-
val = np.ma.asarray(value)
1295-
val = val * (self._upper - self._lower) + self._lower
1296-
return self._inv_transform(val)
1225+
Parameters
1226+
----------
1227+
linthresh : float
1228+
The range within which the plot is linear (to avoid having the plot
1229+
go to infinity around zero).
1230+
linscale : float, default: 1
1231+
This allows the linear range (-*linthresh* to *linthresh*) to be
1232+
stretched relative to the logarithmic range. Its value is the
1233+
number of decades to use for each half of the linear range. For
1234+
example, when *linscale* == 1.0 (the default), the space used for
1235+
the positive and negative halves of the linear range will be equal
1236+
to one decade in the logarithmic range.
1237+
"""
12971238

1298-
def autoscale(self, A):
1299-
# docstring inherited.
1300-
super().autoscale(A)
1301-
self._transform_vmin_vmax()
1239+
@property
1240+
def linthresh(self):
1241+
return self._scale.linthresh
13021242

1303-
def autoscale_None(self, A):
1304-
# docstring inherited.
1305-
super().autoscale_None(A)
1306-
self._transform_vmin_vmax()
1243+
@linthresh.setter
1244+
def linthresh(self, value):
1245+
self._scale.linthresh = value
13071246

13081247

13091248
class PowerNorm(Normalize):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.