colorbar.py 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740
  1. """
  2. Colorbars are a visualization of the mapping from scalar values to colors.
  3. In Matplotlib they are drawn into a dedicated `~.axes.Axes`.
  4. .. note::
  5. Colorbars are typically created through `.Figure.colorbar` or its pyplot
  6. wrapper `.pyplot.colorbar`, which use `.make_axes` and `.Colorbar`
  7. internally.
  8. As an end-user, you most likely won't have to call the methods or
  9. instantiate the classes in this module explicitly.
  10. :class:`ColorbarBase`
  11. The base class with full colorbar drawing functionality.
  12. It can be used as-is to make a colorbar for a given colormap;
  13. a mappable object (e.g., image) is not needed.
  14. :class:`Colorbar`
  15. On top of `.ColorbarBase` this connects the colorbar with a
  16. `.ScalarMappable` such as an image or contour plot.
  17. :class:`ColorbarPatch`
  18. A specialized `.Colorbar` to support hatched contour plots.
  19. :func:`make_axes`
  20. Create an `~.axes.Axes` suitable for a colorbar. This functions can be
  21. used with figures containing a single axes or with freely placed axes.
  22. :func:`make_axes_gridspec`
  23. Create a `~.SubplotBase` suitable for a colorbar. This function should
  24. be used for adding a colorbar to a `.GridSpec`.
  25. """
  26. import copy
  27. import logging
  28. import numpy as np
  29. import matplotlib as mpl
  30. import matplotlib.artist as martist
  31. import matplotlib.cbook as cbook
  32. import matplotlib.collections as collections
  33. import matplotlib.colors as colors
  34. import matplotlib.contour as contour
  35. import matplotlib.cm as cm
  36. import matplotlib.gridspec as gridspec
  37. import matplotlib.patches as mpatches
  38. import matplotlib.path as mpath
  39. import matplotlib.ticker as ticker
  40. import matplotlib.transforms as mtransforms
  41. import matplotlib._layoutbox as layoutbox
  42. import matplotlib._constrained_layout as constrained_layout
  43. from matplotlib import docstring
  44. _log = logging.getLogger(__name__)
  45. _make_axes_param_doc = """
  46. fraction : float, default: 0.15
  47. Fraction of original axes to use for colorbar.
  48. shrink : float, default: 1.0
  49. Fraction by which to multiply the size of the colorbar.
  50. aspect : float, default: 20
  51. Ratio of long to short dimensions.
  52. """
  53. _make_axes_other_param_doc = """
  54. pad : float, default: 0.05 if vertical, 0.15 if horizontal
  55. Fraction of original axes between colorbar and new image axes.
  56. anchor : (float, float), optional
  57. The anchor point of the colorbar axes.
  58. Defaults to (0.0, 0.5) if vertical; (0.5, 1.0) if horizontal.
  59. panchor : (float, float), or *False*, optional
  60. The anchor point of the colorbar parent axes. If *False*, the parent
  61. axes' anchor will be unchanged.
  62. Defaults to (1.0, 0.5) if vertical; (0.5, 0.0) if horizontal.
  63. """
  64. make_axes_kw_doc = _make_axes_param_doc + _make_axes_other_param_doc
  65. colormap_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, `~.ticker.ScalarFormatter` is used.
  102. If a format string is given, e.g., '%.3f', that is used.
  103. An alternative `~.ticker.Formatter` may be given instead.
  104. *drawedges* bool
  105. Whether to draw lines at color boundaries.
  106. *label* str
  107. The label on the colorbar's long axis.
  108. ============ ====================================================
  109. The following will probably be useful only in the context of
  110. indexed colors (that is, when the mappable has norm=NoNorm()),
  111. or other unusual circumstances.
  112. ============ ===================================================
  113. Property Description
  114. ============ ===================================================
  115. *boundaries* None or a sequence
  116. *values* None or a sequence which must be of length 1 less
  117. than the sequence of *boundaries*. For each region
  118. delimited by adjacent entries in *boundaries*, the
  119. color mapped to the corresponding value in values
  120. will be used.
  121. ============ ===================================================
  122. """
  123. colorbar_doc = """
  124. Add a colorbar to a plot.
  125. Function signatures for the :mod:`~matplotlib.pyplot` interface; all
  126. but the first are also method signatures for the `~.Figure.colorbar` method::
  127. colorbar(**kwargs)
  128. colorbar(mappable, **kwargs)
  129. colorbar(mappable, cax=cax, **kwargs)
  130. colorbar(mappable, ax=ax, **kwargs)
  131. Parameters
  132. ----------
  133. mappable
  134. The `matplotlib.cm.ScalarMappable` (i.e., `~matplotlib.image.AxesImage`,
  135. `~matplotlib.contour.ContourSet`, etc.) described by this colorbar.
  136. This argument is mandatory for the `.Figure.colorbar` method but optional
  137. for the `.pyplot.colorbar` function, which sets the default to the current
  138. image.
  139. Note that one can create a `.ScalarMappable` "on-the-fly" to generate
  140. colorbars not attached to a previously drawn artist, e.g. ::
  141. fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax)
  142. cax : `~matplotlib.axes.Axes`, optional
  143. Axes into which the colorbar will be drawn.
  144. ax : `~matplotlib.axes.Axes`, list of Axes, optional
  145. Parent axes from which space for a new colorbar axes will be stolen.
  146. If a list of axes is given they will all be resized to make room for the
  147. colorbar axes.
  148. use_gridspec : bool, optional
  149. If *cax* is ``None``, a new *cax* is created as an instance of Axes. If
  150. *ax* is an instance of Subplot and *use_gridspec* is ``True``, *cax* is
  151. created as an instance of Subplot using the :mod:`~.gridspec` module.
  152. Returns
  153. -------
  154. colorbar : `~matplotlib.colorbar.Colorbar`
  155. See also its base class, `~matplotlib.colorbar.ColorbarBase`.
  156. Notes
  157. -----
  158. Additional keyword arguments are of two kinds:
  159. axes properties:
  160. %s
  161. colorbar properties:
  162. %s
  163. If *mappable* is a `~.contour.ContourSet`, its *extend* kwarg is included
  164. automatically.
  165. The *shrink* kwarg provides a simple way to scale the colorbar with respect
  166. to the axes. Note that if *cax* is specified, it determines the size of the
  167. colorbar and *shrink* and *aspect* kwargs are ignored.
  168. For more precise control, you can manually specify the positions of
  169. the axes objects in which the mappable and the colorbar are drawn. In
  170. this case, do not use any of the axes properties kwargs.
  171. It is known that some vector graphics viewers (svg and pdf) renders white gaps
  172. between segments of the colorbar. This is due to bugs in the viewers, not
  173. Matplotlib. As a workaround, the colorbar can be rendered with overlapping
  174. segments::
  175. cbar = colorbar()
  176. cbar.solids.set_edgecolor("face")
  177. draw()
  178. However this has negative consequences in other circumstances, e.g. with
  179. semi-transparent images (alpha < 1) and colorbar extensions; therefore, this
  180. workaround is not used by default (see issue #1188).
  181. """ % (make_axes_kw_doc, colormap_kw_doc)
  182. docstring.interpd.update(colorbar_doc=colorbar_doc)
  183. def _set_ticks_on_axis_warn(*args, **kw):
  184. # a top level function which gets put in at the axes'
  185. # set_xticks and set_yticks by ColorbarBase.__init__.
  186. cbook._warn_external("Use the colorbar set_ticks() method instead.")
  187. class _ColorbarAutoLocator(ticker.MaxNLocator):
  188. """
  189. AutoLocator for Colorbar
  190. This locator is just a `.MaxNLocator` except the min and max are
  191. clipped by the norm's min and max (i.e. vmin/vmax from the
  192. image/pcolor/contour object). This is necessary so ticks don't
  193. extrude into the "extend regions".
  194. """
  195. def __init__(self, colorbar):
  196. """
  197. This ticker needs to know the *colorbar* so that it can access
  198. its *vmin* and *vmax*. Otherwise it is the same as
  199. `~.ticker.AutoLocator`.
  200. """
  201. self._colorbar = colorbar
  202. nbins = 'auto'
  203. steps = [1, 2, 2.5, 5, 10]
  204. super().__init__(nbins=nbins, steps=steps)
  205. def tick_values(self, vmin, vmax):
  206. # flip if needed:
  207. if vmin > vmax:
  208. vmin, vmax = vmax, vmin
  209. vmin = max(vmin, self._colorbar.norm.vmin)
  210. vmax = min(vmax, self._colorbar.norm.vmax)
  211. ticks = super().tick_values(vmin, vmax)
  212. rtol = (vmax - vmin) * 1e-10
  213. return ticks[(ticks >= vmin - rtol) & (ticks <= vmax + rtol)]
  214. class _ColorbarAutoMinorLocator(ticker.AutoMinorLocator):
  215. """
  216. AutoMinorLocator for Colorbar
  217. This locator is just a `.AutoMinorLocator` except the min and max are
  218. clipped by the norm's min and max (i.e. vmin/vmax from the
  219. image/pcolor/contour object). This is necessary so that the minorticks
  220. don't extrude into the "extend regions".
  221. """
  222. def __init__(self, colorbar, n=None):
  223. """
  224. This ticker needs to know the *colorbar* so that it can access
  225. its *vmin* and *vmax*.
  226. """
  227. self._colorbar = colorbar
  228. self.ndivs = n
  229. super().__init__(n=None)
  230. def __call__(self):
  231. vmin = self._colorbar.norm.vmin
  232. vmax = self._colorbar.norm.vmax
  233. ticks = super().__call__()
  234. rtol = (vmax - vmin) * 1e-10
  235. return ticks[(ticks >= vmin - rtol) & (ticks <= vmax + rtol)]
  236. class _ColorbarLogLocator(ticker.LogLocator):
  237. """
  238. LogLocator for Colorbarbar
  239. This locator is just a `.LogLocator` except the min and max are
  240. clipped by the norm's min and max (i.e. vmin/vmax from the
  241. image/pcolor/contour object). This is necessary so ticks don't
  242. extrude into the "extend regions".
  243. """
  244. def __init__(self, colorbar, *args, **kwargs):
  245. """
  246. This ticker needs to know the *colorbar* so that it can access
  247. its *vmin* and *vmax*. Otherwise it is the same as
  248. `~.ticker.LogLocator`. The ``*args`` and ``**kwargs`` are the
  249. same as `~.ticker.LogLocator`.
  250. """
  251. self._colorbar = colorbar
  252. super().__init__(*args, **kwargs)
  253. def tick_values(self, vmin, vmax):
  254. if vmin > vmax:
  255. vmin, vmax = vmax, vmin
  256. vmin = max(vmin, self._colorbar.norm.vmin)
  257. vmax = min(vmax, self._colorbar.norm.vmax)
  258. ticks = super().tick_values(vmin, vmax)
  259. rtol = (np.log10(vmax) - np.log10(vmin)) * 1e-10
  260. ticks = ticks[(np.log10(ticks) >= np.log10(vmin) - rtol) &
  261. (np.log10(ticks) <= np.log10(vmax) + rtol)]
  262. return ticks
  263. class ColorbarBase:
  264. r"""
  265. Draw a colorbar in an existing axes.
  266. There are only some rare cases in which you would work directly with a
  267. `.ColorbarBase` as an end-user. Typically, colorbars are used
  268. with `.ScalarMappable`\s such as an `.AxesImage` generated via
  269. `~.axes.Axes.imshow`. For these cases you will use `.Colorbar` and
  270. likely create it via `.pyplot.colorbar` or `.Figure.colorbar`.
  271. The main application of using a `.ColorbarBase` explicitly is drawing
  272. colorbars that are not associated with other elements in the figure, e.g.
  273. when showing a colormap by itself.
  274. If the *cmap* kwarg is given but *boundaries* and *values* are left as
  275. None, then the colormap will be displayed on a 0-1 scale. To show the
  276. under- and over-value colors, specify the *norm* as::
  277. norm=colors.Normalize(clip=False)
  278. To show the colors versus index instead of on the 0-1 scale,
  279. use::
  280. norm=colors.NoNorm()
  281. Useful public methods are :meth:`set_label` and :meth:`add_lines`.
  282. Attributes
  283. ----------
  284. ax : `~matplotlib.axes.Axes`
  285. The `~.axes.Axes` instance in which the colorbar is drawn.
  286. lines : list
  287. A list of `.LineCollection` if lines were drawn, otherwise
  288. an empty list.
  289. dividers : `.LineCollection`
  290. A LineCollection if *drawedges* is ``True``, otherwise ``None``.
  291. Parameters
  292. ----------
  293. ax : `~matplotlib.axes.Axes`
  294. The `~.axes.Axes` instance in which the colorbar is drawn.
  295. cmap : `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
  296. The colormap to use.
  297. norm : `~matplotlib.colors.Normalize`
  298. alpha : float
  299. The colorbar transparency between 0 (transparent) and 1 (opaque).
  300. values
  301. boundaries
  302. orientation : {'vertical', 'horizontal'}
  303. ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
  304. extend : {'neither', 'both', 'min', 'max'}
  305. spacing : {'uniform', 'proportional'}
  306. ticks : `~matplotlib.ticker.Locator` or array-like of float
  307. format : str or `~matplotlib.ticker.Formatter`
  308. drawedges : bool
  309. filled : bool
  310. extendfrac
  311. extendrec
  312. label : str
  313. """
  314. n_rasterize = 50 # rasterize solids if number of colors >= n_rasterize
  315. @cbook._make_keyword_only("3.3", "cmap")
  316. def __init__(self, ax, cmap=None,
  317. norm=None,
  318. alpha=None,
  319. values=None,
  320. boundaries=None,
  321. orientation='vertical',
  322. ticklocation='auto',
  323. extend=None,
  324. spacing='uniform', # uniform or proportional
  325. ticks=None,
  326. format=None,
  327. drawedges=False,
  328. filled=True,
  329. extendfrac=None,
  330. extendrect=False,
  331. label='',
  332. ):
  333. cbook._check_isinstance([colors.Colormap, None], cmap=cmap)
  334. cbook._check_in_list(
  335. ['vertical', 'horizontal'], orientation=orientation)
  336. cbook._check_in_list(
  337. ['auto', 'left', 'right', 'top', 'bottom'],
  338. ticklocation=ticklocation)
  339. cbook._check_in_list(
  340. ['uniform', 'proportional'], spacing=spacing)
  341. self.ax = ax
  342. # Bind some methods to the axes to warn users against using them.
  343. ax.set_xticks = ax.set_yticks = _set_ticks_on_axis_warn
  344. ax.set(frame_on=False, navigate=False)
  345. if cmap is None:
  346. cmap = cm.get_cmap()
  347. if norm is None:
  348. norm = colors.Normalize()
  349. if extend is None:
  350. if hasattr(norm, 'extend'):
  351. extend = norm.extend
  352. else:
  353. extend = 'neither'
  354. self.alpha = alpha
  355. self.cmap = cmap
  356. self.norm = norm
  357. self.values = values
  358. self.boundaries = boundaries
  359. self.extend = extend
  360. self._inside = cbook._check_getitem(
  361. {'neither': slice(0, None), 'both': slice(1, -1),
  362. 'min': slice(1, None), 'max': slice(0, -1)},
  363. extend=extend)
  364. self.spacing = spacing
  365. self.orientation = orientation
  366. self.drawedges = drawedges
  367. self.filled = filled
  368. self.extendfrac = extendfrac
  369. self.extendrect = extendrect
  370. self.solids = None
  371. self.lines = []
  372. self.outline = mpatches.Polygon(
  373. np.empty((0, 2)),
  374. edgecolor=mpl.rcParams['axes.edgecolor'], facecolor='none',
  375. linewidth=mpl.rcParams['axes.linewidth'], closed=True, zorder=2)
  376. ax.add_artist(self.outline)
  377. self.outline.set(clip_box=None, clip_path=None)
  378. self.patch = mpatches.Polygon(
  379. np.empty((0, 2)),
  380. color=mpl.rcParams['axes.facecolor'], linewidth=0.01, zorder=-1)
  381. ax.add_artist(self.patch)
  382. self.dividers = None
  383. self.locator = None
  384. self.formatter = None
  385. self._manual_tick_data_values = None
  386. self.__scale = None # linear, log10 for now. Hopefully more?
  387. if ticklocation == 'auto':
  388. ticklocation = 'bottom' if orientation == 'horizontal' else 'right'
  389. self.ticklocation = ticklocation
  390. self.set_label(label)
  391. self._reset_locator_formatter_scale()
  392. if np.iterable(ticks):
  393. self.locator = ticker.FixedLocator(ticks, nbins=len(ticks))
  394. else:
  395. self.locator = ticks # Handle default in _ticker()
  396. if isinstance(format, str):
  397. self.formatter = ticker.FormatStrFormatter(format)
  398. else:
  399. self.formatter = format # Assume it is a Formatter or None
  400. self.draw_all()
  401. def _extend_lower(self):
  402. """Return whether the lower limit is open ended."""
  403. return self.extend in ('both', 'min')
  404. def _extend_upper(self):
  405. """Return whether the upper limit is open ended."""
  406. return self.extend in ('both', 'max')
  407. def draw_all(self):
  408. """
  409. Calculate any free parameters based on the current cmap and norm,
  410. and do all the drawing.
  411. """
  412. # sets self._boundaries and self._values in real data units.
  413. # takes into account extend values:
  414. self._process_values()
  415. # sets self.vmin and vmax in data units, but just for the part of the
  416. # colorbar that is not part of the extend patch:
  417. self._find_range()
  418. # returns the X and Y mesh, *but* this was/is in normalized units:
  419. X, Y = self._mesh()
  420. C = self._values[:, np.newaxis]
  421. self._config_axis() # Inline it after deprecation elapses.
  422. # Configure axes limits, patch, and outline.
  423. xy = self._outline(X, Y)
  424. xmin, ymin = xy.min(axis=0)
  425. xmax, ymax = xy.max(axis=0)
  426. self.ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax))
  427. self.outline.set_xy(xy)
  428. self.patch.set_xy(xy)
  429. self.update_ticks()
  430. if self.filled:
  431. self._add_solids(X, Y, C)
  432. @cbook.deprecated("3.3")
  433. def config_axis(self):
  434. self._config_axis()
  435. def _config_axis(self):
  436. """Set up long and short axis."""
  437. ax = self.ax
  438. if self.orientation == 'vertical':
  439. long_axis, short_axis = ax.yaxis, ax.xaxis
  440. if mpl.rcParams['ytick.minor.visible']:
  441. self.minorticks_on()
  442. else:
  443. long_axis, short_axis = ax.xaxis, ax.yaxis
  444. if mpl.rcParams['xtick.minor.visible']:
  445. self.minorticks_on()
  446. long_axis.set(label_position=self.ticklocation,
  447. ticks_position=self.ticklocation)
  448. short_axis.set_ticks([])
  449. short_axis.set_ticks([], minor=True)
  450. self._set_label()
  451. def _get_ticker_locator_formatter(self):
  452. """
  453. Return the ``locator`` and ``formatter`` of the colorbar.
  454. If they have not been defined (i.e. are *None*), suitable formatter
  455. and locator instances will be created, attached to the respective
  456. attributes and returned.
  457. """
  458. locator = self.locator
  459. formatter = self.formatter
  460. if locator is None:
  461. if self.boundaries is None:
  462. if isinstance(self.norm, colors.NoNorm):
  463. nv = len(self._values)
  464. base = 1 + int(nv / 10)
  465. locator = ticker.IndexLocator(base=base, offset=0)
  466. elif isinstance(self.norm, colors.BoundaryNorm):
  467. b = self.norm.boundaries
  468. locator = ticker.FixedLocator(b, nbins=10)
  469. elif isinstance(self.norm, colors.LogNorm):
  470. locator = _ColorbarLogLocator(self)
  471. elif isinstance(self.norm, colors.SymLogNorm):
  472. # The subs setting here should be replaced
  473. # by logic in the locator.
  474. locator = ticker.SymmetricalLogLocator(
  475. subs=np.arange(1, 10),
  476. linthresh=self.norm.linthresh,
  477. base=10)
  478. else:
  479. if mpl.rcParams['_internal.classic_mode']:
  480. locator = ticker.MaxNLocator()
  481. else:
  482. locator = _ColorbarAutoLocator(self)
  483. else:
  484. b = self._boundaries[self._inside]
  485. locator = ticker.FixedLocator(b, nbins=10)
  486. if formatter is None:
  487. if isinstance(self.norm, colors.LogNorm):
  488. formatter = ticker.LogFormatterSciNotation()
  489. elif isinstance(self.norm, colors.SymLogNorm):
  490. formatter = ticker.LogFormatterSciNotation(
  491. linthresh=self.norm.linthresh)
  492. else:
  493. formatter = ticker.ScalarFormatter()
  494. else:
  495. formatter = self.formatter
  496. self.locator = locator
  497. self.formatter = formatter
  498. _log.debug('locator: %r', locator)
  499. return locator, formatter
  500. def _use_auto_colorbar_locator(self):
  501. """
  502. Return if we should use an adjustable tick locator or a fixed
  503. one. (check is used twice so factored out here...)
  504. """
  505. contouring = self.boundaries is not None and self.spacing == 'uniform'
  506. return (type(self.norm) in [colors.Normalize, colors.LogNorm] and
  507. not contouring)
  508. def _reset_locator_formatter_scale(self):
  509. """
  510. Reset the locator et al to defaults. Any user-hardcoded changes
  511. need to be re-entered if this gets called (either at init, or when
  512. the mappable normal gets changed: Colorbar.update_normal)
  513. """
  514. self.locator = None
  515. self.formatter = None
  516. if isinstance(self.norm, colors.LogNorm):
  517. # *both* axes are made log so that determining the
  518. # mid point is easier.
  519. self.ax.set_xscale('log')
  520. self.ax.set_yscale('log')
  521. self.minorticks_on()
  522. self.__scale = 'log'
  523. else:
  524. self.ax.set_xscale('linear')
  525. self.ax.set_yscale('linear')
  526. if type(self.norm) is colors.Normalize:
  527. self.__scale = 'linear'
  528. else:
  529. self.__scale = 'manual'
  530. def update_ticks(self):
  531. """
  532. Force the update of the ticks and ticklabels. This must be
  533. called whenever the tick locator and/or tick formatter changes.
  534. """
  535. ax = self.ax
  536. # Get the locator and formatter; defaults to self.locator if not None.
  537. locator, formatter = self._get_ticker_locator_formatter()
  538. long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis
  539. if self._use_auto_colorbar_locator():
  540. _log.debug('Using auto colorbar locator %r on colorbar', locator)
  541. long_axis.set_major_locator(locator)
  542. long_axis.set_major_formatter(formatter)
  543. else:
  544. _log.debug('Using fixed locator on colorbar')
  545. ticks, ticklabels, offset_string = self._ticker(locator, formatter)
  546. long_axis.set_ticks(ticks)
  547. long_axis.set_ticklabels(ticklabels)
  548. long_axis.get_major_formatter().set_offset_string(offset_string)
  549. def set_ticks(self, ticks, update_ticks=True):
  550. """
  551. Set tick locations.
  552. Parameters
  553. ----------
  554. ticks : array-like or `~matplotlib.ticker.Locator` or None
  555. The tick positions can be hard-coded by an array of values; or
  556. they can be defined by a `.Locator`. Setting to *None* reverts
  557. to using a default locator.
  558. update_ticks : bool, default: True
  559. If True, tick locations are updated immediately. If False, the
  560. user has to call `update_ticks` later to update the ticks.
  561. """
  562. if np.iterable(ticks):
  563. self.locator = ticker.FixedLocator(ticks, nbins=len(ticks))
  564. else:
  565. self.locator = ticks
  566. if update_ticks:
  567. self.update_ticks()
  568. self.stale = True
  569. def get_ticks(self, minor=False):
  570. """Return the x ticks as a list of locations."""
  571. if self._manual_tick_data_values is None:
  572. ax = self.ax
  573. long_axis = (
  574. ax.yaxis if self.orientation == 'vertical' else ax.xaxis)
  575. return long_axis.get_majorticklocs()
  576. else:
  577. # We made the axes manually, the old way, and the ylim is 0-1,
  578. # so the majorticklocs are in those units, not data units.
  579. return self._manual_tick_data_values
  580. def set_ticklabels(self, ticklabels, update_ticks=True):
  581. """
  582. Set tick labels.
  583. Tick labels are updated immediately unless *update_ticks* is *False*,
  584. in which case one should call `.update_ticks` explicitly.
  585. """
  586. if isinstance(self.locator, ticker.FixedLocator):
  587. self.formatter = ticker.FixedFormatter(ticklabels)
  588. if update_ticks:
  589. self.update_ticks()
  590. else:
  591. cbook._warn_external("set_ticks() must have been called.")
  592. self.stale = True
  593. def minorticks_on(self):
  594. """
  595. Turn the minor ticks of the colorbar on without extruding
  596. into the "extend regions".
  597. """
  598. ax = self.ax
  599. long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis
  600. if long_axis.get_scale() == 'log':
  601. long_axis.set_minor_locator(_ColorbarLogLocator(self, base=10.,
  602. subs='auto'))
  603. long_axis.set_minor_formatter(ticker.LogFormatterSciNotation())
  604. else:
  605. long_axis.set_minor_locator(_ColorbarAutoMinorLocator(self))
  606. def minorticks_off(self):
  607. """Turn the minor ticks of the colorbar off."""
  608. ax = self.ax
  609. long_axis = ax.yaxis if self.orientation == 'vertical' else ax.xaxis
  610. long_axis.set_minor_locator(ticker.NullLocator())
  611. def _set_label(self):
  612. if self.orientation == 'vertical':
  613. self.ax.set_ylabel(self._label, **self._labelkw)
  614. else:
  615. self.ax.set_xlabel(self._label, **self._labelkw)
  616. self.stale = True
  617. def set_label(self, label, *, loc=None, **kwargs):
  618. """Add a label to the long axis of the colorbar."""
  619. _pos_xy = 'y' if self.orientation == 'vertical' else 'x'
  620. _protected_kw = [_pos_xy, 'horizontalalignment', 'ha']
  621. if any([k in kwargs for k in _protected_kw]):
  622. if loc is not None:
  623. raise TypeError(f'Specifying *loc* is disallowed when any of '
  624. f'its corresponding low level keyword '
  625. f'arguments {_protected_kw} are also supplied')
  626. loc = 'center'
  627. else:
  628. if loc is None:
  629. loc = mpl.rcParams['%saxis.labellocation' % _pos_xy]
  630. if self.orientation == 'vertical':
  631. cbook._check_in_list(('bottom', 'center', 'top'), loc=loc)
  632. else:
  633. cbook._check_in_list(('left', 'center', 'right'), loc=loc)
  634. if loc in ['right', 'top']:
  635. kwargs[_pos_xy] = 1.
  636. kwargs['horizontalalignment'] = 'right'
  637. elif loc in ['left', 'bottom']:
  638. kwargs[_pos_xy] = 0.
  639. kwargs['horizontalalignment'] = 'left'
  640. self._label = label
  641. self._labelkw = kwargs
  642. self._set_label()
  643. def _outline(self, X, Y):
  644. """
  645. Return *x*, *y* arrays of colorbar bounding polygon,
  646. taking orientation into account.
  647. """
  648. N = X.shape[0]
  649. ii = [0, 1, N - 2, N - 1, 2 * N - 1, 2 * N - 2, N + 1, N, 0]
  650. x = X.T.reshape(-1)[ii]
  651. y = Y.T.reshape(-1)[ii]
  652. return (np.column_stack([y, x])
  653. if self.orientation == 'horizontal' else
  654. np.column_stack([x, y]))
  655. def _edges(self, X, Y):
  656. """Return the separator line segments; helper for _add_solids."""
  657. N = X.shape[0]
  658. # Using the non-array form of these line segments is much
  659. # simpler than making them into arrays.
  660. if self.orientation == 'vertical':
  661. return [list(zip(X[i], Y[i])) for i in range(1, N - 1)]
  662. else:
  663. return [list(zip(Y[i], X[i])) for i in range(1, N - 1)]
  664. def _add_solids(self, X, Y, C):
  665. """
  666. Draw the colors using `~.axes.Axes.pcolormesh`;
  667. optionally add separators.
  668. """
  669. if C.shape[0] == Y.shape[0]:
  670. # trim the last one to be compatible with old behavior.
  671. C = C[:-1]
  672. if self.orientation == 'vertical':
  673. args = (X, Y, C)
  674. else:
  675. args = (np.transpose(Y), np.transpose(X), np.transpose(C))
  676. kw = dict(cmap=self.cmap,
  677. norm=self.norm,
  678. alpha=self.alpha,
  679. edgecolors='None')
  680. _log.debug('Setting pcolormesh')
  681. col = self.ax.pcolormesh(*args, **kw, shading='flat')
  682. # self.add_observer(col) # We should observe, not be observed...
  683. if self.solids is not None:
  684. self.solids.remove()
  685. self.solids = col
  686. if self.dividers is not None:
  687. self.dividers.remove()
  688. self.dividers = None
  689. if self.drawedges:
  690. linewidths = (0.5 * mpl.rcParams['axes.linewidth'],)
  691. self.dividers = collections.LineCollection(
  692. self._edges(X, Y),
  693. colors=(mpl.rcParams['axes.edgecolor'],),
  694. linewidths=linewidths)
  695. self.ax.add_collection(self.dividers)
  696. elif len(self._y) >= self.n_rasterize:
  697. self.solids.set_rasterized(True)
  698. def add_lines(self, levels, colors, linewidths, erase=True):
  699. """
  700. Draw lines on the colorbar.
  701. The lines are appended to the list :attr:`lines`.
  702. Parameters
  703. ----------
  704. levels : array-like
  705. The positions of the lines.
  706. colors : color or list of colors
  707. Either a single color applying to all lines or one color value for
  708. each line.
  709. linewidths : float or array-like
  710. Either a single linewidth applying to all lines or one linewidth
  711. for each line.
  712. erase : bool, default: True
  713. Whether to remove any previously added lines.
  714. """
  715. y = self._locate(levels)
  716. rtol = (self._y[-1] - self._y[0]) * 1e-10
  717. igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol)
  718. y = y[igood]
  719. if np.iterable(colors):
  720. colors = np.asarray(colors)[igood]
  721. if np.iterable(linewidths):
  722. linewidths = np.asarray(linewidths)[igood]
  723. X, Y = np.meshgrid([self._y[0], self._y[-1]], y)
  724. if self.orientation == 'vertical':
  725. xy = np.stack([X, Y], axis=-1)
  726. else:
  727. xy = np.stack([Y, X], axis=-1)
  728. col = collections.LineCollection(xy, linewidths=linewidths)
  729. if erase and self.lines:
  730. for lc in self.lines:
  731. lc.remove()
  732. self.lines = []
  733. self.lines.append(col)
  734. col.set_color(colors)
  735. self.ax.add_collection(col)
  736. self.stale = True
  737. def _ticker(self, locator, formatter):
  738. """
  739. Return the sequence of ticks (colorbar data locations),
  740. ticklabels (strings), and the corresponding offset string.
  741. """
  742. if isinstance(self.norm, colors.NoNorm) and self.boundaries is None:
  743. intv = self._values[0], self._values[-1]
  744. else:
  745. intv = self.vmin, self.vmax
  746. locator.create_dummy_axis(minpos=intv[0])
  747. formatter.create_dummy_axis(minpos=intv[0])
  748. locator.set_view_interval(*intv)
  749. locator.set_data_interval(*intv)
  750. formatter.set_view_interval(*intv)
  751. formatter.set_data_interval(*intv)
  752. b = np.array(locator())
  753. if isinstance(locator, ticker.LogLocator):
  754. eps = 1e-10
  755. b = b[(b <= intv[1] * (1 + eps)) & (b >= intv[0] * (1 - eps))]
  756. else:
  757. eps = (intv[1] - intv[0]) * 1e-10
  758. b = b[(b <= intv[1] + eps) & (b >= intv[0] - eps)]
  759. self._manual_tick_data_values = b
  760. ticks = self._locate(b)
  761. ticklabels = formatter.format_ticks(b)
  762. offset_string = formatter.get_offset()
  763. return ticks, ticklabels, offset_string
  764. def _process_values(self, b=None):
  765. """
  766. Set the :attr:`_boundaries` and :attr:`_values` attributes
  767. based on the input boundaries and values. Input boundaries
  768. can be *self.boundaries* or the argument *b*.
  769. """
  770. if b is None:
  771. b = self.boundaries
  772. if b is not None:
  773. self._boundaries = np.asarray(b, dtype=float)
  774. if self.values is None:
  775. self._values = 0.5 * (self._boundaries[:-1]
  776. + self._boundaries[1:])
  777. if isinstance(self.norm, colors.NoNorm):
  778. self._values = (self._values + 0.00001).astype(np.int16)
  779. else:
  780. self._values = np.array(self.values)
  781. return
  782. if self.values is not None:
  783. self._values = np.array(self.values)
  784. if self.boundaries is None:
  785. b = np.zeros(len(self.values) + 1)
  786. b[1:-1] = 0.5 * (self._values[:-1] + self._values[1:])
  787. b[0] = 2.0 * b[1] - b[2]
  788. b[-1] = 2.0 * b[-2] - b[-3]
  789. self._boundaries = b
  790. return
  791. self._boundaries = np.array(self.boundaries)
  792. return
  793. # Neither boundaries nor values are specified;
  794. # make reasonable ones based on cmap and norm.
  795. if isinstance(self.norm, colors.NoNorm):
  796. b = self._uniform_y(self.cmap.N + 1) * self.cmap.N - 0.5
  797. v = np.zeros(len(b) - 1, dtype=np.int16)
  798. v[self._inside] = np.arange(self.cmap.N, dtype=np.int16)
  799. if self._extend_lower():
  800. v[0] = -1
  801. if self._extend_upper():
  802. v[-1] = self.cmap.N
  803. self._boundaries = b
  804. self._values = v
  805. return
  806. elif isinstance(self.norm, colors.BoundaryNorm):
  807. b = list(self.norm.boundaries)
  808. if self._extend_lower():
  809. b = [b[0] - 1] + b
  810. if self._extend_upper():
  811. b = b + [b[-1] + 1]
  812. b = np.array(b)
  813. v = np.zeros(len(b) - 1)
  814. bi = self.norm.boundaries
  815. v[self._inside] = 0.5 * (bi[:-1] + bi[1:])
  816. if self._extend_lower():
  817. v[0] = b[0] - 1
  818. if self._extend_upper():
  819. v[-1] = b[-1] + 1
  820. self._boundaries = b
  821. self._values = v
  822. return
  823. else:
  824. if not self.norm.scaled():
  825. self.norm.vmin = 0
  826. self.norm.vmax = 1
  827. self.norm.vmin, self.norm.vmax = mtransforms.nonsingular(
  828. self.norm.vmin,
  829. self.norm.vmax,
  830. expander=0.1)
  831. b = self.norm.inverse(self._uniform_y(self.cmap.N + 1))
  832. if isinstance(self.norm, (colors.PowerNorm, colors.LogNorm)):
  833. # If using a lognorm or powernorm, ensure extensions don't
  834. # go negative
  835. if self._extend_lower():
  836. b[0] = 0.9 * b[0]
  837. if self._extend_upper():
  838. b[-1] = 1.1 * b[-1]
  839. else:
  840. if self._extend_lower():
  841. b[0] = b[0] - 1
  842. if self._extend_upper():
  843. b[-1] = b[-1] + 1
  844. self._process_values(b)
  845. def _find_range(self):
  846. """
  847. Set :attr:`vmin` and :attr:`vmax` attributes to the first and
  848. last boundary excluding extended end boundaries.
  849. """
  850. b = self._boundaries[self._inside]
  851. self.vmin = b[0]
  852. self.vmax = b[-1]
  853. def _central_N(self):
  854. """Return the number of boundaries excluding end extensions."""
  855. nb = len(self._boundaries)
  856. if self.extend == 'both':
  857. nb -= 2
  858. elif self.extend in ('min', 'max'):
  859. nb -= 1
  860. return nb
  861. def _extended_N(self):
  862. """
  863. Based on the colormap and extend variable, return the
  864. number of boundaries.
  865. """
  866. N = self.cmap.N + 1
  867. if self.extend == 'both':
  868. N += 2
  869. elif self.extend in ('min', 'max'):
  870. N += 1
  871. return N
  872. def _get_extension_lengths(self, frac, automin, automax, default=0.05):
  873. """
  874. Return the lengths of colorbar extensions.
  875. This is a helper method for _uniform_y and _proportional_y.
  876. """
  877. # Set the default value.
  878. extendlength = np.array([default, default])
  879. if isinstance(frac, str):
  880. cbook._check_in_list(['auto'], extendfrac=frac.lower())
  881. # Use the provided values when 'auto' is required.
  882. extendlength[:] = [automin, automax]
  883. elif frac is not None:
  884. try:
  885. # Try to set min and max extension fractions directly.
  886. extendlength[:] = frac
  887. # If frac is a sequence containing None then NaN may
  888. # be encountered. This is an error.
  889. if np.isnan(extendlength).any():
  890. raise ValueError()
  891. except (TypeError, ValueError) as err:
  892. # Raise an error on encountering an invalid value for frac.
  893. raise ValueError('invalid value for extendfrac') from err
  894. return extendlength
  895. def _uniform_y(self, N):
  896. """
  897. Return colorbar data coordinates for *N* uniformly
  898. spaced boundaries, plus ends if required.
  899. """
  900. if self.extend == 'neither':
  901. y = np.linspace(0, 1, N)
  902. else:
  903. automin = automax = 1. / (N - 1.)
  904. extendlength = self._get_extension_lengths(self.extendfrac,
  905. automin, automax,
  906. default=0.05)
  907. if self.extend == 'both':
  908. y = np.zeros(N + 2, 'd')
  909. y[0] = 0. - extendlength[0]
  910. y[-1] = 1. + extendlength[1]
  911. elif self.extend == 'min':
  912. y = np.zeros(N + 1, 'd')
  913. y[0] = 0. - extendlength[0]
  914. else:
  915. y = np.zeros(N + 1, 'd')
  916. y[-1] = 1. + extendlength[1]
  917. y[self._inside] = np.linspace(0, 1, N)
  918. return y
  919. def _proportional_y(self):
  920. """
  921. Return colorbar data coordinates for the boundaries of
  922. a proportional colorbar.
  923. """
  924. if isinstance(self.norm, colors.BoundaryNorm):
  925. y = (self._boundaries - self._boundaries[0])
  926. y = y / (self._boundaries[-1] - self._boundaries[0])
  927. else:
  928. y = self.norm(self._boundaries.copy())
  929. y = np.ma.filled(y, np.nan)
  930. if self.extend == 'min':
  931. # Exclude leftmost interval of y.
  932. clen = y[-1] - y[1]
  933. automin = (y[2] - y[1]) / clen
  934. automax = (y[-1] - y[-2]) / clen
  935. elif self.extend == 'max':
  936. # Exclude rightmost interval in y.
  937. clen = y[-2] - y[0]
  938. automin = (y[1] - y[0]) / clen
  939. automax = (y[-2] - y[-3]) / clen
  940. elif self.extend == 'both':
  941. # Exclude leftmost and rightmost intervals in y.
  942. clen = y[-2] - y[1]
  943. automin = (y[2] - y[1]) / clen
  944. automax = (y[-2] - y[-3]) / clen
  945. if self.extend in ('both', 'min', 'max'):
  946. extendlength = self._get_extension_lengths(self.extendfrac,
  947. automin, automax,
  948. default=0.05)
  949. if self.extend in ('both', 'min'):
  950. y[0] = 0. - extendlength[0]
  951. if self.extend in ('both', 'max'):
  952. y[-1] = 1. + extendlength[1]
  953. yi = y[self._inside]
  954. norm = colors.Normalize(yi[0], yi[-1])
  955. y[self._inside] = np.ma.filled(norm(yi), np.nan)
  956. return y
  957. def _mesh(self):
  958. """
  959. Return ``(X, Y)``, the coordinate arrays for the colorbar pcolormesh.
  960. These are suitable for a vertical colorbar; swapping and transposition
  961. for a horizontal colorbar are done outside this function.
  962. These are scaled between vmin and vmax.
  963. """
  964. # copy the norm and change the vmin and vmax to the vmin and
  965. # vmax of the colorbar, not the norm. This allows the situation
  966. # where the colormap has a narrower range than the colorbar, to
  967. # accommodate extra contours:
  968. norm = copy.copy(self.norm)
  969. norm.vmin = self.vmin
  970. norm.vmax = self.vmax
  971. x = np.array([0.0, 1.0])
  972. if self.spacing == 'uniform':
  973. y = self._uniform_y(self._central_N())
  974. else:
  975. y = self._proportional_y()
  976. xmid = np.array([0.5])
  977. if self.__scale != 'manual':
  978. y = norm.inverse(y)
  979. x = norm.inverse(x)
  980. xmid = norm.inverse(xmid)
  981. else:
  982. # if a norm doesn't have a named scale, or
  983. # we are not using a norm
  984. dv = self.vmax - self.vmin
  985. x = x * dv + self.vmin
  986. y = y * dv + self.vmin
  987. xmid = xmid * dv + self.vmin
  988. self._y = y
  989. X, Y = np.meshgrid(x, y)
  990. if self._extend_lower() and not self.extendrect:
  991. X[0, :] = xmid
  992. if self._extend_upper() and not self.extendrect:
  993. X[-1, :] = xmid
  994. return X, Y
  995. def _locate(self, x):
  996. """
  997. Given a set of color data values, return their
  998. corresponding colorbar data coordinates.
  999. """
  1000. if isinstance(self.norm, (colors.NoNorm, colors.BoundaryNorm)):
  1001. b = self._boundaries
  1002. xn = x
  1003. else:
  1004. # Do calculations using normalized coordinates so
  1005. # as to make the interpolation more accurate.
  1006. b = self.norm(self._boundaries, clip=False).filled()
  1007. xn = self.norm(x, clip=False).filled()
  1008. bunique = b
  1009. yunique = self._y
  1010. # trim extra b values at beginning and end if they are
  1011. # not unique. These are here for extended colorbars, and are not
  1012. # wanted for the interpolation.
  1013. if b[0] == b[1]:
  1014. bunique = bunique[1:]
  1015. yunique = yunique[1:]
  1016. if b[-1] == b[-2]:
  1017. bunique = bunique[:-1]
  1018. yunique = yunique[:-1]
  1019. z = np.interp(xn, bunique, yunique)
  1020. return z
  1021. def set_alpha(self, alpha):
  1022. """Set the transparency between 0 (transparent) and 1 (opaque)."""
  1023. self.alpha = alpha
  1024. def remove(self):
  1025. """Remove this colorbar from the figure."""
  1026. self.ax.remove()
  1027. def _add_disjoint_kwargs(d, **kwargs):
  1028. """
  1029. Update dict *d* with entries in *kwargs*, which must be absent from *d*.
  1030. """
  1031. for k, v in kwargs.items():
  1032. if k in d:
  1033. cbook.warn_deprecated(
  1034. "3.3", message=f"The {k!r} parameter to Colorbar has no "
  1035. "effect because it is overridden by the mappable; it is "
  1036. "deprecated since %(since)s and will be removed %(removal)s.")
  1037. d[k] = v
  1038. class Colorbar(ColorbarBase):
  1039. """
  1040. This class connects a `ColorbarBase` to a `~.cm.ScalarMappable`
  1041. such as an `~.image.AxesImage` generated via `~.axes.Axes.imshow`.
  1042. .. note::
  1043. This class is not intended to be instantiated directly; instead, use
  1044. `.Figure.colorbar` or `.pyplot.colorbar` to create a colorbar.
  1045. """
  1046. def __init__(self, ax, mappable, **kwargs):
  1047. # Ensure the given mappable's norm has appropriate vmin and vmax set
  1048. # even if mappable.draw has not yet been called.
  1049. if mappable.get_array() is not None:
  1050. mappable.autoscale_None()
  1051. self.mappable = mappable
  1052. _add_disjoint_kwargs(kwargs, cmap=mappable.cmap, norm=mappable.norm)
  1053. if isinstance(mappable, contour.ContourSet):
  1054. cs = mappable
  1055. _add_disjoint_kwargs(
  1056. kwargs,
  1057. alpha=cs.get_alpha(),
  1058. boundaries=cs._levels,
  1059. values=cs.cvalues,
  1060. extend=cs.extend,
  1061. filled=cs.filled,
  1062. )
  1063. kwargs.setdefault(
  1064. 'ticks', ticker.FixedLocator(cs.levels, nbins=10))
  1065. ColorbarBase.__init__(self, ax, **kwargs)
  1066. if not cs.filled:
  1067. self.add_lines(cs)
  1068. else:
  1069. if getattr(mappable.cmap, 'colorbar_extend', False) is not False:
  1070. kwargs.setdefault('extend', mappable.cmap.colorbar_extend)
  1071. if isinstance(mappable, martist.Artist):
  1072. _add_disjoint_kwargs(kwargs, alpha=mappable.get_alpha())
  1073. ColorbarBase.__init__(self, ax, **kwargs)
  1074. @cbook.deprecated("3.3", alternative="update_normal")
  1075. def on_mappable_changed(self, mappable):
  1076. """
  1077. Update this colorbar to match the mappable's properties.
  1078. Typically this is automatically registered as an event handler
  1079. by :func:`colorbar_factory` and should not be called manually.
  1080. """
  1081. _log.debug('colorbar mappable changed')
  1082. self.update_normal(mappable)
  1083. def add_lines(self, CS, erase=True):
  1084. """
  1085. Add the lines from a non-filled `~.contour.ContourSet` to the colorbar.
  1086. Parameters
  1087. ----------
  1088. CS : `~.contour.ContourSet`
  1089. The line positions are taken from the ContourSet levels. The
  1090. ContourSet must not be filled.
  1091. erase : bool, default: True
  1092. Whether to remove any previously added lines.
  1093. """
  1094. if not isinstance(CS, contour.ContourSet) or CS.filled:
  1095. raise ValueError('add_lines is only for a ContourSet of lines')
  1096. tcolors = [c[0] for c in CS.tcolors]
  1097. tlinewidths = [t[0] for t in CS.tlinewidths]
  1098. # The following was an attempt to get the colorbar lines
  1099. # to follow subsequent changes in the contour lines,
  1100. # but more work is needed: specifically, a careful
  1101. # look at event sequences, and at how
  1102. # to make one object track another automatically.
  1103. #tcolors = [col.get_colors()[0] for col in CS.collections]
  1104. #tlinewidths = [col.get_linewidth()[0] for lw in CS.collections]
  1105. ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths,
  1106. erase=erase)
  1107. def update_normal(self, mappable):
  1108. """
  1109. Update solid patches, lines, etc.
  1110. This is meant to be called when the norm of the image or contour plot
  1111. to which this colorbar belongs changes.
  1112. If the norm on the mappable is different than before, this resets the
  1113. locator and formatter for the axis, so if these have been customized,
  1114. they will need to be customized again. However, if the norm only
  1115. changes values of *vmin*, *vmax* or *cmap* then the old formatter
  1116. and locator will be preserved.
  1117. """
  1118. _log.debug('colorbar update normal %r %r', mappable.norm, self.norm)
  1119. self.mappable = mappable
  1120. self.set_alpha(mappable.get_alpha())
  1121. self.cmap = mappable.cmap
  1122. if mappable.norm != self.norm:
  1123. self.norm = mappable.norm
  1124. self._reset_locator_formatter_scale()
  1125. self.draw_all()
  1126. if isinstance(self.mappable, contour.ContourSet):
  1127. CS = self.mappable
  1128. if not CS.filled:
  1129. self.add_lines(CS)
  1130. self.stale = True
  1131. @cbook.deprecated("3.3", alternative="update_normal")
  1132. def update_bruteforce(self, mappable):
  1133. """
  1134. Destroy and rebuild the colorbar. This is
  1135. intended to become obsolete, and will probably be
  1136. deprecated and then removed. It is not called when
  1137. the pyplot.colorbar function or the Figure.colorbar
  1138. method are used to create the colorbar.
  1139. """
  1140. # We are using an ugly brute-force method: clearing and
  1141. # redrawing the whole thing. The problem is that if any
  1142. # properties have been changed by methods other than the
  1143. # colorbar methods, those changes will be lost.
  1144. self.ax.cla()
  1145. self.locator = None
  1146. self.formatter = None
  1147. # clearing the axes will delete outline, patch, solids, and lines:
  1148. self.outline = mpatches.Polygon(
  1149. np.empty((0, 2)),
  1150. edgecolor=mpl.rcParams['axes.edgecolor'], facecolor='none',
  1151. linewidth=mpl.rcParams['axes.linewidth'], closed=True, zorder=2)
  1152. self.ax.add_artist(self.outline)
  1153. self.outline.set(clip_box=None, clip_path=None)
  1154. self.patch = mpatches.Polygon(
  1155. np.empty((0, 2)),
  1156. color=mpl.rcParams['axes.facecolor'], linewidth=0.01, zorder=-1)
  1157. self.ax.add_artist(self.patch)
  1158. self.solids = None
  1159. self.lines = []
  1160. self.dividers = None
  1161. self.update_normal(mappable)
  1162. self.draw_all()
  1163. if isinstance(self.mappable, contour.ContourSet):
  1164. CS = self.mappable
  1165. if not CS.filled:
  1166. self.add_lines(CS)
  1167. #if self.lines is not None:
  1168. # tcolors = [c[0] for c in CS.tcolors]
  1169. # self.lines.set_color(tcolors)
  1170. #Fixme? Recalculate boundaries, ticks if vmin, vmax have changed.
  1171. #Fixme: Some refactoring may be needed; we should not
  1172. # be recalculating everything if there was a simple alpha
  1173. # change.
  1174. def remove(self):
  1175. """
  1176. Remove this colorbar from the figure.
  1177. If the colorbar was created with ``use_gridspec=True`` the previous
  1178. gridspec is restored.
  1179. """
  1180. ColorbarBase.remove(self)
  1181. self.mappable.callbacksSM.disconnect(self.mappable.colorbar_cid)
  1182. self.mappable.colorbar = None
  1183. self.mappable.colorbar_cid = None
  1184. try:
  1185. ax = self.mappable.axes
  1186. except AttributeError:
  1187. return
  1188. try:
  1189. gs = ax.get_subplotspec().get_gridspec()
  1190. subplotspec = gs.get_topmost_subplotspec()
  1191. except AttributeError:
  1192. # use_gridspec was False
  1193. pos = ax.get_position(original=True)
  1194. ax._set_position(pos)
  1195. else:
  1196. # use_gridspec was True
  1197. ax.set_subplotspec(subplotspec)
  1198. @docstring.Substitution(_make_axes_param_doc, _make_axes_other_param_doc)
  1199. def make_axes(parents, location=None, orientation=None, fraction=0.15,
  1200. shrink=1.0, aspect=20, **kw):
  1201. """
  1202. Create an `~.axes.Axes` suitable for a colorbar.
  1203. The axes is placed in the figure of the *parents* axes, by resizing and
  1204. repositioning *parents*.
  1205. Parameters
  1206. ----------
  1207. parents : `~.axes.Axes` or list of `~.axes.Axes`
  1208. The Axes to use as parents for placing the colorbar.
  1209. location : None or {'left', 'right', 'top', 'bottom'}
  1210. The position, relative to *parents*, where the colorbar axes
  1211. should be created. If None, the value will either come from the
  1212. given ``orientation``, else it will default to 'right'.
  1213. orientation : None or {'vertical', 'horizontal'}
  1214. The orientation of the colorbar. Typically, this keyword shouldn't
  1215. be used, as it can be derived from the ``location`` keyword.
  1216. %s
  1217. Returns
  1218. -------
  1219. cax : `~.axes.Axes`
  1220. The child axes.
  1221. kw : dict
  1222. The reduced keyword dictionary to be passed when creating the colorbar
  1223. instance.
  1224. Other Parameters
  1225. ----------------
  1226. %s
  1227. """
  1228. locations = ["left", "right", "top", "bottom"]
  1229. if orientation is not None and location is not None:
  1230. raise TypeError('position and orientation are mutually exclusive. '
  1231. 'Consider setting the position to any of {}'
  1232. .format(', '.join(locations)))
  1233. # provide a default location
  1234. if location is None and orientation is None:
  1235. location = 'right'
  1236. # allow the user to not specify the location by specifying the
  1237. # orientation instead
  1238. if location is None:
  1239. location = 'right' if orientation == 'vertical' else 'bottom'
  1240. cbook._check_in_list(locations, location=location)
  1241. default_location_settings = {'left': {'anchor': (1.0, 0.5),
  1242. 'panchor': (0.0, 0.5),
  1243. 'pad': 0.10,
  1244. 'orientation': 'vertical'},
  1245. 'right': {'anchor': (0.0, 0.5),
  1246. 'panchor': (1.0, 0.5),
  1247. 'pad': 0.05,
  1248. 'orientation': 'vertical'},
  1249. 'top': {'anchor': (0.5, 0.0),
  1250. 'panchor': (0.5, 1.0),
  1251. 'pad': 0.05,
  1252. 'orientation': 'horizontal'},
  1253. 'bottom': {'anchor': (0.5, 1.0),
  1254. 'panchor': (0.5, 0.0),
  1255. 'pad': 0.15, # backwards compat
  1256. 'orientation': 'horizontal'},
  1257. }
  1258. loc_settings = default_location_settings[location]
  1259. # put appropriate values into the kw dict for passing back to
  1260. # the Colorbar class
  1261. kw['orientation'] = loc_settings['orientation']
  1262. kw['ticklocation'] = location
  1263. anchor = kw.pop('anchor', loc_settings['anchor'])
  1264. parent_anchor = kw.pop('panchor', loc_settings['panchor'])
  1265. parents_iterable = np.iterable(parents)
  1266. # turn parents into a list if it is not already. We do this w/ np
  1267. # because `plt.subplots` can return an ndarray and is natural to
  1268. # pass to `colorbar`.
  1269. parents = np.atleast_1d(parents).ravel()
  1270. # check if using constrained_layout:
  1271. try:
  1272. gs = parents[0].get_subplotspec().get_gridspec()
  1273. using_constrained_layout = (gs._layoutbox is not None)
  1274. except AttributeError:
  1275. using_constrained_layout = False
  1276. # defaults are not appropriate for constrained_layout:
  1277. pad0 = loc_settings['pad']
  1278. if using_constrained_layout:
  1279. pad0 = 0.02
  1280. pad = kw.pop('pad', pad0)
  1281. fig = parents[0].get_figure()
  1282. if not all(fig is ax.get_figure() for ax in parents):
  1283. raise ValueError('Unable to create a colorbar axes as not all '
  1284. 'parents share the same figure.')
  1285. # take a bounding box around all of the given axes
  1286. parents_bbox = mtransforms.Bbox.union(
  1287. [ax.get_position(original=True).frozen() for ax in parents])
  1288. pb = parents_bbox
  1289. if location in ('left', 'right'):
  1290. if location == 'left':
  1291. pbcb, _, pb1 = pb.splitx(fraction, fraction + pad)
  1292. else:
  1293. pb1, _, pbcb = pb.splitx(1 - fraction - pad, 1 - fraction)
  1294. pbcb = pbcb.shrunk(1.0, shrink).anchored(anchor, pbcb)
  1295. else:
  1296. if location == 'bottom':
  1297. pbcb, _, pb1 = pb.splity(fraction, fraction + pad)
  1298. else:
  1299. pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction)
  1300. pbcb = pbcb.shrunk(shrink, 1.0).anchored(anchor, pbcb)
  1301. # define the aspect ratio in terms of y's per x rather than x's per y
  1302. aspect = 1.0 / aspect
  1303. # define a transform which takes us from old axes coordinates to
  1304. # new axes coordinates
  1305. shrinking_trans = mtransforms.BboxTransform(parents_bbox, pb1)
  1306. # transform each of the axes in parents using the new transform
  1307. for ax in parents:
  1308. new_posn = shrinking_trans.transform(ax.get_position(original=True))
  1309. new_posn = mtransforms.Bbox(new_posn)
  1310. ax._set_position(new_posn)
  1311. if parent_anchor is not False:
  1312. ax.set_anchor(parent_anchor)
  1313. cax = fig.add_axes(pbcb, label="<colorbar>")
  1314. # OK, now make a layoutbox for the cb axis. Later, we will use this
  1315. # to make the colorbar fit nicely.
  1316. if not using_constrained_layout:
  1317. # no layout boxes:
  1318. lb = None
  1319. lbpos = None
  1320. # and we need to set the aspect ratio by hand...
  1321. cax.set_aspect(aspect, anchor=anchor, adjustable='box')
  1322. else:
  1323. if not parents_iterable:
  1324. # this is a single axis...
  1325. ax = parents[0]
  1326. lb, lbpos = constrained_layout.layoutcolorbarsingle(
  1327. ax, cax, shrink, aspect, location, pad=pad)
  1328. else: # there is more than one parent, so lets use gridspec
  1329. # the colorbar will be a sibling of this gridspec, so the
  1330. # parent is the same parent as the gridspec. Either the figure,
  1331. # or a subplotspec.
  1332. lb, lbpos = constrained_layout.layoutcolorbargridspec(
  1333. parents, cax, shrink, aspect, location, pad)
  1334. cax._layoutbox = lb
  1335. cax._poslayoutbox = lbpos
  1336. return cax, kw
  1337. @docstring.Substitution(_make_axes_param_doc, _make_axes_other_param_doc)
  1338. def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
  1339. """
  1340. Create a `~.SubplotBase` suitable for a colorbar.
  1341. The axes is placed in the figure of the *parent* axes, by resizing and
  1342. repositioning *parent*.
  1343. This function is similar to `.make_axes`. Primary differences are
  1344. - `.make_axes_gridspec` only handles the *orientation* keyword
  1345. and cannot handle the *location* keyword.
  1346. - `.make_axes_gridspec` should only be used with a `.SubplotBase` parent.
  1347. - `.make_axes` creates an `~.axes.Axes`; `.make_axes_gridspec` creates a
  1348. `.SubplotBase`.
  1349. - `.make_axes` updates the position of the parent. `.make_axes_gridspec`
  1350. replaces the ``grid_spec`` attribute of the parent with a new one.
  1351. While this function is meant to be compatible with `.make_axes`,
  1352. there could be some minor differences.
  1353. Parameters
  1354. ----------
  1355. parent : `~.axes.Axes`
  1356. The Axes to use as parent for placing the colorbar.
  1357. %s
  1358. Returns
  1359. -------
  1360. cax : `~.axes.SubplotBase`
  1361. The child axes.
  1362. kw : dict
  1363. The reduced keyword dictionary to be passed when creating the colorbar
  1364. instance.
  1365. Other Parameters
  1366. ----------------
  1367. orientation : {'vertical', 'horizontal'}, default: 'vertical'
  1368. The orientation of the colorbar.
  1369. %s
  1370. """
  1371. orientation = kw.setdefault('orientation', 'vertical')
  1372. kw['ticklocation'] = 'auto'
  1373. x1 = 1 - fraction
  1374. # for shrinking
  1375. pad_s = (1 - shrink) * 0.5
  1376. wh_ratios = [pad_s, shrink, pad_s]
  1377. # we need to none the tree of layoutboxes because
  1378. # constrained_layout can't remove and replace the tree
  1379. # hierarchy w/o a seg fault.
  1380. gs = parent.get_subplotspec().get_gridspec()
  1381. layoutbox.nonetree(gs._layoutbox)
  1382. gs_from_subplotspec = gridspec.GridSpecFromSubplotSpec
  1383. if orientation == 'vertical':
  1384. pad = kw.pop('pad', 0.05)
  1385. wh_space = 2 * pad / (1 - pad)
  1386. gs = gs_from_subplotspec(1, 2,
  1387. subplot_spec=parent.get_subplotspec(),
  1388. wspace=wh_space,
  1389. width_ratios=[x1 - pad, fraction])
  1390. gs2 = gs_from_subplotspec(3, 1,
  1391. subplot_spec=gs[1],
  1392. hspace=0.,
  1393. height_ratios=wh_ratios)
  1394. anchor = (0.0, 0.5)
  1395. panchor = (1.0, 0.5)
  1396. else:
  1397. pad = kw.pop('pad', 0.15)
  1398. wh_space = 2 * pad / (1 - pad)
  1399. gs = gs_from_subplotspec(2, 1,
  1400. subplot_spec=parent.get_subplotspec(),
  1401. hspace=wh_space,
  1402. height_ratios=[x1 - pad, fraction])
  1403. gs2 = gs_from_subplotspec(1, 3,
  1404. subplot_spec=gs[1],
  1405. wspace=0.,
  1406. width_ratios=wh_ratios)
  1407. aspect = 1 / aspect
  1408. anchor = (0.5, 1.0)
  1409. panchor = (0.5, 0.0)
  1410. parent.set_subplotspec(gs[0])
  1411. parent.update_params()
  1412. parent._set_position(parent.figbox)
  1413. parent.set_anchor(panchor)
  1414. fig = parent.get_figure()
  1415. cax = fig.add_subplot(gs2[1], label="<colorbar>")
  1416. cax.set_aspect(aspect, anchor=anchor, adjustable='box')
  1417. return cax, kw
  1418. class ColorbarPatch(Colorbar):
  1419. """
  1420. A Colorbar that uses a list of `~.patches.Patch` instances rather than the
  1421. default `~.collections.PatchCollection` created by `~.axes.Axes.pcolor`,
  1422. because the latter does not allow the hatch pattern to vary among the
  1423. members of the collection.
  1424. """
  1425. def __init__(self, ax, mappable, **kw):
  1426. # we do not want to override the behaviour of solids
  1427. # so add a new attribute which will be a list of the
  1428. # colored patches in the colorbar
  1429. self.solids_patches = []
  1430. Colorbar.__init__(self, ax, mappable, **kw)
  1431. def _add_solids(self, X, Y, C):
  1432. """
  1433. Draw the colors using `~matplotlib.patches.Patch`;
  1434. optionally add separators.
  1435. """
  1436. n_segments = len(C)
  1437. # ensure there are sufficient hatches
  1438. hatches = self.mappable.hatches * n_segments
  1439. patches = []
  1440. for i in range(len(X) - 1):
  1441. val = C[i][0]
  1442. hatch = hatches[i]
  1443. xy = np.array([[X[i][0], Y[i][0]],
  1444. [X[i][1], Y[i][0]],
  1445. [X[i + 1][1], Y[i + 1][0]],
  1446. [X[i + 1][0], Y[i + 1][1]]])
  1447. if self.orientation == 'horizontal':
  1448. # if horizontal swap the xs and ys
  1449. xy = xy[..., ::-1]
  1450. patch = mpatches.PathPatch(mpath.Path(xy),
  1451. facecolor=self.cmap(self.norm(val)),
  1452. hatch=hatch, linewidth=0,
  1453. antialiased=False, alpha=self.alpha)
  1454. self.ax.add_patch(patch)
  1455. patches.append(patch)
  1456. if self.solids_patches:
  1457. for solid in self.solids_patches:
  1458. solid.remove()
  1459. self.solids_patches = patches
  1460. if self.dividers is not None:
  1461. self.dividers.remove()
  1462. self.dividers = None
  1463. if self.drawedges:
  1464. self.dividers = collections.LineCollection(
  1465. self._edges(X, Y),
  1466. colors=(mpl.rcParams['axes.edgecolor'],),
  1467. linewidths=(0.5 * mpl.rcParams['axes.linewidth'],))
  1468. self.ax.add_collection(self.dividers)
  1469. def colorbar_factory(cax, mappable, **kwargs):
  1470. """
  1471. Create a colorbar on the given axes for the given mappable.
  1472. .. note::
  1473. This is a low-level function to turn an existing axes into a colorbar
  1474. axes. Typically, you'll want to use `~.Figure.colorbar` instead, which
  1475. automatically handles creation and placement of a suitable axes as
  1476. well.
  1477. Parameters
  1478. ----------
  1479. cax : `~matplotlib.axes.Axes`
  1480. The `~.axes.Axes` to turn into a colorbar.
  1481. mappable : `~matplotlib.cm.ScalarMappable`
  1482. The mappable to be described by the colorbar.
  1483. **kwargs
  1484. Keyword arguments are passed to the respective colorbar class.
  1485. Returns
  1486. -------
  1487. `.Colorbar` or `.ColorbarPatch`
  1488. The created colorbar instance. `.ColorbarPatch` is only used if
  1489. *mappable* is a `.ContourSet` with hatches.
  1490. """
  1491. # if the given mappable is a contourset with any hatching, use
  1492. # ColorbarPatch else use Colorbar
  1493. if (isinstance(mappable, contour.ContourSet)
  1494. and any(hatch is not None for hatch in mappable.hatches)):
  1495. cb = ColorbarPatch(cax, mappable, **kwargs)
  1496. else:
  1497. cb = Colorbar(cax, mappable, **kwargs)
  1498. cid = mappable.callbacksSM.connect('changed', cb.update_normal)
  1499. mappable.colorbar = cb
  1500. mappable.colorbar_cid = cid
  1501. return cb