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

Skip to content

Commit 561d771

Browse files
committed
Redo colorbar overhaul.
Previous overhaul packaged an inner and outer axes in a container "ColorbarAxes" and tried to dispatch methods between them. This overhaul takes the _much_ simpler approach of resizing the image using a custom _axes_locator that a) calls any existing locator b) or just uses the axes default position. The custom _axes_locator then shrinks the axes in the appropriate direction to make room for extend tri/rectangles. As with the previous fix, the extend tri/rectangles are drawn as patches in axes co-ordinates, rather than pcolormesh in "data" co-ordinates.
1 parent c6c85e6 commit 561d771

File tree

13 files changed

+466
-520
lines changed

13 files changed

+466
-520
lines changed

doc/api/next_api_changes/behavior/20054-JMK.rst

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
Axes used to make colorbar now wrapped
2-
======================================
3-
4-
The axes used to place a colorbar is now wrapped by a new parent class
5-
(``ColorbarAxes``) when the colorbar is created::
6-
7-
cb = fig.colorbar(im, cax=cax)
8-
9-
This means that ``cb.ax`` is no longer the same object as ``cax``. However,
10-
we map all the methods from ``cb.ax`` onto ``cax`` so ``cax`` should remain
11-
functionally the same as ``cb.ax``.
12-
131
Colorbar lines no longer clipped
142
================================
153

lib/matplotlib/axes/_base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2369,6 +2369,8 @@ def _update_patch_limits(self, patch):
23692369
return
23702370
patch_trf = patch.get_transform()
23712371
updatex, updatey = patch_trf.contains_branch_seperately(self.transData)
2372+
if not (updatex or updatey):
2373+
return
23722374
if self.name != "rectilinear":
23732375
# As in _update_line_limits, but for axvspan.
23742376
if updatex and patch_trf == self.get_yaxis_transform():

lib/matplotlib/colorbar.py

Lines changed: 82 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
import matplotlib as mpl
2121
from matplotlib import _api, collections, cm, colors, contour, ticker
22-
from matplotlib.axes._base import _TransformedBoundsLocator
23-
from matplotlib.axes._axes import Axes
2422
import matplotlib.artist as martist
2523
import matplotlib.patches as mpatches
2624
import matplotlib.path as mpath
@@ -206,76 +204,12 @@ def _set_ticks_on_axis_warn(*args, **kw):
206204
_api.warn_external("Use the colorbar set_ticks() method instead.")
207205

208206

209-
class ColorbarAxes(Axes):
210-
"""
211-
ColorbarAxes packages two axes, a parent axes that takes care of
212-
positioning the axes, and an inset_axes that takes care of the drawing,
213-
labels, ticks, etc. The inset axes is used as a way to properly
214-
position the extensions (triangles or rectangles) that are used to indicate
215-
over/under colors.
216-
217-
Users should not normally instantiate this class, but it is the class
218-
returned by ``cbar = fig.colorbar(im); cax = cbar.ax``.
219-
"""
220-
def __init__(self, parent, userax=True):
221-
"""
222-
Parameters
223-
----------
224-
parent : Axes
225-
Axes that specifies the position of the colorbar.
226-
userax : boolean
227-
True if the user passed `.Figure.colorbar` the axes manually.
228-
"""
229-
230-
if userax:
231-
# copy position:
232-
fig = parent.figure
233-
outer_ax = fig.add_axes(parent.get_position())
234-
# copy the locator if one exists:
235-
outer_ax._axes_locator = parent._axes_locator
236-
# if the parent is a child of another axes, swap these...
237-
if (parent._axes is not None and
238-
parent in parent._axes.child_axes):
239-
parent._axes.add_child_axes(outer_ax)
240-
outer_ax._axes.child_axes.remove(parent)
241-
else:
242-
parent.remove()
243-
else:
244-
outer_ax = parent
245-
246-
inner_ax = outer_ax.inset_axes([0, 0, 1, 1])
247-
self.__dict__.update(inner_ax.__dict__)
248-
249-
self.outer_ax = outer_ax
250-
self.inner_ax = inner_ax
251-
self.outer_ax.xaxis.set_visible(False)
252-
self.outer_ax.yaxis.set_visible(False)
253-
self.outer_ax.set_facecolor('none')
254-
self.outer_ax.tick_params = self.inner_ax.tick_params
255-
self.outer_ax.set_xticks = self.inner_ax.set_xticks
256-
self.outer_ax.set_yticks = self.inner_ax.set_yticks
257-
for attr in ["get_position", "set_position", "set_aspect"]:
258-
setattr(self, attr, getattr(self.outer_ax, attr))
259-
self._colorbar_info = None # used for mpl-created axes
260-
if userax:
261-
self._colorbar_info = 'user'
262-
# point the parent's methods all at this axes...
263-
parent.__dict__ = self.__dict__
264-
265-
def _set_inner_bounds(self, bounds):
266-
"""
267-
Change the inset_axes location...
268-
"""
269-
self.inner_ax._axes_locator = _TransformedBoundsLocator(
270-
bounds, self.outer_ax.transAxes)
271-
272-
273207
class _ColorbarSpine(mspines.Spine):
274208
def __init__(self, axes):
275209
self._ax = axes
276210
super().__init__(axes, 'colorbar',
277211
mpath.Path(np.empty((0, 2)), closed=True))
278-
mpatches.Patch.set_transform(self, axes.outer_ax.transAxes)
212+
mpatches.Patch.set_transform(self, axes.transAxes)
279213

