123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740 |
- """
- Colorbars are a visualization of the mapping from scalar values to colors.
- In Matplotlib they are drawn into a dedicated `~.axes.Axes`.
- .. note::
- Colorbars are typically created through `.Figure.colorbar` or its pyplot
- wrapper `.pyplot.colorbar`, which use `.make_axes` and `.Colorbar`
- internally.
- As an end-user, you most likely won't have to call the methods or
- instantiate the classes in this module explicitly.
- :class:`ColorbarBase`
- The base class with full colorbar drawing functionality.
- It can be used as-is to make a colorbar for a given colormap;
- a mappable object (e.g., image) is not needed.
- :class:`Colorbar`
- On top of `.ColorbarBase` this connects the colorbar with a
- `.ScalarMappable` such as an image or contour plot.
- :class:`ColorbarPatch`
- A specialized `.Colorbar` to support hatched contour plots.
- :func:`make_axes`
- Create an `~.axes.Axes` suitable for a colorbar. This functions can be
- used with figures containing a single axes or with freely placed axes.
- :func:`make_axes_gridspec`
- Create a `~.SubplotBase` suitable for a colorbar. This function should
- be used for adding a colorbar to a `.GridSpec`.
- """
- import copy
- import logging
- import numpy as np
- import matplotlib as mpl
- import matplotlib.artist as martist
- import matplotlib.cbook as cbook
- import matplotlib.collections as collections
- import matplotlib.colors as colors
- import matplotlib.contour as contour
- import matplotlib.cm as cm
- import matplotlib.gridspec as gridspec
- import matplotlib.patches as mpatches
- import matplotlib.path as mpath
- import matplotlib.ticker as ticker
- import matplotlib.transforms as mtransforms
- import matplotlib._layoutbox as layoutbox
- import matplotlib._constrained_layout as constrained_layout
- from matplotlib import docstring
- _log = logging.getLogger(__name__)
- _make_axes_param_doc = """
- fraction : float, default: 0.15
- Fraction of original axes to use for colorbar.
- shrink : float, default: 1.0
- Fraction by which to multiply the size of the colorbar.
- aspect : float, default: 20
- Ratio of long to short dimensions.
- """
- _make_axes_other_param_doc = """
- pad : float, default: 0.05 if vertical, 0.15 if horizontal
- Fraction of original axes between colorbar and new image axes.
- anchor : (float, float), optional
- The anchor point of the colorbar axes.
- Defaults to (0.0, 0.5) if vertical; (0.5, 1.0) if horizontal.
- panchor : (float, float), or *False*, optional
- The anchor point of the colorbar parent axes. If *False*, the parent
- axes' anchor will be unchanged.
- Defaults to (1.0, 0.5) if vertical; (0.5, 0.0) if horizontal.
- """
- make_axes_kw_doc = _make_axes_param_doc + _make_axes_other_param_doc
- colormap_kw_doc = """
- ============ ====================================================
- Property Description
- ============ ====================================================
- *extend* {'neither', 'both', 'min', 'max'}
- If not 'neither', make pointed end(s) for out-of-
- range values. These are set for a given colormap
- using the colormap set_under and set_over methods.
- *extendfrac* {*None*, 'auto', length, lengths}
- If set to *None*, both the minimum and maximum
- triangular colorbar extensions with have a length of
- 5% of the interior colorbar length (this is the
- default setting). If set to 'auto', makes the
- triangular colorbar extensions the same lengths as
- the interior boxes (when *spacing* is set to
- 'uniform') or the same lengths as the respective
- adjacent interior boxes (when *spacing* is set to
- 'proportional'). If a scalar, indicates the length
- of both the minimum and maximum triangular colorbar
- extensions as a fraction of the interior colorbar
- length. A two-element sequence of fractions may also
- be given, indicating the lengths of the minimum and
- maximum colorbar extensions respectively as a
- fraction of the interior colorbar length.
- *extendrect* bool
- If *False* the minimum and maximum colorbar extensions
- will be triangular (the default). If *True* the
- extensions will be rectangular.
- *spacing* {'uniform', 'proportional'}
- Uniform spacing gives each discrete color the same
- space; proportional makes the space proportional to
- the data interval.
- *ticks* *None* or list of ticks or Locator
- If None, ticks are determined automatically from the
- input.
- *format* None or str or Formatter
- If None, `~.ticker.ScalarFormatter` is used.
- If a format string is given, e.g., '%.3f', that is used.
- An alternative `~.ticker.Formatter` may be given instead.
- *drawedges* bool
- Whether to draw lines at color boundaries.
- *label* str
- The label on the colorbar's long axis.
- ============ ====================================================
- The following will probably be useful only in the context of
- indexed colors (that is, when the mappable has norm=NoNorm()),
- or other unusual circumstances.
- ============ ===================================================
- Property Description
- ============ ===================================================
- *boundaries* None or a sequence
- *values* None or a sequence which must be of length 1 less
- than the sequence of *boundaries*. For each region
- delimited by adjacent entries in *boundaries*, the
- color mapped to the corresponding value in values
- will be used.
- ============ ===================================================
- """
- colorbar_doc = """
- Add a colorbar to a plot.
- Function signatures for the :mod:`~matplotlib.pyplot` interface; all
- but the first are also method signatures for the `~.Figure.colorbar` method::
- colorbar(**kwargs)
- colorbar(mappable, **kwargs)
- colorbar(mappable, cax=cax, **kwargs)
- colorbar(mappable, ax=ax, **kwargs)
- Parameters
- ----------
- mappable
- The `matplotlib.cm.ScalarMappable` (i.e., `~matplotlib.image.AxesImage`,
- `~matplotlib.contour.ContourSet`, etc.) described by this colorbar.
- This argument is mandatory for the `.Figure.colorbar` method but optional
- for the `.pyplot.colorbar` function, which sets the default to the current
- image.
- Note that one can create a `.ScalarMappable` "on-the-fly" to generate
- colorbars not attached to a previously drawn artist, e.g. ::
- fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax)
- cax : `~matplotlib.axes.Axes`, optional
- Axes into which the colorbar will be drawn.
- ax : `~matplotlib.axes.Axes`, list of Axes, optional
- Parent axes from which space for a new colorbar axes will be stolen.
- If a list of axes is given they will all be resized to make room for the
- colorbar axes.
- use_gridspec : bool, optional
- If *cax* is ``None``, a new *cax* is created as an instance of Axes. If
- *ax* is an instance of Subplot and *use_gridspec* is ``True``, *cax* is
- created as an instance of Subplot using the :mod:`~.gridspec` module.
- Returns
- -------
- colorbar : `~matplotlib.colorbar.Colorbar`
- See also its base class, `~matplotlib.colorbar.ColorbarBase`.
- Notes
- -----
- Additional keyword arguments are of two kinds:
- axes properties:
- %s
- colorbar properties:
- %s
- If *mappable* is a `~.contour.ContourSet`, its *extend* kwarg is included
- automatically.
- The *shrink* kwarg provides a simple way to scale the colorbar with respect
- to the axes. Note that if *cax* is specified, it determines the size of the
- colorbar and *shrink* and *aspect* kwargs are ignored.
- For more precise control, you can manually specify the positions of
- the axes objects in which the mappable and the colorbar are drawn. In
- this case, do not use any of the axes properties kwargs.
- It is known that some vector graphics viewers (svg and pdf) renders white gaps
- between segments of the colorbar. This is due to bugs in the viewers, not
- Matplotlib. As a workaround, the colorbar can be rendered with overlapping
- segments::
- cbar = colorbar()
- cbar.solids.set_edgecolor("face")
- draw()
- However this has negative consequences in other circumstances, e.g. with
- semi-transparent images (alpha < 1) and colorbar extensions; therefore, this
- workaround is not used by default (see issue #1188).
- """ % (make_axes_kw_doc, colormap_kw_doc)
- docstring.interpd.update(colorbar_doc=colorbar_doc)
- def _set_ticks_on_axis_warn(*args, **kw):
- # a top level function which gets put in at the axes'
- # set_xticks and set_yticks by ColorbarBase.__init__.
- cbook._warn_external("Use the colorbar set_ticks() method instead.")
- class _ColorbarAutoLocator(ticker.MaxNLocator):
- """
- AutoLocator for Colorbar
- This locator is just a `.MaxNLocator` except the min and max are
- clipped by the norm's min and max (i.e. vmin/vmax from the
- image/pcolor/contour object). This is necessary so ticks don't
- extrude into the "extend regions".
- """
- def __init__(self, colorbar):
- """
- This ticker needs to know the *colorbar* so that it can access
- its *vmin* and *vmax*. Otherwise it is the same as
- `~.ticker.AutoLocator`.
- """
- self._colorbar = colorbar
- nbins = 'auto'
- steps = [1, 2, 2.5, 5, 10]
- super().__init__(nbins=nbins, steps=steps)
- def tick_values(self, vmin, vmax):
- # flip if needed:
- if vmin > vmax:
- vmin, vmax = vmax, vmin
- vmin = max(vmin, self._colorbar.norm.vmin)
- vmax = min(vmax, self._colorbar.norm.vmax)
- ticks = super().tick_values(vmin, vmax)
- rtol = (vmax - vmin) * 1e-10
- return ticks[(ticks >= vmin - rtol) & (ticks <= vmax + rtol)]
- class _ColorbarAutoMinorLocator(ticker.AutoMinorLocator):
- """
- AutoMinorLocator for Colorbar
- This locator is just a `.AutoMinorLocator` except the min and max are
- clipped by the norm's min and max (i.e. vmin/vmax from the
- image/pcolor/contour object). This is necessary so that the minorticks
- don't extrude into the "extend regions".
- """
- def __init__(self, colorbar, n=None):
- """
- This ticker needs to know the *colorbar* so that it can access
- its *vmin* and *vmax*.
- """
- self._colorbar = colorbar
- self.ndivs = n
- super().__init__(n=None)
- def __call__(self):
- vmin = self._colorbar.norm.vmin
- vmax = self._colorbar.norm.vmax
- ticks = super().__call__()
- rtol = (vmax - vmin) * 1e-10
- return ticks[(ticks >= vmin - rtol) & (ticks <= vmax + rtol)]
- class _ColorbarLogLocator(ticker.LogLocator):
- """
- LogLocator for Colorbarbar
- This locator is just a `.LogLocator` except the min and max are
- clipped by the norm's min and max (i.e. vmin/vmax from the
- image/pcolor/contour object). This is necessary so ticks don't
- extrude into the "extend regions".
- """
- def __init__(self, colorbar, *args, **kwargs):
- """
- This ticker needs to know the *colorbar* so that it can access
- its *vmin* and *vmax*. Otherwise it is the same as
- `~.ticker.LogLocator`. The ``*args`` and ``**kwargs`` are the
- same as `~.ticker.LogLocator`.
- """
- self._colorbar = colorbar
- super().__init__(*args, **kwargs)
- def tick_values(self, vmin, vmax):
- if vmin > vmax:
- vmin, vmax = vmax, vmin
- vmin = max(vmin, self._colorbar.norm.vmin)
- vmax = min(vmax, self._colorbar.norm.vmax)
- ticks = super().tick_values(vmin, vmax)
- rtol = (np.log10(vmax) - np.log10(vmin)) * 1e-10
- ticks = ticks[(np.log10(ticks) >= np.log10(vmin) - rtol) &
- (np.log10(ticks) <= np.log10(vmax) + rtol)]
- return ticks
- class ColorbarBase:
- r"""
- Draw a colorbar in an existing axes.
- There are only some rare cases in which you would work directly with a
- `.ColorbarBase` as an end-user. Typically, colorbars are used
- with `.ScalarMappable`\s such as an `.AxesImage` generated via
- `~.axes.Axes.imshow`. For these cases you will use `.Colorbar` and
- likely create it via `.pyplot.colorbar` or `.Figure.colorbar`.
- The main application of using a `.ColorbarBase` explicitly is drawing
- colorbars that are not associated with other elements in the figure, e.g.
- when showing a colormap by itself.
- If the *cmap* kwarg is given but *boundaries* and *values* are left as
- None, then the colormap will be displayed on a 0-1 scale. To show the
- under- and over-value colors, specify the *norm* as::
- norm=colors.Normalize(clip=False)
- To show the colors versus index instead of on the 0-1 scale,
- use::
- norm=colors.NoNorm()
- Useful public methods are :meth:`set_label` and :meth:`add_lines`.
- Attributes
- ----------
- ax : `~matplotlib.axes.Axes`
- The `~.axes.Axes` instance in which the colorbar is drawn.
- lines : list
- A list of `.LineCollection` if lines were drawn, otherwise
- an empty list.
- dividers : `.LineCollection`
- A LineCollection if *drawedges* is ``True``, otherwise ``None``.
- Parameters
- ----------
- ax : `~matplotlib.axes.Axes`
- The `~.axes.Axes` instance in which the colorbar is drawn.
- cmap : `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
- The colormap to use.
- norm : `~matplotlib.colors.Normalize`
- alpha : float
- The colorbar transparency between 0 (transparent) and 1 (opaque).
- values
- boundaries
- orientation : {'vertical', 'horizontal'}
- ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
- extend : {'neither', 'both', 'min', 'max'}
- spacing : {'uniform', 'proportional'}
- ticks : `~matplotlib.ticker.Locator` or array-like of float
- format : str or `~matplotlib.ticker.Formatter`
- drawedges : bool
- filled : bool
- extendfrac
- extendrec
- label : str
- """
- n_rasterize = 50 # rasterize solids if number of colors >= n_rasterize
- @cbook._make_keyword_only("3.3", "cmap")
- def __init__(self, ax, cmap=None,
- norm=None,
- alpha=None,
- values=None,
- boundaries=None,
- orientation='vertical',
- ticklocation='auto',
- extend=None,
- spacing='uniform', # uniform or proportional
- ticks=None,
- format=None,
- drawedges=False,
- filled=True,
- extendfrac=None,
- extendrect=False,
- label='',
- ):
- cbook._check_isinstance([colors.Colormap, None], cmap=cmap)
- cbook._check_in_list(
- ['vertical', 'horizontal'], orientation=orientation)
- cbook._check_in_list(
- ['auto', 'left', 'right', 'top', 'bottom'],
- ticklocation=ticklocation)
- cbook._check_in_list(
- ['uniform', 'proportional'], spacing=spacing)
- self.ax = ax
- # Bind some methods to the axes to warn users against using them.
- ax.set_xticks = ax.set_yticks = _set_ticks_on_axis_warn
- ax.set(frame_on=False, navigate=False)
- if cmap is None:
- cmap = cm.get_cmap()
- if norm is None:
- norm = colors.Normalize()
- if extend is None:
- if hasattr(norm, 'extend'):
- extend = norm.extend
- else:
- extend = 'neither'
- self.alpha = alpha
- self.cmap = cmap
- self.norm = norm
- self.values = values
- self.boundaries = boundaries
- self.extend = extend
- self._inside = cbook._check_getitem(
- {'neither': slice(0, None), 'both': slice(1, -1),
- 'min': slice(1, None), 'max': slice(0, -1)},
- extend=extend)
- self.spacing = spacing
- self.orientation = orientation
- self.drawedges = drawedges
- self.filled = filled
- self.extendfrac = extendfrac
- self.extendrect = extendrect
- self.solids = None
- self.lines = []
- self.outline = mpatches.Polygon(
- np.empty((0, 2)),
- edgecolor=mpl.rcParams['axes.edgecolor'], facecolor='none',
- linewidth=mpl.rcParams['axes.linewidth'], closed=True, zorder=2)
- ax.add_artist(self.outline)
- self.outline.set(clip_box=None, clip_path=None)
- self.patch = mpatches.Polygon(
- np.empty((0, 2)),
- color=mpl.rcParams['axes.facecolor'], linewidth=0.01, zorder=-1)
- ax.add_artist(self.patch)
- self.dividers = None
- self.locator = None
- self.formatter = None
- self._manual_tick_data_values = None
- self.__scale = None # linear, log10 for now. Hopefully more?
- if ticklocation == 'auto':
- ticklocation = 'bottom' if orientation == 'horizontal' else 'right'
- self.ticklocation = ticklocation
- self.set_label(label)
- self._reset_locator_formatter_scale()
- if np.iterable(ticks):
- self.locator = ticker.FixedLocator(ticks, nbins=len(ticks))
- else:
- self.locator = ticks # Handle default in _ticker()
- if isinstance(format, str):
- self.formatter = ticker.FormatStrFormatter(format)
- else:
- self.formatter = format # Assume it is a Formatter or None
- self.draw_all()
- def _extend_lower(self):
- """Return whether the lower limit is open ended."""
- return self.extend in ('both', 'min')
- def _extend_upper(self):
- """Return whether the upper limit is open ended."""
- return self.extend in ('both', 'max')
- def draw_all(self):
- """
- Calculate any free parameters based on the current cmap and norm,
- and do all the drawing.
- """
- # sets self._boundaries and self._values in real data units.
- # takes into account extend values:
- self._process_values()
- # sets self.vmin and vmax in data units, but just for the part of the
- # colorbar that is not part of the extend patch:
- self._find_range()
- # returns the X and Y mesh, *but* this was/is in normalized units:
- X, Y = self._mesh()
- C = self._values[:, np.newaxis]
- self._config_axis() # Inline it after deprecation elapses.
- # Configure axes limits, patch, and outline.
- xy = self._outline(X, Y)
- xmin, ymin = xy.min(axis=0)
- xmax, ymax = xy.max(axis=0)
- self.ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax))
- self.outline.set_xy(xy)
- self.patch.set_xy(xy)
- self.update_ticks()
- if self.filled:
- self._add_solids(X, Y, C)
- @cbook.deprecated("3.3")
- def config_axis(self):
- self._config_axis()
- def _config_axis(self):
- """Set up long and short axis."""
- ax = self.ax
- if self.orientation == 'vertical':
- long_axis, short_axis = ax.yaxis, ax.xaxis
- if mpl.rcParams['ytick.minor.visible']:
- self.minorticks_on()
- else:
- long_axis, short_axis = ax.xaxis, ax.yaxis
- if mpl.rcParams['xtick.minor.visible']:
- self.minorticks_on()
- long_axis.set(label_position=self.ticklocation,
- ticks_position=self.ticklocation)
- short_axis.set_ticks([])
- short_axis.set_ticks([], minor=True)
- self._set_label()
- def _get_ticker_locator_formatter(self):
- """
- Return the ``locator`` and ``formatter`` of the colorbar.
- If they have not been defined (i.e. are *None*), suitable formatter
- and locator instances will be created, attached to the respective
- attributes and returned.
- """
- locator = self.locator
- formatter = self.formatter
- if locator is None:
- if self.boundaries is None:
- if isinstance(self.norm, colors.NoNorm):
- nv = len(self._values)
- base = 1 + int(nv / 10)
- locator = ticker.IndexLocator(base=base, offset=0)
- elif isinstance(self.norm, colors.BoundaryNorm):
- b = self.norm.boundaries
- locator = ticker.FixedLocator(b, nbins=10)
- elif isinstance(self.norm, colors.LogNorm):
- locator = _ColorbarLogLocator(self)
- elif isinstance(self.norm, colors.SymLogNorm):
- # The subs setting here should be replaced
- # by logic in the locator.
- locator = ticker.SymmetricalLogLocator(
- subs=np.arange(1, 10),
- linthresh=self.norm.linthresh,
- base=10)
- else:
- if mpl.rcParams['_internal.classic_mode']:
- locator = ticker.MaxNLocator()
- else:
- locator = _ColorbarAutoLocator(self)
- else:
- b = self._boundaries[self._inside]
- locator = ticker.FixedLocator(b, nbins=10)
- if formatter is None:
- if isinstance(self.norm, colors.LogNorm):
- formatter = ticker.LogFormatterSciNotation()
- elif isinstance(self.norm, colors.SymLogNorm):
- formatter = ticker.LogFormatterSciNotation(
- linthresh=self.norm.linthresh)
- else:
- formatter = ticker.ScalarFormatter()
- else:
- formatter = self.formatter
- self.locator = locator
- self.formatter = formatter
- _log.debug('locator: %r', locator)
- return locator, formatter
- def _use_auto_colorbar_locator(self):
- """
- Return if we should use an adjustable tick locator or a fixed
- one. (check is used twice so factored out here...)
- """
- contouring = self.boundaries is not None and self.spacing == 'uniform'
- return (type(self.norm) in [colors.Normalize, colors.LogNorm] and
- not contouring)
- def _reset_locator_formatter_scale(self):
- """
- Reset the locator et al to defaults. Any user-hardcoded changes
- need to be re-entered if this gets called (either at init, or when
- the mappable normal gets changed: Colorbar.update_normal)
- """
- self.locator = None
- self.formatter = None
- if isinstance(self.norm, colors.LogNorm):
- # *both* axes are made log so that determining the
- # mid point is easier.
- self.ax.set_xscale('log')
- self.ax.set_yscale('log')
- self.minorticks_on()
- self.__scale = 'log'
- else:
- self.ax.set_xscale('linear')
- self.ax.set_yscale('linear')
- if type(self.norm) is colors.Normalize:
- self.__scale = 'linear'
- else:
- self.__scale = 'manual'
- def update_ticks(self):
- """
- Force the update of the ticks and ticklabels. This must be
- called whenever the tick locator and/or tick formatter changes.
- """
- ax = self.ax
- # Get the locator and formatter; defaults to self.locator if not None.
- locator, formatter = self._get_ticker_locator_formatter()
- long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis
- if self._use_auto_colorbar_locator():
- _log.debug('Using auto colorbar locator %r on colorbar', locator)
- long_axis.set_major_locator(locator)
- long_axis.set_major_formatter(formatter)
- else:
- _log.debug('Using fixed locator on colorbar')
- ticks, ticklabels, offset_string = self._ticker(locator, formatter)
- long_axis.set_ticks(ticks)
- long_axis.set_ticklabels(ticklabels)
- long_axis.get_major_formatter().set_offset_string(offset_string)
- def set_ticks(self, ticks, update_ticks=True):
- """
- Set tick locations.
- Parameters
- ----------
- ticks : array-like or `~matplotlib.ticker.Locator` or None
- The tick positions can be hard-coded by an array of values; or
- they can be defined by a `.Locator`. Setting to *None* reverts
- to using a default locator.
- update_ticks : bool, default: True
- If True, tick locations are updated immediately. If False, the
- user has to call `update_ticks` later to update the ticks.
- """
- if np.iterable(ticks):
- self.locator = ticker.FixedLocator(ticks, nbins=len(ticks))
- else:
- self.locator = ticks
- if update_ticks:
- self.update_ticks()
- self.stale = True
- def get_ticks(self, minor=False):
- """Return the x ticks as a list of locations."""
- if self._manual_tick_data_values is None:
- ax = self.ax
- long_axis = (
- ax.yaxis if self.orientation == 'vertical' else ax.xaxis)
- return long_axis.get_majorticklocs()
- else:
- # We made the axes manually, the old way, and the ylim is 0-1,
- # so the majorticklocs are in those units, not data units.
- return self._manual_tick_data_values
- def set_ticklabels(self, ticklabels, update_ticks=True):
- """
- Set tick labels.
- Tick labels are updated immediately unless *update_ticks* is *False*,
- in which case one should call `.update_ticks` explicitly.
- """
- if isinstance(self.locator, ticker.FixedLocator):
- self.formatter = ticker.FixedFormatter(ticklabels)
- if update_ticks:
- self.update_ticks()
- else:
- cbook._warn_external("set_ticks() must have been called.")
- self.stale = True
- def minorticks_on(self):
- """
- Turn the minor ticks of the colorbar on without extruding
- into the "extend regions".
- """
- ax = self.ax
- long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis
- if long_axis.get_scale() == 'log':
- long_axis.set_minor_locator(_ColorbarLogLocator(self, base=10.,
- subs='auto'))
- long_axis.set_minor_formatter(ticker.LogFormatterSciNotation())
- else:
- long_axis.set_minor_locator(_ColorbarAutoMinorLocator(self))
- def minorticks_off(self):
- """Turn the minor ticks of the colorbar off."""
- ax = self.ax
- long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis
- long_axis.set_minor_locator(ticker.NullLocator())
- def _set_label(self):
- if self.orientation == 'vertical':
- self.ax.set_ylabel(self._label, **self._labelkw)
- else:
- self.ax.set_xlabel(self._label, **self._labelkw)
- self.stale = True
- def set_label(self, label, *, loc=None, **kwargs):
- """Add a label to the long axis of the colorbar."""
- _pos_xy = 'y' if self.orientation == 'vertical' else 'x'
- _protected_kw = [_pos_xy, 'horizontalalignment', 'ha']
- if any([k in kwargs for k in _protected_kw]):
- if loc is not None:
- raise TypeError(f'Specifying *loc* is disallowed when any of '
- f'its corresponding low level keyword '
- f'arguments {_protected_kw} are also supplied')
- loc = 'center'
- else:
- if loc is None:
- loc = mpl.rcParams['%saxis.labellocation' % _pos_xy]
- if self.orientation == 'vertical':
- cbook._check_in_list(('bottom', 'center', 'top'), loc=loc)
- else:
- cbook._check_in_list(('left', 'center', 'right'), loc=loc)
- if loc in ['right', 'top']:
- kwargs[_pos_xy] = 1.
- kwargs['horizontalalignment'] = 'right'
- elif loc in ['left', 'bottom']:
- kwargs[_pos_xy] = 0.
- kwargs['horizontalalignment'] = 'left'
- self._label = label
- self._labelkw = kwargs
- self._set_label()
- def _outline(self, X, Y):
- """
- Return *x*, *y* arrays of colorbar bounding polygon,
- taking orientation into account.
- """
- N = X.shape[0]
- ii = [0, 1, N - 2, N - 1, 2 * N - 1, 2 * N - 2, N + 1, N, 0]
- x = X.T.reshape(-1)[ii]
- y = Y.T.reshape(-1)[ii]
- return (np.column_stack([y, x])
- if self.orientation == 'horizontal' else
- np.column_stack([x, y]))
- def _edges(self, X, Y):
- """Return the separator line segments; helper for _add_solids."""
- N = X.shape[0]
- # Using the non-array form of these line segments is much
- # simpler than making them into arrays.
- if self.orientation == 'vertical':
- return [list(zip(X[i], Y[i])) for i in range(1, N - 1)]
- else:
- return [list(zip(Y[i], X[i])) for i in range(1, N - 1)]
- def _add_solids(self, X, Y, C):
- """
- Draw the colors using `~.axes.Axes.pcolormesh`;
- optionally add separators.
- """
- if C.shape[0] == Y.shape[0]:
- # trim the last one to be compatible with old behavior.
- C = C[:-1]
- if self.orientation == 'vertical':
- args = (X, Y, C)
- else:
- args = (np.transpose(Y), np.transpose(X), np.transpose(C))
- kw = dict(cmap=self.cmap,
- norm=self.norm,
- alpha=self.alpha,
- edgecolors='None')
- _log.debug('Setting pcolormesh')
- col = self.ax.pcolormesh(*args, **kw, shading='flat')
- # self.add_observer(col) # We should observe, not be observed...
- if self.solids is not None:
- self.solids.remove()
- self.solids = col
- if self.dividers is not None:
- self.dividers.remove()
- self.dividers = None
- if self.drawedges:
- linewidths = (0.5 * mpl.rcParams['axes.linewidth'],)
- self.dividers = collections.LineCollection(
- self._edges(X, Y),
- colors=(mpl.rcParams['axes.edgecolor'],),
- linewidths=linewidths)
- self.ax.add_collection(self.dividers)
- elif len(self._y) >= self.n_rasterize:
- self.solids.set_rasterized(True)
- def add_lines(self, levels, colors, linewidths, erase=True):
- """
- Draw lines on the colorbar.
- The lines are appended to the list :attr:`lines`.
- Parameters
- ----------
- levels : array-like
- The positions of the lines.
- colors : color or list of colors
- Either a single color applying to all lines or one color value for
- each line.
- linewidths : float or array-like
- Either a single linewidth applying to all lines or one linewidth
- for each line.
- erase : bool, default: True
- Whether to remove any previously added lines.
- """
- y = self._locate(levels)
- rtol = (self._y[-1] - self._y[0]) * 1e-10
- igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol)
- y = y[igood]
- if np.iterable(colors):
- colors = np.asarray(colors)[igood]
- if np.iterable(linewidths):
- linewidths = np.asarray(linewidths)[igood]
- X, Y = np.meshgrid([self._y[0], self._y[-1]], y)
- if self.orientation == 'vertical':
- xy = np.stack([X, Y], axis=-1)
- else:
- xy = np.stack([Y, X], axis=-1)
- col = collections.LineCollection(xy, linewidths=linewidths)
- if erase and self.lines:
- for lc in self.lines:
- lc.remove()
- self.lines = []
- self.lines.append(col)
- col.set_color(colors)
- self.ax.add_collection(col)
- self.stale = True
- def _ticker(self, locator, formatter):
- """
- Return the sequence of ticks (colorbar data locations),
- ticklabels (strings), and the corresponding offset string.
- """
- if isinstance(self.norm, colors.NoNorm) and self.boundaries is None:
- intv = self._values[0], self._values[-1]
- else:
- intv = self.vmin, self.vmax
- locator.create_dummy_axis(minpos=intv[0])
- formatter.create_dummy_axis(minpos=intv[0])
- locator.set_view_interval(*intv)
- locator.set_data_interval(*intv)
- formatter.set_view_interval(*intv)
- formatter.set_data_interval(*intv)
- b = np.array(locator())
- if isinstance(locator, ticker.LogLocator):
- eps = 1e-10
- b = b[(b <= intv[1] * (1 + eps)) & (b >= intv[0] * (1 - eps))]
- else:
- eps = (intv[1] - intv[0]) * 1e-10
- b = b[(b <= intv[1] + eps) & (b >= intv[0] - eps)]
- self._manual_tick_data_values = b
- ticks = self._locate(b)
- ticklabels = formatter.format_ticks(b)
- offset_string = formatter.get_offset()
- return ticks, ticklabels, offset_string
- def _process_values(self, b=None):
- """
- Set the :attr:`_boundaries` and :attr:`_values` attributes
- based on the input boundaries and values. Input boundaries
- can be *self.boundaries* or the argument *b*.
- """
- if b is None:
- b = self.boundaries
- if b is not None:
- self._boundaries = np.asarray(b, dtype=float)
- if self.values is None:
- self._values = 0.5 * (self._boundaries[:-1]
- + self._boundaries[1:])
- if isinstance(self.norm, colors.NoNorm):
- self._values = (self._values + 0.00001).astype(np.int16)
- else:
- self._values = np.array(self.values)
- return
- if self.values is not None:
- self._values = np.array(self.values)
- if self.boundaries is None:
- b = np.zeros(len(self.values) + 1)
- b[1:-1] = 0.5 * (self._values[:-1] + self._values[1:])
- b[0] = 2.0 * b[1] - b[2]
- b[-1] = 2.0 * b[-2] - b[-3]
- self._boundaries = b
- return
- self._boundaries = np.array(self.boundaries)
- return
- # Neither boundaries nor values are specified;
- # make reasonable ones based on cmap and norm.
- if isinstance(self.norm, colors.NoNorm):
- b = self._uniform_y(self.cmap.N + 1) * self.cmap.N - 0.5
- v = np.zeros(len(b) - 1, dtype=np.int16)
- v[self._inside] = np.arange(self.cmap.N, dtype=np.int16)
- if self._extend_lower():
- v[0] = -1
- if self._extend_upper():
- v[-1] = self.cmap.N
- self._boundaries = b
- self._values = v
- return
- elif isinstance(self.norm, colors.BoundaryNorm):
- b = list(self.norm.boundaries)
- if self._extend_lower():
- b = [b[0] - 1] + b
- if self._extend_upper():
- b = b + [b[-1] + 1]
- b = np.array(b)
- v = np.zeros(len(b) - 1)
- bi = self.norm.boundaries
- v[self._inside] = 0.5 * (bi[:-1] + bi[1:])
- if self._extend_lower():
- v[0] = b[0] - 1
- if self._extend_upper():
- v[-1] = b[-1] + 1
- self._boundaries = b
- self._values = v
- return
- else:
- if not self.norm.scaled():
- self.norm.vmin = 0
- self.norm.vmax = 1
- self.norm.vmin, self.norm.vmax = mtransforms.nonsingular(
- self.norm.vmin,
- self.norm.vmax,
- expander=0.1)
- b = self.norm.inverse(self._uniform_y(self.cmap.N + 1))
- if isinstance(self.norm, (colors.PowerNorm, colors.LogNorm)):
- # If using a lognorm or powernorm, ensure extensions don't
- # go negative
- if self._extend_lower():
- b[0] = 0.9 * b[0]
- if self._extend_upper():
- b[-1] = 1.1 * b[-1]
- else:
- if self._extend_lower():
- b[0] = b[0] - 1
- if self._extend_upper():
- b[-1] = b[-1] + 1
- self._process_values(b)
- def _find_range(self):
- """
- Set :attr:`vmin` and :attr:`vmax` attributes to the first and
- last boundary excluding extended end boundaries.
- """
- b = self._boundaries[self._inside]
- self.vmin = b[0]
- self.vmax = b[-1]
- def _central_N(self):
- """Return the number of boundaries excluding end extensions."""
- nb = len(self._boundaries)
- if self.extend == 'both':
- nb -= 2
- elif self.extend in ('min', 'max'):
- nb -= 1
- return nb
- def _extended_N(self):
- """
- Based on the colormap and extend variable, return the
- number of boundaries.
- """
- N = self.cmap.N + 1
- if self.extend == 'both':
- N += 2
- elif self.extend in ('min', 'max'):
- N += 1
- return N
- def _get_extension_lengths(self, frac, automin, automax, default=0.05):
- """
- Return the lengths of colorbar extensions.
- This is a helper method for _uniform_y and _proportional_y.
- """
- # Set the default value.
- extendlength = np.array([default, default])
- if isinstance(frac, str):
- cbook._check_in_list(['auto'], extendfrac=frac.lower())
- # Use the provided values when 'auto' is required.
- extendlength[:] = [automin, automax]
- elif frac is not None:
- try:
- # Try to set min and max extension fractions directly.
- extendlength[:] = frac
- # If frac is a sequence containing None then NaN may
- # be encountered. This is an error.
- if np.isnan(extendlength).any():
- raise ValueError()
- except (TypeError, ValueError) as err:
- # Raise an error on encountering an invalid value for frac.
- raise ValueError('invalid value for extendfrac') from err
- return extendlength
- def _uniform_y(self, N):
- """
- Return colorbar data coordinates for *N* uniformly
- spaced boundaries, plus ends if required.
- """
- if self.extend == 'neither':
- y = np.linspace(0, 1, N)
- else:
- automin = automax = 1. / (N - 1.)
- extendlength = self._get_extension_lengths(self.extendfrac,
- automin, automax,
- default=0.05)
- if self.extend == 'both':
- y = np.zeros(N + 2, 'd')
- y[0] = 0. - extendlength[0]
- y[-1] = 1. + extendlength[1]
- elif self.extend == 'min':
- y = np.zeros(N + 1, 'd')
- y[0] = 0. - extendlength[0]
- else:
- y = np.zeros(N + 1, 'd')
- y[-1] = 1. + extendlength[1]
- y[self._inside] = np.linspace(0, 1, N)
- return y
- def _proportional_y(self):
- """
- Return colorbar data coordinates for the boundaries of
- a proportional colorbar.
- """
- if isinstance(self.norm, colors.BoundaryNorm):
- y = (self._boundaries - self._boundaries[0])
- y = y / (self._boundaries[-1] - self._boundaries[0])
- else:
- y = self.norm(self._boundaries.copy())
- y = np.ma.filled(y, np.nan)
- if self.extend == 'min':
- # Exclude leftmost interval of y.
- clen = y[-1] - y[1]
- automin = (y[2] - y[1]) / clen
- automax = (y[-1] - y[-2]) / clen
- elif self.extend == 'max':
- # Exclude rightmost interval in y.
- clen = y[-2] - y[0]
- automin = (y[1] - y[0]) / clen
- automax = (y[-2] - y[-3]) / clen
- elif self.extend == 'both':
- # Exclude leftmost and rightmost intervals in y.
- clen = y[-2] - y[1]
- automin = (y[2] - y[1]) / clen
- automax = (y[-2] - y[-3]) / clen
- if self.extend in ('both', 'min', 'max'):
- extendlength = self._get_extension_lengths(self.extendfrac,
- automin, automax,
- default=0.05)
- if self.extend in ('both', 'min'):
- y[0] = 0. - extendlength[0]
- if self.extend in ('both', 'max'):
- y[-1] = 1. + extendlength[1]
- yi = y[self._inside]
- norm = colors.Normalize(yi[0], yi[-1])
- y[self._inside] = np.ma.filled(norm(yi), np.nan)
- return y
- def _mesh(self):
- """
- Return ``(X, Y)``, the coordinate arrays for the colorbar pcolormesh.
- These are suitable for a vertical colorbar; swapping and transposition
- for a horizontal colorbar are done outside this function.
- These are scaled between vmin and vmax.
- """
- # copy the norm and change the vmin and vmax to the vmin and
- # vmax of the colorbar, not the norm. This allows the situation
- # where the colormap has a narrower range than the colorbar, to
- # accommodate extra contours:
- norm = copy.copy(self.norm)
- norm.vmin = self.vmin
- norm.vmax = self.vmax
- x = np.array([0.0, 1.0])
- if self.spacing == 'uniform':
- y = self._uniform_y(self._central_N())
- else:
- y = self._proportional_y()
- xmid = np.array([0.5])
- if self.__scale != 'manual':
- y = norm.inverse(y)
- x = norm.inverse(x)
- xmid = norm.inverse(xmid)
- else:
- # if a norm doesn't have a named scale, or
- # we are not using a norm
- dv = self.vmax - self.vmin
- x = x * dv + self.vmin
- y = y * dv + self.vmin
- xmid = xmid * dv + self.vmin
- self._y = y
- X, Y = np.meshgrid(x, y)
- if self._extend_lower() and not self.extendrect:
- X[0, :] = xmid
- if self._extend_upper() and not self.extendrect:
- X[-1, :] = xmid
- return X, Y
- def _locate(self, x):
- """
- Given a set of color data values, return their
- corresponding colorbar data coordinates.
- """
- if isinstance(self.norm, (colors.NoNorm, colors.BoundaryNorm)):
- b = self._boundaries
- xn = x
- else:
- # Do calculations using normalized coordinates so
- # as to make the interpolation more accurate.
- b = self.norm(self._boundaries, clip=False).filled()
- xn = self.norm(x, clip=False).filled()
- bunique = b
- yunique = self._y
- # trim extra b values at beginning and end if they are
- # not unique. These are here for extended colorbars, and are not
- # wanted for the interpolation.
- if b[0] == b[1]:
- bunique = bunique[1:]
- yunique = yunique[1:]
- if b[-1] == b[-2]:
- bunique = bunique[:-1]
- yunique = yunique[:-1]
- z = np.interp(xn, bunique, yunique)
- return z
- def set_alpha(self, alpha):
- """Set the transparency between 0 (transparent) and 1 (opaque)."""
- self.alpha = alpha
- def remove(self):
- """Remove this colorbar from the figure."""
- self.ax.remove()
- def _add_disjoint_kwargs(d, **kwargs):
- """
- Update dict *d* with entries in *kwargs*, which must be absent from *d*.
- """
- for k, v in kwargs.items():
- if k in d:
- cbook.warn_deprecated(
- "3.3", message=f"The {k!r} parameter to Colorbar has no "
- "effect because it is overridden by the mappable; it is "
- "deprecated since %(since)s and will be removed %(removal)s.")
- d[k] = v
- class Colorbar(ColorbarBase):
- """
- This class connects a `ColorbarBase` to a `~.cm.ScalarMappable`
- such as an `~.image.AxesImage` generated via `~.axes.Axes.imshow`.
- .. note::
- This class is not intended to be instantiated directly; instead, use
- `.Figure.colorbar` or `.pyplot.colorbar` to create a colorbar.
- """
- def __init__(self, ax, mappable, **kwargs):
- # Ensure the given mappable's norm has appropriate vmin and vmax set
- # even if mappable.draw has not yet been called.
- if mappable.get_array() is not None:
- mappable.autoscale_None()
- self.mappable = mappable
- _add_disjoint_kwargs(kwargs, cmap=mappable.cmap, norm=mappable.norm)
- if isinstance(mappable, contour.ContourSet):
- cs = mappable
- _add_disjoint_kwargs(
- kwargs,
- alpha=cs.get_alpha(),
- boundaries=cs._levels,
- values=cs.cvalues,
- extend=cs.extend,
- filled=cs.filled,
- )
- kwargs.setdefault(
- 'ticks', ticker.FixedLocator(cs.levels, nbins=10))
- ColorbarBase.__init__(self, ax, **kwargs)
- if not cs.filled:
- self.add_lines(cs)
- else:
- if getattr(mappable.cmap, 'colorbar_extend', False) is not False:
- kwargs.setdefault('extend', mappable.cmap.colorbar_extend)
- if isinstance(mappable, martist.Artist):
- _add_disjoint_kwargs(kwargs, alpha=mappable.get_alpha())
- ColorbarBase.__init__(self, ax, **kwargs)
- @cbook.deprecated("3.3", alternative="update_normal")
- def on_mappable_changed(self, mappable):
- """
- Update this colorbar to match the mappable's properties.
- Typically this is automatically registered as an event handler
- by :func:`colorbar_factory` and should not be called manually.
- """
- _log.debug('colorbar mappable changed')
- self.update_normal(mappable)
- def add_lines(self, CS, erase=True):
- """
- Add the lines from a non-filled `~.contour.ContourSet` to the colorbar.
- Parameters
- ----------
- CS : `~.contour.ContourSet`
- The line positions are taken from the ContourSet levels. The
- ContourSet must not be filled.
- erase : bool, default: True
- Whether to remove any previously added lines.
- """
- if not isinstance(CS, contour.ContourSet) or CS.filled:
- raise ValueError('add_lines is only for a ContourSet of lines')
- tcolors = [c[0] for c in CS.tcolors]
- tlinewidths = [t[0] for t in CS.tlinewidths]
- # The following was an attempt to get the colorbar lines
- # to follow subsequent changes in the contour lines,
- # but more work is needed: specifically, a careful
- # look at event sequences, and at how
- # to make one object track another automatically.
- #tcolors = [col.get_colors()[0] for col in CS.collections]
- #tlinewidths = [col.get_linewidth()[0] for lw in CS.collections]
- ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths,
- erase=erase)
- def update_normal(self, mappable):
- """
- Update solid patches, lines, etc.
- This is meant to be called when the norm of the image or contour plot
- to which this colorbar belongs changes.
- If the norm on the mappable is different than before, this resets the
- locator and formatter for the axis, so if these have been customized,
- they will need to be customized again. However, if the norm only
- changes values of *vmin*, *vmax* or *cmap* then the old formatter
- and locator will be preserved.
- """
- _log.debug('colorbar update normal %r %r', mappable.norm, self.norm)
- self.mappable = mappable
- self.set_alpha(mappable.get_alpha())
- self.cmap = mappable.cmap
- if mappable.norm != self.norm:
- self.norm = mappable.norm
- self._reset_locator_formatter_scale()
- self.draw_all()
- if isinstance(self.mappable, contour.ContourSet):
- CS = self.mappable
- if not CS.filled:
- self.add_lines(CS)
- self.stale = True
- @cbook.deprecated("3.3", alternative="update_normal")
- def update_bruteforce(self, mappable):
- """
- Destroy and rebuild the colorbar. This is
- intended to become obsolete, and will probably be
- deprecated and then removed. It is not called when
- the pyplot.colorbar function or the Figure.colorbar
- method are used to create the colorbar.
- """
- # We are using an ugly brute-force method: clearing and
- # redrawing the whole thing. The problem is that if any
- # properties have been changed by methods other than the
- # colorbar methods, those changes will be lost.
- self.ax.cla()
- self.locator = None
- self.formatter = None
- # clearing the axes will delete outline, patch, solids, and lines:
- self.outline = mpatches.Polygon(
- np.empty((0, 2)),
- edgecolor=mpl.rcParams['axes.edgecolor'], facecolor='none',
- linewidth=mpl.rcParams['axes.linewidth'], closed=True, zorder=2)
- self.ax.add_artist(self.outline)
- self.outline.set(clip_box=None, clip_path=None)
- self.patch = mpatches.Polygon(
- np.empty((0, 2)),
- color=mpl.rcParams['axes.facecolor'], linewidth=0.01, zorder=-1)
- self.ax.add_artist(self.patch)
- self.solids = None
- self.lines = []
- self.dividers = None
- self.update_normal(mappable)
- self.draw_all()
- if isinstance(self.mappable, contour.ContourSet):
- CS = self.mappable
- if not CS.filled:
- self.add_lines(CS)
- #if self.lines is not None:
- # tcolors = [c[0] for c in CS.tcolors]
- # self.lines.set_color(tcolors)
- #Fixme? Recalculate boundaries, ticks if vmin, vmax have changed.
- #Fixme: Some refactoring may be needed; we should not
- # be recalculating everything if there was a simple alpha
- # change.
- def remove(self):
- """
- Remove this colorbar from the figure.
- If the colorbar was created with ``use_gridspec=True`` the previous
- gridspec is restored.
- """
- ColorbarBase.remove(self)
- self.mappable.callbacksSM.disconnect(self.mappable.colorbar_cid)
- self.mappable.colorbar = None
- self.mappable.colorbar_cid = None
- try:
- ax = self.mappable.axes
- except AttributeError:
- return
- try:
- gs = ax.get_subplotspec().get_gridspec()
- subplotspec = gs.get_topmost_subplotspec()
- except AttributeError:
- # use_gridspec was False
- pos = ax.get_position(original=True)
- ax._set_position(pos)
- else:
- # use_gridspec was True
- ax.set_subplotspec(subplotspec)
- @docstring.Substitution(_make_axes_param_doc, _make_axes_other_param_doc)
- def make_axes(parents, location=None, orientation=None, fraction=0.15,
- shrink=1.0, aspect=20, **kw):
- """
- Create an `~.axes.Axes` suitable for a colorbar.
- The axes is placed in the figure of the *parents* axes, by resizing and
- repositioning *parents*.
- Parameters
- ----------
- parents : `~.axes.Axes` or list of `~.axes.Axes`
- The Axes to use as parents for placing the colorbar.
- location : None or {'left', 'right', 'top', 'bottom'}
- The position, relative to *parents*, where the colorbar axes
- should be created. If None, the value will either come from the
- given ``orientation``, else it will default to 'right'.
- orientation : None or {'vertical', 'horizontal'}
- The orientation of the colorbar. Typically, this keyword shouldn't
- be used, as it can be derived from the ``location`` keyword.
- %s
- Returns
- -------
- cax : `~.axes.Axes`
- The child axes.
- kw : dict
- The reduced keyword dictionary to be passed when creating the colorbar
- instance.
- Other Parameters
- ----------------
- %s
- """
- locations = ["left", "right", "top", "bottom"]
- if orientation is not None and location is not None:
- raise TypeError('position and orientation are mutually exclusive. '
- 'Consider setting the position to any of {}'
- .format(', '.join(locations)))
- # provide a default location
- if location is None and orientation is None:
- location = 'right'
- # allow the user to not specify the location by specifying the
- # orientation instead
- if location is None:
- location = 'right' if orientation == 'vertical' else 'bottom'
- cbook._check_in_list(locations, location=location)
- default_location_settings = {'left': {'anchor': (1.0, 0.5),
- 'panchor': (0.0, 0.5),
- 'pad': 0.10,
- 'orientation': 'vertical'},
- 'right': {'anchor': (0.0, 0.5),
- 'panchor': (1.0, 0.5),
- 'pad': 0.05,
- 'orientation': 'vertical'},
- 'top': {'anchor': (0.5, 0.0),
- 'panchor': (0.5, 1.0),
- 'pad': 0.05,
- 'orientation': 'horizontal'},
- 'bottom': {'anchor': (0.5, 1.0),
- 'panchor': (0.5, 0.0),
- 'pad': 0.15, # backwards compat
- 'orientation': 'horizontal'},
- }
- loc_settings = default_location_settings[location]
- # put appropriate values into the kw dict for passing back to
- # the Colorbar class
- kw['orientation'] = loc_settings['orientation']
- kw['ticklocation'] = location
- anchor = kw.pop('anchor', loc_settings['anchor'])
- parent_anchor = kw.pop('panchor', loc_settings['panchor'])
- parents_iterable = np.iterable(parents)
- # turn parents into a list if it is not already. We do this w/ np
- # because `plt.subplots` can return an ndarray and is natural to
- # pass to `colorbar`.
- parents = np.atleast_1d(parents).ravel()
- # check if using constrained_layout:
- try:
- gs = parents[0].get_subplotspec().get_gridspec()
- using_constrained_layout = (gs._layoutbox is not None)
- except AttributeError:
- using_constrained_layout = False
- # defaults are not appropriate for constrained_layout:
- pad0 = loc_settings['pad']
- if using_constrained_layout:
- pad0 = 0.02
- pad = kw.pop('pad', pad0)
- fig = parents[0].get_figure()
- if not all(fig is ax.get_figure() for ax in parents):
- raise ValueError('Unable to create a colorbar axes as not all '
- 'parents share the same figure.')
- # take a bounding box around all of the given axes
- parents_bbox = mtransforms.Bbox.union(
- [ax.get_position(original=True).frozen() for ax in parents])
- pb = parents_bbox
- if location in ('left', 'right'):
- if location == 'left':
- pbcb, _, pb1 = pb.splitx(fraction, fraction + pad)
- else:
- pb1, _, pbcb = pb.splitx(1 - fraction - pad, 1 - fraction)
- pbcb = pbcb.shrunk(1.0, shrink).anchored(anchor, pbcb)
- else:
- if location == 'bottom':
- pbcb, _, pb1 = pb.splity(fraction, fraction + pad)
- else:
- pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction)
- pbcb = pbcb.shrunk(shrink, 1.0).anchored(anchor, pbcb)
- # define the aspect ratio in terms of y's per x rather than x's per y
- aspect = 1.0 / aspect
- # define a transform which takes us from old axes coordinates to
- # new axes coordinates
- shrinking_trans = mtransforms.BboxTransform(parents_bbox, pb1)
- # transform each of the axes in parents using the new transform
- for ax in parents:
- new_posn = shrinking_trans.transform(ax.get_position(original=True))
- new_posn = mtransforms.Bbox(new_posn)
- ax._set_position(new_posn)
- if parent_anchor is not False:
- ax.set_anchor(parent_anchor)
- cax = fig.add_axes(pbcb, label="<colorbar>")
- # OK, now make a layoutbox for the cb axis. Later, we will use this
- # to make the colorbar fit nicely.
- if not using_constrained_layout:
- # no layout boxes:
- lb = None
- lbpos = None
- # and we need to set the aspect ratio by hand...
- cax.set_aspect(aspect, anchor=anchor, adjustable='box')
- else:
- if not parents_iterable:
- # this is a single axis...
- ax = parents[0]
- lb, lbpos = constrained_layout.layoutcolorbarsingle(
- ax, cax, shrink, aspect, location, pad=pad)
- else: # there is more than one parent, so lets use gridspec
- # the colorbar will be a sibling of this gridspec, so the
- # parent is the same parent as the gridspec. Either the figure,
- # or a subplotspec.
- lb, lbpos = constrained_layout.layoutcolorbargridspec(
- parents, cax, shrink, aspect, location, pad)
- cax._layoutbox = lb
- cax._poslayoutbox = lbpos
- return cax, kw
- @docstring.Substitution(_make_axes_param_doc, _make_axes_other_param_doc)
- def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
- """
- Create a `~.SubplotBase` suitable for a colorbar.
- The axes is placed in the figure of the *parent* axes, by resizing and
- repositioning *parent*.
- This function is similar to `.make_axes`. Primary differences are
- - `.make_axes_gridspec` only handles the *orientation* keyword
- and cannot handle the *location* keyword.
- - `.make_axes_gridspec` should only be used with a `.SubplotBase` parent.
- - `.make_axes` creates an `~.axes.Axes`; `.make_axes_gridspec` creates a
- `.SubplotBase`.
- - `.make_axes` updates the position of the parent. `.make_axes_gridspec`
- replaces the ``grid_spec`` attribute of the parent with a new one.
- While this function is meant to be compatible with `.make_axes`,
- there could be some minor differences.
- Parameters
- ----------
- parent : `~.axes.Axes`
- The Axes to use as parent for placing the colorbar.
- %s
- Returns
- -------
- cax : `~.axes.SubplotBase`
- The child axes.
- kw : dict
- The reduced keyword dictionary to be passed when creating the colorbar
- instance.
- Other Parameters
- ----------------
- orientation : {'vertical', 'horizontal'}, default: 'vertical'
- The orientation of the colorbar.
- %s
- """
- orientation = kw.setdefault('orientation', 'vertical')
- kw['ticklocation'] = 'auto'
- x1 = 1 - fraction
- # for shrinking
- pad_s = (1 - shrink) * 0.5
- wh_ratios = [pad_s, shrink, pad_s]
- # we need to none the tree of layoutboxes because
- # constrained_layout can't remove and replace the tree
- # hierarchy w/o a seg fault.
- gs = parent.get_subplotspec().get_gridspec()
- layoutbox.nonetree(gs._layoutbox)
- gs_from_subplotspec = gridspec.GridSpecFromSubplotSpec
- if orientation == 'vertical':
- pad = kw.pop('pad', 0.05)
- wh_space = 2 * pad / (1 - pad)
- gs = gs_from_subplotspec(1, 2,
- subplot_spec=parent.get_subplotspec(),
- wspace=wh_space,
- width_ratios=[x1 - pad, fraction])
- gs2 = gs_from_subplotspec(3, 1,
- subplot_spec=gs[1],
- hspace=0.,
- height_ratios=wh_ratios)
- anchor = (0.0, 0.5)
- panchor = (1.0, 0.5)
- else:
- pad = kw.pop('pad', 0.15)
- wh_space = 2 * pad / (1 - pad)
- gs = gs_from_subplotspec(2, 1,
- subplot_spec=parent.get_subplotspec(),
- hspace=wh_space,
- height_ratios=[x1 - pad, fraction])
- gs2 = gs_from_subplotspec(1, 3,
- subplot_spec=gs[1],
- wspace=0.,
- width_ratios=wh_ratios)
- aspect = 1 / aspect
- anchor = (0.5, 1.0)
- panchor = (0.5, 0.0)
- parent.set_subplotspec(gs[0])
- parent.update_params()
- parent._set_position(parent.figbox)
- parent.set_anchor(panchor)
- fig = parent.get_figure()
- cax = fig.add_subplot(gs2[1], label="<colorbar>")
- cax.set_aspect(aspect, anchor=anchor, adjustable='box')
- return cax, kw
- class ColorbarPatch(Colorbar):
- """
- A Colorbar that uses a list of `~.patches.Patch` instances rather than the
- default `~.collections.PatchCollection` created by `~.axes.Axes.pcolor`,
- because the latter does not allow the hatch pattern to vary among the
- members of the collection.
- """
- def __init__(self, ax, mappable, **kw):
- # we do not want to override the behaviour of solids
- # so add a new attribute which will be a list of the
- # colored patches in the colorbar
- self.solids_patches = []
- Colorbar.__init__(self, ax, mappable, **kw)
- def _add_solids(self, X, Y, C):
- """
- Draw the colors using `~matplotlib.patches.Patch`;
- optionally add separators.
- """
- n_segments = len(C)
- # ensure there are sufficient hatches
- hatches = self.mappable.hatches * n_segments
- patches = []
- for i in range(len(X) - 1):
- val = C[i][0]
- hatch = hatches[i]
- xy = np.array([[X[i][0], Y[i][0]],
- [X[i][1], Y[i][0]],
- [X[i + 1][1], Y[i + 1][0]],
- [X[i + 1][0], Y[i + 1][1]]])
- if self.orientation == 'horizontal':
- # if horizontal swap the xs and ys
- xy = xy[..., ::-1]
- patch = mpatches.PathPatch(mpath.Path(xy),
- facecolor=self.cmap(self.norm(val)),
- hatch=hatch, linewidth=0,
- antialiased=False, alpha=self.alpha)
- self.ax.add_patch(patch)
- patches.append(patch)
- if self.solids_patches:
- for solid in self.solids_patches:
- solid.remove()
- self.solids_patches = patches
- if self.dividers is not None:
- self.dividers.remove()
- self.dividers = None
- if self.drawedges:
- self.dividers = collections.LineCollection(
- self._edges(X, Y),
- colors=(mpl.rcParams['axes.edgecolor'],),
- linewidths=(0.5 * mpl.rcParams['axes.linewidth'],))
- self.ax.add_collection(self.dividers)
- def colorbar_factory(cax, mappable, **kwargs):
- """
- Create a colorbar on the given axes for the given mappable.
- .. note::
- This is a low-level function to turn an existing axes into a colorbar
- axes. Typically, you'll want to use `~.Figure.colorbar` instead, which
- automatically handles creation and placement of a suitable axes as
- well.
- Parameters
- ----------
- cax : `~matplotlib.axes.Axes`
- The `~.axes.Axes` to turn into a colorbar.
- mappable : `~matplotlib.cm.ScalarMappable`
- The mappable to be described by the colorbar.
- **kwargs
- Keyword arguments are passed to the respective colorbar class.
- Returns
- -------
- `.Colorbar` or `.ColorbarPatch`
- The created colorbar instance. `.ColorbarPatch` is only used if
- *mappable* is a `.ContourSet` with hatches.
- """
- # if the given mappable is a contourset with any hatching, use
- # ColorbarPatch else use Colorbar
- if (isinstance(mappable, contour.ContourSet)
- and any(hatch is not None for hatch in mappable.hatches)):
- cb = ColorbarPatch(cax, mappable, **kwargs)
- else:
- cb = Colorbar(cax, mappable, **kwargs)
- cid = mappable.callbacksSM.connect('changed', cb.update_normal)
- mappable.colorbar = cb
- mappable.colorbar_cid = cid
- return cb
|