Coverage for /usr/lib/python3/dist-packages/matplotlib/colorbar.py: 13%

704 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

1""" 

2Colorbars are a visualization of the mapping from scalar values to colors. 

3In Matplotlib they are drawn into a dedicated `~.axes.Axes`. 

4 

5.. note:: 

6 Colorbars are typically created through `.Figure.colorbar` or its pyplot 

7 wrapper `.pyplot.colorbar`, which internally use `.Colorbar` together with 

8 `.make_axes_gridspec` (for `.GridSpec`-positioned axes) or `.make_axes` (for 

9 non-`.GridSpec`-positioned axes). 

10 

11 End-users most likely won't need to directly use this module's API. 

12""" 

13 

14import logging 

15 

16import numpy as np 

17 

18import matplotlib as mpl 

19from matplotlib import _api, cbook, collections, cm, colors, contour, ticker 

20import matplotlib.artist as martist 

21import matplotlib.patches as mpatches 

22import matplotlib.path as mpath 

23import matplotlib.scale as mscale 

24import matplotlib.spines as mspines 

25import matplotlib.transforms as mtransforms 

26from matplotlib import _docstring 

27 

28_log = logging.getLogger(__name__) 

29 

30_docstring.interpd.update( 

31 _make_axes_kw_doc=""" 

32location : None or {'left', 'right', 'top', 'bottom'} 

33 The location, relative to the parent axes, where the colorbar axes 

34 is created. It also determines the *orientation* of the colorbar 

35 (colorbars on the left and right are vertical, colorbars at the top 

36 and bottom are horizontal). If None, the location will come from the 

37 *orientation* if it is set (vertical colorbars on the right, horizontal 

38 ones at the bottom), or default to 'right' if *orientation* is unset. 

39 

40orientation : None or {'vertical', 'horizontal'} 

41 The orientation of the colorbar. It is preferable to set the *location* 

42 of the colorbar, as that also determines the *orientation*; passing 

43 incompatible values for *location* and *orientation* raises an exception. 

44 

45fraction : float, default: 0.15 

46 Fraction of original axes to use for colorbar. 

47 

48shrink : float, default: 1.0 

49 Fraction by which to multiply the size of the colorbar. 

50 

51aspect : float, default: 20 

52 Ratio of long to short dimensions. 

53 

54pad : float, default: 0.05 if vertical, 0.15 if horizontal 

55 Fraction of original axes between colorbar and new image axes. 

56 

57anchor : (float, float), optional 

58 The anchor point of the colorbar axes. 

59 Defaults to (0.0, 0.5) if vertical; (0.5, 1.0) if horizontal. 

60 

61panchor : (float, float), or *False*, optional 

62 The anchor point of the colorbar parent axes. If *False*, the parent 

63 axes' anchor will be unchanged. 

64 Defaults to (1.0, 0.5) if vertical; (0.5, 0.0) if horizontal.""", 

65 _colormap_kw_doc=""" 

66extend : {'neither', 'both', 'min', 'max'} 

67 Make pointed end(s) for out-of-range values (unless 'neither'). These are 

68 set for a given colormap using the colormap set_under and set_over methods. 

69 

70extendfrac : {*None*, 'auto', length, lengths} 

71 If set to *None*, both the minimum and maximum triangular colorbar 

72 extensions will have a length of 5% of the interior colorbar length (this 

73 is the default setting). 

74 

75 If set to 'auto', makes the triangular colorbar extensions the same lengths 

76 as the interior boxes (when *spacing* is set to 'uniform') or the same 

77 lengths as the respective adjacent interior boxes (when *spacing* is set to 

78 'proportional'). 

79 

80 If a scalar, indicates the length of both the minimum and maximum 

81 triangular colorbar extensions as a fraction of the interior colorbar 

82 length. A two-element sequence of fractions may also be given, indicating 

83 the lengths of the minimum and maximum colorbar extensions respectively as 

84 a fraction of the interior colorbar length. 

85 

86extendrect : bool 

87 If *False* the minimum and maximum colorbar extensions will be triangular 

88 (the default). If *True* the extensions will be rectangular. 

89 

90spacing : {'uniform', 'proportional'} 

91 For discrete colorbars (`.BoundaryNorm` or contours), 'uniform' gives each 

92 color the same space; 'proportional' makes the space proportional to the 

93 data interval. 

94 

95ticks : None or list of ticks or Locator 

96 If None, ticks are determined automatically from the input. 

97 

98format : None or str or Formatter 

99 If None, `~.ticker.ScalarFormatter` is used. 

100 Format strings, e.g., ``"%4.2e"`` or ``"{x:.2e}"``, are supported. 

101 An alternative `~.ticker.Formatter` may be given instead. 

102 

103drawedges : bool 

104 Whether to draw lines at color boundaries. 

105 

106label : str 

107 The label on the colorbar's long axis. 

108 

109boundaries, values : None or a sequence 

110 If unset, the colormap will be displayed on a 0-1 scale. 

111 If sequences, *values* must have a length 1 less than *boundaries*. For 

112 each region delimited by adjacent entries in *boundaries*, the color mapped 

113 to the corresponding value in values will be used. 

114 Normally only useful for indexed colors (i.e. ``norm=NoNorm()``) or other 

115 unusual circumstances.""") 

116 

117 

118def _set_ticks_on_axis_warn(*args, **kwargs): 

119 # a top level function which gets put in at the axes' 

120 # set_xticks and set_yticks by Colorbar.__init__. 

121 _api.warn_external("Use the colorbar set_ticks() method instead.") 

122 

123 

124class _ColorbarSpine(mspines.Spine): 

125 def __init__(self, axes): 

126 self._ax = axes 

127 super().__init__(axes, 'colorbar', mpath.Path(np.empty((0, 2)))) 

128 mpatches.Patch.set_transform(self, axes.transAxes) 

129 

130 def get_window_extent(self, renderer=None): 

131 # This Spine has no Axis associated with it, and doesn't need to adjust 

132 # its location, so we can directly get the window extent from the 

133 # super-super-class. 

134 return mpatches.Patch.get_window_extent(self, renderer=renderer) 

135 

136 def set_xy(self, xy): 

137 self._path = mpath.Path(xy, closed=True) 

138 self._xy = xy 

139 self.stale = True 

140 

141 def draw(self, renderer): 

142 ret = mpatches.Patch.draw(self, renderer) 

143 self.stale = False 

144 return ret 

145 

146 

147class _ColorbarAxesLocator: 

148 """ 

149 Shrink the axes if there are triangular or rectangular extends. 

150 """ 

151 def __init__(self, cbar): 

152 self._cbar = cbar 

153 self._orig_locator = cbar.ax._axes_locator 

154 

155 def __call__(self, ax, renderer): 

156 if self._orig_locator is not None: 

157 pos = self._orig_locator(ax, renderer) 

158 else: 

159 pos = ax.get_position(original=True) 

160 if self._cbar.extend == 'neither': 

161 return pos 

162 

163 y, extendlen = self._cbar._proportional_y() 

164 if not self._cbar._extend_lower(): 

165 extendlen[0] = 0 

166 if not self._cbar._extend_upper(): 

167 extendlen[1] = 0 

168 len = sum(extendlen) + 1 

169 shrink = 1 / len 

170 offset = extendlen[0] / len 

171 # we need to reset the aspect ratio of the axes to account 

172 # of the extends... 

173 if hasattr(ax, '_colorbar_info'): 

174 aspect = ax._colorbar_info['aspect'] 

175 else: 

176 aspect = False 

177 # now shrink and/or offset to take into account the 

178 # extend tri/rectangles. 

179 if self._cbar.orientation == 'vertical': 

180 if aspect: 

181 self._cbar.ax.set_box_aspect(aspect*shrink) 

182 pos = pos.shrunk(1, shrink).translated(0, offset * pos.height) 

183 else: 

184 if aspect: 

185 self._cbar.ax.set_box_aspect(1/(aspect * shrink)) 

186 pos = pos.shrunk(shrink, 1).translated(offset * pos.width, 0) 

187 return pos 

188 

189 def get_subplotspec(self): 

190 # make tight_layout happy.. 

191 ss = getattr(self._cbar.ax, 'get_subplotspec', None) 

