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

Skip to content

Commit cb7d7cd

Browse files
authored
Merge pull request #24132 from greglucas/centered-norm
API: CenteredNorm changes
2 parents 755d0a9 + 84def85 commit cb7d7cd

File tree

4 files changed

+92
-30
lines changed

4 files changed

+92
-30
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
``CenteredNorm`` halfrange is not modified when vcenter changes
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
Previously, the **halfrange** would expand in proportion to the
5+
amount that **vcenter** was moved away from either **vmin** or **vmax**.
6+
Now, the halfrange remains fixed when vcenter is changed, and **vmin** and
7+
**vmax** are updated based on the **vcenter** and **halfrange** values.
8+
9+
For example, this is what the values were when changing vcenter previously.
10+
11+
.. code-block::
12+
13+
norm = CenteredNorm(vcenter=0, halfrange=1)
14+
# Move vcenter up by one
15+
norm.vcenter = 1
16+
# updates halfrange and vmax (vmin stays the same)
17+
# norm.halfrange == 2, vmin == -1, vmax == 3
18+
19+
and now, with that same example
20+
21+
.. code-block::
22+
23+
norm = CenteredNorm(vcenter=0, halfrange=1)
24+
norm.vcenter = 1
25+
# updates vmin and vmax (halfrange stays the same)
26+
# norm.halfrange == 1, vmin == 0, vmax == 2
27+
28+
The **halfrange** can be set manually or ``norm.autoscale()``
29+
can be used to automatically set the limits after setting **vcenter**.

lib/matplotlib/colors.py

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,28 +1517,44 @@ def __init__(self, vcenter=0, halfrange=None, clip=False):
15171517
# calling the halfrange setter to set vmin and vmax
15181518
self.halfrange = halfrange
15191519

1520-
def _set_vmin_vmax(self):
1521-
"""
1522-
Set *vmin* and *vmax* based on *vcenter* and *halfrange*.
1523-
"""
1524-
self.vmax = self._vcenter + self._halfrange
1525-
self.vmin = self._vcenter - self._halfrange
1526-
15271520
def autoscale(self, A):
15281521
"""
15291522
Set *halfrange* to ``max(abs(A-vcenter))``, then set *vmin* and *vmax*.
15301523
"""
15311524
A = np.asanyarray(A)
1532-
self._halfrange = max(self._vcenter-A.min(),
1533-
A.max()-self._vcenter)
1534-
self._set_vmin_vmax()
1525+
self.halfrange = max(self._vcenter-A.min(),
1526+
A.max()-self._vcenter)
15351527

15361528
def autoscale_None(self, A):
15371529
"""Set *vmin* and *vmax*."""
15381530
A = np.asanyarray(A)
1539-
if self._halfrange is None and A.size:
1531+
if self.halfrange is None and A.size:
15401532
self.autoscale(A)
15411533

1534+
@property
1535+
def vmin(self):
1536+
return self._vmin
1537+
1538+
@vmin.setter
1539+
def vmin(self, value):
1540+
value = _sanitize_extrema(value)
1541+
if value != self._vmin:
1542+
self._vmin = value
1543+
self._vmax = 2*self.vcenter - value
1544+
self._changed()
1545+
1546+
@property
1547+
def vmax(self):
1548+
return self._vmax
1549+
1550+
@vmax.setter
1551+
def vmax(self, value):
1552+
value = _sanitize_extrema(value)
1553+
if value != self._vmax:
1554+
self._vmax = value
1555+
self._vmin = 2*self.vcenter - value
1556+
self._changed()
1557+
15421558
@property
15431559
def vcenter(self):
15441560
return self._vcenter
@@ -1547,32 +1563,24 @@ def vcenter(self):
15471563
def vcenter(self, vcenter):
15481564
if vcenter != self._vcenter:
15491565
self._vcenter = vcenter
1566+
# Trigger an update of the vmin/vmax values through the setter
1567+
self.halfrange = self.halfrange
15501568
self._changed()
1551-
if self.vmax is not None:
1552-
# recompute halfrange assuming vmin and vmax represent
1553-
# min and max of data
1554-
self._halfrange = max(self._vcenter-self.vmin,
1555-
self.vmax-self._vcenter)
1556-
self._set_vmin_vmax()
15571569

15581570
@property
15591571
def halfrange(self):
1560-
return self._halfrange
1572+
if self.vmin is None or self.vmax is None:
1573+
return None
1574+
return (self.vmax - self.vmin) / 2
15611575

15621576
@halfrange.setter
15631577
def halfrange(self, halfrange):
15641578
if halfrange is None:
1565-
self._halfrange = None
15661579
self.vmin = None
15671580
self.vmax = None
15681581
else:
1569-
self._halfrange = abs(halfrange)
1570-
1571-
def __call__(self, value, clip=None):
1572-
if self._halfrange is not None:
1573-
# enforce symmetry, reset vmin and vmax
1574-
self._set_vmin_vmax()
1575-
return super().__call__(value, clip=clip)
1582+
self.vmin = self.vcenter - abs(halfrange)
1583+
self.vmax = self.vcenter + abs(halfrange)
15761584

15771585

15781586
def make_norm_from_scale(scale_cls, base_norm_cls=None, *, init=None):

lib/matplotlib/tests/test_colorbar.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,17 @@ def test_negative_boundarynorm():
10601060
np.testing.assert_allclose(cb.ax.get_yticks(), clevs)
10611061

10621062

1063+
def test_centerednorm():
1064+
# Test default centered norm gets expanded with non-singular limits
1065+
# when plot data is all equal (autoscale halfrange == 0)
1066+
fig, ax = plt.subplots(figsize=(1, 3))
1067+
1068+
norm = mcolors.CenteredNorm()
1069+
mappable = ax.pcolormesh(np.zeros((3, 3)), norm=norm)
1070+
fig.colorbar(mappable)
1071+
assert (norm.vmin, norm.vmax) == (-0.1, 0.1)
1072+
1073+
10631074
@image_comparison(['nonorm_colorbars.svg'], style='mpl20')
10641075
def test_nonorm():
10651076
plt.rcParams['svg.fonttype'] = 'none'

lib/matplotlib/tests/test_colors.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -485,11 +485,25 @@ def test_CenteredNorm():
485485
norm(np.linspace(-1.0, 0.0, 10))
486486
assert norm.vmax == 1.0
487487
assert norm.halfrange == 1.0
488-
# set vcenter to 1, which should double halfrange
488+
# set vcenter to 1, which should move the center but leave the
489+
# halfrange unchanged
489490
norm.vcenter = 1
490-
assert norm.vmin == -1.0
491-
assert norm.vmax == 3.0
492-
assert norm.halfrange == 2.0
491+
assert norm.vmin == 0
492+
assert norm.vmax == 2
493+
assert norm.halfrange == 1
494+
495+
# Check setting vmin directly updates the halfrange and vmax, but
496+
# leaves vcenter alone
497+
norm.vmin = -1
498+
assert norm.halfrange == 2
499+
assert norm.vmax == 3
500+
assert norm.vcenter == 1
501+
502+
# also check vmax updates
503+
norm.vmax = 2
504+
assert norm.halfrange == 1
505+
assert norm.vmin == 0
506+
assert norm.vcenter == 1
493507

494508

495509
@pytest.mark.parametrize("vmin,vmax", [[-1, 2], [3, 1]])

0 commit comments

Comments
 (0)