Coverage for /usr/lib/python3/dist-packages/matplotlib/colorbar.py: 13%
704 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
1"""
2Colorbars are a visualization of the mapping from scalar values to colors.
3In Matplotlib they are drawn into a dedicated `~.axes.Axes`.
5.. note::
6 Colorbars are typically created through `.Figure.colorbar` or its pyplot
7 wrapper `.pyplot.colorbar`, which internally use `.Colorbar` together with
8 `.make_axes_gridspec` (for `.GridSpec`-positioned axes) or `.make_axes` (for
9 non-`.GridSpec`-positioned axes).
11 End-users most likely won't need to directly use this module's API.
12"""
14import logging
16import numpy as np
18import matplotlib as mpl
19from matplotlib import _api, cbook, collections, cm, colors, contour, ticker
20import matplotlib.artist as martist
21import matplotlib.patches as mpatches
22import matplotlib.path as mpath
23import matplotlib.scale as mscale
24import matplotlib.spines as mspines
25import matplotlib.transforms as mtransforms
26from matplotlib import _docstring
28_log = logging.getLogger(__name__)
30_docstring.interpd.update(
31 _make_axes_kw_doc="""
32location : None or {'left', 'right', 'top', 'bottom'}
33 The location, relative to the parent axes, where the colorbar axes
34 is created. It also determines the *orientation* of the colorbar
35 (colorbars on the left and right are vertical, colorbars at the top
36 and bottom are horizontal). If None, the location will come from the
37 *orientation* if it is set (vertical colorbars on the right, horizontal
38 ones at the bottom), or default to 'right' if *orientation* is unset.
40orientation : None or {'vertical', 'horizontal'}
41 The orientation of the colorbar. It is preferable to set the *location*
42 of the colorbar, as that also determines the *orientation*; passing
43 incompatible values for *location* and *orientation* raises an exception.
45fraction : float, default: 0.15
46 Fraction of original axes to use for colorbar.
48shrink : float, default: 1.0
49 Fraction by which to multiply the size of the colorbar.
51aspect : float, default: 20
52 Ratio of long to short dimensions.
54pad : float, default: 0.05 if vertical, 0.15 if horizontal
55 Fraction of original axes between colorbar and new image axes.
57anchor : (float, float), optional
58 The anchor point of the colorbar axes.
59 Defaults to (0.0, 0.5) if vertical; (0.5, 1.0) if horizontal.
61panchor : (float, float), or *False*, optional
62 The anchor point of the colorbar parent axes. If *False*, the parent
63 axes' anchor will be unchanged.
64 Defaults to (1.0, 0.5) if vertical; (0.5, 0.0) if horizontal.""",
65 _colormap_kw_doc="""
66extend : {'neither', 'both', 'min', 'max'}
67 Make pointed end(s) for out-of-range values (unless 'neither'). These are
68 set for a given colormap using the colormap set_under and set_over methods.
70extendfrac : {*None*, 'auto', length, lengths}
71 If set to *None*, both the minimum and maximum triangular colorbar
72 extensions will have a length of 5% of the interior colorbar length (this
73 is the default setting).
75 If set to 'auto', makes the triangular colorbar extensions the same lengths
76 as the interior boxes (when *spacing* is set to 'uniform') or the same
77 lengths as the respective adjacent interior boxes (when *spacing* is set to
78 'proportional').
80 If a scalar, indicates the length of both the minimum and maximum
81 triangular colorbar extensions as a fraction of the interior colorbar
82 length. A two-element sequence of fractions may also be given, indicating
83 the lengths of the minimum and maximum colorbar extensions respectively as
84 a fraction of the interior colorbar length.
86extendrect : bool
87 If *False* the minimum and maximum colorbar extensions will be triangular
88 (the default). If *True* the extensions will be rectangular.
90spacing : {'uniform', 'proportional'}
91 For discrete colorbars (`.BoundaryNorm` or contours), 'uniform' gives each
92 color the same space; 'proportional' makes the space proportional to the
93 data interval.
95ticks : None or list of ticks or Locator
96 If None, ticks are determined automatically from the input.
98format : None or str or Formatter
99 If None, `~.ticker.ScalarFormatter` is used.
100 Format strings, e.g., ``"%4.2e"`` or ``"{x:.2e}"``, are supported.
101 An alternative `~.ticker.Formatter` may be given instead.
103drawedges : bool
104 Whether to draw lines at color boundaries.
106label : str
107 The label on the colorbar's long axis.
109boundaries, values : None or a sequence
110 If unset, the colormap will be displayed on a 0-1 scale.
111 If sequences, *values* must have a length 1 less than *boundaries*. For
112 each region delimited by adjacent entries in *boundaries*, the color mapped
113 to the corresponding value in values will be used.
114 Normally only useful for indexed colors (i.e. ``norm=NoNorm()``) or other
115 unusual circumstances.""")
118def _set_ticks_on_axis_warn(*args, **kwargs):
119 # a top level function which gets put in at the axes'
120 # set_xticks and set_yticks by Colorbar.__init__.
121 _api.warn_external("Use the colorbar set_ticks() method instead.")
124class _ColorbarSpine(mspines.Spine):
125 def __init__(self, axes):
126 self._ax = axes
127 super().__init__(axes, 'colorbar', mpath.Path(np.empty((0, 2))))
128 mpatches.Patch.set_transform(self, axes.transAxes)
130 def get_window_extent(self, renderer=None):
131 # This Spine has no Axis associated with it, and doesn't need to adjust
132 # its location, so we can directly get the window extent from the
133 # super-super-class.
134 return mpatches.Patch.get_window_extent(self, renderer=renderer)
136 def set_xy(self, xy):
137 self._path = mpath.Path(xy, closed=True)
138 self._xy = xy
139 self.stale = True
141 def draw(self, renderer):
142 ret = mpatches.Patch.draw(self, renderer)
143 self.stale = False
144 return ret
147class _ColorbarAxesLocator:
148 """
149 Shrink the axes if there are triangular or rectangular extends.
150 """
151 def __init__(self, cbar):
152 self._cbar = cbar
153 self._orig_locator = cbar.ax._axes_locator
155 def __call__(self, ax, renderer):
156 if self._orig_locator is not None:
157 pos = self._orig_locator(ax, renderer)
158 else:
159 pos = ax.get_position(original=True)
160 if self._cbar.extend == 'neither':
161 return pos
163 y, extendlen = self._cbar._proportional_y()
164 if not self._cbar._extend_lower():
165 extendlen[0] = 0
166 if not self._cbar._extend_upper():
167 extendlen[1] = 0
168 len = sum(extendlen) + 1
169 shrink = 1 / len
170 offset = extendlen[0] / len
171 # we need to reset the aspect ratio of the axes to account
172 # of the extends...
173 if hasattr(ax, '_colorbar_info'):
174 aspect = ax._colorbar_info['aspect']
175 else:
176 aspect = False
177 # now shrink and/or offset to take into account the
178 # extend tri/rectangles.
179 if self._cbar.orientation == 'vertical':
180 if aspect:
181 self._cbar.ax.set_box_aspect(aspect*shrink)
182 pos = pos.shrunk(1, shrink).translated(0, offset * pos.height)
183 else:
184 if aspect:
185 self._cbar.ax.set_box_aspect(1/(aspect * shrink))
186 pos = pos.shrunk(shrink, 1).translated(offset * pos.width, 0)
187 return pos
189 def get_subplotspec(self):
190 # make tight_layout happy..
191 ss = getattr(self._cbar.ax, 'get_subplotspec', None)
192 if ss is None:
193 if not hasattr(self._orig_locator, "get_subplotspec"):
194 return None
195 ss = self._orig_locator.get_subplotspec
196 return ss()
199@_docstring.interpd
200class Colorbar:
201 r"""
202 Draw a colorbar in an existing axes.
204 Typically, colorbars are created using `.Figure.colorbar` or
205 `.pyplot.colorbar` and associated with `.ScalarMappable`\s (such as an
206 `.AxesImage` generated via `~.axes.Axes.imshow`).
208 In order to draw a colorbar not associated with other elements in the
209 figure, e.g. when showing a colormap by itself, one can create an empty
210 `.ScalarMappable`, or directly pass *cmap* and *norm* instead of *mappable*
211 to `Colorbar`.
213 Useful public methods are :meth:`set_label` and :meth:`add_lines`.
215 Attributes
216 ----------
217 ax : `~matplotlib.axes.Axes`
218 The `~.axes.Axes` instance in which the colorbar is drawn.
219 lines : list
220 A list of `.LineCollection` (empty if no lines were drawn).
221 dividers : `.LineCollection`
222 A LineCollection (empty if *drawedges* is ``False``).
224 Parameters
225 ----------
226 ax : `~matplotlib.axes.Axes`
227 The `~.axes.Axes` instance in which the colorbar is drawn.
229 mappable : `.ScalarMappable`
230 The mappable whose colormap and norm will be used.
232 To show the under- and over- value colors, the mappable's norm should
233 be specified as ::
235 norm = colors.Normalize(clip=False)
237 To show the colors versus index instead of on a 0-1 scale, use::
239 norm=colors.NoNorm()
241 cmap : `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
242 The colormap to use. This parameter is ignored, unless *mappable* is
243 None.
245 norm : `~matplotlib.colors.Normalize`
246 The normalization to use. This parameter is ignored, unless *mappable*
247 is None.
249 alpha : float
250 The colorbar transparency between 0 (transparent) and 1 (opaque).
252 orientation : {'vertical', 'horizontal'}
254 ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
256 drawedges : bool
258 filled : bool
259 %(_colormap_kw_doc)s
260 """
262 n_rasterize = 50 # rasterize solids if number of colors >= n_rasterize
264 @_api.delete_parameter("3.6", "filled")
265 def __init__(self, ax, mappable=None, *, cmap=None,
266 norm=None,
267 alpha=None,
268 values=None,
269 boundaries=None,
270 orientation='vertical',
271 ticklocation='auto',
272 extend=None,
273 spacing='uniform', # uniform or proportional
274 ticks=None,
275 format=None,
276 drawedges=False,
277 filled=True,
278 extendfrac=None,
279 extendrect=False,
280 label='',
281 ):
283 if mappable is None:
284 mappable = cm.ScalarMappable(norm=norm, cmap=cmap)
286 # Ensure the given mappable's norm has appropriate vmin and vmax
287 # set even if mappable.draw has not yet been called.
288 if mappable.get_array() is not None:
289 mappable.autoscale_None()
291 self.mappable = mappable
292 cmap = mappable.cmap
293 norm = mappable.norm
295 if isinstance(mappable, contour.ContourSet):
296 cs = mappable
297 alpha = cs.get_alpha()
298 boundaries = cs._levels
299 values = cs.cvalues
300 extend = cs.extend
301 filled = cs.filled
302 if ticks is None:
303 ticks = ticker.FixedLocator(cs.levels, nbins=10)
304 elif isinstance(mappable, martist.Artist):
305 alpha = mappable.get_alpha()
307 mappable.colorbar = self
308 mappable.colorbar_cid = mappable.callbacks.connect(
309 'changed', self.update_normal)
311 _api.check_in_list(
312 ['vertical', 'horizontal'], orientation=orientation)
313 _api.check_in_list(
314 ['auto', 'left', 'right', 'top', 'bottom'],
315 ticklocation=ticklocation)
316 _api.check_in_list(
317 ['uniform', 'proportional'], spacing=spacing)
319 self.ax = ax
320 self.ax._axes_locator = _ColorbarAxesLocator(self)
322 if extend is None:
323 if (not isinstance(mappable, contour.ContourSet)
324 and getattr(cmap, 'colorbar_extend', False) is not False):
325 extend = cmap.colorbar_extend
326 elif hasattr(norm, 'extend'):
327 extend = norm.extend
328 else:
329 extend = 'neither'
330 self.alpha = None
331 # Call set_alpha to handle array-like alphas properly
332 self.set_alpha(alpha)
333 self.cmap = cmap
334 self.norm = norm
335 self.values = values
336 self.boundaries = boundaries
337 self.extend = extend
338 self._inside = _api.check_getitem(
339 {'neither': slice(0, None), 'both': slice(1, -1),
340 'min': slice(1, None), 'max': slice(0, -1)},
341 extend=extend)
342 self.spacing = spacing
343 self.orientation = orientation
344 self.drawedges = drawedges
345 self._filled = filled
346 self.extendfrac = extendfrac
347 self.extendrect = extendrect
348 self._extend_patches = []
349 self.solids = None
350 self.solids_patches = []
351 self.lines = []
353 for spine in self.ax.spines.values():
354 spine.set_visible(False)
355 self.outline = self.ax.spines['outline'] = _ColorbarSpine(self.ax)
356 # Only kept for backcompat; remove after deprecation of .patch elapses.
357 self._patch = mpatches.Polygon(
358 np.empty((0, 2)),
359 color=mpl.rcParams['axes.facecolor'], linewidth=0.01, zorder=-1)
360 ax.add_artist(self._patch)
362 self.dividers = collections.LineCollection(
363 [],
364 colors=[mpl.rcParams['axes.edgecolor']],
365 linewidths=[0.5 * mpl.rcParams['axes.linewidth']],
366 clip_on=False)
367 self.ax.add_collection(self.dividers)
369 self._locator = None
370 self._minorlocator = None
371 self._formatter = None
372 self._minorformatter = None
373 self.__scale = None # linear, log10 for now. Hopefully more?
375 if ticklocation == 'auto':
376 ticklocation = 'bottom' if orientation == 'horizontal' else 'right'
377 self.ticklocation = ticklocation
379 self.set_label(label)
380 self._reset_locator_formatter_scale()
382 if np.iterable(ticks):
383 self._locator = ticker.FixedLocator(ticks, nbins=len(ticks))
384 else:
385 self._locator = ticks
387 if isinstance(format, str):
388 # Check format between FormatStrFormatter and StrMethodFormatter
389 try:
390 self._formatter = ticker.FormatStrFormatter(format)
391 _ = self._formatter(0)
392 except TypeError:
393 self._formatter = ticker.StrMethodFormatter(format)
394 else:
395 self._formatter = format # Assume it is a Formatter or None
396 self._draw_all()
398 if isinstance(mappable, contour.ContourSet) and not mappable.filled:
399 self.add_lines(mappable)
401 # Link the Axes and Colorbar for interactive use
402 self.ax._colorbar = self
403 # Don't navigate on any of these types of mappables
404 if (isinstance(self.norm, (colors.BoundaryNorm, colors.NoNorm)) or
405 isinstance(self.mappable, contour.ContourSet)):
406 self.ax.set_navigate(False)
408 # These are the functions that set up interactivity on this colorbar
409 self._interactive_funcs = ["_get_view", "_set_view",
410 "_set_view_from_bbox", "drag_pan"]
411 for x in self._interactive_funcs:
412 setattr(self.ax, x, getattr(self, x))
413 # Set the cla function to the cbar's method to override it
414 self.ax.cla = self._cbar_cla
415 # Callbacks for the extend calculations to handle inverting the axis
416 self._extend_cid1 = self.ax.callbacks.connect(
417 "xlim_changed", self._do_extends)
418 self._extend_cid2 = self.ax.callbacks.connect(
419 "ylim_changed", self._do_extends)
421 @property
422 def locator(self):
423 """Major tick `.Locator` for the colorbar."""
424 return self._long_axis().get_major_locator()
426 @locator.setter
427 def locator(self, loc):
428 self._long_axis().set_major_locator(loc)
429 self._locator = loc
431 @property
432 def minorlocator(self):
433 """Minor tick `.Locator` for the colorbar."""
434 return self._long_axis().get_minor_locator()
436 @minorlocator.setter
437 def minorlocator(self, loc):
438 self._long_axis().set_minor_locator(loc)
439 self._minorlocator = loc
441 @property
442 def formatter(self):
443 """Major tick label `.Formatter` for the colorbar."""
444 return self._long_axis().get_major_formatter()
446 @formatter.setter
447 def formatter(self, fmt):
448 self._long_axis().set_major_formatter(fmt)
449 self._formatter = fmt
451 @property
452 def minorformatter(self):
453 """Minor tick `.Formatter` for the colorbar."""
454 return self._long_axis().get_minor_formatter()
456 @minorformatter.setter
457 def minorformatter(self, fmt):
458 self._long_axis().set_minor_formatter(fmt)
459 self._minorformatter = fmt
461 def _cbar_cla(self):
462 """Function to clear the interactive colorbar state."""
463 for x in self._interactive_funcs:
464 delattr(self.ax, x)
465 # We now restore the old cla() back and can call it directly
466 del self.ax.cla
467 self.ax.cla()
469 # Also remove ._patch after deprecation elapses.
470 patch = _api.deprecate_privatize_attribute("3.5", alternative="ax")
472 filled = _api.deprecate_privatize_attribute("3.6")
474 def update_normal(self, mappable):
475 """
476 Update solid patches, lines, etc.
478 This is meant to be called when the norm of the image or contour plot
479 to which this colorbar belongs changes.
481 If the norm on the mappable is different than before, this resets the
482 locator and formatter for the axis, so if these have been customized,
483 they will need to be customized again. However, if the norm only
484 changes values of *vmin*, *vmax* or *cmap* then the old formatter
485 and locator will be preserved.
486 """
487 _log.debug('colorbar update normal %r %r', mappable.norm, self.norm)
488 self.mappable = mappable
489 self.set_alpha(mappable.get_alpha())
490 self.cmap = mappable.cmap
491 if mappable.norm != self.norm:
492 self.norm = mappable.norm
493 self._reset_locator_formatter_scale()
495 self._draw_all()
496 if isinstance(self.mappable, contour.ContourSet):
497 CS = self.mappable
498 if not CS.filled:
499 self.add_lines(CS)
500 self.stale = True
502 @_api.deprecated("3.6", alternative="fig.draw_without_rendering()")
503 def draw_all(self):
504 """
505 Calculate any free parameters based on the current cmap and norm,
506 and do all the drawing.
507 """
508 self._draw_all()
510 def _draw_all(self):
511 """
512 Calculate any free parameters based on the current cmap and norm,
513 and do all the drawing.
514 """
515 if self.orientation == 'vertical':
516 if mpl.rcParams['ytick.minor.visible']:
517 self.minorticks_on()
518 else:
519 if mpl.rcParams['xtick.minor.visible']:
520 self.minorticks_on()
521 self._long_axis().set(label_position=self.ticklocation,
522 ticks_position=self.ticklocation)
523 self._short_axis().set_ticks([])
524 self._short_axis().set_ticks([], minor=True)
526 # Set self._boundaries and self._values, including extensions.
527 # self._boundaries are the edges of each square of color, and
528 # self._values are the value to map into the norm to get the
529 # color:
530 self._process_values()
531 # Set self.vmin and self.vmax to first and last boundary, excluding
532 # extensions:
533 self.vmin, self.vmax = self._boundaries[self._inside][[0, -1]]
534 # Compute the X/Y mesh.
535 X, Y = self._mesh()
536 # draw the extend triangles, and shrink the inner axes to accommodate.
537 # also adds the outline path to self.outline spine:
538 self._do_extends()
539 lower, upper = self.vmin, self.vmax
540 if self._long_axis().get_inverted():
541 # If the axis is inverted, we need to swap the vmin/vmax
542 lower, upper = upper, lower
543 if self.orientation == 'vertical':
544 self.ax.set_xlim(0, 1)
545 self.ax.set_ylim(lower, upper)
546 else:
547 self.ax.set_ylim(0, 1)
548 self.ax.set_xlim(lower, upper)
550 # set up the tick locators and formatters. A bit complicated because
551 # boundary norms + uniform spacing requires a manual locator.
552 self.update_ticks()
554 if self._filled:
555 ind = np.arange(len(self._values))
556 if self._extend_lower():
557 ind = ind[1:]
558 if self._extend_upper():
559 ind = ind[:-1]
560 self._add_solids(X, Y, self._values[ind, np.newaxis])
562 def _add_solids(self, X, Y, C):
563 """Draw the colors; optionally add separators."""
564 # Cleanup previously set artists.
565 if self.solids is not None:
566 self.solids.remove()
567 for solid in self.solids_patches:
568 solid.remove()
569 # Add new artist(s), based on mappable type. Use individual patches if
570 # hatching is needed, pcolormesh otherwise.
571 mappable = getattr(self, 'mappable', None)
572 if (isinstance(mappable, contour.ContourSet)
573 and any(hatch is not None for hatch in mappable.hatches)):
574 self._add_solids_patches(X, Y, C, mappable)
575 else:
576 self.solids = self.ax.pcolormesh(
577 X, Y, C, cmap=self.cmap, norm=self.norm, alpha=self.alpha,
578 edgecolors='none', shading='flat')
579 if not self.drawedges:
580 if len(self._y) >= self.n_rasterize:
581 self.solids.set_rasterized(True)
582 self._update_dividers()
584 def _update_dividers(self):
585 if not self.drawedges:
586 self.dividers.set_segments([])
587 return
588 # Place all *internal* dividers.
589 if self.orientation == 'vertical':
590 lims = self.ax.get_ylim()
591 bounds = (lims[0] < self._y) & (self._y < lims[1])
592 else:
593 lims = self.ax.get_xlim()
594 bounds = (lims[0] < self._y) & (self._y < lims[1])
595 y = self._y[bounds]
596 # And then add outer dividers if extensions are on.
597 if self._extend_lower():
598 y = np.insert(y, 0, lims[0])
599 if self._extend_upper():
600 y = np.append(y, lims[1])
601 X, Y = np.meshgrid([0, 1], y)
602 if self.orientation == 'vertical':
603 segments = np.dstack([X, Y])
604 else:
605 segments = np.dstack([Y, X])
606 self.dividers.set_segments(segments)
608 def _add_solids_patches(self, X, Y, C, mappable):
609 hatches = mappable.hatches * (len(C) + 1) # Have enough hatches.
610 if self._extend_lower():
611 # remove first hatch that goes into the extend patch
612 hatches = hatches[1:]
613 patches = []
614 for i in range(len(X) - 1):
615 xy = np.array([[X[i, 0], Y[i, 1]],
616 [X[i, 1], Y[i, 0]],
617 [X[i + 1, 1], Y[i + 1, 0]],
618 [X[i + 1, 0], Y[i + 1, 1]]])
619 patch = mpatches.PathPatch(mpath.Path(xy),
620 facecolor=self.cmap(self.norm(C[i][0])),
621 hatch=hatches[i], linewidth=0,
622 antialiased=False, alpha=self.alpha)
623 self.ax.add_patch(patch)
624 patches.append(patch)
625 self.solids_patches = patches
627 def _do_extends(self, ax=None):
628 """
629 Add the extend tri/rectangles on the outside of the axes.
631 ax is unused, but required due to the callbacks on xlim/ylim changed
632 """
633 # Clean up any previous extend patches
634 for patch in self._extend_patches:
635 patch.remove()
636 self._extend_patches = []
637 # extend lengths are fraction of the *inner* part of colorbar,
638 # not the total colorbar:
639 _, extendlen = self._proportional_y()
640 bot = 0 - (extendlen[0] if self._extend_lower() else 0)
641 top = 1 + (extendlen[1] if self._extend_upper() else 0)
643 # xyout is the outline of the colorbar including the extend patches:
644 if not self.extendrect:
645 # triangle:
646 xyout = np.array([[0, 0], [0.5, bot], [1, 0],
647 [1, 1], [0.5, top], [0, 1], [0, 0]])
648 else:
649 # rectangle:
650 xyout = np.array([[0, 0], [0, bot], [1, bot], [1, 0],
651 [1, 1], [1, top], [0, top], [0, 1],
652 [0, 0]])
654 if self.orientation == 'horizontal':
655 xyout = xyout[:, ::-1]
657 # xyout is the path for the spine:
658 self.outline.set_xy(xyout)
659 if not self._filled:
660 return
662 # Make extend triangles or rectangles filled patches. These are
663 # defined in the outer parent axes' coordinates:
664 mappable = getattr(self, 'mappable', None)
665 if (isinstance(mappable, contour.ContourSet)
666 and any(hatch is not None for hatch in mappable.hatches)):
667 hatches = mappable.hatches * (len(self._y) + 1)
668 else:
669 hatches = [None] * (len(self._y) + 1)
671 if self._extend_lower():
672 if not self.extendrect:
673 # triangle
674 xy = np.array([[0, 0], [0.5, bot], [1, 0]])
675 else:
676 # rectangle
677 xy = np.array([[0, 0], [0, bot], [1., bot], [1, 0]])
678 if self.orientation == 'horizontal':
679 xy = xy[:, ::-1]
680 # add the patch
681 val = -1 if self._long_axis().get_inverted() else 0
682 color = self.cmap(self.norm(self._values[val]))
683 patch = mpatches.PathPatch(
684 mpath.Path(xy), facecolor=color, alpha=self.alpha,
685 linewidth=0, antialiased=False,
686 transform=self.ax.transAxes,
687 hatch=hatches[0], clip_on=False,
688 # Place it right behind the standard patches, which is
689 # needed if we updated the extends
690 zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
691 self.ax.add_patch(patch)
692 self._extend_patches.append(patch)
693 # remove first hatch that goes into the extend patch
694 hatches = hatches[1:]
695 if self._extend_upper():
696 if not self.extendrect:
697 # triangle
698 xy = np.array([[0, 1], [0.5, top], [1, 1]])
699 else:
700 # rectangle
701 xy = np.array([[0, 1], [0, top], [1, top], [1, 1]])
702 if self.orientation == 'horizontal':
703 xy = xy[:, ::-1]
704 # add the patch
705 val = 0 if self._long_axis().get_inverted() else -1
706 color = self.cmap(self.norm(self._values[val]))
707 hatch_idx = len(self._y) - 1
708 patch = mpatches.PathPatch(
709 mpath.Path(xy), facecolor=color, alpha=self.alpha,
710 linewidth=0, antialiased=False,
711 transform=self.ax.transAxes, hatch=hatches[hatch_idx],
712 clip_on=False,
713 # Place it right behind the standard patches, which is
714 # needed if we updated the extends
715 zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
716 self.ax.add_patch(patch)
717 self._extend_patches.append(patch)
719 self._update_dividers()
721 def add_lines(self, *args, **kwargs):
722 """
723 Draw lines on the colorbar.
725 The lines are appended to the list :attr:`lines`.
727 Parameters
728 ----------
729 levels : array-like
730 The positions of the lines.
731 colors : color or list of colors
732 Either a single color applying to all lines or one color value for
733 each line.
734 linewidths : float or array-like
735 Either a single linewidth applying to all lines or one linewidth
736 for each line.
737 erase : bool, default: True
738 Whether to remove any previously added lines.
740 Notes
741 -----
742 Alternatively, this method can also be called with the signature
743 ``colorbar.add_lines(contour_set, erase=True)``, in which case
744 *levels*, *colors*, and *linewidths* are taken from *contour_set*.
745 """
746 params = _api.select_matching_signature(
747 [lambda self, CS, erase=True: locals(),
748 lambda self, levels, colors, linewidths, erase=True: locals()],
749 self, *args, **kwargs)
750 if "CS" in params:
751 self, CS, erase = params.values()
752 if not isinstance(CS, contour.ContourSet) or CS.filled:
753 raise ValueError("If a single artist is passed to add_lines, "
754 "it must be a ContourSet of lines")
755 # TODO: Make colorbar lines auto-follow changes in contour lines.
756 return self.add_lines(
757 CS.levels,
758 [c[0] for c in CS.tcolors],
759 [t[0] for t in CS.tlinewidths],
760 erase=erase)
761 else:
762 self, levels, colors, linewidths, erase = params.values()
764 y = self._locate(levels)
765 rtol = (self._y[-1] - self._y[0]) * 1e-10
766 igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol)
767 y = y[igood]
768 if np.iterable(colors):
769 colors = np.asarray(colors)[igood]
770 if np.iterable(linewidths):
771 linewidths = np.asarray(linewidths)[igood]
772 X, Y = np.meshgrid([0, 1], y)
773 if self.orientation == 'vertical':
774 xy = np.stack([X, Y], axis=-1)
775 else:
776 xy = np.stack([Y, X], axis=-1)
777 col = collections.LineCollection(xy, linewidths=linewidths,
778 colors=colors)
780 if erase and self.lines:
781 for lc in self.lines:
782 lc.remove()
783 self.lines = []
784 self.lines.append(col)
786 # make a clip path that is just a linewidth bigger than the axes...
787 fac = np.max(linewidths) / 72
788 xy = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
789 inches = self.ax.get_figure().dpi_scale_trans
790 # do in inches:
791 xy = inches.inverted().transform(self.ax.transAxes.transform(xy))
792 xy[[0, 1, 4], 1] -= fac
793 xy[[2, 3], 1] += fac
794 # back to axes units...
795 xy = self.ax.transAxes.inverted().transform(inches.transform(xy))
796 col.set_clip_path(mpath.Path(xy, closed=True),
797 self.ax.transAxes)
798 self.ax.add_collection(col)
799 self.stale = True
801 def update_ticks(self):
802 """
803 Set up the ticks and ticklabels. This should not be needed by users.
804 """
805 # Get the locator and formatter; defaults to self._locator if not None.
806 self._get_ticker_locator_formatter()
807 self._long_axis().set_major_locator(self._locator)
808 self._long_axis().set_minor_locator(self._minorlocator)
809 self._long_axis().set_major_formatter(self._formatter)
811 def _get_ticker_locator_formatter(self):
812 """
813 Return the ``locator`` and ``formatter`` of the colorbar.
815 If they have not been defined (i.e. are *None*), the formatter and
816 locator are retrieved from the axis, or from the value of the
817 boundaries for a boundary norm.
819 Called by update_ticks...
820 """
821 locator = self._locator
822 formatter = self._formatter
823 minorlocator = self._minorlocator
824 if isinstance(self.norm, colors.BoundaryNorm):
825 b = self.norm.boundaries
826 if locator is None:
827 locator = ticker.FixedLocator(b, nbins=10)
828 if minorlocator is None:
829 minorlocator = ticker.FixedLocator(b)
830 elif isinstance(self.norm, colors.NoNorm):
831 if locator is None:
832 # put ticks on integers between the boundaries of NoNorm
833 nv = len(self._values)
834 base = 1 + int(nv / 10)
835 locator = ticker.IndexLocator(base=base, offset=.5)
836 elif self.boundaries is not None:
837 b = self._boundaries[self._inside]
838 if locator is None:
839 locator = ticker.FixedLocator(b, nbins=10)
840 else: # most cases:
841 if locator is None:
842 # we haven't set the locator explicitly, so use the default
843 # for this axis:
844 locator = self._long_axis().get_major_locator()
845 if minorlocator is None:
846 minorlocator = self._long_axis().get_minor_locator()
848 if minorlocator is None:
849 minorlocator = ticker.NullLocator()
851 if formatter is None:
852 formatter = self._long_axis().get_major_formatter()
854 self._locator = locator
855 self._formatter = formatter
856 self._minorlocator = minorlocator
857 _log.debug('locator: %r', locator)
859 @_api.delete_parameter("3.5", "update_ticks")
860 def set_ticks(self, ticks, update_ticks=True, labels=None, *,
861 minor=False, **kwargs):
862 """
863 Set tick locations.
865 Parameters
866 ----------
867 ticks : list of floats
868 List of tick locations.
869 labels : list of str, optional
870 List of tick labels. If not set, the labels show the data value.
871 minor : bool, default: False
872 If ``False``, set the major ticks; if ``True``, the minor ticks.
873 **kwargs
874 `.Text` properties for the labels. These take effect only if you
875 pass *labels*. In other cases, please use `~.Axes.tick_params`.
876 """
877 if np.iterable(ticks):
878 self._long_axis().set_ticks(ticks, labels=labels, minor=minor,
879 **kwargs)
880 self._locator = self._long_axis().get_major_locator()
881 else:
882 self._locator = ticks
883 self._long_axis().set_major_locator(self._locator)
884 self.stale = True
886 def get_ticks(self, minor=False):
887 """
888 Return the ticks as a list of locations.
890 Parameters
891 ----------
892 minor : boolean, default: False
893 if True return the minor ticks.
894 """
895 if minor:
896 return self._long_axis().get_minorticklocs()
897 else:
898 return self._long_axis().get_majorticklocs()
900 @_api.delete_parameter("3.5", "update_ticks")
901 def set_ticklabels(self, ticklabels, update_ticks=True, *, minor=False,
902 **kwargs):
903 """
904 [*Discouraged*] Set tick labels.
906 .. admonition:: Discouraged
908 The use of this method is discouraged, because of the dependency
909 on tick positions. In most cases, you'll want to use
910 ``set_ticks(positions, labels=labels)`` instead.
912 If you are using this method, you should always fix the tick
913 positions before, e.g. by using `.Colorbar.set_ticks` or by
914 explicitly setting a `~.ticker.FixedLocator` on the long axis
915 of the colorbar. Otherwise, ticks are free to move and the
916 labels may end up in unexpected positions.
918 Parameters
919 ----------
920 ticklabels : sequence of str or of `.Text`
921 Texts for labeling each tick location in the sequence set by
922 `.Colorbar.set_ticks`; the number of labels must match the number
923 of locations.
925 update_ticks : bool, default: True
926 This keyword argument is ignored and will be removed.
927 Deprecated
929 minor : bool
930 If True, set minor ticks instead of major ticks.
932 **kwargs
933 `.Text` properties for the labels.
934 """
935 self._long_axis().set_ticklabels(ticklabels, minor=minor, **kwargs)
937 def minorticks_on(self):
938 """
939 Turn on colorbar minor ticks.
940 """
941 self.ax.minorticks_on()
942 self._short_axis().set_minor_locator(ticker.NullLocator())
944 def minorticks_off(self):
945 """Turn the minor ticks of the colorbar off."""
946 self._minorlocator = ticker.NullLocator()
947 self._long_axis().set_minor_locator(self._minorlocator)
949 def set_label(self, label, *, loc=None, **kwargs):
950 """
951 Add a label to the long axis of the colorbar.
953 Parameters
954 ----------
955 label : str
956 The label text.
957 loc : str, optional
958 The location of the label.
960 - For horizontal orientation one of {'left', 'center', 'right'}
961 - For vertical orientation one of {'bottom', 'center', 'top'}
963 Defaults to :rc:`xaxis.labellocation` or :rc:`yaxis.labellocation`
964 depending on the orientation.
965 **kwargs
966 Keyword arguments are passed to `~.Axes.set_xlabel` /
967 `~.Axes.set_ylabel`.
968 Supported keywords are *labelpad* and `.Text` properties.
969 """
970 if self.orientation == "vertical":
971 self.ax.set_ylabel(label, loc=loc, **kwargs)
972 else:
973 self.ax.set_xlabel(label, loc=loc, **kwargs)
974 self.stale = True
976 def set_alpha(self, alpha):
977 """
978 Set the transparency between 0 (transparent) and 1 (opaque).
980 If an array is provided, *alpha* will be set to None to use the
981 transparency values associated with the colormap.
982 """
983 self.alpha = None if isinstance(alpha, np.ndarray) else alpha
985 def _set_scale(self, scale, **kwargs):
986 """
987 Set the colorbar long axis scale.
989 Parameters
990 ----------
991 value : {"linear", "log", "symlog", "logit", ...} or `.ScaleBase`
992 The axis scale type to apply.
994 **kwargs
995 Different keyword arguments are accepted, depending on the scale.
996 See the respective class keyword arguments:
998 - `matplotlib.scale.LinearScale`
999 - `matplotlib.scale.LogScale`
1000 - `matplotlib.scale.SymmetricalLogScale`
1001 - `matplotlib.scale.LogitScale`
1002 - `matplotlib.scale.FuncScale`
1004 Notes
1005 -----
1006 By default, Matplotlib supports the above-mentioned scales.
1007 Additionally, custom scales may be registered using
1008 `matplotlib.scale.register_scale`. These scales can then also
1009 be used here.
1010 """
1011 if self.orientation == 'vertical':
1012 self.ax.set_yscale(scale, **kwargs)
1013 else:
1014 self.ax.set_xscale(scale, **kwargs)
1015 if isinstance(scale, mscale.ScaleBase):
1016 self.__scale = scale.name
1017 else:
1018 self.__scale = scale
1020 def remove(self):
1021 """
1022 Remove this colorbar from the figure.
1024 If the colorbar was created with ``use_gridspec=True`` the previous
1025 gridspec is restored.
1026 """
1027 if hasattr(self.ax, '_colorbar_info'):
1028 parents = self.ax._colorbar_info['parents']
1029 for a in parents:
1030 if self.ax in a._colorbars:
1031 a._colorbars.remove(self.ax)
1033 self.ax.remove()
1035 self.mappable.callbacks.disconnect(self.mappable.colorbar_cid)
1036 self.mappable.colorbar = None
1037 self.mappable.colorbar_cid = None
1038 # Remove the extension callbacks
1039 self.ax.callbacks.disconnect(self._extend_cid1)
1040 self.ax.callbacks.disconnect(self._extend_cid2)
1042 try:
1043 ax = self.mappable.axes
1044 except AttributeError:
1045 return
1046 try:
1047 gs = ax.get_subplotspec().get_gridspec()
1048 subplotspec = gs.get_topmost_subplotspec()
1049 except AttributeError:
1050 # use_gridspec was False
1051 pos = ax.get_position(original=True)
1052 ax._set_position(pos)
1053 else:
1054 # use_gridspec was True
1055 ax.set_subplotspec(subplotspec)
1057 def _process_values(self):
1058 """
1059 Set `_boundaries` and `_values` based on the self.boundaries and
1060 self.values if not None, or based on the size of the colormap and
1061 the vmin/vmax of the norm.
1062 """
1063 if self.values is not None:
1064 # set self._boundaries from the values...
1065 self._values = np.array(self.values)
1066 if self.boundaries is None:
1067 # bracket values by 1/2 dv:
1068 b = np.zeros(len(self.values) + 1)
1069 b[1:-1] = 0.5 * (self._values[:-1] + self._values[1:])
1070 b[0] = 2.0 * b[1] - b[2]
1071 b[-1] = 2.0 * b[-2] - b[-3]
1072 self._boundaries = b
1073 return
1074 self._boundaries = np.array(self.boundaries)
1075 return
1077 # otherwise values are set from the boundaries
1078 if isinstance(self.norm, colors.BoundaryNorm):
1079 b = self.norm.boundaries
1080 elif isinstance(self.norm, colors.NoNorm):
1081 # NoNorm has N blocks, so N+1 boundaries, centered on integers:
1082 b = np.arange(self.cmap.N + 1) - .5
1083 elif self.boundaries is not None:
1084 b = self.boundaries
1085 else:
1086 # otherwise make the boundaries from the size of the cmap:
1087 N = self.cmap.N + 1
1088 b, _ = self._uniform_y(N)
1089 # add extra boundaries if needed:
1090 if self._extend_lower():
1091 b = np.hstack((b[0] - 1, b))
1092 if self._extend_upper():
1093 b = np.hstack((b, b[-1] + 1))
1095 # transform from 0-1 to vmin-vmax:
1096 if not self.norm.scaled():
1097 self.norm.vmin = 0
1098 self.norm.vmax = 1
1099 self.norm.vmin, self.norm.vmax = mtransforms.nonsingular(
1100 self.norm.vmin, self.norm.vmax, expander=0.1)
1101 if (not isinstance(self.norm, colors.BoundaryNorm) and
1102 (self.boundaries is None)):
1103 b = self.norm.inverse(b)
1105 self._boundaries = np.asarray(b, dtype=float)
1106 self._values = 0.5 * (self._boundaries[:-1] + self._boundaries[1:])
1107 if isinstance(self.norm, colors.NoNorm):
1108 self._values = (self._values + 0.00001).astype(np.int16)
1110 def _mesh(self):
1111 """
1112 Return the coordinate arrays for the colorbar pcolormesh/patches.
1114 These are scaled between vmin and vmax, and already handle colorbar
1115 orientation.
1116 """
1117 y, _ = self._proportional_y()
1118 # Use the vmin and vmax of the colorbar, which may not be the same
1119 # as the norm. There are situations where the colormap has a
1120 # narrower range than the colorbar and we want to accommodate the
1121 # extra contours.
1122 if (isinstance(self.norm, (colors.BoundaryNorm, colors.NoNorm))
1123 or self.boundaries is not None):
1124 # not using a norm.
1125 y = y * (self.vmax - self.vmin) + self.vmin
1126 else:
1127 # Update the norm values in a context manager as it is only
1128 # a temporary change and we don't want to propagate any signals
1129 # attached to the norm (callbacks.blocked).
1130 with self.norm.callbacks.blocked(), \
1131 cbook._setattr_cm(self.norm,
1132 vmin=self.vmin,
1133 vmax=self.vmax):
1134 y = self.norm.inverse(y)
1135 self._y = y
1136 X, Y = np.meshgrid([0., 1.], y)
1137 if self.orientation == 'vertical':
1138 return (X, Y)
1139 else:
1140 return (Y, X)
1142 def _forward_boundaries(self, x):
1143 # map boundaries equally between 0 and 1...
1144 b = self._boundaries
1145 y = np.interp(x, b, np.linspace(0, 1, len(b)))
1146 # the following avoids ticks in the extends:
1147 eps = (b[-1] - b[0]) * 1e-6
1148 # map these _well_ out of bounds to keep any ticks out
1149 # of the extends region...
1150 y[x < b[0]-eps] = -1
1151 y[x > b[-1]+eps] = 2
1152 return y
1154 def _inverse_boundaries(self, x):
1155 # invert the above...
1156 b = self._boundaries
1157 return np.interp(x, np.linspace(0, 1, len(b)), b)
1159 def _reset_locator_formatter_scale(self):
1160 """
1161 Reset the locator et al to defaults. Any user-hardcoded changes
1162 need to be re-entered if this gets called (either at init, or when
1163 the mappable normal gets changed: Colorbar.update_normal)
1164 """
1165 self._process_values()
1166 self._locator = None
1167 self._minorlocator = None
1168 self._formatter = None
1169 self._minorformatter = None
1170 if (isinstance(self.mappable, contour.ContourSet) and
1171 isinstance(self.norm, colors.LogNorm)):
1172 # if contours have lognorm, give them a log scale...
1173 self._set_scale('log')
1174 elif (self.boundaries is not None or
1175 isinstance(self.norm, colors.BoundaryNorm)):
1176 if self.spacing == 'uniform':
1177 funcs = (self._forward_boundaries, self._inverse_boundaries)
1178 self._set_scale('function', functions=funcs)
1179 elif self.spacing == 'proportional':
1180 self._set_scale('linear')
1181 elif getattr(self.norm, '_scale', None):
1182 # use the norm's scale (if it exists and is not None):
1183 self._set_scale(self.norm._scale)
1184 elif type(self.norm) is colors.Normalize:
1185 # plain Normalize:
1186 self._set_scale('linear')
1187 else:
1188 # norm._scale is None or not an attr: derive the scale from
1189 # the Norm:
1190 funcs = (self.norm, self.norm.inverse)
1191 self._set_scale('function', functions=funcs)
1193 def _locate(self, x):
1194 """
1195 Given a set of color data values, return their
1196 corresponding colorbar data coordinates.
1197 """
1198 if isinstance(self.norm, (colors.NoNorm, colors.BoundaryNorm)):
1199 b = self._boundaries
1200 xn = x
1201 else:
1202 # Do calculations using normalized coordinates so
1203 # as to make the interpolation more accurate.
1204 b = self.norm(self._boundaries, clip=False).filled()
1205 xn = self.norm(x, clip=False).filled()
1207 bunique = b[self._inside]
1208 yunique = self._y
1210 z = np.interp(xn, bunique, yunique)
1211 return z
1213 # trivial helpers
1215 def _uniform_y(self, N):
1216 """
1217 Return colorbar data coordinates for *N* uniformly
1218 spaced boundaries, plus extension lengths if required.
1219 """
1220 automin = automax = 1. / (N - 1.)
1221 extendlength = self._get_extension_lengths(self.extendfrac,
1222 automin, automax,
1223 default=0.05)
1224 y = np.linspace(0, 1, N)
1225 return y, extendlength
1227 def _proportional_y(self):
1228 """
1229 Return colorbar data coordinates for the boundaries of
1230 a proportional colorbar, plus extension lengths if required:
1231 """
1232 if (isinstance(self.norm, colors.BoundaryNorm) or
1233 self.boundaries is not None):
1234 y = (self._boundaries - self._boundaries[self._inside][0])
1235 y = y / (self._boundaries[self._inside][-1] -
1236 self._boundaries[self._inside][0])
1237 # need yscaled the same as the axes scale to get
1238 # the extend lengths.
1239 if self.spacing == 'uniform':
1240 yscaled = self._forward_boundaries(self._boundaries)
1241 else:
1242 yscaled = y
1243 else:
1244 y = self.norm(self._boundaries.copy())
1245 y = np.ma.filled(y, np.nan)
1246 # the norm and the scale should be the same...
1247 yscaled = y
1248 y = y[self._inside]
1249 yscaled = yscaled[self._inside]
1250 # normalize from 0..1:
1251 norm = colors.Normalize(y[0], y[-1])
1252 y = np.ma.filled(norm(y), np.nan)
1253 norm = colors.Normalize(yscaled[0], yscaled[-1])
1254 yscaled = np.ma.filled(norm(yscaled), np.nan)
1255 # make the lower and upper extend lengths proportional to the lengths
1256 # of the first and last boundary spacing (if extendfrac='auto'):
1257 automin = yscaled[1] - yscaled[0]
1258 automax = yscaled[-1] - yscaled[-2]
1259 extendlength = [0, 0]
1260 if self._extend_lower() or self._extend_upper():
1261 extendlength = self._get_extension_lengths(
1262 self.extendfrac, automin, automax, default=0.05)
1263 return y, extendlength
1265 def _get_extension_lengths(self, frac, automin, automax, default=0.05):
1266 """
1267 Return the lengths of colorbar extensions.
1269 This is a helper method for _uniform_y and _proportional_y.
1270 """
1271 # Set the default value.
1272 extendlength = np.array([default, default])
1273 if isinstance(frac, str):
1274 _api.check_in_list(['auto'], extendfrac=frac.lower())
1275 # Use the provided values when 'auto' is required.
1276 extendlength[:] = [automin, automax]
1277 elif frac is not None:
1278 try:
1279 # Try to set min and max extension fractions directly.
1280 extendlength[:] = frac
1281 # If frac is a sequence containing None then NaN may
1282 # be encountered. This is an error.
1283 if np.isnan(extendlength).any():
1284 raise ValueError()
1285 except (TypeError, ValueError) as err:
1286 # Raise an error on encountering an invalid value for frac.
1287 raise ValueError('invalid value for extendfrac') from err
1288 return extendlength
1290 def _extend_lower(self):
1291 """Return whether the lower limit is open ended."""
1292 minmax = "max" if self._long_axis().get_inverted() else "min"
1293 return self.extend in ('both', minmax)
1295 def _extend_upper(self):
1296 """Return whether the upper limit is open ended."""
1297 minmax = "min" if self._long_axis().get_inverted() else "max"
1298 return self.extend in ('both', minmax)
1300 def _long_axis(self):
1301 """Return the long axis"""
1302 if self.orientation == 'vertical':
1303 return self.ax.yaxis
1304 return self.ax.xaxis
1306 def _short_axis(self):
1307 """Return the short axis"""
1308 if self.orientation == 'vertical':
1309 return self.ax.xaxis
1310 return self.ax.yaxis
1312 def _get_view(self):
1313 # docstring inherited
1314 # An interactive view for a colorbar is the norm's vmin/vmax
1315 return self.norm.vmin, self.norm.vmax
1317 def _set_view(self, view):
1318 # docstring inherited
1319 # An interactive view for a colorbar is the norm's vmin/vmax
1320 self.norm.vmin, self.norm.vmax = view
1322 def _set_view_from_bbox(self, bbox, direction='in',
1323 mode=None, twinx=False, twiny=False):
1324 # docstring inherited
1325 # For colorbars, we use the zoom bbox to scale the norm's vmin/vmax
1326 new_xbound, new_ybound = self.ax._prepare_view_from_bbox(
1327 bbox, direction=direction, mode=mode, twinx=twinx, twiny=twiny)
1328 if self.orientation == 'horizontal':
1329 self.norm.vmin, self.norm.vmax = new_xbound
1330 elif self.orientation == 'vertical':
1331 self.norm.vmin, self.norm.vmax = new_ybound
1333 def drag_pan(self, button, key, x, y):
1334 # docstring inherited
1335 points = self.ax._get_pan_points(button, key, x, y)
1336 if points is not None:
1337 if self.orientation == 'horizontal':
1338 self.norm.vmin, self.norm.vmax = points[:, 0]
1339 elif self.orientation == 'vertical':
1340 self.norm.vmin, self.norm.vmax = points[:, 1]
1343ColorbarBase = Colorbar # Backcompat API
1346def _normalize_location_orientation(location, orientation):
1347 if location is None:
1348 location = _api.check_getitem(
1349 {None: "right", "vertical": "right", "horizontal": "bottom"},
1350 orientation=orientation)
1351 loc_settings = _api.check_getitem({
1352 "left": {"location": "left", "orientation": "vertical",
1353 "anchor": (1.0, 0.5), "panchor": (0.0, 0.5), "pad": 0.10},
1354 "right": {"location": "right", "orientation": "vertical",
1355 "anchor": (0.0, 0.5), "panchor": (1.0, 0.5), "pad": 0.05},
1356 "top": {"location": "top", "orientation": "horizontal",
1357 "anchor": (0.5, 0.0), "panchor": (0.5, 1.0), "pad": 0.05},
1358 "bottom": {"location": "bottom", "orientation": "horizontal",
1359 "anchor": (0.5, 1.0), "panchor": (0.5, 0.0), "pad": 0.15},
1360 }, location=location)
1361 if orientation is not None and orientation != loc_settings["orientation"]:
1362 # Allow the user to pass both if they are consistent.
1363 raise TypeError("location and orientation are mutually exclusive")
1364 return loc_settings
1367@_docstring.interpd
1368def make_axes(parents, location=None, orientation=None, fraction=0.15,
1369 shrink=1.0, aspect=20, **kwargs):
1370 """
1371 Create an `~.axes.Axes` suitable for a colorbar.
1373 The axes is placed in the figure of the *parents* axes, by resizing and
1374 repositioning *parents*.
1376 Parameters
1377 ----------
1378 parents : `~.axes.Axes` or list or `numpy.ndarray` of `~.axes.Axes`
1379 The Axes to use as parents for placing the colorbar.
1380 %(_make_axes_kw_doc)s
1382 Returns
1383 -------
1384 cax : `~.axes.Axes`
1385 The child axes.
1386 kwargs : dict
1387 The reduced keyword dictionary to be passed when creating the colorbar
1388 instance.
1389 """
1390 loc_settings = _normalize_location_orientation(location, orientation)
1391 # put appropriate values into the kwargs dict for passing back to
1392 # the Colorbar class
1393 kwargs['orientation'] = loc_settings['orientation']
1394 location = kwargs['ticklocation'] = loc_settings['location']
1396 anchor = kwargs.pop('anchor', loc_settings['anchor'])
1397 panchor = kwargs.pop('panchor', loc_settings['panchor'])
1398 aspect0 = aspect
1399 # turn parents into a list if it is not already. Note we cannot
1400 # use .flatten or .ravel as these copy the references rather than
1401 # reuse them, leading to a memory leak
1402 if isinstance(parents, np.ndarray):
1403 parents = list(parents.flat)
1404 elif not isinstance(parents, list):
1405 parents = [parents]
1406 fig = parents[0].get_figure()
1408 pad0 = 0.05 if fig.get_constrained_layout() else loc_settings['pad']
1409 pad = kwargs.pop('pad', pad0)
1411 if not all(fig is ax.get_figure() for ax in parents):
1412 raise ValueError('Unable to create a colorbar axes as not all '
1413 'parents share the same figure.')
1415 # take a bounding box around all of the given axes
1416 parents_bbox = mtransforms.Bbox.union(
1417 [ax.get_position(original=True).frozen() for ax in parents])
1419 pb = parents_bbox
1420 if location in ('left', 'right'):
1421 if location == 'left':
1422 pbcb, _, pb1 = pb.splitx(fraction, fraction + pad)
1423 else:
1424 pb1, _, pbcb = pb.splitx(1 - fraction - pad, 1 - fraction)
1425 pbcb = pbcb.shrunk(1.0, shrink).anchored(anchor, pbcb)
1426 else:
1427 if location == 'bottom':
1428 pbcb, _, pb1 = pb.splity(fraction, fraction + pad)
1429 else:
1430 pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction)
1431 pbcb = pbcb.shrunk(shrink, 1.0).anchored(anchor, pbcb)
1433 # define the aspect ratio in terms of y's per x rather than x's per y
1434 aspect = 1.0 / aspect
1436 # define a transform which takes us from old axes coordinates to
1437 # new axes coordinates
1438 shrinking_trans = mtransforms.BboxTransform(parents_bbox, pb1)
1440 # transform each of the axes in parents using the new transform
1441 for ax in parents:
1442 new_posn = shrinking_trans.transform(ax.get_position(original=True))
1443 new_posn = mtransforms.Bbox(new_posn)
1444 ax._set_position(new_posn)
1445 if panchor is not False:
1446 ax.set_anchor(panchor)
1448 cax = fig.add_axes(pbcb, label="<colorbar>")
1449 for a in parents:
1450 # tell the parent it has a colorbar
1451 a._colorbars += [cax]
1452 cax._colorbar_info = dict(
1453 parents=parents,
1454 location=location,
1455 shrink=shrink,
1456 anchor=anchor,
1457 panchor=panchor,
1458 fraction=fraction,
1459 aspect=aspect0,
1460 pad=pad)
1461 # and we need to set the aspect ratio by hand...
1462 cax.set_anchor(anchor)
1463 cax.set_box_aspect(aspect)
1464 cax.set_aspect('auto')
1466 return cax, kwargs
1469@_docstring.interpd
1470def make_axes_gridspec(parent, *, location=None, orientation=None,
1471 fraction=0.15, shrink=1.0, aspect=20, **kwargs):
1472 """
1473 Create a `.SubplotBase` suitable for a colorbar.
1475 The axes is placed in the figure of the *parent* axes, by resizing and
1476 repositioning *parent*.
1478 This function is similar to `.make_axes`. Primary differences are
1480 - `.make_axes_gridspec` should only be used with a `.SubplotBase` parent.
1482 - `.make_axes` creates an `~.axes.Axes`; `.make_axes_gridspec` creates a
1483 `.SubplotBase`.
1485 - `.make_axes` updates the position of the parent. `.make_axes_gridspec`
1486 replaces the ``grid_spec`` attribute of the parent with a new one.
1488 While this function is meant to be compatible with `.make_axes`,
1489 there could be some minor differences.
1491 Parameters
1492 ----------
1493 parent : `~.axes.Axes`
1494 The Axes to use as parent for placing the colorbar.
1495 %(_make_axes_kw_doc)s
1497 Returns
1498 -------
1499 cax : `~.axes.SubplotBase`
1500 The child axes.
1501 kwargs : dict
1502 The reduced keyword dictionary to be passed when creating the colorbar
1503 instance.
1504 """
1506 loc_settings = _normalize_location_orientation(location, orientation)
1507 kwargs['orientation'] = loc_settings['orientation']
1508 location = kwargs['ticklocation'] = loc_settings['location']
1510 aspect0 = aspect
1511 anchor = kwargs.pop('anchor', loc_settings['anchor'])
1512 panchor = kwargs.pop('panchor', loc_settings['panchor'])
1513 pad = kwargs.pop('pad', loc_settings["pad"])
1514 wh_space = 2 * pad / (1 - pad)
1516 if location in ('left', 'right'):
1517 # for shrinking
1518 height_ratios = [
1519 (1-anchor[1])*(1-shrink), shrink, anchor[1]*(1-shrink)]
1521 if location == 'left':
1522 gs = parent.get_subplotspec().subgridspec(
1523 1, 2, wspace=wh_space,
1524 width_ratios=[fraction, 1-fraction-pad])
1525 ss_main = gs[1]
1526 ss_cb = gs[0].subgridspec(
1527 3, 1, hspace=0, height_ratios=height_ratios)[1]
1528 else:
1529 gs = parent.get_subplotspec().subgridspec(
1530 1, 2, wspace=wh_space,
1531 width_ratios=[1-fraction-pad, fraction])
1532 ss_main = gs[0]
1533 ss_cb = gs[1].subgridspec(
1534 3, 1, hspace=0, height_ratios=height_ratios)[1]
1535 else:
1536 # for shrinking
1537 width_ratios = [
1538 anchor[0]*(1-shrink), shrink, (1-anchor[0])*(1-shrink)]
1540 if location == 'bottom':
1541 gs = parent.get_subplotspec().subgridspec(
1542 2, 1, hspace=wh_space,
1543 height_ratios=[1-fraction-pad, fraction])
1544 ss_main = gs[0]
1545 ss_cb = gs[1].subgridspec(
1546 1, 3, wspace=0, width_ratios=width_ratios)[1]
1547 aspect = 1 / aspect
1548 else:
1549 gs = parent.get_subplotspec().subgridspec(
1550 2, 1, hspace=wh_space,
1551 height_ratios=[fraction, 1-fraction-pad])
1552 ss_main = gs[1]
1553 ss_cb = gs[0].subgridspec(
1554 1, 3, wspace=0, width_ratios=width_ratios)[1]
1555 aspect = 1 / aspect
1557 parent.set_subplotspec(ss_main)
1558 if panchor is not False:
1559 parent.set_anchor(panchor)
1561 fig = parent.get_figure()
1562 cax = fig.add_subplot(ss_cb, label="<colorbar>")
1563 cax.set_anchor(anchor)
1564 cax.set_box_aspect(aspect)
1565 cax.set_aspect('auto')
1566 cax._colorbar_info = dict(
1567 location=location,
1568 parents=[parent],
1569 shrink=shrink,
1570 anchor=anchor,
1571 panchor=panchor,
1572 fraction=fraction,
1573 aspect=aspect0,
1574 pad=pad)
1576 return cax, kwargs