192 if ss is None: 

193 if not hasattr(self._orig_locator, "get_subplotspec"): 

194 return None 

195 ss = self._orig_locator.get_subplotspec 

196 return ss() 

197 

198 

199@_docstring.interpd 

200class Colorbar: 

201 r""" 

202 Draw a colorbar in an existing axes. 

203 

204 Typically, colorbars are created using `.Figure.colorbar` or 

205 `.pyplot.colorbar` and associated with `.ScalarMappable`\s (such as an 

206 `.AxesImage` generated via `~.axes.Axes.imshow`). 

207 

208 In order to draw a colorbar not associated with other elements in the 

209 figure, e.g. when showing a colormap by itself, one can create an empty 

210 `.ScalarMappable`, or directly pass *cmap* and *norm* instead of *mappable* 

211 to `Colorbar`. 

212 

213 Useful public methods are :meth:`set_label` and :meth:`add_lines`. 

214 

215 Attributes 

216 ---------- 

217 ax : `~matplotlib.axes.Axes` 

218 The `~.axes.Axes` instance in which the colorbar is drawn. 

219 lines : list 

220 A list of `.LineCollection` (empty if no lines were drawn). 

221 dividers : `.LineCollection` 

222 A LineCollection (empty if *drawedges* is ``False``). 

223 

224 Parameters 

225 ---------- 

226 ax : `~matplotlib.axes.Axes` 

227 The `~.axes.Axes` instance in which the colorbar is drawn. 

228 

229 mappable : `.ScalarMappable` 

230 The mappable whose colormap and norm will be used. 

231 

232 To show the under- and over- value colors, the mappable's norm should 

233 be specified as :: 

234 

235 norm = colors.Normalize(clip=False) 

236 

237 To show the colors versus index instead of on a 0-1 scale, use:: 

238 

239 norm=colors.NoNorm() 

240 

241 cmap : `~matplotlib.colors.Colormap`, default: :rc:`image.cmap` 

242 The colormap to use. This parameter is ignored, unless *mappable* is 

243 None. 

244 

245 norm : `~matplotlib.colors.Normalize` 

246 The normalization to use. This parameter is ignored, unless *mappable* 

247 is None. 

248 

249 alpha : float 

250 The colorbar transparency between 0 (transparent) and 1 (opaque). 

251 

252 orientation : {'vertical', 'horizontal'} 

253 

254 ticklocation : {'auto', 'left', 'right', 'top', 'bottom'} 

255 

256 drawedges : bool 

257 

258 filled : bool 

259 %(_colormap_kw_doc)s 

260 """ 

261 

262 n_rasterize = 50 # rasterize solids if number of colors >= n_rasterize 

263 

264 @_api.delete_parameter("3.6", "filled") 

265 def __init__(self, ax, mappable=None, *, cmap=None, 

266 norm=None, 

267 alpha=None, 

268 values=None, 

269 boundaries=None, 

270 orientation='vertical', 

271 ticklocation='auto', 

272 extend=None, 

273 spacing='uniform', # uniform or proportional 

274 ticks=None, 

275 format=None, 

276 drawedges=False, 

277 filled=True, 

278 extendfrac=None, 

279 extendrect=False, 

280 label='', 

281 ): 

282 

283 if mappable is None: 

284 mappable = cm.ScalarMappable(norm=norm, cmap=cmap) 

285 

286 # Ensure the given mappable's norm has appropriate vmin and vmax 

287 # set even if mappable.draw has not yet been called. 

288 if mappable.get_array() is not None: 

289 mappable.autoscale_None() 

290 

291 self.mappable = mappable 

292 cmap = mappable.cmap 

293 norm = mappable.norm 

294 

295 if isinstance(mappable, contour.ContourSet): 

296 cs = mappable 

297 alpha = cs.get_alpha() 

298 boundaries = cs._levels 

299 values = cs.cvalues 

300 extend = cs.extend 

301 filled = cs.filled 

302 if ticks is None: 

303 ticks = ticker.FixedLocator(cs.levels, nbins=10) 

304 elif isinstance(mappable, martist.Artist): 

305 alpha = mappable.get_alpha() 

306 

307 mappable.colorbar = self 

308 mappable.colorbar_cid = mappable.callbacks.connect( 

309 'changed', self.update_normal) 

310 

311 _api.check_in_list( 

312 ['vertical', 'horizontal'], orientation=orientation) 

313 _api.check_in_list( 

314 ['auto', 'left', 'right', 'top', 'bottom'], 

315 ticklocation=ticklocation) 

316 _api.check_in_list( 

317 ['uniform', 'proportional'], spacing=spacing) 

318 

319 self.ax = ax 

320 self.ax._axes_locator = _ColorbarAxesLocator(self) 

321 

322 if extend is None: 

323 if (not isinstance(mappable, contour.ContourSet) 

324 and getattr(cmap, 'colorbar_extend', False) is not False): 

325 extend = cmap.colorbar_extend 

326 elif hasattr(norm, 'extend'): 

327 extend = norm.extend 

328 else: 

329 extend = 'neither' 

330 self.alpha = None 

331 # Call set_alpha to handle array-like alphas properly 

332 self.set_alpha(alpha) 

333 self.cmap = cmap 

334 self.norm = norm 

335 self.values = values 

336 self.boundaries = boundaries 

337 self.extend = extend 

338 self._inside = _api.check_getitem( 

339 {'neither': slice(0, None), 'both': slice(1, -1), 

340 'min': slice(1, None), 'max': slice(0, -1)}, 

341 extend=extend) 

342 self.spacing = spacing 

343 self.orientation = orientation 

344 self.drawedges = drawedges 

345 self._filled = filled 

346 self.extendfrac = extendfrac 

347 self.extendrect = extendrect 

348 self._extend_patches = [] 

349 self.solids = None 

350 self.solids_patches = [] 

351 self.lines = [] 

352 

353 for spine in self.ax.spines.values(): 

354 spine.set_visible(False) 

355 self.outline = self.ax.spines['outline'] = _ColorbarSpine(self.ax) 

356 # Only kept for backcompat; remove after deprecation of .patch elapses. 

357 self._patch = mpatches.Polygon( 

358 np.empty((0, 2)), 

359 color=mpl.rcParams['axes.facecolor'], linewidth=0.01, zorder=-1) 

360 ax.add_artist(self._patch) 

361 

362 self.dividers = collections.LineCollection( 

363 [], 

364 colors=[mpl.rcParams['axes.edgecolor']], 

365 linewidths=[0.5 * mpl.rcParams['axes.linewidth']], 

366 clip_on=False) 

367 self.ax.add_collection(self.dividers) 

368 

369 self._locator = None 

370 self._minorlocator = None 

371 self._formatter = None 

372 self._minorformatter = None 

373 self.__scale = None # linear, log10 for now. Hopefully more? 

374 

375 if ticklocation == 'auto': 

376 ticklocation = 'bottom' if orientation == 'horizontal' else 'right' 

377 self.ticklocation = ticklocation 

378 

379 self.set_label(label) 

380 self._reset_locator_formatter_scale() 

381 

382 if np.iterable(ticks): 

383 self._locator = ticker.FixedLocator(ticks, nbins=len(ticks)) 

384 else: 

385 self._locator = ticks 

386 

387 if isinstance(format, str): 

388 # Check format between FormatStrFormatter and StrMethodFormatter 

389 try: 

390 self._formatter = ticker.FormatStrFormatter(format) 

391 _ = self._formatter(0) 

392 except TypeError: 

393 self._formatter = ticker.StrMethodFormatter(format) 

394 else: 

395 self._formatter = format # Assume it is a Formatter or None 

396 self._draw_all() 

397 

398 if isinstance(mappable, contour.ContourSet) and not mappable.filled: 

399 self.add_lines(mappable) 

400 

401 # Link the Axes and Colorbar for interactive use 

402 self.ax._colorbar = self 

403 # Don't navigate on any of these types of mappables 

404 if (isinstance(self.norm, (colors.BoundaryNorm, colors.NoNorm)) or 

405 isinstance(self.mappable, contour.ContourSet)): 

406 self.ax.set_navigate(False) 

407 

