Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/colorbar.py : 12%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1'''
2Colorbar toolkit with two classes and a function:
4 :class:`ColorbarBase`
5 the base class with full colorbar drawing functionality.
6 It can be used as-is to make a colorbar for a given colormap;
7 a mappable object (e.g., image) is not needed.
9 :class:`Colorbar`
10 the derived class for use with images or contour plots.
12 :func:`make_axes`
13 a function for resizing an axes and adding a second axes
14 suitable for a colorbar
16The :meth:`~matplotlib.figure.Figure.colorbar` method uses :func:`make_axes`
17and :class:`Colorbar`; the :func:`~matplotlib.pyplot.colorbar` function
18is a thin wrapper over :meth:`~matplotlib.figure.Figure.colorbar`.
20'''
21import copy
22import logging
24import numpy as np
26import matplotlib as mpl
27import matplotlib.artist as martist
28import matplotlib.cbook as cbook
29import matplotlib.collections as collections
30import matplotlib.colors as colors
31import matplotlib.contour as contour
32import matplotlib.cm as cm
33import matplotlib.gridspec as gridspec
34import matplotlib.patches as mpatches
35import matplotlib.path as mpath
36import matplotlib.ticker as ticker
37import matplotlib.transforms as mtransforms
38import matplotlib._layoutbox as layoutbox
39import matplotlib._constrained_layout as constrained_layout
40from matplotlib import docstring
42_log = logging.getLogger(__name__)
44make_axes_kw_doc = '''
46 ============= ====================================================
47 Property Description
48 ============= ====================================================
49 *orientation* vertical or horizontal
50 *fraction* 0.15; fraction of original axes to use for colorbar
51 *pad* 0.05 if vertical, 0.15 if horizontal; fraction
52 of original axes between colorbar and new image axes
53 *shrink* 1.0; fraction by which to multiply the size of the colorbar
54 *aspect* 20; ratio of long to short dimensions
55 *anchor* (0.0, 0.5) if vertical; (0.5, 1.0) if horizontal;
56 the anchor point of the colorbar axes
57 *panchor* (1.0, 0.5) if vertical; (0.5, 0.0) if horizontal;
58 the anchor point of the colorbar parent axes. If
59 False, the parent axes' anchor will be unchanged
60 ============= ====================================================
62'''
64colormap_kw_doc = '''
66 ============ ====================================================
67 Property Description
68 ============ ====================================================
69 *extend* {'neither', 'both', 'min', 'max'}
70 If not 'neither', make pointed end(s) for out-of-
71 range values. These are set for a given colormap
72 using the colormap set_under and set_over methods.
73 *extendfrac* {*None*, 'auto', length, lengths}
74 If set to *None*, both the minimum and maximum
75 triangular colorbar extensions with have a length of
76 5% of the interior colorbar length (this is the
77 default setting). If set to 'auto', makes the
78 triangular colorbar extensions the same lengths as
79 the interior boxes (when *spacing* is set to
80 'uniform') or the same lengths as the respective
81 adjacent interior boxes (when *spacing* is set to
82 'proportional'). If a scalar, indicates the length
83 of both the minimum and maximum triangular colorbar
84 extensions as a fraction of the interior colorbar
85 length. A two-element sequence of fractions may also
86 be given, indicating the lengths of the minimum and
87 maximum colorbar extensions respectively as a
88 fraction of the interior colorbar length.
89 *extendrect* bool
90 If *False* the minimum and maximum colorbar extensions
91 will be triangular (the default). If *True* the
92 extensions will be rectangular.
93 *spacing* {'uniform', 'proportional'}
94 Uniform spacing gives each discrete color the same
95 space; proportional makes the space proportional to
96 the data interval.
97 *ticks* *None* or list of ticks or Locator
98 If None, ticks are determined automatically from the
99 input.
100 *format* None or str or Formatter
101 If None, the
102 :class:`~matplotlib.ticker.ScalarFormatter` is used.
103 If a format string is given, e.g., '%.3f', that is
104 used. An alternative
105 :class:`~matplotlib.ticker.Formatter` object may be
106 given instead.
107 *drawedges* bool
108 Whether to draw lines at color boundaries.
109 *label* str
110 The label on the colorbar's long axis.
111 ============ ====================================================
113 The following will probably be useful only in the context of
114 indexed colors (that is, when the mappable has norm=NoNorm()),
115 or other unusual circumstances.
117 ============ ===================================================
118 Property Description
119 ============ ===================================================
120 *boundaries* None or a sequence
121 *values* None or a sequence which must be of length 1 less
122 than the sequence of *boundaries*. For each region
123 delimited by adjacent entries in *boundaries*, the
124 color mapped to the corresponding value in values
125 will be used.
126 ============ ===================================================
128'''
130colorbar_doc = '''
132Add a colorbar to a plot.
134Function signatures for the :mod:`~matplotlib.pyplot` interface; all
135but the first are also method signatures for the
136:meth:`~matplotlib.figure.Figure.colorbar` method::
138 colorbar(**kwargs)
139 colorbar(mappable, **kwargs)
140 colorbar(mappable, cax=cax, **kwargs)
141 colorbar(mappable, ax=ax, **kwargs)
143Parameters
144----------
145mappable
146 The `matplotlib.cm.ScalarMappable` (i.e., `~matplotlib.image.Image`,
147 `~matplotlib.contour.ContourSet`, etc.) described by this colorbar.
148 This argument is mandatory for the `.Figure.colorbar` method but optional
149 for the `.pyplot.colorbar` function, which sets the default to the current
150 image.
152 Note that one can create a `ScalarMappable` "on-the-fly" to generate
153 colorbars not attached to a previously drawn artist, e.g. ::
155 fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax)
157cax : :class:`~matplotlib.axes.Axes` object, optional
158 Axes into which the colorbar will be drawn.
160ax : :class:`~matplotlib.axes.Axes`, list of Axes, optional
161 Parent axes from which space for a new colorbar axes will be stolen.
162 If a list of axes is given they will all be resized to make room for the
163 colorbar axes.
165use_gridspec : bool, optional
166 If *cax* is ``None``, a new *cax* is created as an instance of Axes. If
167 *ax* is an instance of Subplot and *use_gridspec* is ``True``, *cax* is
168 created as an instance of Subplot using the :mod:`~.gridspec` module.
170Returns
171-------
172colorbar : `~matplotlib.colorbar.Colorbar`
173 See also its base class, `~matplotlib.colorbar.ColorbarBase`.
175Notes
176-----
177Additional keyword arguments are of two kinds:
179 axes properties:
180%s
181 colorbar properties:
182%s
184If *mappable* is a :class:`~matplotlib.contours.ContourSet`, its *extend*
185kwarg is included automatically.
187The *shrink* kwarg provides a simple way to scale the colorbar with respect
188to the axes. Note that if *cax* is specified, it determines the size of the
189colorbar and *shrink* and *aspect* kwargs are ignored.
191For more precise control, you can manually specify the positions of
192the axes objects in which the mappable and the colorbar are drawn. In
193this case, do not use any of the axes properties kwargs.
195It is known that some vector graphics viewers (svg and pdf) renders white gaps
196between segments of the colorbar. This is due to bugs in the viewers, not
197Matplotlib. As a workaround, the colorbar can be rendered with overlapping
198segments::
200 cbar = colorbar()
201 cbar.solids.set_edgecolor("face")
202 draw()
204However this has negative consequences in other circumstances, e.g. with
205semi-transparent images (alpha < 1) and colorbar extensions; therefore, this
206workaround is not used by default (see issue #1188).
208''' % (make_axes_kw_doc, colormap_kw_doc)
210docstring.interpd.update(colorbar_doc=colorbar_doc)
213def _set_ticks_on_axis_warn(*args, **kw):
214 # a top level function which gets put in at the axes'
215 # set_xticks set_yticks by _patch_ax
216 cbook._warn_external("Use the colorbar set_ticks() method instead.")
219class _ColorbarAutoLocator(ticker.MaxNLocator):
220 """
221 AutoLocator for Colorbar
223 This locator is just a `.MaxNLocator` except the min and max are
224 clipped by the norm's min and max (i.e. vmin/vmax from the
225 image/pcolor/contour object). This is necessary so ticks don't
226 extrude into the "extend regions".
227 """
229 def __init__(self, colorbar):
230 """
231 This ticker needs to know the *colorbar* so that it can access
232 its *vmin* and *vmax*. Otherwise it is the same as
233 `~.ticker.AutoLocator`.
234 """
236 self._colorbar = colorbar
237 nbins = 'auto'
238 steps = [1, 2, 2.5, 5, 10]
239 super().__init__(nbins=nbins, steps=steps)
241 def tick_values(self, vmin, vmax):
242 # flip if needed:
243 if vmin > vmax:
244 vmin, vmax = vmax, vmin
245 vmin = max(vmin, self._colorbar.norm.vmin)
246 vmax = min(vmax, self._colorbar.norm.vmax)
247 ticks = super().tick_values(vmin, vmax)
248 rtol = (vmax - vmin) * 1e-10
249 return ticks[(ticks >= vmin - rtol) & (ticks <= vmax + rtol)]
252class _ColorbarAutoMinorLocator(ticker.AutoMinorLocator):
253 """
254 AutoMinorLocator for Colorbar
256 This locator is just a `.AutoMinorLocator` except the min and max are
257 clipped by the norm's min and max (i.e. vmin/vmax from the
258 image/pcolor/contour object). This is necessary so that the minorticks
259 don't extrude into the "extend regions".
260 """
262 def __init__(self, colorbar, n=None):
263 """
264 This ticker needs to know the *colorbar* so that it can access
265 its *vmin* and *vmax*.
266 """
267 self._colorbar = colorbar
268 self.ndivs = n
269 super().__init__(n=None)
271 def __call__(self):
272 vmin = self._colorbar.norm.vmin
273 vmax = self._colorbar.norm.vmax
274 ticks = super().__call__()
275 rtol = (vmax - vmin) * 1e-10
276 return ticks[(ticks >= vmin - rtol) & (ticks <= vmax + rtol)]
279class _ColorbarLogLocator(ticker.LogLocator):
280 """
281 LogLocator for Colorbarbar
283 This locator is just a `.LogLocator` except the min and max are
284 clipped by the norm's min and max (i.e. vmin/vmax from the
285 image/pcolor/contour object). This is necessary so ticks don't
286 extrude into the "extend regions".
288 """
289 def __init__(self, colorbar, *args, **kwargs):
290 """
291 _ColorbarLogLocator(colorbar, *args, **kwargs)
293 This ticker needs to know the *colorbar* so that it can access
294 its *vmin* and *vmax*. Otherwise it is the same as
295 `~.ticker.LogLocator`. The ``*args`` and ``**kwargs`` are the
296 same as `~.ticker.LogLocator`.
297 """
298 self._colorbar = colorbar
299 super().__init__(*args, **kwargs)
301 def tick_values(self, vmin, vmax):
302 if vmin > vmax:
303 vmin, vmax = vmax, vmin
304 vmin = max(vmin, self._colorbar.norm.vmin)
305 vmax = min(vmax, self._colorbar.norm.vmax)
306 ticks = super().tick_values(vmin, vmax)
307 rtol = (np.log10(vmax) - np.log10(vmin)) * 1e-10
308 ticks = ticks[(np.log10(ticks) >= np.log10(vmin) - rtol) &
309 (np.log10(ticks) <= np.log10(vmax) + rtol)]
310 return ticks
313class _ColorbarMappableDummy:
314 """
315 Private class to hold deprecated ColorbarBase methods that used to be
316 inhereted from ScalarMappable.
317 """
318 @cbook.deprecated("3.1", alternative="ScalarMappable.set_norm")
319 def set_norm(self, norm):
320 """
321 `.colorbar.Colorbar.set_norm` does nothing; set the norm on
322 the mappable associated with this colorbar.
323 """
324 pass
326 @cbook.deprecated("3.1", alternative="ScalarMappable.set_cmap")
327 def set_cmap(self, cmap):
328 """
329 `.colorbar.Colorbar.set_cmap` does nothing; set the norm on
330 the mappable associated with this colorbar.
331 """
332 pass
334 @cbook.deprecated("3.1", alternative="ScalarMappable.set_clim")
335 def set_clim(self, vmin=None, vmax=None):
336 """
337 `.colorbar.Colorbar.set_clim` does nothing; set the limits on
338 the mappable associated with this colorbar.
339 """
340 pass
342 @cbook.deprecated("3.1", alternative="ScalarMappable.get_cmap")
343 def get_cmap(self):
344 """Return the colormap."""
345 return self.cmap
347 @cbook.deprecated("3.1", alternative="ScalarMappable.get_clim")
348 def get_clim(self):
349 """Return the min, max of the color limits for image scaling."""
350 return self.norm.vmin, self.norm.vmax
353class ColorbarBase(_ColorbarMappableDummy):
354 r"""
355 Draw a colorbar in an existing axes.
357 There are only some rare cases in which you would work directly with a
358 `.ColorbarBase` as an end-user. Typically, colorbars are used
359 with `.ScalarMappable`\s such as an `.AxesImage` generated via
360 `~.axes.Axes.imshow`. For these cases you will use `.Colorbar` and
361 likely create it via `.pyplot.colorbar` or `.Figure.colorbar`.
363 The main application of using a `.ColorbarBase` explicitly is drawing
364 colorbars that are not associated with other elements in the figure, e.g.
365 when showing a colormap by itself.
367 If the *cmap* kwarg is given but *boundaries* and *values* are left as
368 None, then the colormap will be displayed on a 0-1 scale. To show the
369 under- and over-value colors, specify the *norm* as::
371 norm=colors.Normalize(clip=False)
373 To show the colors versus index instead of on the 0-1 scale,
374 use::
376 norm=colors.NoNorm()
378 Useful public methods are :meth:`set_label` and :meth:`add_lines`.
380 Attributes
381 ----------
382 ax : `~matplotlib.axes.Axes`
383 The `~.axes.Axes` instance in which the colorbar is drawn.
384 lines : list
385 A list of `.LineCollection` if lines were drawn, otherwise
386 an empty list.
387 dividers : `.LineCollection`
388 A LineCollection if *drawedges* is ``True``, otherwise ``None``.
390 Parameters
391 ----------
392 ax : `~matplotlib.axes.Axes`
393 The `~.axes.Axes` instance in which the colorbar is drawn.
394 cmap : `~matplotlib.colors.Colormap` or None
395 The colormap to use. If *None*, use :rc:`image.cmap`.
396 norm : `~matplotlib.colors.Normalize`
398 alpha : float
400 values
402 boundaries
404 orientation : {'vertical', 'horizontal'}
406 ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
408 extend : {'neither', 'both', 'min', 'max'}
410 spacing : {'uniform', 'proportional'}
412 ticks : `~matplotlib.ticker.Locator` or array-like of float
414 format : str or `~matplotlib.ticker.Formatter`
416 drawedges : bool
418 filled : bool
420 extendfrac
422 extendrec
424 label : str
425 """
426 _slice_dict = {'neither': slice(0, None),
427 'both': slice(1, -1),
428 'min': slice(1, None),
429 'max': slice(0, -1)}
431 n_rasterize = 50 # rasterize solids if number of colors >= n_rasterize
433 def __init__(self, ax, cmap=None,
434 norm=None,
435 alpha=None,
436 values=None,
437 boundaries=None,
438 orientation='vertical',
439 ticklocation='auto',
440 extend='neither',
441 spacing='uniform', # uniform or proportional
442 ticks=None,
443 format=None,
444 drawedges=False,
445 filled=True,
446 extendfrac=None,
447 extendrect=False,
448 label='',
449 ):
450 cbook._check_isinstance([colors.Colormap, None], cmap=cmap)
451 cbook._check_in_list(
452 ['vertical', 'horizontal'], orientation=orientation)
453 cbook._check_in_list(
454 ['auto', 'left', 'right', 'top', 'bottom'],
455 ticklocation=ticklocation)
456 cbook._check_in_list(
457 self._slice_dict, extend=extend)
458 cbook._check_in_list(
459 ['uniform', 'proportional'], spacing=spacing)
461 self.ax = ax
462 self._patch_ax()
463 if cmap is None:
464 cmap = cm.get_cmap()
465 if norm is None:
466 norm = colors.Normalize()
467 self.alpha = alpha
468 self.cmap = cmap
469 self.norm = norm
470 self.values = values
471 self.boundaries = boundaries
472 self.extend = extend
473 self._inside = self._slice_dict[extend]
474 self.spacing = spacing
475 self.orientation = orientation
476 self.drawedges = drawedges
477 self.filled = filled
478 self.extendfrac = extendfrac
479 self.extendrect = extendrect
480 self.solids = None
481 self.lines = list()
482 self.outline = None
483 self.patch = None
484 self.dividers = None
485 self.locator = None
486 self.formatter = None
487 self._manual_tick_data_values = None
488 self.__scale = None # linear, log10 for now. Hopefully more?
490 if ticklocation == 'auto':
491 ticklocation = 'bottom' if orientation == 'horizontal' else 'right'
492 self.ticklocation = ticklocation
494 self.set_label(label)
495 self._reset_locator_formatter_scale()
497 if np.iterable(ticks):
498 self.locator = ticker.FixedLocator(ticks, nbins=len(ticks))
499 else:
500 self.locator = ticks # Handle default in _ticker()
502 if isinstance(format, str):
503 self.formatter = ticker.FormatStrFormatter(format)
504 else:
505 self.formatter = format # Assume it is a Formatter or None
506 self.draw_all()
508 def _extend_lower(self):
509 """Return whether the lower limit is open ended."""
510 return self.extend in ('both', 'min')
512 def _extend_upper(self):
513 """Return whether the uper limit is open ended."""
514 return self.extend in ('both', 'max')
516 def _patch_ax(self):
517 # bind some methods to the axes to warn users
518 # against using those methods.
519 self.ax.set_xticks = _set_ticks_on_axis_warn
520 self.ax.set_yticks = _set_ticks_on_axis_warn
522 def draw_all(self):
523 '''
524 Calculate any free parameters based on the current cmap and norm,
525 and do all the drawing.
526 '''
527 # sets self._boundaries and self._values in real data units.
528 # takes into account extend values:
529 self._process_values()
530 # sets self.vmin and vmax in data units, but just for
531 # the part of the colorbar that is not part of the extend
532 # patch:
533 self._find_range()
534 # returns the X and Y mesh, *but* this was/is in normalized
535 # units:
536 X, Y = self._mesh()
537 C = self._values[:, np.newaxis]
538 # decide minor/major axis
539 self.config_axis()
540 self._config_axes(X, Y)
541 if self.filled:
542 self._add_solids(X, Y, C)
544 def config_axis(self):
545 ax = self.ax
547 if self.orientation == 'vertical':
548 long_axis, short_axis = ax.yaxis, ax.xaxis
549 else:
550 long_axis, short_axis = ax.xaxis, ax.yaxis
552 long_axis.set_label_position(self.ticklocation)
553 long_axis.set_ticks_position(self.ticklocation)
554 short_axis.set_ticks([])
555 short_axis.set_ticks([], minor=True)
556 self._set_label()
558 def _get_ticker_locator_formatter(self):
559 """
560 This code looks at the norm being used by the colorbar
561 and decides what locator and formatter to use. If ``locator`` has
562 already been set by hand, it just returns
563 ``self.locator, self.formatter``.
564 """
565 locator = self.locator
566 formatter = self.formatter
567 if locator is None:
568 if self.boundaries is None:
569 if isinstance(self.norm, colors.NoNorm):
570 nv = len(self._values)
571 base = 1 + int(nv / 10)
572 locator = ticker.IndexLocator(base=base, offset=0)
573 elif isinstance(self.norm, colors.BoundaryNorm):
574 b = self.norm.boundaries
575 locator = ticker.FixedLocator(b, nbins=10)
576 elif isinstance(self.norm, colors.LogNorm):
577 locator = _ColorbarLogLocator(self)
578 elif isinstance(self.norm, colors.SymLogNorm):
579 # The subs setting here should be replaced
580 # by logic in the locator.
581 locator = ticker.SymmetricalLogLocator(
582 subs=np.arange(1, 10),
583 linthresh=self.norm.linthresh,
584 base=10)
585 else:
586 if mpl.rcParams['_internal.classic_mode']:
587 locator = ticker.MaxNLocator()
588 else:
589 locator = _ColorbarAutoLocator(self)
590 else:
591 b = self._boundaries[self._inside]
592 locator = ticker.FixedLocator(b, nbins=10)
594 if formatter is None:
595 if isinstance(self.norm, colors.LogNorm):
596 formatter = ticker.LogFormatterSciNotation()
597 elif isinstance(self.norm, colors.SymLogNorm):
598 formatter = ticker.LogFormatterSciNotation(
599 linthresh=self.norm.linthresh)
600 else:
601 formatter = ticker.ScalarFormatter()
602 else:
603 formatter = self.formatter
605 self.locator = locator
606 self.formatter = formatter
607 _log.debug('locator: %r', locator)
608 return locator, formatter
610 def _use_auto_colorbar_locator(self):
611 """
612 Return if we should use an adjustable tick locator or a fixed
613 one. (check is used twice so factored out here...)
614 """
615 contouring = self.boundaries is not None and self.spacing == 'uniform'
616 return (type(self.norm) in [colors.Normalize, colors.LogNorm] and
617 not contouring)
619 def _reset_locator_formatter_scale(self):
620 """
621 Reset the locator et al to defaults. Any user-hardcoded changes
622 need to be re-entered if this gets called (either at init, or when
623 the mappable normal gets changed: Colorbar.update_normal)
624 """
625 self.locator = None
626 self.formatter = None
627 if (isinstance(self.norm, colors.LogNorm)):
628 # *both* axes are made log so that determining the
629 # mid point is easier.
630 self.ax.set_xscale('log')
631 self.ax.set_yscale('log')
632 self.minorticks_on()
633 self.__scale = 'log'
634 else:
635 self.ax.set_xscale('linear')
636 self.ax.set_yscale('linear')
637 if type(self.norm) is colors.Normalize:
638 self.__scale = 'linear'
639 else:
640 self.__scale = 'manual'
642 def update_ticks(self):
643 """
644 Force the update of the ticks and ticklabels. This must be
645 called whenever the tick locator and/or tick formatter changes.
646 """
647 ax = self.ax
648 # Get the locator and formatter; defaults to self.locator if not None.
649 locator, formatter = self._get_ticker_locator_formatter()
650 long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis
651 if self._use_auto_colorbar_locator():
652 _log.debug('Using auto colorbar locator on colorbar')
653 _log.debug('locator: %r', locator)
654 long_axis.set_major_locator(locator)
655 long_axis.set_major_formatter(formatter)
656 else:
657 _log.debug('Using fixed locator on colorbar')
658 ticks, ticklabels, offset_string = self._ticker(locator, formatter)
659 long_axis.set_ticks(ticks)
660 long_axis.set_ticklabels(ticklabels)
661 long_axis.get_major_formatter().set_offset_string(offset_string)
663 def set_ticks(self, ticks, update_ticks=True):
664 """
665 Set tick locations.
667 Parameters
668 ----------
669 ticks : {None, sequence, :class:`~matplotlib.ticker.Locator` instance}
670 If None, a default Locator will be used.
672 update_ticks : {True, False}, optional
673 If True, tick locations are updated immediately. If False,
674 use :meth:`update_ticks` to manually update the ticks.
676 """
677 if np.iterable(ticks):
678 self.locator = ticker.FixedLocator(ticks, nbins=len(ticks))
679 else:
680 self.locator = ticks
682 if update_ticks:
683 self.update_ticks()
684 self.stale = True
686 def get_ticks(self, minor=False):
687 """Return the x ticks as a list of locations."""
688 if self._manual_tick_data_values is None:
689 ax = self.ax
690 long_axis = (
691 ax.yaxis if self.orientation == 'vertical' else ax.xaxis)
692 return long_axis.get_majorticklocs()
693 else:
694 # We made the axes manually, the old way, and the ylim is 0-1,
695 # so the majorticklocs are in those units, not data units.
696 return self._manual_tick_data_values
698 def set_ticklabels(self, ticklabels, update_ticks=True):
699 """
700 Set tick labels.
702 Tick labels are updated immediately unless *update_ticks* is *False*,
703 in which case one should call `.update_ticks` explicitly.
704 """
705 if isinstance(self.locator, ticker.FixedLocator):
706 self.formatter = ticker.FixedFormatter(ticklabels)
707 if update_ticks:
708 self.update_ticks()
709 else:
710 cbook._warn_external("set_ticks() must have been called.")
711 self.stale = True
713 def minorticks_on(self):
714 """
715 Turns on the minor ticks on the colorbar without extruding
716 into the "extend regions".
717 """
718 ax = self.ax
719 long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis
721 if long_axis.get_scale() == 'log':
722 long_axis.set_minor_locator(_ColorbarLogLocator(self, base=10.,
723 subs='auto'))
724 long_axis.set_minor_formatter(ticker.LogFormatterSciNotation())
725 else:
726 long_axis.set_minor_locator(_ColorbarAutoMinorLocator(self))
728 def minorticks_off(self):
729 """
730 Turns off the minor ticks on the colorbar.
731 """
732 ax = self.ax
733 long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis
735 long_axis.set_minor_locator(ticker.NullLocator())
737 def _config_axes(self, X, Y):
738 '''
739 Make an axes patch and outline.
740 '''
741 ax = self.ax
742 ax.set_frame_on(False)
743 ax.set_navigate(False)
744 xy = self._outline(X, Y)
745 ax.ignore_existing_data_limits = True
746 ax.update_datalim(xy)
747 ax.set_xlim(*ax.dataLim.intervalx)
748 ax.set_ylim(*ax.dataLim.intervaly)
749 if self.outline is not None:
750 self.outline.remove()
751 self.outline = mpatches.Polygon(
752 xy, edgecolor=mpl.rcParams['axes.edgecolor'],
753 facecolor='none',
754 linewidth=mpl.rcParams['axes.linewidth'],
755 closed=True,
756 zorder=2)
757 ax.add_artist(self.outline)
758 self.outline.set_clip_box(None)
759 self.outline.set_clip_path(None)
760 c = mpl.rcParams['axes.facecolor']
761 if self.patch is not None:
762 self.patch.remove()
763 self.patch = mpatches.Polygon(xy, edgecolor=c,
764 facecolor=c,
765 linewidth=0.01,
766 zorder=-1)
767 ax.add_artist(self.patch)
769 self.update_ticks()
771 def _set_label(self):
772 if self.orientation == 'vertical':
773 self.ax.set_ylabel(self._label, **self._labelkw)
774 else:
775 self.ax.set_xlabel(self._label, **self._labelkw)
776 self.stale = True
778 def set_label(self, label, **kw):
779 """Label the long axis of the colorbar."""
780 self._label = label
781 self._labelkw = kw
782 self._set_label()
784 def _outline(self, X, Y):
785 '''
786 Return *x*, *y* arrays of colorbar bounding polygon,
787 taking orientation into account.
788 '''
789 N = X.shape[0]
790 ii = [0, 1, N - 2, N - 1, 2 * N - 1, 2 * N - 2, N + 1, N, 0]
791 x = X.T.reshape(-1)[ii]
792 y = Y.T.reshape(-1)[ii]
793 return (np.column_stack([y, x])
794 if self.orientation == 'horizontal' else
795 np.column_stack([x, y]))
797 def _edges(self, X, Y):
798 '''
799 Return the separator line segments; helper for _add_solids.
800 '''
801 N = X.shape[0]
802 # Using the non-array form of these line segments is much
803 # simpler than making them into arrays.
804 if self.orientation == 'vertical':
805 return [list(zip(X[i], Y[i])) for i in range(1, N - 1)]
806 else:
807 return [list(zip(Y[i], X[i])) for i in range(1, N - 1)]
809 def _add_solids(self, X, Y, C):
810 '''
811 Draw the colors using :meth:`~matplotlib.axes.Axes.pcolormesh`;
812 optionally add separators.
813 '''
814 if self.orientation == 'vertical':
815 args = (X, Y, C)
816 else:
817 args = (np.transpose(Y), np.transpose(X), np.transpose(C))
818 kw = dict(cmap=self.cmap,
819 norm=self.norm,
820 alpha=self.alpha,
821 edgecolors='None')
822 _log.debug('Setting pcolormesh')
823 col = self.ax.pcolormesh(*args, **kw)
824 # self.add_observer(col) # We should observe, not be observed...
826 if self.solids is not None:
827 self.solids.remove()
828 self.solids = col
829 if self.dividers is not None:
830 self.dividers.remove()
831 self.dividers = None
832 if self.drawedges:
833 linewidths = (0.5 * mpl.rcParams['axes.linewidth'],)
834 self.dividers = collections.LineCollection(
835 self._edges(X, Y),
836 colors=(mpl.rcParams['axes.edgecolor'],),
837 linewidths=linewidths)
838 self.ax.add_collection(self.dividers)
839 elif len(self._y) >= self.n_rasterize:
840 self.solids.set_rasterized(True)
842 def add_lines(self, levels, colors, linewidths, erase=True):
843 '''
844 Draw lines on the colorbar.
846 *colors* and *linewidths* must be scalars or
847 sequences the same length as *levels*.
849 Set *erase* to False to add lines without first
850 removing any previously added lines.
851 '''
852 y = self._locate(levels)
853 rtol = (self._y[-1] - self._y[0]) * 1e-10
854 igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol)
855 y = y[igood]
856 if np.iterable(colors):
857 colors = np.asarray(colors)[igood]
858 if np.iterable(linewidths):
859 linewidths = np.asarray(linewidths)[igood]
860 X, Y = np.meshgrid([self._y[0], self._y[-1]], y)
861 if self.orientation == 'vertical':
862 xy = np.stack([X, Y], axis=-1)
863 else:
864 xy = np.stack([Y, X], axis=-1)
865 col = collections.LineCollection(xy, linewidths=linewidths)
867 if erase and self.lines:
868 for lc in self.lines:
869 lc.remove()
870 self.lines = []
871 self.lines.append(col)
872 col.set_color(colors)
873 self.ax.add_collection(col)
874 self.stale = True
876 def _ticker(self, locator, formatter):
877 '''
878 Return the sequence of ticks (colorbar data locations),
879 ticklabels (strings), and the corresponding offset string.
880 '''
881 if isinstance(self.norm, colors.NoNorm) and self.boundaries is None:
882 intv = self._values[0], self._values[-1]
883 else:
884 intv = self.vmin, self.vmax
885 locator.create_dummy_axis(minpos=intv[0])
886 formatter.create_dummy_axis(minpos=intv[0])
887 locator.set_view_interval(*intv)
888 locator.set_data_interval(*intv)
889 formatter.set_view_interval(*intv)
890 formatter.set_data_interval(*intv)
892 b = np.array(locator())
893 if isinstance(locator, ticker.LogLocator):
894 eps = 1e-10
895 b = b[(b <= intv[1] * (1 + eps)) & (b >= intv[0] * (1 - eps))]
896 else:
897 eps = (intv[1] - intv[0]) * 1e-10
898 b = b[(b <= intv[1] + eps) & (b >= intv[0] - eps)]
899 self._manual_tick_data_values = b
900 ticks = self._locate(b)
901 ticklabels = formatter.format_ticks(b)
902 offset_string = formatter.get_offset()
903 return ticks, ticklabels, offset_string
905 def _process_values(self, b=None):
906 '''
907 Set the :attr:`_boundaries` and :attr:`_values` attributes
908 based on the input boundaries and values. Input boundaries
909 can be *self.boundaries* or the argument *b*.
910 '''
911 if b is None:
912 b = self.boundaries
913 if b is not None:
914 self._boundaries = np.asarray(b, dtype=float)
915 if self.values is None:
916 self._values = 0.5 * (self._boundaries[:-1]
917 + self._boundaries[1:])
918 if isinstance(self.norm, colors.NoNorm):
919 self._values = (self._values + 0.00001).astype(np.int16)
920 else:
921 self._values = np.array(self.values)
922 return
923 if self.values is not None:
924 self._values = np.array(self.values)
925 if self.boundaries is None:
926 b = np.zeros(len(self.values) + 1)
927 b[1:-1] = 0.5 * (self._values[:-1] + self._values[1:])
928 b[0] = 2.0 * b[1] - b[2]
929 b[-1] = 2.0 * b[-2] - b[-3]
930 self._boundaries = b
931 return
932 self._boundaries = np.array(self.boundaries)
933 return
934 # Neither boundaries nor values are specified;
935 # make reasonable ones based on cmap and norm.
936 if isinstance(self.norm, colors.NoNorm):
937 b = self._uniform_y(self.cmap.N + 1) * self.cmap.N - 0.5
938 v = np.zeros(len(b) - 1, dtype=np.int16)
939 v[self._inside] = np.arange(self.cmap.N, dtype=np.int16)
940 if self._extend_lower():
941 v[0] = -1
942 if self._extend_upper():
943 v[-1] = self.cmap.N
944 self._boundaries = b
945 self._values = v
946 return
947 elif isinstance(self.norm, colors.BoundaryNorm):
948 b = list(self.norm.boundaries)
949 if self._extend_lower():
950 b = [b[0] - 1] + b
951 if self._extend_upper():
952 b = b + [b[-1] + 1]
953 b = np.array(b)
954 v = np.zeros(len(b) - 1)
955 bi = self.norm.boundaries
956 v[self._inside] = 0.5 * (bi[:-1] + bi[1:])
957 if self._extend_lower():
958 v[0] = b[0] - 1
959 if self._extend_upper():
960 v[-1] = b[-1] + 1
961 self._boundaries = b
962 self._values = v
963 return
964 else:
965 if not self.norm.scaled():
966 self.norm.vmin = 0
967 self.norm.vmax = 1
969 self.norm.vmin, self.norm.vmax = mtransforms.nonsingular(
970 self.norm.vmin,
971 self.norm.vmax,
972 expander=0.1)
974 b = self.norm.inverse(self._uniform_y(self.cmap.N + 1))
976 if isinstance(self.norm, (colors.PowerNorm, colors.LogNorm)):
977 # If using a lognorm or powernorm, ensure extensions don't
978 # go negative
979 if self._extend_lower():
980 b[0] = 0.9 * b[0]
981 if self._extend_upper():
982 b[-1] = 1.1 * b[-1]
983 else:
984 if self._extend_lower():
985 b[0] = b[0] - 1
986 if self._extend_upper():
987 b[-1] = b[-1] + 1
988 self._process_values(b)
990 def _find_range(self):
991 '''
992 Set :attr:`vmin` and :attr:`vmax` attributes to the first and
993 last boundary excluding extended end boundaries.
994 '''
995 b = self._boundaries[self._inside]
996 self.vmin = b[0]
997 self.vmax = b[-1]
999 def _central_N(self):
1000 """Return the number of boundaries excluding end extensions."""
1001 nb = len(self._boundaries)
1002 if self.extend == 'both':
1003 nb -= 2
1004 elif self.extend in ('min', 'max'):
1005 nb -= 1
1006 return nb
1008 def _extended_N(self):
1009 '''
1010 Based on the colormap and extend variable, return the
1011 number of boundaries.
1012 '''
1013 N = self.cmap.N + 1
1014 if self.extend == 'both':
1015 N += 2
1016 elif self.extend in ('min', 'max'):
1017 N += 1
1018 return N
1020 def _get_extension_lengths(self, frac, automin, automax, default=0.05):
1021 '''
1022 Get the lengths of colorbar extensions.
1024 A helper method for _uniform_y and _proportional_y.
1025 '''
1026 # Set the default value.
1027 extendlength = np.array([default, default])
1028 if isinstance(frac, str):
1029 cbook._check_in_list(['auto'], extendfrac=frac.lower())
1030 # Use the provided values when 'auto' is required.
1031 extendlength[:] = [automin, automax]
1032 elif frac is not None:
1033 try:
1034 # Try to set min and max extension fractions directly.
1035 extendlength[:] = frac
1036 # If frac is a sequence containing None then NaN may
1037 # be encountered. This is an error.
1038 if np.isnan(extendlength).any():
1039 raise ValueError()
1040 except (TypeError, ValueError):
1041 # Raise an error on encountering an invalid value for frac.
1042 raise ValueError('invalid value for extendfrac')
1043 return extendlength
1045 def _uniform_y(self, N):
1046 '''
1047 Return colorbar data coordinates for *N* uniformly
1048 spaced boundaries, plus ends if required.
1049 '''
1050 if self.extend == 'neither':
1051 y = np.linspace(0, 1, N)
1052 else:
1053 automin = automax = 1. / (N - 1.)
1054 extendlength = self._get_extension_lengths(self.extendfrac,
1055 automin, automax,
1056 default=0.05)
1057 if self.extend == 'both':
1058 y = np.zeros(N + 2, 'd')
1059 y[0] = 0. - extendlength[0]
1060 y[-1] = 1. + extendlength[1]
1061 elif self.extend == 'min':
1062 y = np.zeros(N + 1, 'd')
1063 y[0] = 0. - extendlength[0]
1064 else:
1065 y = np.zeros(N + 1, 'd')
1066 y[-1] = 1. + extendlength[1]
1067 y[self._inside] = np.linspace(0, 1, N)
1068 return y
1070 def _proportional_y(self):
1071 '''
1072 Return colorbar data coordinates for the boundaries of
1073 a proportional colorbar.
1074 '''
1075 if isinstance(self.norm, colors.BoundaryNorm):
1076 y = (self._boundaries - self._boundaries[0])
1077 y = y / (self._boundaries[-1] - self._boundaries[0])
1078 else:
1079 y = self.norm(self._boundaries.copy())
1080 y = np.ma.filled(y, np.nan)
1081 if self.extend == 'min':
1082 # Exclude leftmost interval of y.
1083 clen = y[-1] - y[1]
1084 automin = (y[2] - y[1]) / clen
1085 automax = (y[-1] - y[-2]) / clen
1086 elif self.extend == 'max':
1087 # Exclude rightmost interval in y.
1088 clen = y[-2] - y[0]
1089 automin = (y[1] - y[0]) / clen
1090 automax = (y[-2] - y[-3]) / clen
1091 elif self.extend == 'both':
1092 # Exclude leftmost and rightmost intervals in y.
1093 clen = y[-2] - y[1]
1094 automin = (y[2] - y[1]) / clen
1095 automax = (y[-2] - y[-3]) / clen
1096 if self.extend in ('both', 'min', 'max'):
1097 extendlength = self._get_extension_lengths(self.extendfrac,
1098 automin, automax,
1099 default=0.05)
1100 if self.extend in ('both', 'min'):
1101 y[0] = 0. - extendlength[0]
1102 if self.extend in ('both', 'max'):
1103 y[-1] = 1. + extendlength[1]
1104 yi = y[self._inside]
1105 norm = colors.Normalize(yi[0], yi[-1])
1106 y[self._inside] = np.ma.filled(norm(yi), np.nan)
1107 return y
1109 def _mesh(self):
1110 '''
1111 Return ``(X, Y)``, the coordinate arrays for the colorbar pcolormesh.
1112 These are suitable for a vertical colorbar; swapping and transposition
1113 for a horizontal colorbar are done outside this function.
1115 These are scaled between vmin and vmax.
1116 '''
1117 # copy the norm and change the vmin and vmax to the vmin and
1118 # vmax of the colorbar, not the norm. This allows the situation
1119 # where the colormap has a narrower range than the colorbar, to
1120 # accomodate extra contours:
1121 norm = copy.copy(self.norm)
1122 norm.vmin = self.vmin
1123 norm.vmax = self.vmax
1124 x = np.array([0.0, 1.0])
1125 if self.spacing == 'uniform':
1126 y = self._uniform_y(self._central_N())
1127 else:
1128 y = self._proportional_y()
1129 xmid = np.array([0.5])
1130 if self.__scale != 'manual':
1131 y = norm.inverse(y)
1132 x = norm.inverse(x)
1133 xmid = norm.inverse(xmid)
1134 else:
1135 # if a norm doesn't have a named scale, or
1136 # we are not using a norm
1137 dv = self.vmax - self.vmin
1138 x = x * dv + self.vmin
1139 y = y * dv + self.vmin
1140 xmid = xmid * dv + self.vmin
1141 self._y = y
1142 X, Y = np.meshgrid(x, y)
1143 if self._extend_lower() and not self.extendrect:
1144 X[0, :] = xmid
1145 if self._extend_upper() and not self.extendrect:
1146 X[-1, :] = xmid
1147 return X, Y
1149 def _locate(self, x):
1150 '''
1151 Given a set of color data values, return their
1152 corresponding colorbar data coordinates.
1153 '''
1154 if isinstance(self.norm, (colors.NoNorm, colors.BoundaryNorm)):
1155 b = self._boundaries
1156 xn = x
1157 else:
1158 # Do calculations using normalized coordinates so
1159 # as to make the interpolation more accurate.
1160 b = self.norm(self._boundaries, clip=False).filled()
1161 xn = self.norm(x, clip=False).filled()
1163 bunique = b
1164 yunique = self._y
1165 # trim extra b values at beginning and end if they are
1166 # not unique. These are here for extended colorbars, and are not
1167 # wanted for the interpolation.
1168 if b[0] == b[1]:
1169 bunique = bunique[1:]
1170 yunique = yunique[1:]
1171 if b[-1] == b[-2]:
1172 bunique = bunique[:-1]
1173 yunique = yunique[:-1]
1175 z = np.interp(xn, bunique, yunique)
1176 return z
1178 def set_alpha(self, alpha):
1179 self.alpha = alpha
1181 def remove(self):
1182 """
1183 Remove this colorbar from the figure
1184 """
1186 fig = self.ax.figure
1187 fig.delaxes(self.ax)
1190class Colorbar(ColorbarBase):
1191 """
1192 This class connects a :class:`ColorbarBase` to a
1193 :class:`~matplotlib.cm.ScalarMappable` such as a
1194 :class:`~matplotlib.image.AxesImage` generated via
1195 :meth:`~matplotlib.axes.Axes.imshow`.
1197 It is not intended to be instantiated directly; instead,
1198 use :meth:`~matplotlib.figure.Figure.colorbar` or
1199 :func:`~matplotlib.pyplot.colorbar` to make your colorbar.
1201 """
1202 def __init__(self, ax, mappable, **kw):
1203 # Ensure the given mappable's norm has appropriate vmin and vmax set
1204 # even if mappable.draw has not yet been called.
1205 if mappable.get_array() is not None:
1206 mappable.autoscale_None()
1208 self.mappable = mappable
1209 kw['cmap'] = cmap = mappable.cmap
1210 kw['norm'] = mappable.norm
1212 if isinstance(mappable, contour.ContourSet):
1213 CS = mappable
1214 kw['alpha'] = mappable.get_alpha()
1215 kw['boundaries'] = CS._levels
1216 kw['values'] = CS.cvalues
1217 kw['extend'] = CS.extend
1218 kw.setdefault('ticks', ticker.FixedLocator(CS.levels, nbins=10))
1219 kw['filled'] = CS.filled
1220 ColorbarBase.__init__(self, ax, **kw)
1221 if not CS.filled:
1222 self.add_lines(CS)
1223 else:
1224 if getattr(cmap, 'colorbar_extend', False) is not False:
1225 kw.setdefault('extend', cmap.colorbar_extend)
1227 if isinstance(mappable, martist.Artist):
1228 kw['alpha'] = mappable.get_alpha()
1230 ColorbarBase.__init__(self, ax, **kw)
1232 def on_mappable_changed(self, mappable):
1233 """
1234 Updates this colorbar to match the mappable's properties.
1236 Typically this is automatically registered as an event handler
1237 by :func:`colorbar_factory` and should not be called manually.
1239 """
1240 _log.debug('colorbar mappable changed')
1241 self.update_normal(mappable)
1243 def add_lines(self, CS, erase=True):
1244 '''
1245 Add the lines from a non-filled
1246 :class:`~matplotlib.contour.ContourSet` to the colorbar.
1248 Set *erase* to False if these lines should be added to
1249 any pre-existing lines.
1250 '''
1251 if not isinstance(CS, contour.ContourSet) or CS.filled:
1252 raise ValueError('add_lines is only for a ContourSet of lines')
1253 tcolors = [c[0] for c in CS.tcolors]
1254 tlinewidths = [t[0] for t in CS.tlinewidths]
1255 # The following was an attempt to get the colorbar lines
1256 # to follow subsequent changes in the contour lines,
1257 # but more work is needed: specifically, a careful
1258 # look at event sequences, and at how
1259 # to make one object track another automatically.
1260 #tcolors = [col.get_colors()[0] for col in CS.collections]
1261 #tlinewidths = [col.get_linewidth()[0] for lw in CS.collections]
1262 ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths,
1263 erase=erase)
1265 def update_normal(self, mappable):
1266 """
1267 Update solid patches, lines, etc.
1269 Unlike `.update_bruteforce`, this does not clear the axes. This is
1270 meant to be called when the norm of the image or contour plot to which
1271 this colorbar belongs changes.
1273 If the norm on the mappable is different than before, this resets the
1274 locator and formatter for the axis, so if these have been customized,
1275 they will need to be customized again. However, if the norm only
1276 changes values of *vmin*, *vmax* or *cmap* then the old formatter
1277 and locator will be preserved.
1278 """
1280 _log.debug('colorbar update normal %r %r', mappable.norm, self.norm)
1281 self.mappable = mappable
1282 self.set_alpha(mappable.get_alpha())
1283 self.cmap = mappable.cmap
1284 if mappable.norm != self.norm:
1285 self.norm = mappable.norm
1286 self._reset_locator_formatter_scale()
1288 self.draw_all()
1289 if isinstance(self.mappable, contour.ContourSet):
1290 CS = self.mappable
1291 if not CS.filled:
1292 self.add_lines(CS)
1293 self.stale = True
1295 def update_bruteforce(self, mappable):
1296 '''
1297 Destroy and rebuild the colorbar. This is
1298 intended to become obsolete, and will probably be
1299 deprecated and then removed. It is not called when
1300 the pyplot.colorbar function or the Figure.colorbar
1301 method are used to create the colorbar.
1303 '''
1304 # We are using an ugly brute-force method: clearing and
1305 # redrawing the whole thing. The problem is that if any
1306 # properties have been changed by methods other than the
1307 # colorbar methods, those changes will be lost.
1308 self.ax.cla()
1309 self.locator = None
1310 self.formatter = None
1312 # clearing the axes will delete outline, patch, solids, and lines:
1313 self.outline = None
1314 self.patch = None
1315 self.solids = None
1316 self.lines = list()
1317 self.dividers = None
1318 self.update_normal(mappable)
1319 self.draw_all()
1320 if isinstance(self.mappable, contour.ContourSet):
1321 CS = self.mappable
1322 if not CS.filled:
1323 self.add_lines(CS)
1324 #if self.lines is not None:
1325 # tcolors = [c[0] for c in CS.tcolors]
1326 # self.lines.set_color(tcolors)
1327 #Fixme? Recalculate boundaries, ticks if vmin, vmax have changed.
1328 #Fixme: Some refactoring may be needed; we should not
1329 # be recalculating everything if there was a simple alpha
1330 # change.
1332 def remove(self):
1333 """
1334 Remove this colorbar from the figure. If the colorbar was created with
1335 ``use_gridspec=True`` then restore the gridspec to its previous value.
1336 """
1338 ColorbarBase.remove(self)
1339 self.mappable.callbacksSM.disconnect(self.mappable.colorbar_cid)
1340 self.mappable.colorbar = None
1341 self.mappable.colorbar_cid = None
1343 try:
1344 ax = self.mappable.axes
1345 except AttributeError:
1346 return
1348 try:
1349 gs = ax.get_subplotspec().get_gridspec()
1350 subplotspec = gs.get_topmost_subplotspec()
1351 except AttributeError:
1352 # use_gridspec was False
1353 pos = ax.get_position(original=True)
1354 ax._set_position(pos)
1355 else:
1356 # use_gridspec was True
1357 ax.set_subplotspec(subplotspec)
1360@docstring.Substitution(make_axes_kw_doc)
1361def make_axes(parents, location=None, orientation=None, fraction=0.15,
1362 shrink=1.0, aspect=20, **kw):
1363 '''
1364 Resize and reposition parent axes, and return a child
1365 axes suitable for a colorbar.
1367 Keyword arguments may include the following (with defaults):
1369 location : [None|'left'|'right'|'top'|'bottom']
1370 The position, relative to **parents**, where the colorbar axes
1371 should be created. If None, the value will either come from the
1372 given ``orientation``, else it will default to 'right'.
1374 orientation : [None|'vertical'|'horizontal']
1375 The orientation of the colorbar. Typically, this keyword shouldn't
1376 be used, as it can be derived from the ``location`` keyword.
1378 %s
1380 Returns (cax, kw), the child axes and the reduced kw dictionary to be
1381 passed when creating the colorbar instance.
1382 '''
1384 locations = ["left", "right", "top", "bottom"]
1385 if orientation is not None and location is not None:
1386 raise TypeError('position and orientation are mutually exclusive. '
1387 'Consider setting the position to any of {}'
1388 .format(', '.join(locations)))
1390 # provide a default location
1391 if location is None and orientation is None:
1392 location = 'right'
1394 # allow the user to not specify the location by specifying the
1395 # orientation instead
1396 if location is None:
1397 location = 'right' if orientation == 'vertical' else 'bottom'
1399 cbook._check_in_list(locations, location=location)
1401 default_location_settings = {'left': {'anchor': (1.0, 0.5),
1402 'panchor': (0.0, 0.5),
1403 'pad': 0.10,
1404 'orientation': 'vertical'},
1405 'right': {'anchor': (0.0, 0.5),
1406 'panchor': (1.0, 0.5),
1407 'pad': 0.05,
1408 'orientation': 'vertical'},
1409 'top': {'anchor': (0.5, 0.0),
1410 'panchor': (0.5, 1.0),
1411 'pad': 0.05,
1412 'orientation': 'horizontal'},
1413 'bottom': {'anchor': (0.5, 1.0),
1414 'panchor': (0.5, 0.0),
1415 'pad': 0.15, # backwards compat
1416 'orientation': 'horizontal'},
1417 }
1419 loc_settings = default_location_settings[location]
1421 # put appropriate values into the kw dict for passing back to
1422 # the Colorbar class
1423 kw['orientation'] = loc_settings['orientation']
1424 kw['ticklocation'] = location
1426 anchor = kw.pop('anchor', loc_settings['anchor'])
1427 parent_anchor = kw.pop('panchor', loc_settings['panchor'])
1429 parents_iterable = np.iterable(parents)
1430 # turn parents into a list if it is not already. We do this w/ np
1431 # because `plt.subplots` can return an ndarray and is natural to
1432 # pass to `colorbar`.
1433 parents = np.atleast_1d(parents).ravel()
1435 # check if using constrained_layout:
1436 try:
1437 gs = parents[0].get_subplotspec().get_gridspec()
1438 using_constrained_layout = (gs._layoutbox is not None)
1439 except AttributeError:
1440 using_constrained_layout = False
1442 # defaults are not appropriate for constrained_layout:
1443 pad0 = loc_settings['pad']
1444 if using_constrained_layout:
1445 pad0 = 0.02
1446 pad = kw.pop('pad', pad0)
1448 fig = parents[0].get_figure()
1449 if not all(fig is ax.get_figure() for ax in parents):
1450 raise ValueError('Unable to create a colorbar axes as not all '
1451 'parents share the same figure.')
1453 # take a bounding box around all of the given axes
1454 parents_bbox = mtransforms.Bbox.union(
1455 [ax.get_position(original=True).frozen() for ax in parents])
1457 pb = parents_bbox
1458 if location in ('left', 'right'):
1459 if location == 'left':
1460 pbcb, _, pb1 = pb.splitx(fraction, fraction + pad)
1461 else:
1462 pb1, _, pbcb = pb.splitx(1 - fraction - pad, 1 - fraction)
1463 pbcb = pbcb.shrunk(1.0, shrink).anchored(anchor, pbcb)
1464 else:
1465 if location == 'bottom':
1466 pbcb, _, pb1 = pb.splity(fraction, fraction + pad)
1467 else:
1468 pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction)
1469 pbcb = pbcb.shrunk(shrink, 1.0).anchored(anchor, pbcb)
1471 # define the aspect ratio in terms of y's per x rather than x's per y
1472 aspect = 1.0 / aspect
1474 # define a transform which takes us from old axes coordinates to
1475 # new axes coordinates
1476 shrinking_trans = mtransforms.BboxTransform(parents_bbox, pb1)
1478 # transform each of the axes in parents using the new transform
1479 for ax in parents:
1480 new_posn = shrinking_trans.transform(ax.get_position(original=True))
1481 new_posn = mtransforms.Bbox(new_posn)
1482 ax._set_position(new_posn)
1483 if parent_anchor is not False:
1484 ax.set_anchor(parent_anchor)
1486 cax = fig.add_axes(pbcb, label="<colorbar>")
1488 # OK, now make a layoutbox for the cb axis. Later, we will use this
1489 # to make the colorbar fit nicely.
1490 if not using_constrained_layout:
1491 # no layout boxes:
1492 lb = None
1493 lbpos = None
1494 # and we need to set the aspect ratio by hand...
1495 cax.set_aspect(aspect, anchor=anchor, adjustable='box')
1496 else:
1497 if not parents_iterable:
1498 # this is a single axis...
1499 ax = parents[0]
1500 lb, lbpos = constrained_layout.layoutcolorbarsingle(
1501 ax, cax, shrink, aspect, location, pad=pad)
1502 else: # there is more than one parent, so lets use gridspec
1503 # the colorbar will be a sibling of this gridspec, so the
1504 # parent is the same parent as the gridspec. Either the figure,
1505 # or a subplotspec.
1507 lb, lbpos = constrained_layout.layoutcolorbargridspec(
1508 parents, cax, shrink, aspect, location, pad)
1510 cax._layoutbox = lb
1511 cax._poslayoutbox = lbpos
1513 return cax, kw
1516@docstring.Substitution(make_axes_kw_doc)
1517def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
1518 '''
1519 Resize and reposition a parent axes, and return a child axes
1520 suitable for a colorbar. This function is similar to
1521 make_axes. Prmary differences are
1523 * *make_axes_gridspec* only handles the *orientation* keyword
1524 and cannot handle the "location" keyword.
1526 * *make_axes_gridspec* should only be used with a subplot parent.
1528 * *make_axes* creates an instance of Axes. *make_axes_gridspec*
1529 creates an instance of Subplot.
1531 * *make_axes* updates the position of the
1532 parent. *make_axes_gridspec* replaces the grid_spec attribute
1533 of the parent with a new one.
1535 While this function is meant to be compatible with *make_axes*,
1536 there could be some minor differences.
1538 Keyword arguments may include the following (with defaults):
1540 *orientation*
1541 'vertical' or 'horizontal'
1543 %s
1545 All but the first of these are stripped from the input kw set.
1547 Returns (cax, kw), the child axes and the reduced kw dictionary to be
1548 passed when creating the colorbar instance.
1549 '''
1551 orientation = kw.setdefault('orientation', 'vertical')
1552 kw['ticklocation'] = 'auto'
1554 x1 = 1 - fraction
1556 # for shrinking
1557 pad_s = (1 - shrink) * 0.5
1558 wh_ratios = [pad_s, shrink, pad_s]
1560 # we need to none the tree of layoutboxes because
1561 # constrained_layout can't remove and replace the tree
1562 # hierarchy w/o a seg fault.
1563 gs = parent.get_subplotspec().get_gridspec()
1564 layoutbox.nonetree(gs._layoutbox)
1565 gs_from_subplotspec = gridspec.GridSpecFromSubplotSpec
1566 if orientation == 'vertical':
1567 pad = kw.pop('pad', 0.05)
1568 wh_space = 2 * pad / (1 - pad)
1569 gs = gs_from_subplotspec(1, 2,
1570 subplot_spec=parent.get_subplotspec(),
1571 wspace=wh_space,
1572 width_ratios=[x1 - pad, fraction])
1573 gs2 = gs_from_subplotspec(3, 1,
1574 subplot_spec=gs[1],
1575 hspace=0.,
1576 height_ratios=wh_ratios)
1577 anchor = (0.0, 0.5)
1578 panchor = (1.0, 0.5)
1579 else:
1580 pad = kw.pop('pad', 0.15)
1581 wh_space = 2 * pad / (1 - pad)
1582 gs = gs_from_subplotspec(2, 1,
1583 subplot_spec=parent.get_subplotspec(),
1584 hspace=wh_space,
1585 height_ratios=[x1 - pad, fraction])
1586 gs2 = gs_from_subplotspec(1, 3,
1587 subplot_spec=gs[1],
1588 wspace=0.,
1589 width_ratios=wh_ratios)
1590 aspect = 1 / aspect
1591 anchor = (0.5, 1.0)
1592 panchor = (0.5, 0.0)
1594 parent.set_subplotspec(gs[0])
1595 parent.update_params()
1596 parent._set_position(parent.figbox)
1597 parent.set_anchor(panchor)
1599 fig = parent.get_figure()
1600 cax = fig.add_subplot(gs2[1], label="<colorbar>")
1601 cax.set_aspect(aspect, anchor=anchor, adjustable='box')
1602 return cax, kw
1605class ColorbarPatch(Colorbar):
1606 """
1607 A Colorbar which is created using :class:`~matplotlib.patches.Patch`
1608 rather than the default :func:`~matplotlib.axes.pcolor`.
1610 It uses a list of Patch instances instead of a
1611 :class:`~matplotlib.collections.PatchCollection` because the
1612 latter does not allow the hatch pattern to vary among the
1613 members of the collection.
1614 """
1615 def __init__(self, ax, mappable, **kw):
1616 # we do not want to override the behaviour of solids
1617 # so add a new attribute which will be a list of the
1618 # colored patches in the colorbar
1619 self.solids_patches = []
1620 Colorbar.__init__(self, ax, mappable, **kw)
1622 def _add_solids(self, X, Y, C):
1623 """
1624 Draw the colors using :class:`~matplotlib.patches.Patch`;
1625 optionally add separators.
1626 """
1627 n_segments = len(C)
1629 # ensure there are sufficient hatches
1630 hatches = self.mappable.hatches * n_segments
1632 patches = []
1633 for i in range(len(X) - 1):
1634 val = C[i][0]
1635 hatch = hatches[i]
1637 xy = np.array([[X[i][0], Y[i][0]],
1638 [X[i][1], Y[i][0]],
1639 [X[i + 1][1], Y[i + 1][0]],
1640 [X[i + 1][0], Y[i + 1][1]]])
1642 if self.orientation == 'horizontal':
1643 # if horizontal swap the xs and ys
1644 xy = xy[..., ::-1]
1646 patch = mpatches.PathPatch(mpath.Path(xy),
1647 facecolor=self.cmap(self.norm(val)),
1648 hatch=hatch, linewidth=0,
1649 antialiased=False, alpha=self.alpha)
1650 self.ax.add_patch(patch)
1651 patches.append(patch)
1653 if self.solids_patches:
1654 for solid in self.solids_patches:
1655 solid.remove()
1657 self.solids_patches = patches
1659 if self.dividers is not None:
1660 self.dividers.remove()
1661 self.dividers = None
1663 if self.drawedges:
1664 self.dividers = collections.LineCollection(
1665 self._edges(X, Y),
1666 colors=(mpl.rcParams['axes.edgecolor'],),
1667 linewidths=(0.5 * mpl.rcParams['axes.linewidth'],))
1668 self.ax.add_collection(self.dividers)
1671def colorbar_factory(cax, mappable, **kwargs):
1672 """
1673 Creates a colorbar on the given axes for the given mappable.
1675 Typically, for automatic colorbar placement given only a mappable use
1676 :meth:`~matplotlib.figure.Figure.colorbar`.
1678 """
1679 # if the given mappable is a contourset with any hatching, use
1680 # ColorbarPatch else use Colorbar
1681 if (isinstance(mappable, contour.ContourSet)
1682 and any(hatch is not None for hatch in mappable.hatches)):
1683 cb = ColorbarPatch(cax, mappable, **kwargs)
1684 else:
1685 cb = Colorbar(cax, mappable, **kwargs)
1687 cid = mappable.callbacksSM.connect('changed', cb.on_mappable_changed)
1688 mappable.colorbar = cb
1689 mappable.colorbar_cid = cid
1691 return cb