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 ec374f5

Browse filesBrowse files
committed
FIX: Handle inverted colorbar axes with extensions
This fixes the colorbar extensions to use the proper color when the long axis is inverted.
1 parent 698397f commit ec374f5
Copy full SHA for ec374f5

File tree

Expand file treeCollapse file tree

2 files changed

+70
-15
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+70
-15
lines changed

‎lib/matplotlib/colorbar.py

Copy file name to clipboardExpand all lines: lib/matplotlib/colorbar.py
+46-15Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ def __init__(self, ax, mappable=None, *, cmap=None,
417417
self._filled = filled
418418
self.extendfrac = extendfrac
419419
self.extendrect = extendrect
420+
self._extend_patches = []
420421
self.solids = None
421422
self.solids_patches = []
422423
self.lines = []
@@ -483,6 +484,11 @@ def __init__(self, ax, mappable=None, *, cmap=None,
483484
setattr(self.ax, x, getattr(self, x))
484485
# Set the cla function to the cbar's method to override it
485486
self.ax.cla = self._cbar_cla
487+
# Callbacks for the extend calculations to handle inverting the axis
488+
self._extend_cid1 = self.ax.callbacks.connect(
489+
"xlim_changed", self._do_extends)
490+
self._extend_cid2 = self.ax.callbacks.connect(
491+
"ylim_changed", self._do_extends)
486492

487493
@property
488494
def locator(self):
@@ -598,17 +604,20 @@ def _draw_all(self):
598604
# extensions:
599605
self.vmin, self.vmax = self._boundaries[self._inside][[0, -1]]
600606
# Compute the X/Y mesh.
601-
X, Y, extendlen = self._mesh()
607+
X, Y = self._mesh()
602608
# draw the extend triangles, and shrink the inner axes to accommodate.
603609
# also adds the outline path to self.outline spine:
604-
self._do_extends(extendlen)
605-
610+
self._do_extends()
611+
lower, upper = self.vmin, self.vmax
612+
if self._long_axis().get_inverted():
613+
# If the axis is inverted, we need to swap the vmin/vmax
614+
lower, upper = upper, lower
606615
if self.orientation == 'vertical':
607616
self.ax.set_xlim(0, 1)
608-
self.ax.set_ylim(self.vmin, self.vmax)
617+
self.ax.set_ylim(lower, upper)
609618
else:
610619
self.ax.set_ylim(0, 1)
611-
self.ax.set_xlim(self.vmin, self.vmax)
620+
self.ax.set_xlim(lower, upper)
612621

613622
# set up the tick locators and formatters. A bit complicated because
614623
# boundary norms + uniform spacing requires a manual locator.
@@ -661,12 +670,19 @@ def _add_solids_patches(self, X, Y, C, mappable):
661670
patches.append(patch)
662671
self.solids_patches = patches
663672

664-
def _do_extends(self, extendlen):
673+
def _do_extends(self, ax=None):
665674
"""
666675
Add the extend tri/rectangles on the outside of the axes.
676+
677+
ax is unused, but required due to the callbacks on xlim/ylim changed
667678
"""
679+
# Clean up any previous extend patches
680+
for patch in self._extend_patches:
681+
patch.remove()
682+
self._extend_patches = []
668683
# extend lengths are fraction of the *inner* part of colorbar,
669684
# not the total colorbar:
685+
_, extendlen = self._proportional_y()
670686
bot = 0 - (extendlen[0] if self._extend_lower() else 0)
671687
top = 1 + (extendlen[1] if self._extend_upper() else 0)
672688

@@ -708,12 +724,17 @@ def _do_extends(self, extendlen):
708724
if self.orientation == 'horizontal':
709725
xy = xy[:, ::-1]
710726
# add the patch
711-
color = self.cmap(self.norm(self._values[0]))
727+
val = -1 if self._long_axis().get_inverted() else 0
728+
color = self.cmap(self.norm(self._values[val]))
712729
patch = mpatches.PathPatch(
713730
mpath.Path(xy), facecolor=color, linewidth=0,
714731
antialiased=False, transform=self.ax.transAxes,
715-
hatch=hatches[0], clip_on=False)
732+
hatch=hatches[0], clip_on=False,
733+
# Place it right behind the standard patches, which is
734+
# needed if we updated the extends
735+
zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
716736
self.ax.add_patch(patch)
737+
self._extend_patches.append(patch)
717738
if self._extend_upper():
718739
if not self.extendrect:
719740
# triangle
@@ -724,12 +745,17 @@ def _do_extends(self, extendlen):
724745
if self.orientation == 'horizontal':
725746
xy = xy[:, ::-1]
726747
# add the patch
727-
color = self.cmap(self.norm(self._values[-1]))
748+
val = 0 if self._long_axis().get_inverted() else -1
749+
color = self.cmap(self.norm(self._values[val]))
728750
patch = mpatches.PathPatch(
729751
mpath.Path(xy), facecolor=color,
730752
linewidth=0, antialiased=False,
731-
transform=self.ax.transAxes, hatch=hatches[-1], clip_on=False)
753+
transform=self.ax.transAxes, hatch=hatches[-1], clip_on=False,
754+
# Place it right behind the standard patches, which is
755+
# needed if we updated the extends
756+
zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
732757
self.ax.add_patch(patch)
758+
self._extend_patches.append(patch)
733759
return
734760

735761
def add_lines(self, *args, **kwargs):
@@ -1049,6 +1075,9 @@ def remove(self):
10491075
self.mappable.callbacks.disconnect(self.mappable.colorbar_cid)
10501076
self.mappable.colorbar = None
10511077
self.mappable.colorbar_cid = None
1078+
# Remove the extension callbacks
1079+
self.ax.callbacks.disconnect(self._extend_cid1)
1080+
self.ax.callbacks.disconnect(self._extend_cid2)
10521081

10531082
try:
10541083
ax = self.mappable.axes
@@ -1151,7 +1180,7 @@ def _mesh(self):
11511180
These are scaled between vmin and vmax, and already handle colorbar
11521181
orientation.
11531182
"""
1154-
y, extendlen = self._proportional_y()
1183+
y, _ = self._proportional_y()
11551184
# Use the vmin and vmax of the colorbar, which may not be the same
11561185
# as the norm. There are situations where the colormap has a
11571186
# narrower range than the colorbar and we want to accommodate the
@@ -1172,9 +1201,9 @@ def _mesh(self):
11721201
self._y = y
11731202
X, Y = np.meshgrid([0., 1.], y)
11741203
if self.orientation == 'vertical':
1175-
return (X, Y, extendlen)
1204+
return (X, Y)
11761205
else:
1177-
return (Y, X, extendlen)
1206+
return (Y, X)
11781207

11791208
def _forward_boundaries(self, x):
11801209
# map boundaries equally between 0 and 1...
@@ -1322,11 +1351,13 @@ def _get_extension_lengths(self, frac, automin, automax, default=0.05):
13221351

13231352
def _extend_lower(self):
13241353
"""Return whether the lower limit is open ended."""
1325-
return self.extend in ('both', 'min')
1354+
minmax = "max" if self._long_axis().get_inverted() else "min"
1355+
return self.extend in ('both', minmax)
13261356

13271357
def _extend_upper(self):
13281358
"""Return whether the upper limit is open ended."""
1329-
return self.extend in ('both', 'max')
1359+
minmax = "min" if self._long_axis().get_inverted() else "max"
1360+
return self.extend in ('both', minmax)
13301361

13311362
def _long_axis(self):
13321363
"""Return the long axis"""

‎lib/matplotlib/tests/test_colorbar.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_colorbar.py
+24Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.