408 # These are the functions that set up interactivity on this colorbar 

409 self._interactive_funcs = ["_get_view", "_set_view", 

410 "_set_view_from_bbox", "drag_pan"] 

411 for x in self._interactive_funcs: 

412 setattr(self.ax, x, getattr(self, x)) 

413 # Set the cla function to the cbar's method to override it 

414 self.ax.cla = self._cbar_cla 

415 # Callbacks for the extend calculations to handle inverting the axis 

416 self._extend_cid1 = self.ax.callbacks.connect( 

417 "xlim_changed", self._do_extends) 

418 self._extend_cid2 = self.ax.callbacks.connect( 

419 "ylim_changed", self._do_extends) 

420 

421 @property 

422 def locator(self): 

423 """Major tick `.Locator` for the colorbar.""" 

424 return self._long_axis().get_major_locator() 

425 

426 @locator.setter 

427 def locator(self, loc): 

428 self._long_axis().set_major_locator(loc) 

429 self._locator = loc 

430 

431 @property 

432 def minorlocator(self): 

433 """Minor tick `.Locator` for the colorbar.""" 

434 return self._long_axis().get_minor_locator() 

435 

436 @minorlocator.setter 

437 def minorlocator(self, loc): 

438 self._long_axis().set_minor_locator(loc) 

439 self._minorlocator = loc 

440 

441 @property 

442 def formatter(self): 

443 """Major tick label `.Formatter` for the colorbar.""" 

444 return self._long_axis().get_major_formatter() 

445 

446 @formatter.setter 

447 def formatter(self, fmt): 

448 self._long_axis().set_major_formatter(fmt) 

449 self._formatter = fmt 

450 

451 @property 

452 def minorformatter(self): 

453 """Minor tick `.Formatter` for the colorbar.""" 

454 return self._long_axis().get_minor_formatter() 

455 

456 @minorformatter.setter 

457 def minorformatter(self, fmt): 

458 self._long_axis().set_minor_formatter(fmt) 

459 self._minorformatter = fmt 

460 

461 def _cbar_cla(self): 

462 """Function to clear the interactive colorbar state.""" 

463 for x in self._interactive_funcs: 

464 delattr(self.ax, x) 

465 # We now restore the old cla() back and can call it directly 

466 del self.ax.cla 

467 self.ax.cla() 

468 

469 # Also remove ._patch after deprecation elapses. 

470 patch = _api.deprecate_privatize_attribute("3.5", alternative="ax") 

471 

472 filled = _api.deprecate_privatize_attribute("3.6") 

473 

474 def update_normal(self, mappable): 

475 """ 

476 Update solid patches, lines, etc. 

477 

478 This is meant to be called when the norm of the image or contour plot 

479 to which this colorbar belongs changes. 

480 

481 If the norm on the mappable is different than before, this resets the 

482 locator and formatter for the axis, so if these have been customized, 

483 they will need to be customized again. However, if the norm only 

484 changes values of *vmin*, *vmax* or *cmap* then the old formatter 

485 and locator will be preserved. 

486 """ 

487 _log.debug('colorbar update normal %r %r', mappable.norm, self.norm) 

488 self.mappable = mappable 

489 self.set_alpha(mappable.get_alpha()) 

490 self.cmap = mappable.cmap 

491 if mappable.norm != self.norm: 

492 self.norm = mappable.norm 

493 self._reset_locator_formatter_scale() 

494 

495 self._draw_all() 

496 if isinstance(self.mappable, contour.ContourSet): 

497 CS = self.mappable 

498 if not CS.filled: 

499 self.add_lines(CS) 

500 self.stale = True 

501 

502 @_api.deprecated("3.6", alternative="fig.draw_without_rendering()") 

503 def draw_all(self): 

504 """ 

505 Calculate any free parameters based on the current cmap and norm, 

506 and do all the drawing. 

507 """ 

508 self._draw_all() 

509 

510 def _draw_all(self): 

511 """ 

512 Calculate any free parameters based on the current cmap and norm, 

513 and do all the drawing. 

514 """ 

515 if self.orientation == 'vertical': 

516 if mpl.rcParams['ytick.minor.visible']: 

517 self.minorticks_on() 

518 else: 

519 if mpl.rcParams['xtick.minor.visible']: 

520 self.minorticks_on() 

521 self._long_axis().set(label_position=self.ticklocation, 

522 ticks_position=self.ticklocation) 

523 self._short_axis().set_ticks([]) 

524 self._short_axis().set_ticks([], minor=True) 

525 

526 # Set self._boundaries and self._values, including extensions. 

527 # self._boundaries are the edges of each square of color, and 

528 # self._values are the value to map into the norm to get the 

529 # color: 

530 self._process_values() 

531 # Set self.vmin and self.vmax to first and last boundary, excluding 

532 # extensions: 

533 self.vmin, self.vmax = self._boundaries[self._inside][[0, -1]] 

534 # Compute the X/Y mesh. 

535 X, Y = self._mesh() 

536 # draw the extend triangles, and shrink the inner axes to accommodate. 

537 # also adds the outline path to self.outline spine: 

538 self._do_extends() 

539 lower, upper = self.vmin, self.vmax 

540 if self._long_axis().get_inverted(): 

541 # If the axis is inverted, we need to swap the vmin/vmax 

542 lower, upper = upper, lower 

543 if self.orientation == 'vertical': 

544 self.ax.set_xlim(0, 1) 

545 self.ax.set_ylim(lower, upper) 

546 else: 

547 self.ax.set_ylim(0, 1) 

548 self.ax.set_xlim(lower, upper) 

549 

550 # set up the tick locators and formatters. A bit complicated because 

551 # boundary norms + uniform spacing requires a manual locator. 

552 self.update_ticks() 

553 

554 if self._filled: 

555 ind = np.arange(len(self._values)) 

556 if self._extend_lower(): 

557 ind = ind[1:] 

558 if self._extend_upper(): 

559 ind = ind[:-1] 

560 self._add_solids(X, Y, self._values[ind, np.newaxis]) 

561 

562 def _add_solids(self, X, Y, C): 

563 """Draw the colors; optionally add separators.""" 

564 # Cleanup previously set artists. 

565 if self.solids is not None: 

566 self.solids.remove() 

567 for solid in self.solids_patches: 

568 solid.remove() 

569 # Add new artist(s), based on mappable type. Use individual patches if 

570 # hatching is needed, pcolormesh otherwise. 

571 mappable = getattr(self, 'mappable', None) 

572 if (isinstance(mappable, contour.ContourSet) 

573 and any(hatch is not None for hatch in mappable.hatches)): 

574 self._add_solids_patches(X, Y, C, mappable) 

575 else: 

576 self.solids = self.ax.pcolormesh( 

577 X, Y, C, cmap=self.cmap, norm=self.norm, alpha=self.alpha, 

578 edgecolors='none', shading='flat') 

579 if not self.drawedges: 

580 if len(self._y) >= self.n_rasterize: 

581 self.solids.set_rasterized(True) 

582 self._update_dividers() 

583 

584 def _update_dividers(self): 

585 if not self.drawedges: 

586 self.dividers.set_segments([]) 

587 return 

588 # Place all *internal* dividers. 

589 if self.orientation == 'vertical': 

590 lims = self.ax.get_ylim() 

591 bounds = (lims[0] < self._y) & (self._y < lims[1]) 

592 else: 

593 lims = self.ax.get_xlim() 

594 bounds = (lims[0] < self._y) & (self._y < lims[1]) 

595 y = self._y[bounds] 

596 # And then add outer dividers if extensions are on. 

597 if self._extend_lower(): 

598 y = np.insert(y, 0, lims[0]) 

599 if self._extend_upper(): 

600 y = np.append(y, lims[1]) 

601 X, Y = np.meshgrid([0, 1], y) 

602 if self.orientation == 'vertical': 

603 segments = np.dstack([X, Y]) 

604 else: 

605 segments = np.dstack([Y, X]) 

606 self.dividers.set_segments(segments) 

607 

608 def _add_solids_patches(self, X, Y, C, mappable): 

609 hatches = mappable.hatches * (len(C) + 1) # Have enough hatches. 

610 if self._extend_lower(): 

