-
-
Notifications
You must be signed in to change notification settings - Fork 7.4k
/
colorbar.py
1576 lines (1364 loc) · 58.9 KB
/
colorbar.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Colorbars are a visualization of the mapping from scalar values to colors.
In Matplotlib they are drawn into a dedicated `~.axes.Axes`.
.. note::
Colorbars are typically created through `.Figure.colorbar` or its pyplot
wrapper `.pyplot.colorbar`, which internally use `.Colorbar` together with
`.make_axes_gridspec` (for `.GridSpec`-positioned axes) or `.make_axes` (for
non-`.GridSpec`-positioned axes).
End-users most likely won't need to directly use this module's API.
"""
import logging
import numpy as np
import matplotlib as mpl
from matplotlib import _api, cbook, collections, cm, colors, contour, ticker
import matplotlib.artist as martist
import matplotlib.patches as mpatches
import matplotlib.path as mpath
import matplotlib.scale as mscale
import matplotlib.spines as mspines
import matplotlib.transforms as mtransforms
from matplotlib import _docstring
_log = logging.getLogger(__name__)
_docstring.interpd.update(
_make_axes_kw_doc="""
location : None or {'left', 'right', 'top', 'bottom'}
The location, relative to the parent axes, where the colorbar axes
is created. It also determines the *orientation* of the colorbar
(colorbars on the left and right are vertical, colorbars at the top
and bottom are horizontal). If None, the location will come from the
*orientation* if it is set (vertical colorbars on the right, horizontal
ones at the bottom), or default to 'right' if *orientation* is unset.
orientation : None or {'vertical', 'horizontal'}
The orientation of the colorbar. It is preferable to set the *location*
of the colorbar, as that also determines the *orientation*; passing
incompatible values for *location* and *orientation* raises an exception.
fraction : float, default: 0.15
Fraction of original axes to use for colorbar.
shrink : float, default: 1.0
Fraction by which to multiply the size of the colorbar.
aspect : float, default: 20
Ratio of long to short dimensions.
pad : float, default: 0.05 if vertical, 0.15 if horizontal
Fraction of original axes between colorbar and new image axes.
anchor : (float, float), optional
The anchor point of the colorbar axes.
Defaults to (0.0, 0.5) if vertical; (0.5, 1.0) if horizontal.
panchor : (float, float), or *False*, optional
The anchor point of the colorbar parent axes. If *False*, the parent
axes' anchor will be unchanged.
Defaults to (1.0, 0.5) if vertical; (0.5, 0.0) if horizontal.""",
_colormap_kw_doc="""
extend : {'neither', 'both', 'min', 'max'}
Make pointed end(s) for out-of-range values (unless 'neither'). These are
set for a given colormap using the colormap set_under and set_over methods.
extendfrac : {*None*, 'auto', length, lengths}
If set to *None*, both the minimum and maximum triangular colorbar
extensions will have a length of 5% of the interior colorbar length (this
is the default setting).
If set to 'auto', makes the triangular colorbar extensions the same lengths
as the interior boxes (when *spacing* is set to 'uniform') or the same
lengths as the respective adjacent interior boxes (when *spacing* is set to
'proportional').
If a scalar, indicates the length of both the minimum and maximum
triangular colorbar extensions as a fraction of the interior colorbar
length. A two-element sequence of fractions may also be given, indicating
the lengths of the minimum and maximum colorbar extensions respectively as
a fraction of the interior colorbar length.
extendrect : bool
If *False* the minimum and maximum colorbar extensions will be triangular
(the default). If *True* the extensions will be rectangular.
spacing : {'uniform', 'proportional'}
For discrete colorbars (`.BoundaryNorm` or contours), 'uniform' gives each
color the same space; 'proportional' makes the space proportional to the
data interval.
ticks : None or list of ticks or Locator
If None, ticks are determined automatically from the input.
format : None or str or Formatter
If None, `~.ticker.ScalarFormatter` is used.
Format strings, e.g., ``"%4.2e"`` or ``"{x:.2e}"``, are supported.
An alternative `~.ticker.Formatter` may be given instead.
drawedges : bool
Whether to draw lines at color boundaries.
label : str
The label on the colorbar's long axis.
boundaries, values : None or a sequence
If unset, the colormap will be displayed on a 0-1 scale.
If sequences, *values* must have a length 1 less than *boundaries*. For
each region delimited by adjacent entries in *boundaries*, the color mapped
to the corresponding value in values will be used.
Normally only useful for indexed colors (i.e. ``norm=NoNorm()``) or other
unusual circumstances.""")
def _set_ticks_on_axis_warn(*args, **kwargs):
# a top level function which gets put in at the axes'
# set_xticks and set_yticks by Colorbar.__init__.
_api.warn_external("Use the colorbar set_ticks() method instead.")
class _ColorbarSpine(mspines.Spine):
def __init__(self, axes):
self._ax = axes
super().__init__(axes, 'colorbar', mpath.Path(np.empty((0, 2))))
mpatches.Patch.set_transform(self, axes.transAxes)
def get_window_extent(self, renderer=None):
# This Spine has no Axis associated with it, and doesn't need to adjust
# its location, so we can directly get the window extent from the
# super-super-class.
return mpatches.Patch.get_window_extent(self, renderer=renderer)
def set_xy(self, xy):
self._path = mpath.Path(xy, closed=True)
self._xy = xy
self.stale = True
def draw(self, renderer):
ret = mpatches.Patch.draw(self, renderer)
self.stale = False
return ret
class _ColorbarAxesLocator:
"""
Shrink the axes if there are triangular or rectangular extends.
"""
def __init__(self, cbar):
self._cbar = cbar
self._orig_locator = cbar.ax._axes_locator
def __call__(self, ax, renderer):
if self._orig_locator is not None:
pos = self._orig_locator(ax, renderer)
else:
pos = ax.get_position(original=True)
if self._cbar.extend == 'neither':
return pos
y, extendlen = self._cbar._proportional_y()
if not self._cbar._extend_lower():
extendlen[0] = 0
if not self._cbar._extend_upper():
extendlen[1] = 0
len = sum(extendlen) + 1
shrink = 1 / len
offset = extendlen[0] / len
# we need to reset the aspect ratio of the axes to account
# of the extends...
if hasattr(ax, '_colorbar_info'):
aspect = ax._colorbar_info['aspect']
else:
aspect = False
# now shrink and/or offset to take into account the
# extend tri/rectangles.
if self._cbar.orientation == 'vertical':
if aspect:
self._cbar.ax.set_box_aspect(aspect*shrink)
pos = pos.shrunk(1, shrink).translated(0, offset * pos.height)
else:
if aspect:
self._cbar.ax.set_box_aspect(1/(aspect * shrink))
pos = pos.shrunk(shrink, 1).translated(offset * pos.width, 0)
return pos
def get_subplotspec(self):
# make tight_layout happy..
ss = getattr(self._cbar.ax, 'get_subplotspec', None)
if ss is None:
if not hasattr(self._orig_locator, "get_subplotspec"):
return None
ss = self._orig_locator.get_subplotspec
return ss()
@_docstring.interpd
class Colorbar:
r"""
Draw a colorbar in an existing axes.
Typically, colorbars are created using `.Figure.colorbar` or
`.pyplot.colorbar` and associated with `.ScalarMappable`\s (such as an
`.AxesImage` generated via `~.axes.Axes.imshow`).
In order to draw a colorbar not associated with other elements in the
figure, e.g. when showing a colormap by itself, one can create an empty
`.ScalarMappable`, or directly pass *cmap* and *norm* instead of *mappable*
to `Colorbar`.
Useful public methods are :meth:`set_label` and :meth:`add_lines`.
Attributes
----------
ax : `~matplotlib.axes.Axes`
The `~.axes.Axes` instance in which the colorbar is drawn.
lines : list
A list of `.LineCollection` (empty if no lines were drawn).
dividers : `.LineCollection`
A LineCollection (empty if *drawedges* is ``False``).
Parameters
----------
ax : `~matplotlib.axes.Axes`
The `~.axes.Axes` instance in which the colorbar is drawn.
mappable : `.ScalarMappable`
The mappable whose colormap and norm will be used.
To show the under- and over- value colors, the mappable's norm should
be specified as ::
norm = colors.Normalize(clip=False)
To show the colors versus index instead of on a 0-1 scale, use::
norm=colors.NoNorm()
cmap : `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
The colormap to use. This parameter is ignored, unless *mappable* is
None.
norm : `~matplotlib.colors.Normalize`
The normalization to use. This parameter is ignored, unless *mappable*
is None.
alpha : float
The colorbar transparency between 0 (transparent) and 1 (opaque).
orientation : {'vertical', 'horizontal'}
ticklocation : {'auto', 'left', 'right', 'top', 'bottom'}
drawedges : bool
filled : bool
%(_colormap_kw_doc)s
"""
n_rasterize = 50 # rasterize solids if number of colors >= n_rasterize
@_api.delete_parameter("3.6", "filled")
def __init__(self, ax, mappable=None, *, cmap=None,
norm=None,
alpha=None,
values=None,
boundaries=None,
orientation='vertical',
ticklocation='auto',
extend=None,
spacing='uniform', # uniform or proportional
ticks=None,
format=None,
drawedges=False,
filled=True,
extendfrac=None,
extendrect=False,
label='',
):
if mappable is None:
mappable = cm.ScalarMappable(norm=norm, cmap=cmap)
# Ensure the given mappable's norm has appropriate vmin and vmax
# set even if mappable.draw has not yet been called.
if mappable.get_array() is not None:
mappable.autoscale_None()
self.mappable = mappable
cmap = mappable.cmap
norm = mappable.norm
if isinstance(mappable, contour.ContourSet):
cs = mappable
alpha = cs.get_alpha()
boundaries = cs._levels
values = cs.cvalues
extend = cs.extend
filled = cs.filled
if ticks is None:
ticks = ticker.FixedLocator(cs.levels, nbins=10)
elif isinstance(mappable, martist.Artist):
alpha = mappable.get_alpha()
mappable.colorbar = self
mappable.colorbar_cid = mappable.callbacks.connect(
'changed', self.update_normal)
_api.check_in_list(
['vertical', 'horizontal'], orientation=orientation)
_api.check_in_list(
['auto', 'left', 'right', 'top', 'bottom'],
ticklocation=ticklocation)
_api.check_in_list(
['uniform', 'proportional'], spacing=spacing)
self.ax = ax
self.ax._axes_locator = _ColorbarAxesLocator(self)
if extend is None:
if (not isinstance(mappable, contour.ContourSet)
and getattr(cmap, 'colorbar_extend', False) is not False):
extend = cmap.colorbar_extend
elif hasattr(norm, 'extend'):
extend = norm.extend
else:
extend = 'neither'
self.alpha = None
# Call set_alpha to handle array-like alphas properly
self.set_alpha(alpha)
self.cmap = cmap
self.norm = norm
self.values = values
self.boundaries = boundaries
self.extend = extend
self._inside = _api.check_getitem(
{'neither': slice(0, None), 'both': slice(1, -1),
'min': slice(1, None), 'max': slice(0, -1)},
extend=extend)
self.spacing = spacing
self.orientation = orientation
self.drawedges = drawedges
self._filled = filled
self.extendfrac = extendfrac
self.extendrect = extendrect
self._extend_patches = []
self.solids = None
self.solids_patches = []
self.lines = []
for spine in self.ax.spines.values():
spine.set_visible(False)
self.outline = self.ax.spines['outline'] = _ColorbarSpine(self.ax)
# Only kept for backcompat; remove after deprecation of .patch elapses.
self._patch = mpatches.Polygon(
np.empty((0, 2)),
color=mpl.rcParams['axes.facecolor'], linewidth=0.01, zorder=-1)
ax.add_artist(self._patch)
self.dividers = collections.LineCollection(
[],
colors=[mpl.rcParams['axes.edgecolor']],
linewidths=[0.5 * mpl.rcParams['axes.linewidth']],
clip_on=False)
self.ax.add_collection(self.dividers)
self._locator = None
self._minorlocator = None
self._formatter = None
self._minorformatter = None
self.__scale = None # linear, log10 for now. Hopefully more?
if ticklocation == 'auto':
ticklocation = 'bottom' if orientation == 'horizontal' else 'right'
self.ticklocation = ticklocation
self.set_label(label)
self._reset_locator_formatter_scale()
if np.iterable(ticks):
self._locator = ticker.FixedLocator(ticks, nbins=len(ticks))
else:
self._locator = ticks
if isinstance(format, str):
# Check format between FormatStrFormatter and StrMethodFormatter
try:
self._formatter = ticker.FormatStrFormatter(format)
_ = self._formatter(0)
except TypeError:
self._formatter = ticker.StrMethodFormatter(format)
else:
self._formatter = format # Assume it is a Formatter or None
self._draw_all()
if isinstance(mappable, contour.ContourSet) and not mappable.filled:
self.add_lines(mappable)
# Link the Axes and Colorbar for interactive use
self.ax._colorbar = self
# Don't navigate on any of these types of mappables
if (isinstance(self.norm, (colors.BoundaryNorm, colors.NoNorm)) or
isinstance(self.mappable, contour.ContourSet)):
self.ax.set_navigate(False)
# These are the functions that set up interactivity on this colorbar
self._interactive_funcs = ["_get_view", "_set_view",
"_set_view_from_bbox", "drag_pan"]
for x in self._interactive_funcs:
setattr(self.ax, x, getattr(self, x))
# Set the cla function to the cbar's method to override it
self.ax.cla = self._cbar_cla
# Callbacks for the extend calculations to handle inverting the axis
self._extend_cid1 = self.ax.callbacks.connect(
"xlim_changed", self._do_extends)
self._extend_cid2 = self.ax.callbacks.connect(
"ylim_changed", self._do_extends)
@property
def locator(self):
"""Major tick `.Locator` for the colorbar."""
return self._long_axis().get_major_locator()
@locator.setter
def locator(self, loc):
self._long_axis().set_major_locator(loc)
self._locator = loc
@property
def minorlocator(self):
"""Minor tick `.Locator` for the colorbar."""
return self._long_axis().get_minor_locator()
@minorlocator.setter
def minorlocator(self, loc):
self._long_axis().set_minor_locator(loc)
self._minorlocator = loc
@property
def formatter(self):
"""Major tick label `.Formatter` for the colorbar."""
return self._long_axis().get_major_formatter()
@formatter.setter
def formatter(self, fmt):
self._long_axis().set_major_formatter(fmt)
self._formatter = fmt
@property
def minorformatter(self):
"""Minor tick `.Formatter` for the colorbar."""
return self._long_axis().get_minor_formatter()
@minorformatter.setter
def minorformatter(self, fmt):
self._long_axis().set_minor_formatter(fmt)
self._minorformatter = fmt
def _cbar_cla(self):
"""Function to clear the interactive colorbar state."""
for x in self._interactive_funcs:
delattr(self.ax, x)
# We now restore the old cla() back and can call it directly
del self.ax.cla
self.ax.cla()
# Also remove ._patch after deprecation elapses.
patch = _api.deprecate_privatize_attribute("3.5", alternative="ax")
filled = _api.deprecate_privatize_attribute("3.6")
def update_normal(self, mappable):
"""
Update solid patches, lines, etc.
This is meant to be called when the norm of the image or contour plot
to which this colorbar belongs changes.
If the norm on the mappable is different than before, this resets the
locator and formatter for the axis, so if these have been customized,
they will need to be customized again. However, if the norm only
changes values of *vmin*, *vmax* or *cmap* then the old formatter
and locator will be preserved.
"""
_log.debug('colorbar update normal %r %r', mappable.norm, self.norm)
self.mappable = mappable
self.set_alpha(mappable.get_alpha())
self.cmap = mappable.cmap
if mappable.norm != self.norm:
self.norm = mappable.norm
self._reset_locator_formatter_scale()
self._draw_all()
if isinstance(self.mappable, contour.ContourSet):
CS = self.mappable
if not CS.filled:
self.add_lines(CS)
self.stale = True
@_api.deprecated("3.6", alternative="fig.draw_without_rendering()")
def draw_all(self):
"""
Calculate any free parameters based on the current cmap and norm,
and do all the drawing.
"""
self._draw_all()
def _draw_all(self):
"""
Calculate any free parameters based on the current cmap and norm,
and do all the drawing.
"""
if self.orientation == 'vertical':
if mpl.rcParams['ytick.minor.visible']:
self.minorticks_on()
else:
if mpl.rcParams['xtick.minor.visible']:
self.minorticks_on()
self._long_axis().set(label_position=self.ticklocation,
ticks_position=self.ticklocation)
self._short_axis().set_ticks([])
self._short_axis().set_ticks([], minor=True)
# Set self._boundaries and self._values, including extensions.
# self._boundaries are the edges of each square of color, and
# self._values are the value to map into the norm to get the
# color:
self._process_values()
# Set self.vmin and self.vmax to first and last boundary, excluding
# extensions:
self.vmin, self.vmax = self._boundaries[self._inside][[0, -1]]
# Compute the X/Y mesh.
X, Y = self._mesh()
# draw the extend triangles, and shrink the inner axes to accommodate.
# also adds the outline path to self.outline spine:
self._do_extends()
lower, upper = self.vmin, self.vmax
if self._long_axis().get_inverted():
# If the axis is inverted, we need to swap the vmin/vmax
lower, upper = upper, lower
if self.orientation == 'vertical':
self.ax.set_xlim(0, 1)
self.ax.set_ylim(lower, upper)
else:
self.ax.set_ylim(0, 1)
self.ax.set_xlim(lower, upper)
# set up the tick locators and formatters. A bit complicated because
# boundary norms + uniform spacing requires a manual locator.
self.update_ticks()
if self._filled:
ind = np.arange(len(self._values))
if self._extend_lower():
ind = ind[1:]
if self._extend_upper():
ind = ind[:-1]
self._add_solids(X, Y, self._values[ind, np.newaxis])
def _add_solids(self, X, Y, C):
"""Draw the colors; optionally add separators."""
# Cleanup previously set artists.
if self.solids is not None:
self.solids.remove()
for solid in self.solids_patches:
solid.remove()
# Add new artist(s), based on mappable type. Use individual patches if
# hatching is needed, pcolormesh otherwise.
mappable = getattr(self, 'mappable', None)
if (isinstance(mappable, contour.ContourSet)
and any(hatch is not None for hatch in mappable.hatches)):
self._add_solids_patches(X, Y, C, mappable)
else:
self.solids = self.ax.pcolormesh(
X, Y, C, cmap=self.cmap, norm=self.norm, alpha=self.alpha,
edgecolors='none', shading='flat')
if not self.drawedges:
if len(self._y) >= self.n_rasterize:
self.solids.set_rasterized(True)
self._update_dividers()
def _update_dividers(self):
if not self.drawedges:
self.dividers.set_segments([])
return
# Place all *internal* dividers.
if self.orientation == 'vertical':
lims = self.ax.get_ylim()
bounds = (lims[0] < self._y) & (self._y < lims[1])
else:
lims = self.ax.get_xlim()
bounds = (lims[0] < self._y) & (self._y < lims[1])
y = self._y[bounds]
# And then add outer dividers if extensions are on.
if self._extend_lower():
y = np.insert(y, 0, lims[0])
if self._extend_upper():
y = np.append(y, lims[1])
X, Y = np.meshgrid([0, 1], y)
if self.orientation == 'vertical':
segments = np.dstack([X, Y])
else:
segments = np.dstack([Y, X])
self.dividers.set_segments(segments)
def _add_solids_patches(self, X, Y, C, mappable):
hatches = mappable.hatches * (len(C) + 1) # Have enough hatches.
if self._extend_lower():
# remove first hatch that goes into the extend patch
hatches = hatches[1:]
patches = []
for i in range(len(X) - 1):
xy = np.array([[X[i, 0], Y[i, 1]],
[X[i, 1], Y[i, 0]],
[X[i + 1, 1], Y[i + 1, 0]],
[X[i + 1, 0], Y[i + 1, 1]]])
patch = mpatches.PathPatch(mpath.Path(xy),
facecolor=self.cmap(self.norm(C[i][0])),
hatch=hatches[i], linewidth=0,
antialiased=False, alpha=self.alpha)
self.ax.add_patch(patch)
patches.append(patch)
self.solids_patches = patches
def _do_extends(self, ax=None):
"""
Add the extend tri/rectangles on the outside of the axes.
ax is unused, but required due to the callbacks on xlim/ylim changed
"""
# Clean up any previous extend patches
for patch in self._extend_patches:
patch.remove()
self._extend_patches = []
# extend lengths are fraction of the *inner* part of colorbar,
# not the total colorbar:
_, extendlen = self._proportional_y()
bot = 0 - (extendlen[0] if self._extend_lower() else 0)
top = 1 + (extendlen[1] if self._extend_upper() else 0)
# xyout is the outline of the colorbar including the extend patches:
if not self.extendrect:
# triangle:
xyout = np.array([[0, 0], [0.5, bot], [1, 0],
[1, 1], [0.5, top], [0, 1], [0, 0]])
else:
# rectangle:
xyout = np.array([[0, 0], [0, bot], [1, bot], [1, 0],
[1, 1], [1, top], [0, top], [0, 1],
[0, 0]])
if self.orientation == 'horizontal':
xyout = xyout[:, ::-1]
# xyout is the path for the spine:
self.outline.set_xy(xyout)
if not self._filled:
return
# Make extend triangles or rectangles filled patches. These are
# defined in the outer parent axes' coordinates:
mappable = getattr(self, 'mappable', None)
if (isinstance(mappable, contour.ContourSet)
and any(hatch is not None for hatch in mappable.hatches)):
hatches = mappable.hatches * (len(self._y) + 1)
else:
hatches = [None] * (len(self._y) + 1)
if self._extend_lower():
if not self.extendrect:
# triangle
xy = np.array([[0, 0], [0.5, bot], [1, 0]])
else:
# rectangle
xy = np.array([[0, 0], [0, bot], [1., bot], [1, 0]])
if self.orientation == 'horizontal':
xy = xy[:, ::-1]
# add the patch
val = -1 if self._long_axis().get_inverted() else 0
color = self.cmap(self.norm(self._values[val]))
patch = mpatches.PathPatch(
mpath.Path(xy), facecolor=color, alpha=self.alpha,
linewidth=0, antialiased=False,
transform=self.ax.transAxes,
hatch=hatches[0], clip_on=False,
# Place it right behind the standard patches, which is
# needed if we updated the extends
zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
self.ax.add_patch(patch)
self._extend_patches.append(patch)
# remove first hatch that goes into the extend patch
hatches = hatches[1:]
if self._extend_upper():
if not self.extendrect:
# triangle
xy = np.array([[0, 1], [0.5, top], [1, 1]])
else:
# rectangle
xy = np.array([[0, 1], [0, top], [1, top], [1, 1]])
if self.orientation == 'horizontal':
xy = xy[:, ::-1]
# add the patch
val = 0 if self._long_axis().get_inverted() else -1
color = self.cmap(self.norm(self._values[val]))
hatch_idx = len(self._y) - 1
patch = mpatches.PathPatch(
mpath.Path(xy), facecolor=color, alpha=self.alpha,
linewidth=0, antialiased=False,
transform=self.ax.transAxes, hatch=hatches[hatch_idx],
clip_on=False,
# Place it right behind the standard patches, which is
# needed if we updated the extends
zorder=np.nextafter(self.ax.patch.zorder, -np.inf))
self.ax.add_patch(patch)
self._extend_patches.append(patch)
self._update_dividers()
def add_lines(self, *args, **kwargs):
"""
Draw lines on the colorbar.
The lines are appended to the list :attr:`lines`.
Parameters
----------
levels : array-like
The positions of the lines.
colors : color or list of colors
Either a single color applying to all lines or one color value for
each line.
linewidths : float or array-like
Either a single linewidth applying to all lines or one linewidth
for each line.
erase : bool, default: True
Whether to remove any previously added lines.
Notes
-----
Alternatively, this method can also be called with the signature
``colorbar.add_lines(contour_set, erase=True)``, in which case
*levels*, *colors*, and *linewidths* are taken from *contour_set*.
"""
params = _api.select_matching_signature(
[lambda self, CS, erase=True: locals(),
lambda self, levels, colors, linewidths, erase=True: locals()],
self, *args, **kwargs)
if "CS" in params:
self, CS, erase = params.values()
if not isinstance(CS, contour.ContourSet) or CS.filled:
raise ValueError("If a single artist is passed to add_lines, "
"it must be a ContourSet of lines")
# TODO: Make colorbar lines auto-follow changes in contour lines.
return self.add_lines(
CS.levels,
[c[0] for c in CS.tcolors],
[t[0] for t in CS.tlinewidths],
erase=erase)
else:
self, levels, colors, linewidths, erase = params.values()
y = self._locate(levels)
rtol = (self._y[-1] - self._y[0]) * 1e-10
igood = (y < self._y[-1] + rtol) & (y > self._y[0] - rtol)
y = y[igood]
if np.iterable(colors):
colors = np.asarray(colors)[igood]
if np.iterable(linewidths):
linewidths = np.asarray(linewidths)[igood]
X, Y = np.meshgrid([0, 1], y)
if self.orientation == 'vertical':
xy = np.stack([X, Y], axis=-1)
else:
xy = np.stack([Y, X], axis=-1)
col = collections.LineCollection(xy, linewidths=linewidths,
colors=colors)
if erase and self.lines:
for lc in self.lines:
lc.remove()
self.lines = []
self.lines.append(col)
# make a clip path that is just a linewidth bigger than the axes...
fac = np.max(linewidths) / 72
xy = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
inches = self.ax.get_figure().dpi_scale_trans
# do in inches:
xy = inches.inverted().transform(self.ax.transAxes.transform(xy))
xy[[0, 1, 4], 1] -= fac
xy[[2, 3], 1] += fac
# back to axes units...
xy = self.ax.transAxes.inverted().transform(inches.transform(xy))
col.set_clip_path(mpath.Path(xy, closed=True),
self.ax.transAxes)
self.ax.add_collection(col)
self.stale = True
def update_ticks(self):
"""
Set up the ticks and ticklabels. This should not be needed by users.
"""
# Get the locator and formatter; defaults to self._locator if not None.
self._get_ticker_locator_formatter()
self._long_axis().set_major_locator(self._locator)
self._long_axis().set_minor_locator(self._minorlocator)
self._long_axis().set_major_formatter(self._formatter)
def _get_ticker_locator_formatter(self):
"""
Return the ``locator`` and ``formatter`` of the colorbar.
If they have not been defined (i.e. are *None*), the formatter and
locator are retrieved from the axis, or from the value of the
boundaries for a boundary norm.
Called by update_ticks...
"""
locator = self._locator
formatter = self._formatter
minorlocator = self._minorlocator
if isinstance(self.norm, colors.BoundaryNorm):
b = self.norm.boundaries
if locator is None:
locator = ticker.FixedLocator(b, nbins=10)
if minorlocator is None:
minorlocator = ticker.FixedLocator(b)
elif isinstance(self.norm, colors.NoNorm):
if locator is None:
# put ticks on integers between the boundaries of NoNorm
nv = len(self._values)
base = 1 + int(nv / 10)
locator = ticker.IndexLocator(base=base, offset=.5)
elif self.boundaries is not None:
b = self._boundaries[self._inside]
if locator is None:
locator = ticker.FixedLocator(b, nbins=10)
else: # most cases:
if locator is None:
# we haven't set the locator explicitly, so use the default
# for this axis:
locator = self._long_axis().get_major_locator()
if minorlocator is None:
minorlocator = self._long_axis().get_minor_locator()
if minorlocator is None:
minorlocator = ticker.NullLocator()
if formatter is None:
formatter = self._long_axis().get_major_formatter()
self._locator = locator
self._formatter = formatter
self._minorlocator = minorlocator
_log.debug('locator: %r', locator)
@_api.delete_parameter("3.5", "update_ticks")
def set_ticks(self, ticks, update_ticks=True, labels=None, *,
minor=False, **kwargs):
"""
Set tick locations.
Parameters
----------
ticks : list of floats
List of tick locations.
labels : list of str, optional
List of tick labels. If not set, the labels show the data value.
minor : bool, default: False
If ``False``, set the major ticks; if ``True``, the minor ticks.
**kwargs
`.Text` properties for the labels. These take effect only if you
pass *labels*. In other cases, please use `~.Axes.tick_params`.
"""
if np.iterable(ticks):
self._long_axis().set_ticks(ticks, labels=labels, minor=minor,
**kwargs)
self._locator = self._long_axis().get_major_locator()
else:
self._locator = ticks
self._long_axis().set_major_locator(self._locator)
self.stale = True
def get_ticks(self, minor=False):
"""
Return the ticks as a list of locations.
Parameters
----------
minor : boolean, default: False
if True return the minor ticks.
"""
if minor:
return self._long_axis().get_minorticklocs()
else:
return self._long_axis().get_majorticklocs()
@_api.delete_parameter("3.5", "update_ticks")
def set_ticklabels(self, ticklabels, update_ticks=True, *, minor=False,
**kwargs):
"""
[*Discouraged*] Set tick labels.
.. admonition:: Discouraged
The use of this method is discouraged, because of the dependency
on tick positions. In most cases, you'll want to use
``set_ticks(positions, labels=labels)`` instead.
If you are using this method, you should always fix the tick
positions before, e.g. by using `.Colorbar.set_ticks` or by
explicitly setting a `~.ticker.FixedLocator` on the long axis
of the colorbar. Otherwise, ticks are free to move and the
labels may end up in unexpected positions.
Parameters
----------
ticklabels : sequence of str or of `.Text`
Texts for labeling each tick location in the sequence set by
`.Colorbar.set_ticks`; the number of labels must match the number
of locations.
update_ticks : bool, default: True
This keyword argument is ignored and will be removed.
Deprecated
minor : bool
If True, set minor ticks instead of major ticks.
**kwargs
`.Text` properties for the labels.
"""
self._long_axis().set_ticklabels(ticklabels, minor=minor, **kwargs)
def minorticks_on(self):
"""
Turn on colorbar minor ticks.
"""
self.ax.minorticks_on()
self._short_axis().set_minor_locator(ticker.NullLocator())
def minorticks_off(self):
"""Turn the minor ticks of the colorbar off."""
self._minorlocator = ticker.NullLocator()
self._long_axis().set_minor_locator(self._minorlocator)
def set_label(self, label, *, loc=None, **kwargs):
"""
Add a label to the long axis of the colorbar.
Parameters
----------
label : str
The label text.
loc : str, optional
The location of the label.
- For horizontal orientation one of {'left', 'center', 'right'}
- For vertical orientation one of {'bottom', 'center', 'top'}
Defaults to :rc:`xaxis.labellocation` or :rc:`yaxis.labellocation`
depending on the orientation.
**kwargs
Keyword arguments are passed to `~.Axes.set_xlabel` /
`~.Axes.set_ylabel`.
Supported keywords are *labelpad* and `.Text` properties.
"""
if self.orientation == "vertical":
self.ax.set_ylabel(label, loc=loc, **kwargs)
else:
self.ax.set_xlabel(label, loc=loc, **kwargs)
self.stale = True
def set_alpha(self, alpha):
"""
Set the transparency between 0 (transparent) and 1 (opaque).
If an array is provided, *alpha* will be set to None to use the
transparency values associated with the colormap.
"""
self.alpha = None if isinstance(alpha, np.ndarray) else alpha
def _set_scale(self, scale, **kwargs):
"""
Set the colorbar long axis scale.
Parameters
----------
value : {"linear", "log", "symlog", "logit", ...} or `.ScaleBase`
The axis scale type to apply.
**kwargs
Different keyword arguments are accepted, depending on the scale.
See the respective class keyword arguments:
- `matplotlib.scale.LinearScale`
- `matplotlib.scale.LogScale`
- `matplotlib.scale.SymmetricalLogScale`