280214
def get_window_extent(self, renderer=None):
281215
# This Spine has no Axis associated with it, and doesn't need to adjust
@@ -294,6 +228,66 @@ def draw(self, renderer):
294228
return ret
295229

296230

231+
class _ColorbarAxesLocator:
232+
"""
233+
Wrap any locator on the axes, or its position, to shrink the
234+
inner axes if there are extend triangles at either min or max.
235+
"""
236+
def __init__(self, cbar):
237+
"""
238+
*bounds* (a ``[l, b, w, h]`` rectangle) and *transform* together
239+
specify the position of the inset axes.
240+
"""
241+
self._cbar = cbar
242+
self._orig_locator = cbar.ax._axes_locator
243+
244+
def __call__(self, ax, renderer):
245+
246+
# make sure that lims and scales are the same
247+
scale = self._cbar._long_axis().get_scale()
248+
try:
249+
self._cbar._short_axis()._set_scale(scale)
250+
except TypeError:
251+
pass
252+
lim = self._cbar._long_axis().get_view_interval()
253+
self._cbar._short_axis().set_view_interval(*lim)
254+
255+
if self._orig_locator is not None:
256+
pos = self._orig_locator(ax, renderer)
257+
else:
258+
pos = ax.get_position(original=True)
259+
if self._cbar.extend == 'neither':
260+
return pos
261+
262+
y, extendlen = self._cbar._proportional_y()
263+
if not self._cbar._extend_lower():
264+
extendlen[0] = 0
265+
if not self._cbar._extend_upper():
266+
extendlen[1] = 0
267+
len = sum(extendlen) + 1
268+
shrink = 1 / len
269+
offset = extendlen[0] / len
270+
# we need to reset the aspect ratio of the axes to account
271+
# of the extends...
272+
if not self._cbar._userax:
273+
aspect = ax._colorbar_info['aspect']
274+
else:
275+
aspect = False
276+
# now shrink and/or offset to take into account the
277+
# extend tri/rectangles.
278+
if self._cbar.orientation == 'vertical':
279+
if aspect:
280+
ax.set_aspect(aspect*shrink)
281+
offset = offset * pos.height
282+
pos = pos.shrunk(1, shrink).translated(0, offset)
283+
else:
284+
if aspect:
285+
ax.set_aspect(aspect/shrink)
286+
offset = offset * pos.width
287+
pos = pos.shrunk(shrink, 1).translated(offset, 0)
288+
return pos
289+
290+
297291
class Colorbar:
298292
r"""
299293
Draw a colorbar in an existing axes.
@@ -393,7 +387,7 @@ def __init__(self, ax, mappable=None, *, cmap=None,
393387
extendfrac=None,
394388
extendrect=False,
395389
label='',
396-
userax=False,
390+
userax=True,
397391
):
398392

399393
if mappable is None:
@@ -432,9 +426,9 @@ def __init__(self, ax, mappable=None, *, cmap=None,
432426
_api.check_in_list(
433427
['uniform', 'proportional'], spacing=spacing)
434428

435-
# wrap the axes so that it can be positioned as an inset axes:
436-
ax = ColorbarAxes(ax, userax=userax)
437429
self.ax = ax
430+
self._userax = userax
431+
self.ax._axes_locator = _ColorbarAxesLocator(self)
438432
ax.set(navigate=False)
439433

440434
if extend is None:
@@ -467,10 +461,8 @@ def __init__(self, ax, mappable=None, *, cmap=None,
467461

468462
for spine in self.ax.spines.values():
469463
spine.set_visible(False)
470-
for spine in self.ax.outer_ax.spines.values():
471-
spine.set_visible(False)
472464
self.outline = self.ax.spines['outline'] = _ColorbarSpine(self.ax)
473-
465+
self._short_axis().set_visible(False)
474466
self.patch = mpatches.Polygon(
475467
np.empty((0, 2)),
476468
color=mpl.rcParams['axes.facecolor'], linewidth=0.01, zorder=-1)
@@ -621,37 +613,27 @@ def _add_solids_patches(self, X, Y, C, mappable):
621613

622614
def _do_extends(self, extendlen):
623615
"""
624-
Make adjustments of the inner axes for the extend triangles (or
625-
rectanges) and add them as patches.
616+
Add the extend tri/rectangles on the outside of the axes.
626617
"""
627618
# extend lengths are fraction of the *inner* part of colorbar,
628619
# not the total colorbar:
629620
elower = extendlen[0] if self._extend_lower() else 0
630621
eupper = extendlen[1] if self._extend_upper() else 0
631-
total_len = eupper + elower + 1
632-
elower = elower / total_len
633-
eupper = eupper / total_len
634-
inner_length = 1 / total_len
635-
636-
# make the inner axes smaller to make room for the extend rectangle
637-
top = elower + inner_length
622+
top = eupper + 1
638623

639624
# xyout is the outline of the colorbar including the extend patches:
640625
if not self.extendrect:
641626
# triangle:
642-
xyout = np.array([[0, elower], [0.5, 0], [1, elower],
643-
[1, top], [0.5, 1], [0, top], [0, elower]])
627+
xyout = np.array([[0, 0], [0.5, -elower], [1, 0],
628+
[1, 1], [0.5, top], [0, 1], [0, 0]])
644629
else:
645630
# rectangle:
646-
xyout = np.array([[0, elower], [0, 0], [1, 0], [1, elower],
647-
[1, top], [1, 1], [0, 1], [0, top],
648-
[0, elower]])
631+
xyout = np.array([[0, 0], [0, -elower], [1, -elower], [1, 0],
632+
[1, 1], [1, top], [0, top], [0, 1],
633+
[0, -elower]])
649634

650-
bounds = np.array([0.0, elower, 1.0, inner_length])
651635
if self.orientation == 'horizontal':
652-
bounds = bounds[[1, 0, 3, 2]]
653636
xyout = xyout[:, ::-1]
654-
self.ax._set_inner_bounds(bounds)
655637

656638
# xyout is the path for the spine:
657639
self.outline.set_xy(xyout)
@@ -670,35 +652,35 @@ def _do_extends(self, extendlen):
670652
if self._extend_lower():
671653
if not self.extendrect:
672654
# triangle
673-
xy = np.array([[0.5, 0], [1, elower], [0, elower]])
655+
xy = np.array([[0.5, -elower], [1, 0], [0, 0]])
674656
else:
675657
# rectangle
676-
xy = np.array([[0, 0], [1., 0], [1, elower], [0, elower]])
658+
xy = np.array([[0, -elower], [1., -elower], [1, 0], [0, 0]])
677659
if self.orientation == 'horizontal':
678660
xy = xy[:, ::-1]
679661
# add the patch
680662
color = self.cmap(self.norm(self._values[0]))
681663
patch = mpatches.PathPatch(
682664
mpath.Path(xy), facecolor=color, linewidth=0,
683-
antialiased=False, transform=self.ax.outer_ax.transAxes,
684-
hatch=hatches[0])
685-
self.ax.outer_ax.add_patch(patch)
665+
antialiased=False, transform=self.ax.transAxes,
666+
hatch=hatches[0], clip_on=False)
667+
self.ax.add_patch(patch)
686668
if self._extend_upper():
687669
if not self.extendrect:
688670
# triangle
689-
xy = np.array([[0.5, 1], [1, 1-eupper], [0, 1-eupper]])
671+
xy = np.array([[0.5, top], [1, 1], [0, 1]])
690672
else:
691673
# rectangle
692-
xy = np.array([[0, 1], [1, 1], [1, 1-eupper], [0, 1-eupper]])
674+
xy = np.array([[0, top], [1, top], [1, 1], [0, 1]])
693675
if self.orientation == 'horizontal':
694676
xy = xy[:, ::-1]
695677
# add the patch
696678
color = self.cmap(self.norm(self._values[-1]))
697679
patch = mpatches.PathPatch(
698680
mpath.Path(xy), facecolor=color,
699681
linewidth=0, antialiased=False,
700-
transform=self.ax.outer_ax.transAxes, hatch=hatches[-1])
701-
self.ax.outer_ax.add_patch(patch)
682+
transform=self.ax.transAxes, hatch=hatches[-1], clip_on=False)
683+
self.ax.add_patch(patch)
702684
return
703685

704686
def add_lines(self, *args, **kwargs):
@@ -949,8 +931,7 @@ def remove(self):
949931
If the colorbar was created with ``use_gridspec=True`` the previous
950932
gridspec is restored.
951933
"""
952-
self.ax.inner_ax.remove()
953-
self.ax.outer_ax.remove()
934+
self.ax.remove()
954935

955936
self.mappable.callbacksSM.disconnect(self.mappable.colorbar_cid)
956937
self.mappable.colorbar = None

lib/matplotlib/figure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1200,7 +1200,7 @@ def subplots_adjust(self, left=None, bottom=None, right=None, top=None,
12001200
"disabling constrained_layout.")
12011201
self.subplotpars.update(left, bottom, right, top, wspace, hspace)
12021202
for ax in self.axes:
1203-
if isinstance(ax, SubplotBase):
1203+
if hasattr(ax, 'get_subplotspec'):
12041204
ax._set_position(ax.get_subplotspec().get_position(self))
12051205
self.stale = True
12061206

Binary file not shown.

0 commit comments

Comments
 (0)