611 # remove first hatch that goes into the extend patch 

612 hatches = hatches[1:] 

613 patches = [] 

614 for i in range(len(X) - 1): 

615 xy = np.array([[X[i, 0], Y[i, 1]], 

616 [X[i, 1], Y[i, 0]], 

617 [X[i + 1, 1], Y[i + 1, 0]], 

618 [X[i + 1, 0], Y[i + 1, 1]]]) 

619 patch = mpatches.PathPatch(mpath.Path(xy), 

620 facecolor=self.cmap(self.norm(C[i][0])), 

621 hatch=hatches[i], linewidth=0, 

622 antialiased=False, alpha=self.alpha) 

623 self.ax.add_patch(patch) 

624 patches.append(patch) 

625 self.solids_patches = patches 

626 

627 def _do_extends(self, ax=None): 

628 """ 

629 Add the extend tri/rectangles on the outside of the axes. 

630 

631 ax is unused, but required due to the callbacks on xlim/ylim changed 

632 """ 

633 # Clean up any previous extend patches 

634 for patch in self._extend_patches: 

635 patch.remove() 

636 self._extend_patches = [] 

637 # extend lengths are fraction of the *inner* part of colorbar, 

638 # not the total colorbar: 

639 _, extendlen = self._proportional_y() 

640 bot = 0 - (extendlen[0] if self._extend_lower() else 0) 

641 top = 1 + (extendlen[1] if self._extend_upper() else 0) 

642 

643 # xyout is the outline of the colorbar including the extend patches: 

644 if not self.extendrect: 

645 # triangle: 

646 xyout = np.array([[0, 0], [0.5, bot], [1, 0], 

647 [1, 1], [0.5, top], [0, 1], [0, 0]]) 

648 else: 

649 # rectangle: 

650 xyout = np.array([[0, 0], [0, bot], [1, bot], [1, 0], 

651 [1, 1], [1, top], [0, top], [0, 1], 

652 [0, 0]]) 

653 

654 if self.orientation == 'horizontal': 

655 xyout = xyout[:, ::-1] 

656 

657 # xyout is the path for the spine: 

658 self.outline.set_xy(xyout) 

659 if not self._filled: 

660 return 

661 

662 # Make extend triangles or rectangles filled patches. These are 

663 # defined in the outer parent axes' coordinates: 

664 mappable = getattr(self, 'mappable', None) 

665 if (isinstance(mappable, contour.ContourSet) 

666 and any(hatch is not None for hatch in mappable.hatches)): 

667 hatches = mappable.hatches * (len(self._y) + 1) 

668 else: 

669 hatches = [None] * (len(self._y) + 1) 

670 

671 if self._extend_lower(): 

672 if not self.extendrect: 

673 # triangle 

674 xy = np.array([[0, 0], [0.5, bot], [1, 0]]) 

675 else: 

676 # rectangle 

677 xy = np.array([[0, 0], [0, bot], [1., bot], [1, 0]]) 

678 if self.orientation == 'horizontal': 

679 xy = xy[:, ::-1] 

680 # add the patch 

681 val = -1 if self._long_axis().get_inverted() else 0 

682 color = self.cmap(self.norm(self._values[val])) 

683 patch = mpatches.PathPatch( 

684 mpath.Path(xy), facecolor=color, alpha=self.alpha, 

685 linewidth=0, antialiased=False, 

686 transform=self.ax.transAxes, 

687 hatch=hatches[0], clip_on=False, 

688 # Place it right behind the standard patches, which is 

689 # needed if we updated the extends 

690 zorder=np.nextafter(self.ax.patch.zorder, -np.inf)) 

691 self.ax.add_patch(patch) 

692 self._extend_patches.append(patch) 

693 # remove first hatch that goes into the extend patch 

694 hatches = hatches[1:] 

695 if self._extend_upper(): 

696 if not self.extendrect: 

697 # triangle 

698 xy = np.array([[0, 1], [0.5, top], [1, 1]]) 

699 else: 

700 # rectangle 

701 xy = np.array([[0, 1], [0, top], [1, top], [1, 1]]) 

702 if self.orientation == 'horizontal': 

703 xy = xy[:, ::-1] 

704 # add the patch 

705 val = 0 if self._long_axis().get_inverted() else -1 

706 color = self.cmap(self.norm(self._values[val])) 

707 hatch_idx = len(self._y) - 1 

708 patch = mpatches.PathPatch( 

709 mpath.Path(xy), facecolor=color, alpha=self.alpha, 

710 linewidth=0, antialiased=False, 

711 transform=self.ax.transAxes, hatch=hatches[hatch_idx], 

712 clip_on=False, 

713 # Place it right behind the standard patches, which is 

714 # needed if we updated the extends 

715 zorder=np.nextafter(self.ax.patch.zorder, -np.inf)) 

716 self.ax.add_patch(patch) 

717 self._extend_patches.append(patch) 

718 

719 self._update_dividers() 

720 

721 def add_lines(self, *args, **kwargs): 

722 """ 

723 Draw lines on the colorbar. 

724 

725 The lines are appended to the list :attr:`lines`. 

726 

727 Parameters 

728 ---------- 

729 levels : array-like 

730 The positions of the lines. 

731 colors : color or list of colors 

732 Either a single color applying to all lines or one color value for 

733 each line. 

734 linewidths : float or array-like 

735 Either a single linewidth applying to all lines or one linewidth 

736 for each line. 

737 erase : bool, default: True 

738 Whether to remove any previously added lines. 

739 

740 Notes 

741 ----- 

742 Alternatively, this method can also be called with the signature 

743 ``colorbar.add_lines(contour_set, erase=True)``, in which case 

744 *levels*, *colors*, and *linewidths* are taken from *contour_set*. 

745 """ 

746 params = _api.select_matching_signature( 

747 [lambda self, CS, erase=True: locals(), 

748 lambda self, levels, colors, linewidths, erase=True: locals()], 

749 self, *args, **kwargs) 

750 if "CS" in params: 

751 self, CS, erase = params.values() 

752 if not isinstance(CS, contour.ContourSet) or CS.filled: 

753 raise ValueError("If a single artist is passed to add_lines, " 

754 "it must be a ContourSet of lines") 

755 # TODO: Make colorbar lines auto-follow changes in contour lines. 

756 return self.add_lines( 

757 CS.levels, 

758 [c[0] for c in CS.tcolors], 

759 [t[0] for t in CS.tlinewidths], 

760 erase=erase) 

761 else: 

762 self, levels, colors, linewidths, erase = params.values() 

763 

764 y = self._locate(levels) 

765 rtol = (self._y[-1] - self._y[0]) * 1e-10 

766 igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol) 

767 y = y[igood] 

768 if np.iterable(colors): 

769 colors = np.asarray(colors)[igood] 

770 if np.iterable(linewidths): 

771 linewidths = np.asarray(linewidths)[igood] 

772 X, Y = np.meshgrid([0, 1], y) 

773 if self.orientation == 'vertical': 

774 xy = np.stack([X, Y], axis=-1) 

775 else: 

776 xy = np.stack([Y, X], axis=-1) 

777 col = collections.LineCollection(xy, linewidths=linewidths, 

778 colors=colors) 

779 

780 if erase and self.lines: 

781 for lc in self.lines: 

782 lc.remove() 

783 self.lines = [] 

784 self.lines.append(col) 

785 

786 # make a clip path that is just a linewidth bigger than the axes... 

787 fac = np.max(linewidths) / 72 

788 xy = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]) 

789 inches = self.ax.get_figure().dpi_scale_trans 

790 # do in inches: 

791 xy = inches.inverted().transform(self.ax.transAxes.transform(xy)) 

792 xy[[0, 1, 4], 1] -= fac 

793 xy[[2, 3], 1] += fac 

794 # back to axes units... 

795 xy = self.ax.transAxes.inverted().transform(inches.transform(xy)) 

796 col.set_clip_path(mpath.Path(xy, closed=True), 

797 self.ax.transAxes) 

798 self.ax.add_collection(col) 

799 self.stale = True 

800 

801 def update_ticks(self): 

802 """ 

803 Set up the ticks and ticklabels. This should not be needed by users. 

804 """ 

