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

Skip to content

Commit 34e6c97

Browse files
authored
Merge pull request #22646 from greglucas/auto-backport-of-pr-22635-on-v3.5.x
Manual backport of pr 22635 on v3.5.x
2 parents e1610c2 + f73384e commit 34e6c97

File tree

2 files changed

+78
-19
lines changed

2 files changed

+78
-19
lines changed

lib/matplotlib/colorbar.py

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ def __init__(self, ax, mappable=None, *, cmap=None,
449449
self.filled = filled
450450
self.extendfrac = extendfrac
451451
self.extendrect = extendrect
452+
self._extend_patches = []
452453
self.solids = None
453454
self.solids_patches = []
454455
self.lines = []
@@ -509,6 +510,11 @@ def __init__(self, ax, mappable=None, *, cmap=None,
509510
setattr(self.ax, x, getattr(self, x))
510511
# Set the cla function to the cbar's method to override it
511512
self.ax.cla = self._cbar_cla
513+
# Callbacks for the extend calculations to handle inverting the axis
514+
self._extend_cid1 = self.ax.callbacks.connect(
515+
"xlim_changed", self._do_extends)
516+
self._extend_cid2 = self.ax.callbacks.connect(
517+
"ylim_changed", self._do_extends)
512518

513519
def _cbar_cla(self):
514520
"""Function to clear the interactive colorbar state."""
@@ -574,17 +580,20 @@ def draw_all(self):
574580
# extensions:
575581
self.vmin, self.vmax = self._boundaries[self._inside][[0, -1]]
576582
# Compute the X/Y mesh.
577-
X, Y, extendlen = self._mesh()
583+
X, Y = self._mesh()
578584
# draw the extend triangles, and shrink the inner axes to accommodate.
579585
# also adds the outline path to self.outline spine:
580-
self._do_extends(extendlen)
581-
586+
self._do_extends()
587+
lower, upper = self.vmin, self.vmax
588+
if self._long_axis().get_inverted():
589+
# If the axis is inverted, we need to swap the vmin/vmax
590+
lower, upper = upper, lower
582591
if self.orientation == 'vertical':
583592
self.ax.set_xlim(0, 1)
584-
self.ax.set_ylim(self.vmin, self.vmax)
593+
self.ax.set_ylim(lower, upper)
585594
else:
586595
self.ax.set_ylim(0, 1)
587-
self.ax.set_xlim(self.vmin, self.vmax)
596+
self.ax.set_xlim(lower, upper)
588597

589598
# set up the tick locators and formatters. A bit complicated because
590599
# boundary norms + uniform spacing requires a manual locator.
@@ -637,12 +646,19 @@ def _add_solids_patches(self, X, Y, C, mappable):
637646
patches.append(patch)
638647
self.solids_patches = patches
639648

640-
def _do_extends(self, extendlen):
649+
def _do_extends(self, ax=None):
641650
"""
642651
Add the extend tri/rectangles on the outside of the axes.
652+
653+
ax is unused, but required due to the callbacks on xlim/ylim changed
643654
"""
655+
# Clean up any previous extend patches
656+
for patch in self._extend_patches:
657+
patch.remove()
658+
self._extend_patches = []
644659
# extend lengths are fraction of the *inner* part of colorbar,
645660
# not the total colorbar:
661+
_, extendlen = self._proportional_y()
646662
bot = 0 - (extendlen[0] if self._extend_lower() else 0)
647663
top = 1 + (extendlen[1] if self._extend_upper() else 0)
648664

@@ -684,12 +700,17 @@ def _do_extends(self, extendlen):
684700
if self.orientation == 'horizontal':
685701
xy = xy[:, ::-1]
686702
# add the patch
687-
color = self.cmap(self.norm(self._values[0]))
703+
val = -1 if self._long_axis().get_inverted() else 0
704+
color = self.cmap(self.norm(self._values[val]))
688705
patch = mpatches.PathPatch(
689706
mpath.Path(xy), facecolor=color, linewidth=0,
690707
antialiased=False, transform=self.ax.transAxes,
691-
hatch=hatches[0], clip_on=False)
708+
hatch=hatches[0], clip_on=False,
709+
# Place it right behind the standard patches, which is
710+
# needed if we updated the extends
711+
zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
692712
self.ax.add_patch(patch)
713+
self._extend_patches.append(patch)
693714
if self._extend_upper():
694715
if not self.extendrect:
695716
# triangle
@@ -700,12 +721,17 @@ def _do_extends(self, extendlen):
700721
if self.orientation == 'horizontal':
701722
xy = xy[:, ::-1]
702723
# add the patch
703-
color = self.cmap(self.norm(self._values[-1]))
724+
val = 0 if self._long_axis().get_inverted() else -1
725+
color = self.cmap(self.norm(self._values[val]))
704726
patch = mpatches.PathPatch(
705727
mpath.Path(xy), facecolor=color,
706728
linewidth=0, antialiased=False,
707-
transform=self.ax.transAxes, hatch=hatches[-1], clip_on=False)
729+
transform=self.ax.transAxes, hatch=hatches[-1], clip_on=False,
730+
# Place it right behind the standard patches, which is
731+
# needed if we updated the extends
732+
zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
708733
self.ax.add_patch(patch)
734+
self._extend_patches.append(patch)
709735
return
710736

711737
def add_lines(self, *args, **kwargs):
@@ -1024,6 +1050,9 @@ def remove(self):
10241050
self.mappable.callbacks.disconnect(self.mappable.colorbar_cid)
10251051
self.mappable.colorbar = None
10261052
self.mappable.colorbar_cid = None
1053+
# Remove the extension callbacks
1054+
self.ax.callbacks.disconnect(self._extend_cid1)
1055+
self.ax.callbacks.disconnect(self._extend_cid2)
10271056

10281057
try:
10291058
ax = self.mappable.axes
@@ -1133,19 +1162,23 @@ def _mesh(self):
11331162
norm = copy.deepcopy(self.norm)
11341163
norm.vmin = self.vmin
11351164
norm.vmax = self.vmax
1136-
y, extendlen = self._proportional_y()
1137-
# invert:
1138-
if (isinstance(norm, (colors.BoundaryNorm, colors.NoNorm)) or
1139-
self.boundaries is not None):
1140-
y = y * (self.vmax - self.vmin) + self.vmin # not using a norm.
1165+
y, _ = self._proportional_y()
1166+
# Use the vmin and vmax of the colorbar, which may not be the same
1167+
# as the norm. There are situations where the colormap has a
1168+
# narrower range than the colorbar and we want to accommodate the
1169+
# extra contours.
1170+
if (isinstance(norm, (colors.BoundaryNorm, colors.NoNorm))
1171+
or self.boundaries is not None):
1172+
# not using a norm.
1173+
y = y * (self.vmax - self.vmin) + self.vmin
11411174
else:
11421175
y = norm.inverse(y)
11431176
self._y = y
11441177
X, Y = np.meshgrid([0., 1.], y)
11451178
if self.orientation == 'vertical':
1146-
return (X, Y, extendlen)
1179+
return (X, Y)
11471180
else:
1148-
return (Y, X, extendlen)
1181+
return (Y, X)
11491182

11501183
def _forward_boundaries(self, x):
11511184
# map boundaries equally between 0 and 1...
@@ -1292,11 +1325,13 @@ def _get_extension_lengths(self, frac, automin, automax, default=0.05):
12921325

12931326
def _extend_lower(self):
12941327
"""Return whether the lower limit is open ended."""
1295-
return self.extend in ('both', 'min')
1328+
minmax = "max" if self._long_axis().get_inverted() else "min"
1329+
return self.extend in ('both', minmax)
12961330

12971331
def _extend_upper(self):
12981332
"""Return whether the upper limit is open ended."""
1299-
return self.extend in ('both', 'max')
1333+
minmax = "min" if self._long_axis().get_inverted() else "max"
1334+
return self.extend in ('both', minmax)
13001335

13011336
def _long_axis(self):
13021337
"""Return the long axis"""

lib/matplotlib/tests/test_colorbar.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,30 @@ def test_colorbar_extension_length():
124124
_colorbar_extension_length('proportional')
125125

126126

127+
@pytest.mark.parametrize("orientation", ["horizontal", "vertical"])
128+
@pytest.mark.parametrize("extend,expected", [("min", (0, 0, 0, 1)),
129+
("max", (1, 1, 1, 1)),
130+
("both", (1, 1, 1, 1))])
131+
def test_colorbar_extension_inverted_axis(orientation, extend, expected):
132+
"""Test extension color with an inverted axis"""
133+
data = np.arange(12).reshape(3, 4)
134+
fig, ax = plt.subplots()
135+
cmap = plt.get_cmap("viridis").with_extremes(under=(0, 0, 0, 1),
136+
over=(1, 1, 1, 1))
137+
im = ax.imshow(data, cmap=cmap)
138+
cbar = fig.colorbar(im, orientation=orientation, extend=extend)
139+
if orientation == "horizontal":
140+
cbar.ax.invert_xaxis()
141+
else:
142+
cbar.ax.invert_yaxis()
143+
assert cbar._extend_patches[0].get_facecolor() == expected
144+
if extend == "both":
145+
assert len(cbar._extend_patches) == 2
146+
assert cbar._extend_patches[1].get_facecolor() == (0, 0, 0, 1)
147+
else:
148+
assert len(cbar._extend_patches) == 1
149+
150+
127151
@pytest.mark.parametrize('use_gridspec', [True, False])
128152
@image_comparison(['cbar_with_orientation',
129153
'cbar_locationing',

0 commit comments

Comments
 (0)