805 # Get the locator and formatter; defaults to self._locator if not None. 

806 self._get_ticker_locator_formatter() 

807 self._long_axis().set_major_locator(self._locator) 

808 self._long_axis().set_minor_locator(self._minorlocator) 

809 self._long_axis().set_major_formatter(self._formatter) 

810 

811 def _get_ticker_locator_formatter(self): 

812 """ 

813 Return the ``locator`` and ``formatter`` of the colorbar. 

814 

815 If they have not been defined (i.e. are *None*), the formatter and 

816 locator are retrieved from the axis, or from the value of the 

817 boundaries for a boundary norm. 

818 

819 Called by update_ticks... 

820 """ 

821 locator = self._locator 

822 formatter = self._formatter 

823 minorlocator = self._minorlocator 

824 if isinstance(self.norm, colors.BoundaryNorm): 

825 b = self.norm.boundaries 

826 if locator is None: 

827 locator = ticker.FixedLocator(b, nbins=10) 

828 if minorlocator is None: 

829 minorlocator = ticker.FixedLocator(b) 

830 elif isinstance(self.norm, colors.NoNorm): 

831 if locator is None: 

832 # put ticks on integers between the boundaries of NoNorm 

833 nv = len(self._values) 

834 base = 1 + int(nv / 10) 

835 locator = ticker.IndexLocator(base=base, offset=.5) 

836 elif self.boundaries is not None: 

837 b = self._boundaries[self._inside] 

838 if locator is None: 

839 locator = ticker.FixedLocator(b, nbins=10) 

840 else: # most cases: 

841 if locator is None: 

842 # we haven't set the locator explicitly, so use the default 

843 # for this axis: 

844 locator = self._long_axis().get_major_locator() 

845 if minorlocator is None: 

846 minorlocator = self._long_axis().get_minor_locator() 

847 

848 if minorlocator is None: 

849 minorlocator = ticker.NullLocator() 

850 

851 if formatter is None: 

852 formatter = self._long_axis().get_major_formatter() 

853 

854 self._locator = locator 

855 self._formatter = formatter 

856 self._minorlocator = minorlocator 

857 _log.debug('locator: %r', locator) 

858 

859 @_api.delete_parameter("3.5", "update_ticks") 

860 def set_ticks(self, ticks, update_ticks=True, labels=None, *, 

861 minor=False, **kwargs): 

862 """ 

863 Set tick locations. 

864 

865 Parameters 

866 ---------- 

867 ticks : list of floats 

868 List of tick locations. 

869 labels : list of str, optional 

870 List of tick labels. If not set, the labels show the data value. 

871 minor : bool, default: False 

872 If ``False``, set the major ticks; if ``True``, the minor ticks. 

873 **kwargs 

874 `.Text` properties for the labels. These take effect only if you 

875 pass *labels*. In other cases, please use `~.Axes.tick_params`. 

876 """ 

877 if np.iterable(ticks): 

878 self._long_axis().set_ticks(ticks, labels=labels, minor=minor, 

879 **kwargs) 

880 self._locator = self._long_axis().get_major_locator() 

881 else: 

882 self._locator = ticks 

883 self._long_axis().set_major_locator(self._locator) 

884 self.stale = True 

885 

886 def get_ticks(self, minor=False): 

887 """ 

888 Return the ticks as a list of locations. 

889 

890 Parameters 

891 ---------- 

892 minor : boolean, default: False 

893 if True return the minor ticks. 

894 """ 

895 if minor: 

896 return self._long_axis().get_minorticklocs() 

897 else: 

898 return self._long_axis().get_majorticklocs() 

899 

900 @_api.delete_parameter("3.5", "update_ticks") 

901 def set_ticklabels(self, ticklabels, update_ticks=True, *, minor=False, 

902 **kwargs): 

903 """ 

904 [*Discouraged*] Set tick labels. 

905 

906 .. admonition:: Discouraged 

907 

908 The use of this method is discouraged, because of the dependency 

909 on tick positions. In most cases, you'll want to use 

910 ``set_ticks(positions, labels=labels)`` instead. 

911 

912 If you are using this method, you should always fix the tick 

913 positions before, e.g. by using `.Colorbar.set_ticks` or by 

914 explicitly setting a `~.ticker.FixedLocator` on the long axis 

915 of the colorbar. Otherwise, ticks are free to move and the 

916 labels may end up in unexpected positions. 

917 

918 Parameters 

919 ---------- 

920 ticklabels : sequence of str or of `.Text` 

921 Texts for labeling each tick location in the sequence set by 

922 `.Colorbar.set_ticks`; the number of labels must match the number 

923 of locations. 

924 

925 update_ticks : bool, default: True 

926 This keyword argument is ignored and will be removed. 

927 Deprecated 

928 

929 minor : bool 

930 If True, set minor ticks instead of major ticks. 

931 

932 **kwargs 

933 `.Text` properties for the labels. 

934 """ 

935 self._long_axis().set_ticklabels(ticklabels, minor=minor, **kwargs) 

936 

937 def minorticks_on(self): 

938 """ 

939 Turn on colorbar minor ticks. 

940 """ 

941 self.ax.minorticks_on() 

942 self._short_axis().set_minor_locator(ticker.NullLocator()) 

943 

944 def minorticks_off(self): 

945 """Turn the minor ticks of the colorbar off.""" 

946 self._minorlocator = ticker.NullLocator() 

947 self._long_axis().set_minor_locator(self._minorlocator) 

948 

949 def set_label(self, label, *, loc=None, **kwargs): 

950 """ 

951 Add a label to the long axis of the colorbar. 

952 

953 Parameters 

954 ---------- 

955 label : str 

956 The label text. 

957 loc : str, optional 

958 The location of the label. 

959 

960 - For horizontal orientation one of {'left', 'center', 'right'} 

961 - For vertical orientation one of {'bottom', 'center', 'top'} 

962 

963 Defaults to :rc:`xaxis.labellocation` or :rc:`yaxis.labellocation` 

964 depending on the orientation. 

965 **kwargs 

966 Keyword arguments are passed to `~.Axes.set_xlabel` / 

967 `~.Axes.set_ylabel`. 

968 Supported keywords are *labelpad* and `.Text` properties. 

969 """ 

970 if self.orientation == "vertical": 

971 self.ax.set_ylabel(label, loc=loc, **kwargs) 

972 else: 

973 self.ax.set_xlabel(label, loc=loc, **kwargs) 

974 self.stale = True 

975 

976 def set_alpha(self, alpha): 

977 """ 

978 Set the transparency between 0 (transparent) and 1 (opaque). 

979 

980 If an array is provided, *alpha* will be set to None to use the 

981 transparency values associated with the colormap. 

982 """ 

983 self.alpha = None if isinstance(alpha, np.ndarray) else alpha 

984 

985 def _set_scale(self, scale, **kwargs): 

986 """ 

987 Set the colorbar long axis scale. 

988 

989 Parameters 

990 ---------- 

991 value : {"linear", "log", "symlog", "logit", ...} or `.ScaleBase` 

992 The axis scale type to apply. 

993 

994 **kwargs 

995 Different keyword arguments are accepted, depending on the scale. 

996 See the respective class keyword arguments: 

997 

998 - `matplotlib.scale.LinearScale` 

999 - `matplotlib.scale.LogScale` 

1000 - `matplotlib.scale.SymmetricalLogScale` 

1001 - `matplotlib.scale.LogitScale` 

1002 - `matplotlib.scale.FuncScale` 

1003 

1004 Notes 

1005 ----- 

1006 By default, Matplotlib supports the above-mentioned scales. 

1007 Additionally, custom scales may be registered using 

1008 `matplotlib.scale.register_scale`. These scales can then also 

1009 be used here. 

1010 """ 

1011 if self.orientation == 'vertical': 

1012 self.ax.set_yscale(scale, **kwargs) 

1013 else: 

1014 self.ax.set_xscale(scale, **kwargs) 

1015 if isinstance(scale, mscale.ScaleBase): 

1016 self.__scale = scale.name 

1017 else: 

1018 self.__scale = scale 

1019 

1020 def remove(self): 

1021 """ 

1022 Remove this colorbar from the figure. 

1023 

1024 If the colorbar was created with ``use_gridspec=True`` the previous 

1025 gridspec is restored. 

1026 """ 

1027 if hasattr(self.ax, '_colorbar_info'): 

1028 parents = self.ax._colorbar_info['parents'] 

1029 for a in parents: 

1030 if self.ax in a._colorbars: 

1031 a._colorbars.remove(self.ax) 

1032 

1033 self.ax.remove() 

1034 

1035 self.mappable.callbacks.disconnect(self.mappable.colorbar_cid) 

1036 self.mappable.colorbar = None 

1037 self.mappable.colorbar_cid = None 

1038 # Remove the extension callbacks 

1039 self.ax.callbacks.disconnect(self._extend_cid1) 

1040 self.ax.callbacks.disconnect(self._extend_cid2) 

1041 

1042 try: 

1043 ax = self.mappable.axes 

1044 except AttributeError: 

1045 return 

1046 try: 

1047 gs = ax.get_subplotspec().get_gridspec() 

1048 subplotspec = gs.get_topmost_subplotspec() 

1049 except AttributeError: 

1050 # use_gridspec was False 

1051 pos = ax.get_position(original=True) 

1052 ax._set_position(pos) 

1053 else: 

1054 # use_gridspec was True 

1055 ax.set_subplotspec(subplotspec) 

1056 

1057 def _process_values(self): 

1058 """ 

1059 Set `_boundaries` and `_values` based on the self.boundaries and 

1060 self.values if not None, or based on the size of the colormap and 

1061 the vmin/vmax of the norm. 

1062 """ 

1063 if self.values is not None: 

1064 # set self._boundaries from the values... 

1065 self._values = np.array(self.values) 

1066 if self.boundaries is None: 

1067 # bracket values by 1/2 dv: 

1068 b = np.zeros(len(self.values) + 1) 

1069 b[1:-1] = 0.5 * (self._values[:-1] + self._values[1:]) 

1070 b[0] = 2.0 * b[1] - b[2] 

1071 b[-1] = 2.0 * b[-2] - b[-3] 

1072 self._boundaries = b 

1073 return 

1074 self._boundaries = np.array(self.boundaries) 

1075 return 

1076 

1077 # otherwise values are set from the boundaries 

1078 if isinstance(self.norm, colors.BoundaryNorm): 

1079 b = self.norm.boundaries 

1080 elif isinstance(self.norm, colors.NoNorm): 

1081 # NoNorm has N blocks, so N+1 boundaries, centered on integers: 

1082 b = np.arange(self.cmap.N + 1) - .5 

1083 elif self.boundaries is not None: 

1084 b = self.boundaries 

1085 else: 

1086 # otherwise make the boundaries from the size of the cmap: 

1087 N = self.cmap.N + 1 

1088 b, _ = self._uniform_y(N) 

1089 # add extra boundaries if needed: 

1090 if self._extend_lower(): 

1091 b = np.hstack((b[0] - 1, b)) 

1092 if self._extend_upper(): 

1093 b = np.hstack((b, b[-1] + 1)) 

1094 

1095 # transform from 0-1 to vmin-vmax: 

1096 if not self.norm.scaled(): 

1097 self.norm.vmin = 0 

1098 self.norm.vmax = 1 

1099 self.norm.vmin, self.norm.vmax = mtransforms.nonsingular( 

1100 self.norm.vmin, self.norm.vmax, expander=0.1) 

1101 if (not isinstance(self.norm, colors.BoundaryNorm) and 

1102 (self.boundaries is None)): 

1103 b = self.norm.inverse(b) 

1104 

1105 self._boundaries = np.asarray(b, dtype=float) 

1106 self._values = 0.5 * (self._boundaries[:-1] + self._boundaries[1:]) 

1107 if isinstance(self.norm, colors.NoNorm): 

1108 self._values = (self._values + 0.00001).astype(np.int16) 

1109 

1110 def _mesh(self): 

1111 """ 

1112 Return the coordinate arrays for the colorbar pcolormesh/patches. 

1113 

1114 These are scaled between vmin and vmax, and already handle colorbar 

1115 orientation. 

1116 """ 

1117 y, _ = self._proportional_y() 

1118 # Use the vmin and vmax of the colorbar, which may not be the same 

1119 # as the norm. There are situations where the colormap has a 

1120 # narrower range than the colorbar and we want to accommodate the 

1121 # extra contours. 

1122 if (isinstance(self.norm, (colors.BoundaryNorm, colors.NoNorm)) 

1123 or self.boundaries is not None): 

1124 # not using a norm. 

1125 y = y * (self.vmax - self.vmin) + self.vmin 

1126 else: 

1127 # Update the norm values in a context manager as it is only 

1128 # a temporary change and we don't want to propagate any signals 

1129 # attached to the norm (callbacks.blocked). 

1130 with self.norm.callbacks.blocked(), \ 

1131 cbook._setattr_cm(self.norm, 

1132 vmin=self.vmin, 

1133 vmax=self.vmax): 

1134 y = self.norm.inverse(y) 

1135 self._y = y 

1136 X, Y = np.meshgrid([0., 1.], y) 

1137 if self.orientation == 'vertical': 

1138 return (X, Y) 

1139 else: 

1140 return (Y, X) 

1141 

1142 def _forward_boundaries(self, x): 

1143 # map boundaries equally between 0 and 1... 

1144 b = self._boundaries 

1145 y = np.interp(x, b, np.linspace(0, 1, len(b))) 

1146 # the following avoids ticks in the extends: 

1147 eps = (b[-1] - b[0]) * 1e-6 

1148 # map these _well_ out of bounds to keep any ticks out 

1149 # of the extends region... 

1150 y[x < b[0]-eps] = -1 

1151 y[x > b[-1]+eps] = 2 

1152 return y 

1153 

1154 def _inverse_boundaries(self, x): 

1155 # invert the above... 

1156 b = self._boundaries 

1157 return np.interp(x, np.linspace(0, 1, len(b)), b) 

1158 

1159 def _reset_locator_formatter_scale(self): 

1160 """ 

1161 Reset the locator et al to defaults. Any user-hardcoded changes 

1162 need to be re-entered if this gets called (either at init, or when 

1163 the mappable normal gets changed: Colorbar.update_normal) 

1164 """ 

1165 self._process_values() 

1166 self._locator = None 

1167 self._minorlocator = None 

1168 self._formatter = None 

1169 self._minorformatter = None 

1170 if (isinstance(self.mappable, contour.ContourSet) and 

1171 isinstance(self.norm, colors.LogNorm)): 

1172 # if contours have lognorm, give them a log scale... 

1173 self._set_scale('log') 

1174 elif (self.boundaries is not None or 

1175 isinstance(self.norm, colors.BoundaryNorm)): 

1176 if self.spacing == 'uniform': 

1177 funcs = (self._forward_boundaries, self._inverse_boundaries) 

1178 self._set_scale('function', functions=funcs) 

1179 elif self.spacing == 'proportional': 

1180 self._set_scale('linear') 

1181 elif getattr(self.norm, '_scale', None): 

1182 # use the norm's scale (if it exists and is not None): 

1183 self._set_scale(self.norm._scale) 

1184 elif type(self.norm) is colors.Normalize: 

1185 # plain Normalize: 

1186 self._set_scale('linear') 

1187 else: 

1188 # norm._scale is None or not an attr: derive the scale from 

1189 # the Norm: 

1190 funcs = (self.norm, self.norm.inverse) 

1191 self._set_scale('function', functions=funcs) 

1192 

1193 def _locate(self, x): 

1194 """ 

1195 Given a set of color data values, return their 

1196 corresponding colorbar data coordinates. 

1197 """ 

1198 if isinstance(self.norm, (colors.NoNorm, colors.BoundaryNorm)): 

1199 b = self._boundaries 

1200 xn = x 

1201 else: 

1202 # Do calculations using normalized coordinates so 

1203 # as to make the interpolation more accurate. 

1204 b = self.norm(self._boundaries, clip=False).filled() 

1205 xn = self.norm(x, clip=False).filled() 

1206 

1207 bunique = b[self._inside] 

1208 yunique = self._y 

1209 

1210 z = np.interp(xn, bunique, yunique) 

1211 return z 

1212 

1213 # trivial helpers 

1214 

1215 def _uniform_y(self, N): 

1216 """ 

1217 Return colorbar data coordinates for *N* uniformly 

1218 spaced boundaries, plus extension lengths if required. 

1219 """ 

1220 automin = automax = 1. / (N - 1.) 

1221 extendlength = self._get_extension_lengths(self.extendfrac, 

1222 automin, automax, 

1223 default=0.05) 

1224 y = np.linspace(0, 1, N) 

1225 return y, extendlength 

1226 

1227 def _proportional_y(self): 

1228 """ 

1229 Return colorbar data coordinates for the boundaries of 

1230 a proportional colorbar, plus extension lengths if required: 

1231 """ 

1232 if (isinstance(self.norm, colors.BoundaryNorm) or 

1233 self.boundaries is not None): 

1234 y = (self._boundaries - self._boundaries[self._inside][0]) 

1235 y = y / (self._boundaries[self._inside][-1] - 

1236 self._boundaries[self._inside][0]) 

1237 # need yscaled the same as the axes scale to get 

1238 # the extend lengths. 

1239 if self.spacing == 'uniform': 

1240 yscaled = self._forward_boundaries(self._boundaries) 

1241 else: 

1242 yscaled = y 

1243 else: 

1244 y = self.norm(self._boundaries.copy()) 

1245 y = np.ma.filled(y, np.nan) 

1246 # the norm and the scale should be the same... 

1247 yscaled = y 

1248 y = y[self._inside] 

1249 yscaled = yscaled[self._inside] 

1250 # normalize from 0..1: 

1251 norm = colors.Normalize(y[0], y[-1]) 

1252 y = np.ma.filled(norm(y), np.nan) 

1253 norm = colors.Normalize(yscaled[0], yscaled[-1]) 

1254 yscaled = np.ma.filled(norm(yscaled), np.nan) 

1255 # make the lower and upper extend lengths proportional to the lengths 

1256 # of the first and last boundary spacing (if extendfrac='auto'): 

1257 automin = yscaled[1] - yscaled[0] 

1258 automax = yscaled[-1] - yscaled[-2] 

1259 extendlength = [0, 0] 

1260 if self._extend_lower() or self._extend_upper(): 

1261 extendlength = self._get_extension_lengths( 

1262 self.extendfrac, automin, automax, default=0.05) 

1263 return y, extendlength 

1264 

1265 def _get_extension_lengths(self, frac, automin, automax, default=0.05): 

1266 """ 

1267 Return the lengths of colorbar extensions. 

1268 

1269 This is a helper method for _uniform_y and _proportional_y. 

1270 """ 

1271 # Set the default value. 

1272 extendlength = np.array([default, default]) 

1273 if isinstance(frac, str): 

1274 _api.check_in_list(['auto'], extendfrac=frac.lower()) 

1275 # Use the provided values when 'auto' is required. 

1276 extendlength[:] = [automin, automax] 

1277 elif frac is not None: 

1278 try: 

1279 # Try to set min and max extension fractions directly. 

1280 extendlength[:] = frac 

1281 # If frac is a sequence containing None then NaN may 

1282 # be encountered. This is an error. 

1283 if np.isnan(extendlength).any(): 

1284 raise ValueError() 

1285 except (TypeError, ValueError) as err: 

1286 # Raise an error on encountering an invalid value for frac. 

1287 raise ValueError('invalid value for extendfrac') from err 

1288 return extendlength 

1289 

1290 def _extend_lower(self): 

1291 """Return whether the lower limit is open ended.""" 

1292 minmax = "max" if self._long_axis().get_inverted() else "min" 

1293 return self.extend in ('both', minmax) 

1294 

1295 def _extend_upper(self): 

1296 """Return whether the upper limit is open ended.""" 

1297 minmax = "min" if self._long_axis().get_inverted() else "max" 

1298 return self.extend in ('both', minmax) 

1299 

1300 def _long_axis(self): 

1301 """Return the long axis""" 

1302 if self.orientation == 'vertical': 

1303 return self.ax.yaxis 

1304 return self.ax.xaxis 

1305 

1306 def _short_axis(self): 

1307 """Return the short axis""" 

1308 if self.orientation == 'vertical': 

1309 return self.ax.xaxis 

1310 return self.ax.yaxis 

1311 

1312 def _get_view(self): 

1313 # docstring inherited 

1314 # An interactive view for a colorbar is the norm's vmin/vmax 

1315 return self.norm.vmin, self.norm.vmax 

1316 

1317 def _set_view(self, view): 

1318 # docstring inherited 

1319 # An interactive view for a colorbar is the norm's vmin/vmax 

1320 self.norm.vmin, self.norm.vmax = view 

1321 

1322 def _set_view_from_bbox(self, bbox, direction='in', 

1323 mode=None, twinx=False, twiny=False): 

1324 # docstring inherited 

1325 # For colorbars, we use the zoom bbox to scale the norm's vmin/vmax 

1326 new_xbound, new_ybound = self.ax._prepare_view_from_bbox( 

1327 bbox, direction=direction, mode=mode, twinx=twinx, twiny=twiny) 

1328 if self.orientation == 'horizontal': 

1329 self.norm.vmin, self.norm.vmax = new_xbound 

1330 elif self.orientation == 'vertical': 

1331 self.norm.vmin, self.norm.vmax = new_ybound 

1332 

1333 def drag_pan(self, button, key, x, y): 

1334 # docstring inherited 

1335 points = self.ax._get_pan_points(button, key, x, y) 

1336 if points is not None: 

1337 if self.orientation == 'horizontal': 

1338 self.norm.vmin, self.norm.vmax = points[:, 0] 

1339 elif self.orientation == 'vertical': 

1340 self.norm.vmin, self.norm.vmax = points[:, 1] 

1341 

1342 

1343ColorbarBase = Colorbar # Backcompat API 

1344 

1345 

1346def _normalize_location_orientation(location, orientation): 

1347 if location is None: 

1348 location = _api.check_getitem( 

1349 {None: "right", "vertical": "right", "horizontal": "bottom"}, 

1350 orientation=orientation) 

1351 loc_settings = _api.check_getitem({ 

1352 "left": {"location": "left", "orientation": "vertical", 

1353 "anchor": (1.0, 0.5), "panchor": (0.0, 0.5), "pad": 0.10}, 

1354 "right": {"location": "right", "orientation": "vertical", 

1355 "anchor": (0.0, 0.5), "panchor": (1.0, 0.5), "pad": 0.05}, 

1356 "top": {"location": "top", "orientation": "horizontal", 

1357 "anchor": (0.5, 0.0), "panchor": (0.5, 1.0), "pad": 0.05}, 

1358 "bottom": {"location": "bottom", "orientation": "horizontal", 

1359 "anchor": (0.5, 1.0), "panchor": (0.5, 0.0), "pad": 0.15}, 

1360 }, location=location) 

1361 if orientation is not None and orientation != loc_settings["orientation"]: 

1362 # Allow the user to pass both if they are consistent. 

1363 raise TypeError("location and orientation are mutually exclusive") 

1364 return loc_settings 

1365 

1366 

1367@_docstring.interpd 

1368def make_axes(parents, location=None, orientation=None, fraction=0.15, 

1369 shrink=1.0, aspect=20, **kwargs): 

1370 """ 

1371 Create an `~.axes.Axes` suitable for a colorbar. 

1372 

1373 The axes is placed in the figure of the *parents* axes, by resizing and 

1374 repositioning *parents*. 

1375 

1376 Parameters 

1377 ---------- 

1378 parents : `~.axes.Axes` or list or `numpy.ndarray` of `~.axes.Axes` 

1379 The Axes to use as parents for placing the colorbar. 

1380 %(_make_axes_kw_doc)s 

1381 

1382 Returns 

1383 ------- 

1384 cax : `~.axes.Axes` 

1385 The child axes. 

1386 kwargs : dict 

1387 The reduced keyword dictionary to be passed when creating the colorbar 

1388 instance. 

1389 """ 

1390 loc_settings = _normalize_location_orientation(location, orientation) 

1391 # put appropriate values into the kwargs dict for passing back to 

1392 # the Colorbar class 

1393 kwargs['orientation'] = loc_settings['orientation'] 

1394 location = kwargs['ticklocation'] = loc_settings['location'] 

1395 

1396 anchor = kwargs.pop('anchor', loc_settings['anchor']) 

1397 panchor = kwargs.pop('panchor', loc_settings['panchor']) 

1398 aspect0 = aspect 

1399 # turn parents into a list if it is not already. Note we cannot 

1400 # use .flatten or .ravel as these copy the references rather than 

1401 # reuse them, leading to a memory leak 

1402 if isinstance(parents, np.ndarray): 

1403 parents = list(parents.flat) 

1404 elif not isinstance(parents, list): 

1405 parents = [parents] 

1406 fig = parents[0].get_figure() 

1407 

1408 pad0 = 0.05 if fig.get_constrained_layout() else loc_settings['pad'] 

1409 pad = kwargs.pop('pad', pad0) 

1410 

1411 if not all(fig is ax.get_figure() for ax in parents): 

1412 raise ValueError('Unable to create a colorbar axes as not all ' 

1413 'parents share the same figure.') 

1414 

1415 # take a bounding box around all of the given axes 

1416 parents_bbox = mtransforms.Bbox.union( 

1417 [ax.get_position(original=True).frozen() for ax in parents]) 

1418 

1419 pb = parents_bbox 

1420 if location in ('left', 'right'): 

1421 if location == 'left': 

1422 pbcb, _, pb1 = pb.splitx(fraction, fraction + pad) 

1423 else: 

1424 pb1, _, pbcb = pb.splitx(1 - fraction - pad, 1 - fraction) 

1425 pbcb = pbcb.shrunk(1.0, shrink).anchored(anchor, pbcb) 

1426 else: 

1427 if location == 'bottom': 

1428 pbcb, _, pb1 = pb.splity(fraction, fraction + pad) 

1429 else: 

1430 pb1, _, pbcb = pb.splity(1 - fraction - pad, 1 - fraction) 

1431 pbcb = pbcb.shrunk(shrink, 1.0).anchored(anchor, pbcb) 

1432 

1433 # define the aspect ratio in terms of y's per x rather than x's per y 

1434 aspect = 1.0 / aspect 

1435 

1436 # define a transform which takes us from old axes coordinates to 

1437 # new axes coordinates 

1438 shrinking_trans = mtransforms.BboxTransform(parents_bbox, pb1) 

1439 

1440 # transform each of the axes in parents using the new transform 

1441 for ax in parents: 

1442 new_posn = shrinking_trans.transform(ax.get_position(original=True)) 

1443 new_posn = mtransforms.Bbox(new_posn) 

1444 ax._set_position(new_posn) 

1445 if panchor is not False: 

1446 ax.set_anchor(panchor) 

1447 

1448 cax = fig.add_axes(pbcb, label="<colorbar>") 

1449 for a in parents: 

1450 # tell the parent it has a colorbar 

1451 a._colorbars += [cax] 

1452 cax._colorbar_info = dict( 

1453 parents=parents, 

1454 location=location, 

1455 shrink=shrink, 

1456 anchor=anchor, 

1457 panchor=panchor, 

1458 fraction=fraction, 

1459 aspect=aspect0, 

1460 pad=pad) 

1461 # and we need to set the aspect ratio by hand... 

1462 cax.set_anchor(anchor) 

1463 cax.set_box_aspect(aspect) 

1464 cax.set_aspect('auto') 

1465 

1466 return cax, kwargs 

1467 

1468 

1469@_docstring.interpd 

1470def make_axes_gridspec(parent, *, location=None, orientation=None, 

1471 fraction=0.15, shrink=1.0, aspect=20, **kwargs): 

1472 """ 

1473 Create a `.SubplotBase` suitable for a colorbar. 

1474 

1475 The axes is placed in the figure of the *parent* axes, by resizing and 

1476 repositioning *parent*. 

1477 

1478 This function is similar to `.make_axes`. Primary differences are 

1479 

1480 - `.make_axes_gridspec` should only be used with a `.SubplotBase` parent. 

1481 

1482 - `.make_axes` creates an `~.axes.Axes`; `.make_axes_gridspec` creates a 

1483 `.SubplotBase`. 

1484 

1485 - `.make_axes` updates the position of the parent. `.make_axes_gridspec` 

1486 replaces the ``grid_spec`` attribute of the parent with a new one. 

1487 

1488 While this function is meant to be compatible with `.make_axes`, 

1489 there could be some minor differences. 

1490 

1491 Parameters 

1492 ---------- 

1493 parent : `~.axes.Axes` 

1494 The Axes to use as parent for placing the colorbar. 

1495 %(_make_axes_kw_doc)s 

1496 

1497 Returns 

1498 ------- 

1499 cax : `~.axes.SubplotBase` 

1500 The child axes. 

1501 kwargs : dict 

1502 The reduced keyword dictionary to be passed when creating the colorbar 

1503 instance. 

1504 """ 

1505 

1506 loc_settings = _normalize_location_orientation(location, orientation) 

1507 kwargs['orientation'] = loc_settings['orientation'] 

1508 location = kwargs['ticklocation'] = loc_settings['location'] 

1509 

1510 aspect0 = aspect 

1511 anchor = kwargs.pop('anchor', loc_settings['anchor']) 

1512 panchor = kwargs.pop('panchor', loc_settings['panchor']) 

1513 pad = kwargs.pop('pad', loc_settings["pad"]) 

1514 wh_space = 2 * pad / (1 - pad) 

1515 

1516 if location in ('left', 'right'): 

1517 # for shrinking 

1518 height_ratios = [ 

1519 (1-anchor[1])*(1-shrink), shrink, anchor[1]*(1-shrink)] 

1520 

1521 if location == 'left': 

1522 gs = parent.get_subplotspec().subgridspec( 

1523 1, 2, wspace=wh_space, 

1524 width_ratios=[fraction, 1-fraction-pad]) 

1525 ss_main = gs[1] 

1526 ss_cb = gs[0].subgridspec( 

1527 3, 1, hspace=0, height_ratios=height_ratios)[1] 

1528 else: 

1529 gs = parent.get_subplotspec().subgridspec( 

1530 1, 2, wspace=wh_space, 

1531 width_ratios=[1-fraction-pad, fraction]) 

1532 ss_main = gs[0] 

1533 ss_cb = gs[1].subgridspec( 

1534 3, 1, hspace=0, height_ratios=height_ratios)[1] 

1535 else: 

1536 # for shrinking 

1537 width_ratios = [ 

1538 anchor[0]*(1-shrink), shrink, (1-anchor[0])*(1-shrink)] 

1539 

1540 if location == 'bottom': 

1541 gs = parent.get_subplotspec().subgridspec( 

1542 2, 1, hspace=wh_space, 

1543 height_ratios=[1-fraction-pad, fraction]) 

1544 ss_main = gs[0] 

1545 ss_cb = gs[1].subgridspec( 

1546 1, 3, wspace=0, width_ratios=width_ratios)[1] 

1547 aspect = 1 / aspect 

1548 else: 

1549 gs = parent.get_subplotspec().subgridspec( 

1550 2, 1, hspace=wh_space, 

1551 height_ratios=[fraction, 1-fraction-pad]) 

1552 ss_main = gs[1] 

1553 ss_cb = gs[0].subgridspec( 

1554 1, 3, wspace=0, width_ratios=width_ratios)[1] 

1555 aspect = 1 / aspect 

1556 

1557 parent.set_subplotspec(ss_main) 

1558 if panchor is not False: 

1559 parent.set_anchor(panchor) 

1560 

1561 fig = parent.get_figure() 

1562 cax = fig.add_subplot(ss_cb, label="<colorbar>") 

1563 cax.set_anchor(anchor) 

1564 cax.set_box_aspect(aspect) 

1565 cax.set_aspect('auto') 

1566 cax._colorbar_info = dict( 

1567 location=location, 

1568 parents=[parent], 

1569 shrink=shrink, 

1570 anchor=anchor, 

1571 panchor=panchor, 

1572 fraction=fraction, 

1573 aspect=aspect0, 

1574 pad=pad) 

1575 

1576 return cax, kwargs