-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathbase.py
More file actions
4098 lines (3792 loc) · 169 KB
/
base.py
File metadata and controls
4098 lines (3792 loc) · 169 KB
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
#!/usr/bin/env python3
"""
The first-level axes subclass used for all ultraplot figures.
Implements basic shared functionality.
"""
import copy
import inspect
import re
import sys
import types
from collections.abc import Iterable as IterableType
from numbers import Integral, Number
from typing import Iterable, MutableMapping, Optional, Tuple, Union
try:
# From python 3.12
from typing import override
except ImportError:
# From Python 3.5
from typing_extensions import override
import matplotlib.axes as maxes
import matplotlib.axis as maxis
import matplotlib.cm as mcm
import matplotlib.colors as mcolors
import matplotlib.container as mcontainer
import matplotlib.contour as mcontour
import matplotlib.offsetbox as moffsetbox
import matplotlib.patches as mpatches
import matplotlib.projections as mproj
import matplotlib.text as mtext
import matplotlib.ticker as mticker
import matplotlib.transforms as mtransforms
import numpy as np
from matplotlib import cbook
from packaging import version
from .. import colors as pcolors
from .. import constructor
from .. import legend as plegend
from .. import ticker as pticker
from ..config import rc
from ..internals import (
_kwargs_to_args,
_not_none,
_pop_kwargs,
_pop_params,
_pop_props,
_pop_rc,
_translate_loc,
_version_mpl,
docstring,
guides,
ic, # noqa: F401
labels,
rcsetup,
warnings,
)
from ..utils import _fontsize_to_pt, edges, units
try:
from cartopy.crs import CRS, PlateCarree
except Exception:
CRS = PlateCarree = object
__all__ = ["Axes"]
# A-b-c label string
ABC_STRING = "abcdefghijklmnopqrstuvwxyz"
# Legend align options
ALIGN_OPTS = {
None: {
"center": "center",
"left": "center left",
"right": "center right",
"top": "upper center",
"bottom": "lower center",
},
"left": {
"top": "upper right",
"center": "center right",
"bottom": "lower right",
},
"right": {
"top": "upper left",
"center": "center left",
"bottom": "lower left",
},
"top": {"left": "lower left", "center": "lower center", "right": "lower right"},
"bottom": {"left": "upper left", "center": "upper center", "right": "upper right"},
}
# Projection docstring
_proj_docstring = """
proj, projection : \\
str, `cartopy.crs.Projection`, or `~mpl_toolkits.basemap.Basemap`, optional
The map projection specification(s). If ``'cart'`` or ``'cartesian'``
(the default), a `~ultraplot.axes.CartesianAxes` is created. If ``'polar'``,
a :class:`~ultraplot.axes.PolarAxes` is created. Otherwise, the argument is
interpreted by `~ultraplot.constructor.Proj`, and the result is used
to make a `~ultraplot.axes.GeoAxes` (in this case the argument can be
a `cartopy.crs.Projection` instance, a `~mpl_toolkits.basemap.Basemap`
instance, or a projection name listed in :ref:`this table <proj_table>`).
"""
_proj_kw_docstring = """
proj_kw, projection_kw : dict-like, optional
Keyword arguments passed to `~mpl_toolkits.basemap.Basemap` or
cartopy `~cartopy.crs.Projection` classes on instantiation.
"""
_backend_docstring = """
backend : {'cartopy', 'basemap'}, default: :rc:`geo.backend`
Whether to use `~mpl_toolkits.basemap.Basemap` or
`~cartopy.crs.Projection` for map projections.
"""
docstring._snippet_manager["axes.proj"] = _proj_docstring
docstring._snippet_manager["axes.proj_kw"] = _proj_kw_docstring
docstring._snippet_manager["axes.backend"] = _backend_docstring
# Colorbar and legend space
_space_docstring = """
queue : bool, optional
If ``True`` and `loc` is the same as an existing {name}, the input
arguments are added to a queue and this function returns ``None``.
This is used to "update" the same {name} with successive ``ax.{name}(...)``
calls. If ``False`` (the default) and `loc` is the same as an existing
*inset* {name}, the old {name} is removed. If ``False`` and `loc` is an
*outer* {name}, the {name}s are "stacked".
space : unit-spec, default: None
For outer {name}s only. The fixed space between the {name} and the subplot
edge. %(units.em)s
When the :ref:`tight layout algorithm <ug_tight>` is active for the figure,
`space` is computed automatically (see `pad`). Otherwise, `space` is set to
a suitable default.
pad : unit-spec, default: :rc:`subplots.panelpad` or :rc:`{default}`
For outer {name}s, this is the :ref:`tight layout padding <ug_tight>`
between the {name} and the subplot (default is :rcraw:`subplots.panelpad`).
For inset {name}s, this is the fixed space between the axes
edge and the {name} (default is :rcraw:`{default}`).
%(units.em)s
align : {{'center', 'top', 'bottom', 'left', 'right', 't', 'b', 'l', 'r'}}, optional
For outer {name}s only. How to align the {name} against the subplot edge.
The values ``'top'`` and ``'bottom'`` are valid for left and right {name}s
and ``'left'`` and ``'right'`` are valid for top and bottom {name}s.
The default is always ``'center'``.
"""
docstring._snippet_manager["axes.legend_space"] = _space_docstring.format(
name="legend", default="legend.borderaxespad"
)
docstring._snippet_manager["axes.colorbar_space"] = _space_docstring.format(
name="colorbar", default="colorbar.insetpad"
)
# Transform docstring
# Used for text and add_axes
_transform_docstring = """
transform : {'data', 'axes', 'figure', 'subfigure'} \\
or `~matplotlib.transforms.Transform`, optional
The transform used to interpret the bounds. Can be a
`~matplotlib.transforms.Transform` instance or a string representing
the `~matplotlib.axes.Axes.transData`, `~matplotlib.axes.Axes.transAxes`,
`~matplotlib.figure.Figure.transFigure`, or
`~matplotlib.figure.Figure.transSubfigure`, transforms.
"""
docstring._snippet_manager["axes.transform"] = _transform_docstring
# Inset docstring
# NOTE: Used by SubplotGrid.inset_axes
_inset_docstring = """
Add an inset axes.
This is similar to `matplotlib.axes.Axes.inset_axes`.
Parameters
-----------
bounds : 4-tuple of float
The (left, bottom, width, height) coordinates for the axes.
%(axes.transform)s
Default is to use the same projection as the current axes.
%(axes.proj)s
%(axes.proj_kw)s
%(axes.backend)s
zorder : float, default: 4
The `zorder <https://matplotlib.org/stable/gallery/misc/zorder_demo.html>`__
of the axes. Should be greater than the zorder of elements in the parent axes.
zoom : bool, default: True or False
Whether to draw lines indicating the inset zoom using `~Axes.indicate_inset_zoom`.
The line positions will automatically adjust when the parent or inset axes limits
change. Default is ``True`` only if both axes are `~ultraplot.axes.CartesianAxes`.
zoom_kw : dict, optional
Passed to `~Axes.indicate_inset_zoom`.
Other parameters
-----------------
**kwargs
Passed to `ultraplot.axes.Axes`.
Returns
--------
ultraplot.axes.Axes
The inset axes.
See also
---------
Axes.indicate_inset_zoom
matplotlib.axes.Axes.inset_axes
matplotlib.axes.Axes.indicate_inset
matplotlib.axes.Axes.indicate_inset_zoom
"""
_indicate_inset_docstring = """
Add indicators denoting the zoom range of the inset axes.
This will replace previously drawn zoom indicators.
Parameters
-----------
%(artist.patch)s
zorder : float, default: 3.5
The `zorder <https://matplotlib.org/stable/gallery/misc/zorder_demo.html>`__ of
the indicators. Should be greater than the zorder of elements in the parent axes.
Other parameters
-----------------
**kwargs
Passed to `~matplotlib.patches.Patch`.
Note
-----
This command must be called from the inset axes rather than the parent axes.
It is called automatically when ``zoom=True`` is passed to `~Axes.inset_axes`
and whenever the axes are drawn (so the line positions always track the axis
limits even if they are later changed).
See also
---------
matplotlib.axes.Axes.indicate_inset
matplotlib.axes.Axes.indicate_inset_zoom
"""
docstring._snippet_manager["axes.inset"] = _inset_docstring
docstring._snippet_manager["axes.indicate_inset"] = _indicate_inset_docstring
# Panel docstring
# NOTE: Used by SubplotGrid.panel_axes
_panel_loc_docstring = """
========== =====================
Location Valid keys
========== =====================
left ``'left'``, ``'l'``
right ``'right'``, ``'r'``
bottom ``'bottom'``, ``'b'``
top ``'top'``, ``'t'``
========== =====================
"""
_panel_docstring = """
Add a panel axes.
Parameters
-----------
side : str, optional
The panel location. Valid location keys are as follows.
%(axes.panel_loc)s
width : unit-spec, default: :rc:`subplots.panelwidth`
The panel width.
%(units.in)s
space : unit-spec, default: None
The fixed space between the panel and the subplot edge.
%(units.em)s
When the :ref:`tight layout algorithm <ug_tight>` is active for the figure,
`space` is computed automatically (see `pad`). Otherwise, `space` is set to
a suitable default.
pad : unit-spec, default: :rc:`subplots.panelpad`
The :ref:`tight layout padding <ug_tight>` between the panel and the subplot.
%(units.em)s
row, rows
Aliases for `span` for panels on the left or right side (vertical panels).
col, cols
Aliases for `span` for panels on the top or bottom side (horizontal panels).
span : int or 2-tuple of int, default: None
Integer(s) indicating the span of the panel across rows and columns of
subplots. For panels on the left or right side, use `rows` or `row` to
specify which rows the panel should span. For panels on the top or bottom
side, use `cols` or `col` to specify which columns the panel should span.
For example, ``ax.panel('b', col=1)`` draws a panel beneath only the
leftmost column, and ``ax.panel('b', cols=(1, 2))`` draws a panel beneath
the left two columns. By default the panel will span all rows or columns
aligned with the parent axes.
share : bool, default: True
Whether to enable axis sharing between the *x* and *y* axes of the
main subplot and the panel long axes for each panel in the "stack".
Sharing between the panel short axis and other panel short axes
is determined by figure-wide `sharex` and `sharey` settings.
Other parameters
-----------------
**kwargs
Passed to `ultraplot.axes.CartesianAxes`. Supports all valid
`~ultraplot.axes.CartesianAxes.format` keywords.
Returns
--------
ultraplot.axes.CartesianAxes
The panel axes.
"""
docstring._snippet_manager["axes.panel_loc"] = _panel_loc_docstring
docstring._snippet_manager["axes.panel"] = _panel_docstring
# Format docstrings
_axes_format_docstring = """
title : str or sequence, optional
The axes title. Can optionally be a sequence strings, in which case
the title will be selected from the sequence according to `~Axes.number`.
abc : bool or str or sequence, default: :rc:`abc`
The "a-b-c" subplot label style. Must contain the character `a` or `A`,
for example ``'a.'``, or ``'A'``. If ``True`` then the default style of
``'a'`` is used. The `a` or ``A`` is replaced with the alphabetic character
matching the `~Axes.number`. If `~Axes.number` is greater than 26, the
characters loop around to a, ..., z, aa, ..., zz, aaa, ..., zzz, etc.
Can also be a sequence of strings, in which case the "a-b-c" label will be selected sequentially from the list. For example `axs.format(abc = ["X", "Y"])` for a two-panel figure, and `axes[3:5].format(abc = ["X", "Y"])` for a two-panel subset of a larger figure.
abcloc, titleloc : str, default: :rc:`abc.loc`, :rc:`title.loc`
Strings indicating the location for the a-b-c label and main title.
The following locations are valid:
.. _title_table:
======================== ============================
Location Valid keys
======================== ============================
center above axes ``'center'``, ``'c'``
left above axes ``'left'``, ``'l'``
right above axes ``'right'``, ``'r'``
lower center inside axes ``'lower center'``, ``'lc'``
upper center inside axes ``'upper center'``, ``'uc'``
upper right inside axes ``'upper right'``, ``'ur'``
upper left inside axes ``'upper left'``, ``'ul'``
lower left inside axes ``'lower left'``, ``'ll'``
lower right inside axes ``'lower right'``, ``'lr'``
left of y axis ``'outer left'``, ``'ol'``
right of y axis ``'outer right'``, ``'or'``
======================== ============================
abcborder, titleborder : bool, default: :rc:`abc.border` and :rc:`title.border`
Whether to draw a white border around titles and a-b-c labels positioned
inside the axes. This can help them stand out on top of artists
plotted inside the axes.
abcbbox, titlebbox : bool, default: :rc:`abc.bbox` and :rc:`title.bbox`
Whether to draw a white bbox around titles and a-b-c labels positioned
inside the axes. This can help them stand out on top of artists plotted
inside the axes.
abcpad : float or unit-spec, default: :rc:`abc.pad`
Horizontal offset to shift the a-b-c label position. Positive values move
the label right, negative values move it left. This is separate from
`abctitlepad`, which controls spacing between abc and title when co-located.
%(units.pt)s
abc_kw, title_kw : dict-like, optional
Additional settings used to update the a-b-c label and title
with ``text.update()``.
titlepad : float, default: :rc:`title.pad`
The padding for the inner and outer titles and a-b-c labels.
%(units.pt)s
titleabove : bool, default: :rc:`title.above`
Whether to try to put outer titles and a-b-c labels above panels,
colorbars, or legends that are above the axes.
abctitlepad : float, default: :rc:`abc.titlepad`
The horizontal padding between a-b-c labels and titles in the same location.
%(units.pt)s
ltitle, ctitle, rtitle, ultitle, uctitle, urtitle, lltitle, lctitle, lrtitle : str or sequence, optional \\
Shorthands for the below keywords.
lefttitle, centertitle, righttitle, upperlefttitle, uppercentertitle, upperrighttitle : str or sequence, optional
lowerlefttitle, lowercentertitle, lowerrighttitle : str or sequence, optional
Additional titles in specific positions (see `title` for details). This works as
an alternative to the ``ax.format(title='Title', titleloc=loc)`` workflow and
permits adding more than one title-like label for a single axes.
a, alpha, fc, facecolor, ec, edgecolor, lw, linewidth, ls, linestyle : default:
:rc:`axes.alpha` (default: 1.0), :rc:`axes.facecolor` (default: white), :rc:`axes.edgecolor` (default: black), :rc:`axes.linewidth` (default: 0.6), -
Additional settings applied to the background patch, and their
shorthands. Their defaults values are the ``'axes'`` properties.
"""
_figure_format_docstring = """
rowlabels, collabels, llabels, tlabels, rlabels, blabels
Aliases for `leftlabels` and `toplabels`, and for `leftlabels`,
`toplabels`, `rightlabels`, and `bottomlabels`, respectively.
leftlabels, toplabels, rightlabels, bottomlabels : sequence of str, optional
Labels for the subplots lying along the left, top, right, and
bottom edges of the figure. The length of each list must match
the number of subplots along the corresponding edge.
leftlabelpad, toplabelpad, rightlabelpad, bottomlabelpad : float or unit-spec, default\\
: :rc:`leftlabel.pad`, :rc:`toplabel.pad`, :rc:`rightlabel.pad`, :rc:`bottomlabel.pad`
The padding between the labels and the axes content.
%(units.pt)s
leftlabels_kw, toplabels_kw, rightlabels_kw, bottomlabels_kw : dict-like, optional
Additional settings used to update the labels with ``text.update()``.
figtitle
Alias for `suptitle`.
suptitle : str, optional
The figure "super" title, centered between the left edge of the leftmost
subplot and the right edge of the rightmost subplot.
suptitlepad : float, default: :rc:`suptitle.pad`
The padding between the super title and the axes content.
%(units.pt)s
suptitle_kw : optional
Additional settings used to update the super title with ``text.update()``.
includepanels : bool, default: False
Whether to include panels when aligning figure "super titles" along the top
of the subplot grid and when aligning the `spanx` *x* axis labels and
`spany` *y* axis labels along the sides of the subplot grid.
"""
_rc_init_docstring = """
"""
_rc_format_docstring = """
rc_mode : int, optional
The context mode passed to `~ultraplot.config.Configurator.context`.
rc_kw : dict-like, optional
An alternative to passing extra keyword arguments. See below.
**kwargs
{}Keyword arguments that match the name of an `~ultraplot.config.rc` setting are
passed to `ultraplot.config.Configurator.context` and used to update the axes.
If the setting name has "dots" you can simply omit the dots. For example,
``abc='A.'`` modifies the :rcraw:`abc` setting, ``titleloc='left'`` modifies the
:rcraw:`title.loc` setting, ``gridminor=True`` modifies the :rcraw:`gridminor`
setting, and ``gridbelow=True`` modifies the :rcraw:`grid.below` setting. Many
of the keyword arguments documented above are internally applied by retrieving
settings passed to `~ultraplot.config.Configurator.context`.
"""
docstring._snippet_manager["rc.init"] = _rc_format_docstring.format(
"Remaining keyword arguments are passed to `matplotlib.axes.Axes`.\\n "
)
docstring._snippet_manager["rc.format"] = _rc_format_docstring.format("")
docstring._snippet_manager["axes.format"] = _axes_format_docstring
docstring._snippet_manager["figure.format"] = _figure_format_docstring
# Colorbar docstrings
_colorbar_args_docstring = """
mappable : mappable, colormap-spec, sequence of color-spec, \\
or sequence of `~matplotlib.artist.Artist`
There are four options here:
1. A `~matplotlib.cm.ScalarMappable` (e.g., an object returned by
`~ultraplot.axes.PlotAxes.contourf` or `~ultraplot.axes.PlotAxes.pcolormesh`).
2. A `~matplotlib.colors.Colormap` or registered colormap name used to build a
`~matplotlib.cm.ScalarMappable` on-the-fly. The colorbar range and ticks depend
on the arguments `values`, `vmin`, `vmax`, and `norm`. The default for a
:class:`~ultraplot.colors.ContinuousColormap` is ``vmin=0`` and ``vmax=1`` (note that
passing `values` will "discretize" the colormap). The default for a
:class:`~ultraplot.colors.DiscreteColormap` is ``values=np.arange(0, cmap.N)``.
3. A sequence of hex strings, color names, or RGB[A] tuples. A
:class:`~ultraplot.colors.DiscreteColormap` will be generated from these colors and
used to build a `~matplotlib.cm.ScalarMappable` on-the-fly. The colorbar
range and ticks depend on the arguments `values`, `norm`, and
`norm_kw`. The default is ``values=np.arange(0, len(mappable))``.
4. A sequence of `matplotlib.artist.Artist` instances (e.g., a list of
`~matplotlib.lines.Line2D` instances returned by `~ultraplot.axes.PlotAxes.plot`).
A colormap will be generated from the colors of these objects (where the
color is determined by ``get_color``, if available, or ``get_facecolor``).
The colorbar range and ticks depend on the arguments `values`, `norm`, and
`norm_kw`. The default is to infer colorbar ticks and tick labels
by calling `~matplotlib.artist.Artist.get_label` on each artist.
values : sequence of float or str, optional
Ignored if `mappable` is a `~matplotlib.cm.ScalarMappable`. This maps the colormap
colors to numeric values using `~ultraplot.colors.DiscreteNorm`. If the colormap is
a :class:`~ultraplot.colors.ContinuousColormap` then its colors will be "discretized".
These These can also be strings, in which case the list indices are used for
tick locations and the strings are applied as tick labels.
"""
_colorbar_kwargs_docstring = """
orientation : {None, 'horizontal', 'vertical'}, optional
The colorbar orientation. By default this depends on the "side" of the subplot
or figure where the colorbar is drawn. Inset colorbars are always horizontal.
norm : norm-spec, optional
Ignored if `mappable` is a `~matplotlib.cm.ScalarMappable`. This is the continuous
normalizer used to scale the :class:`~ultraplot.colors.ContinuousColormap` (or passed
to `~ultraplot.colors.DiscreteNorm` if `values` was passed). Passed to the
`~ultraplot.constructor.Norm` constructor function.
norm_kw : dict-like, optional
Ignored if `mappable` is a `~matplotlib.cm.ScalarMappable`. These are the
normalizer keyword arguments. Passed to `~ultraplot.constructor.Norm`.
vmin, vmax : float, optional
Ignored if `mappable` is a `~matplotlib.cm.ScalarMappable`. These are the minimum
and maximum colorbar values. Passed to `~ultraplot.constructor.Norm`.
label, title : str, optional
The colorbar label. The `title` keyword is also accepted for
consistency with `~matplotlib.axes.Axes.legend`.
reverse : bool, optional
Whether to reverse the direction of the colorbar. This is done automatically
when descending levels are used with `~ultraplot.colors.DiscreteNorm`.
rotation : float, default: 0
The tick label rotation.
grid, edges, drawedges : bool, default: :rc:`colorbar.grid`
Whether to draw "grid" dividers between each distinct color.
extend : {'neither', 'both', 'min', 'max'}, optional
Direction for drawing colorbar "extensions" (i.e. color keys for out-of-bounds
data on the end of the colorbar). Default behavior is to use the value of `extend`
passed to the plotting command or use ``'neither'`` if the value is unknown.
extendfrac : float, optional
The length of the colorbar "extensions" relative to the length of the colorbar.
This is a native matplotlib `~matplotlib.figure.Figure.colorbar` keyword.
extendsize : unit-spec, default: :rc:`colorbar.extend` or :rc:`colorbar.insetextend`
The length of the colorbar "extensions" in physical units. Default is
:rcraw:`colorbar.extend` for outer colorbars and :rcraw:`colorbar.insetextend`
for inset colorbars. %(units.em)s
extendrect : bool, default: False
Whether to draw colorbar "extensions" as rectangles. If ``False`` then
the extensions are drawn as triangles.
locator, ticks : locator-spec, optional
Used to determine the colorbar tick positions. Passed to the
`~ultraplot.constructor.Locator` constructor function. By default
`~matplotlib.ticker.AutoLocator` is used for continuous color levels
and `~ultraplot.ticker.DiscreteLocator` is used for discrete color levels.
locator_kw : dict-like, optional
Keyword arguments passed to `matplotlib.ticker.Locator` class.
minorlocator, minorticks
As with `locator`, `ticks` but for the minor ticks. By default
`~matplotlib.ticker.AutoMinorLocator` is used for continuous color levels
and `~ultraplot.ticker.DiscreteLocator` is used for discrete color levels.
minorlocator_kw
As with `locator_kw`, but for the minor ticks.
format, formatter, ticklabels : formatter-spec, optional
The tick label format. Passed to the `~ultraplot.constructor.Formatter`
constructor function.
formatter_kw : dict-like, optional
Keyword arguments passed to `matplotlib.ticker.Formatter` class.
frame, frameon : bool, default: :rc:`colorbar.frameon`
For inset colorbars only. Indicates whether to draw a "frame",
just like `~matplotlib.axes.Axes.legend`.
tickminor : bool, optional
Whether to add minor ticks using `~matplotlib.colorbar.ColorbarBase.minorticks_on`.
tickloc, ticklocation : {'bottom', 'top', 'left', 'right'}, optional
Where to draw tick marks on the colorbar. Default is toward the outside
of the subplot for outer colorbars and ``'bottom'`` for inset colorbars.
tickdir, tickdirection : {'out', 'in', 'inout'}, default: :rc:`tick.dir`
Direction of major and minor colorbar ticks.
ticklen : unit-spec, default: :rc:`tick.len`
Major tick lengths for the colorbar ticks.
ticklenratio : float, default: :rc:`tick.lenratio`
Relative scaling of `ticklen` used to determine minor tick lengths.
tickwidth : unit-spec, default: `linewidth`
Major tick widths for the colorbar ticks.
or :rc:`tick.width` if `linewidth` was not passed.
tickwidthratio : float, default: :rc:`tick.widthratio`
Relative scaling of `tickwidth` used to determine minor tick widths.
ticklabelcolor, ticklabelsize, ticklabelweight \\
: default: :rc:`tick.labelcolor`, :rc:`tick.labelsize`, :rc:`tick.labelweight`.
The font color, size, and weight for colorbar tick labels
labelloc, labellocation : {'bottom', 'top', 'left', 'right'}
The colorbar label location. Inherits from `tickloc` by default. Default is toward
the outside of the subplot for outer colorbars and ``'bottom'`` for inset colorbars.
labelcolor, labelsize, labelweight \\
: default: :rc:`label.color`, :rc:`label.size`, and :rc:`label.weight`.
The font color, size, and weight for the colorbar label.
a, alpha, framealpha, fc, facecolor, framecolor, ec, edgecolor, ew, edgewidth : default\\
: :rc:`colorbar.framealpha`, :rc:`colorbar.framecolor`
For inset colorbars only. Controls the transparency and color of
the background frame.
lw, linewidth, c, color : optional
Controls the line width and edge color for both the colorbar
outline and the level dividers.
%(axes.edgefix)s
rasterize : bool, default: :rc:`colorbar.rasterized`
Whether to rasterize the colorbar solids. The matplotlib default was ``True``
but ultraplot changes this to ``False`` since rasterization can cause misalignment
between the color patches and the colorbar outline.
outline : bool, None default : None
Controls the visibility of the frame. When set to False, the spines of the colorbar are hidden. If set to `None` it uses the `rc['colorbar.outline']` value.
labelrotation : str, float, default: None
Controls the rotation of the colorbar label. When set to None it takes on the value of `rc["colorbar.labelrotation"]`. When set to auto it produces a sensible default where the rotation is adjusted to where the colorbar is located. For example, a horizontal colorbar with a label to the left or right will match the horizontal alignment and rotate the label to 0 degrees. Users can provide a float to rotate to any arbitrary angle.
**kwargs
Passed to `~matplotlib.figure.Figure.colorbar`.
"""
_edgefix_docstring = """
edgefix : bool or float, default: :rc:`edgefix`
Whether to fix the common issue where white lines appear between adjacent
patches in saved vector graphics (this can slow down figure rendering).
See this `github repo <https://github.com/jklymak/contourfIssues>`__ for a
demonstration of the problem. If ``True``, a small default linewidth of
``0.3`` is used to cover up the white lines. If float (e.g. ``edgefix=0.5``),
this specific linewidth is used to cover up the white lines. This feature is
automatically disabled when the patches have transparency.
"""
docstring._snippet_manager["axes.edgefix"] = _edgefix_docstring
docstring._snippet_manager["axes.colorbar_args"] = _colorbar_args_docstring
docstring._snippet_manager["axes.colorbar_kwargs"] = _colorbar_kwargs_docstring
# Legend docstrings
_legend_args_docstring = """
handles : list of artist, optional
List of matplotlib artists, or a list of lists of artist instances (see the `center`
keyword). If not passed, artists with valid labels (applied by passing `label` or
`labels` to a plotting command or calling `~matplotlib.artist.Artist.set_label`)
are retrieved automatically. If the object is a `~matplotlib.contour.ContourSet`,
`~matplotlib.contour.ContourSet.legend_elements` is used to select the central
artist in the list (generally useful for single-color contour plots). Note that
ultraplot's `~ultraplot.axes.PlotAxes.contour` and `~ultraplot.axes.PlotAxes.contourf`
accept a legend `label` keyword argument.
labels : list of str, optional
A matching list of string labels or ``None`` placeholders, or a matching list of
lists (see the `center` keyword). Wherever ``None`` appears in the list (or
if no labels were passed at all), labels are retrieved by calling
`~matplotlib.artist.Artist.get_label` on each `~matplotlib.artist.Artist` in the
handle list. If a handle consists of a tuple group of artists, labels are inferred
from the artists in the tuple (if there are multiple unique labels in the tuple
group of artists, the tuple group is expanded into unique legend entries --
otherwise, the tuple group elements are drawn on top of eachother). For details
on matplotlib legend handlers and tuple groups, see the matplotlib `legend guide \\
-<https://matplotlib.org/stable/tutorials/intermediate/legend_guide.html>`__.
"""
_legend_kwargs_docstring = """
frame, frameon : bool, optional
Toggles the legend frame. For centered-row legends, a frame
independent from matplotlib's built-in legend frame is created.
ncol, ncols : int, optional
The number of columns. `ncols` is an alias, added
for consistency with `~matplotlib.pyplot.subplots`.
order : {'C', 'F'}, optional
Whether legend handles are drawn in row-major (``'C'``) or column-major
(``'F'``) order. Analagous to `numpy.array` ordering. The matplotlib
default was ``'F'`` but ultraplot changes this to ``'C'``.
center : bool, optional
Whether to center each legend row individually. If ``True``, we draw
successive single-row legends "stacked" on top of each other. If ``None``,
we infer this setting from `handles`. By default, `center` is set to ``True``
if `handles` is a list of lists (each sublist is used as a row in the legend).
alphabetize : bool, default: False
Whether to alphabetize the legend entries according to
the legend labels.
title, label : str, optional
The legend title. The `label` keyword is also accepted, for consistency
with `~matplotlib.figure.Figure.colorbar`.
fontsize, fontweight, fontcolor : optional
The font size, weight, and color for the legend text. Font size is interpreted
by `~ultraplot.utils.units`. The default font size is :rcraw:`legend.fontsize`.
titlefontsize, titlefontweight, titlefontcolor : optional
The font size, weight, and color for the legend title. Font size is interpreted
by `~ultraplot.utils.units`. The default size is `fontsize`.
borderpad, borderaxespad, handlelength, handleheight, handletextpad, \\
labelspacing, columnspacing : unit-spec, optional
Various matplotlib `~matplotlib.axes.Axes.legend` spacing arguments.
%(units.em)s
a, alpha, framealpha, fc, facecolor, framecolor, ec, edgecolor, ew, edgewidth \\
: default: :rc:`legend.framealpha`, :rc:`legend.facecolor`, :rc:`legend.edgecolor`, \\
:rc:`axes.linewidth`
The opacity, face color, edge color, and edge width for the legend frame.
c, color, lw, linewidth, m, marker, ls, linestyle, dashes, ms, markersize : optional
Properties used to override the legend handles. For example, for a
legend describing variations in line style ignoring variations
in color, you might want to use ``color='black'``.
handle_kw : dict-like, optional
Additional properties used to override legend handles, e.g.
``handle_kw={'edgecolor': 'black'}``. Only line properties
can be passed as keyword arguments.
handler_map : dict-like, optional
A dictionary mapping instances or types to a legend handler.
This `handler_map` updates the default handler map found at
`matplotlib.legend.Legend.get_legend_handler_map`.
**kwargs
Passed to `~matplotlib.axes.Axes.legend`.
"""
docstring._snippet_manager["axes.legend_args"] = _legend_args_docstring
docstring._snippet_manager["axes.legend_kwargs"] = _legend_kwargs_docstring
def _align_bbox(align, length):
"""
Return a simple alignment bounding box for intersection calculations.
"""
if align in ("left", "bottom"):
bounds = [[0, 0], [length, 0]]
elif align in ("top", "right"):
bounds = [[1 - length, 0], [1, 0]]
elif align == "center":
bounds = [[0.5 * (1 - length), 0], [0.5 * (1 + length), 0]]
else:
raise ValueError(f"Invalid align {align!r}.")
return mtransforms.Bbox(bounds)
class _TransformedBoundsLocator:
"""
Axes locator for `~Axes.inset_axes` and other axes.
"""
def __init__(self, bounds, transform):
self._bounds = bounds
self._transform = transform
def __call__(self, ax, renderer): # noqa: U100
transfig = getattr(ax.figure, "transSubfigure", ax.figure.transFigure)
bbox = mtransforms.Bbox.from_bounds(*self._bounds)
bbox = mtransforms.TransformedBbox(bbox, self._transform)
bbox = mtransforms.TransformedBbox(bbox, transfig.inverted())
return bbox
class _ExternalModeMixin:
"""
Mixin providing explicit external-mode control and a context manager.
"""
def set_external(self, value=True):
"""
Set explicit external-mode override for this axes.
value:
- True: force external behavior (defer on-the-fly guides, etc.)
- False: force UltraPlot behavior
"""
if value not in (True, False):
raise ValueError("set_external expects True or False")
setattr(self, "_integration_external", value)
return self
class _ExternalContext:
def __init__(self, ax, value=True):
self._ax = ax
self._value = True if value is None else value
self._prev = getattr(ax, "_integration_external", None)
def __enter__(self):
self._ax._integration_external = self._value
return self._ax
def __exit__(self, exc_type, exc, tb):
self._ax._integration_external = self._prev
def external(self, value=True):
"""
Context manager toggling external mode during the block.
"""
return _ExternalModeMixin._ExternalContext(self, value)
def _in_external_context(self):
"""
Return True if UltraPlot helper behaviors should be suppressed.
"""
mode = getattr(self, "_integration_external", None)
return mode is True
class Axes(_ExternalModeMixin, maxes.Axes):
"""
The lowest-level `~matplotlib.axes.Axes` subclass used by ultraplot.
Implements basic universal features.
"""
_name = None # derived must override
_name_aliases = ()
_make_inset_locator = _TransformedBoundsLocator
def __repr__(self):
# Show the position in the geometry excluding panels. Panels are
# indicated by showing their parent geometry plus a 'side' argument.
# WARNING: This will not be used in matplotlib 3.3.0 (and probably next
# minor releases) because native __repr__ is defined in SubplotBase.
ax = self._get_topmost_axes()
name = type(self).__name__
prefix = "" if ax is self else "parent_"
params = {}
if self._name in ("cartopy", "basemap"):
name = name.replace("_" + self._name.title(), "Geo")
params["backend"] = self._name
if self._inset_parent:
name = re.sub("Axes(Subplot)?", "AxesInset", name)
params["bounds"] = tuple(np.round(self._inset_bounds, 2))
if self._altx_parent or self._alty_parent:
name = re.sub("Axes(Subplot)?", "AxesTwin", name)
params["axis"] = "x" if self._altx_parent else "y"
if self._colorbar_fill:
name = re.sub("Axes(Subplot)?", "AxesFill", name)
params["side"] = self._axes._panel_side
if self._panel_side:
name = re.sub("Axes(Subplot)?", "AxesPanel", name)
params["side"] = self._panel_side
try:
nrows, ncols, num1, num2 = (
ax.get_subplotspec().get_topmost_subplotspec()._get_geometry()
) # noqa: E501
params[prefix + "index"] = (num1, num2)
except (IndexError, ValueError, AttributeError): # e.g. a loose axes
left, bottom, width, height = np.round(self._position.bounds, 2)
params["left"], params["bottom"], params["size"] = (
left,
bottom,
(width, bottom),
) # noqa: E501
if ax.number:
params[prefix + "number"] = ax.number
params = ", ".join(f"{key}={value!r}" for key, value in params.items())
return f"{name}({params})"
def __str__(self):
return self.__repr__()
@docstring._snippet_manager
def __init__(self, *args, **kwargs):
"""
Parameters
----------
*args
Passed to `matplotlib.axes.Axes`.
%(axes.format)s
Other parameters
----------------
%(rc.init)s
See also
--------
Axes.format
matplotlib.axes.Axes
ultraplot.axes.PlotAxes
ultraplot.axes.CartesianAxes
ultraplot.axes.PolarAxes
ultraplot.axes.GeoAxes
ultraplot.figure.Figure.subplot
ultraplot.figure.Figure.add_subplot
"""
# Remove subplot-related args
# NOTE: These are documented on add_subplot()
ss = kwargs.pop("_subplot_spec", None) # see below
number = kwargs.pop("number", None)
autoshare = kwargs.pop("autoshare", None)
autoshare = _not_none(autoshare, True)
# Remove format-related args and initialize
rc_kw, rc_mode = _pop_rc(kwargs)
kw_format = _pop_props(kwargs, "patch") # background properties
if "zorder" in kw_format: # special case: refers to the entire axes
kwargs["zorder"] = kw_format.pop("zorder")
for cls, sig in self._format_signatures.items():
if isinstance(self, cls):
kw_format.update(_pop_params(kwargs, sig))
super().__init__(*args, **kwargs)
# Varous scalar properties
self._active_cycle = rc["axes.prop_cycle"]
self._auto_format = None # manipulated by wrapper functions
self._abc_border_kwargs = {}
self._abc_loc = None
self._abc_pad = 0 # User's horizontal offset for abc label (in points)
self._abc_title_pad = rc[
"abc.titlepad"
] # Spacing between abc and title when co-located
self._title_above = rc["title.above"]
self._title_border_kwargs = {} # title border properties
self._title_loc = None
self._title_pad = rc["title.pad"]
self._title_pad_current = None
self._altx_parent = None # for cartesian axes only
self._alty_parent = None
self._colorbar_fill = None
self._inset_parent = None
self._inset_bounds = None # for introspection ony
self._inset_zoom = False
self._inset_zoom_artists = None
self._panel_hidden = False # True when "filled" with cbar/legend
self._panel_align = {} # store 'align' and 'length' for "filled" cbar/legend
self._panel_parent = None
self._panel_share = False
self._panel_sharex_group = False # see _apply_auto_share
self._panel_sharey_group = False # see _apply_auto_share
self._panel_side = None
self._tight_bbox = None # bounding boxes are saved
self._integration_external = None # explicit external-mode override (None=auto)
self.xaxis.isDefault_minloc = True # ensure enabled at start (needed for dual)
self.yaxis.isDefault_minloc = True
# Various dictionary properties
# NOTE: Critical to use self.text() so they are patched with _update_label
self._legend_dict = {}
self._colorbar_dict = {}
d = self._panel_dict = {}
d["left"] = [] # NOTE: panels will be sorted inside-to-outside
d["right"] = []
d["bottom"] = []
d["top"] = []
d = self._title_dict = {}
kw = {"zorder": 3.5, "transform": self.transAxes}
d["abc"] = self.text(0, 0, "", **kw)
d["left"] = self._left_title # WARNING: track in case mpl changes this
d["center"] = self.title
d["right"] = self._right_title
d["upper left"] = self.text(0, 0, "", va="top", ha="left", **kw)
d["upper center"] = self.text(0, 0.5, "", va="top", ha="center", **kw)
d["upper right"] = self.text(0, 1, "", va="top", ha="right", **kw)
d["lower left"] = self.text(0, 0, "", va="bottom", ha="left", **kw)
d["lower center"] = self.text(0, 0.5, "", va="bottom", ha="center", **kw)
d["lower right"] = self.text(0, 1, "", va="bottom", ha="right", **kw)
d["outer left"] = self.text(0, 1, "", va="bottom", ha="right", **kw)
d["outer right"] = self.text(1, 1, "", va="bottom", ha="left", **kw)
# Subplot-specific settings
# NOTE: Default number for any axes is None (i.e., no a-b-c labels allowed)
# and for subplots added with add_subplot is incremented automatically
# WARNING: For mpl>=3.4.0 subplotspec assigned *after* initialization using
# set_subplotspec. Tried to defer to setter but really messes up both format()
# and _apply_auto_share(). Instead use workaround: Have Figure.add_subplot pass
# subplotspec as a hidden keyword arg. Non-subplots don't need this arg.
# See: https://github.com/matplotlib/matplotlib/pull/18564
self._number = None
if number: # not None or False
self.number = number
if ss is not None: # always passed from add_subplot
self.set_subplotspec(ss)
if autoshare:
self._apply_auto_share()
# Default formatting
# NOTE: This ignores user-input rc_mode. Mode '1' applies ultraplot
# features which is necessary on first run. Default otherwise is mode '2'
self.format(rc_kw=rc_kw, rc_mode=1, skip_figure=True, **kw_format)
def _add_inset_axes(
self,
bounds,
transform=None,
*,
proj=None,
projection=None,
zoom=None,
zoom_kw=None,
zorder=None,
**kwargs,
):
"""
Add an inset axes using arbitrary projection.
"""
# Converting transform to figure-relative coordinates
transform = self._get_transform(transform, "axes")
locator = self._make_inset_locator(bounds, transform)
bounds = locator(self, None).bounds
label = kwargs.pop("label", "inset_axes")
zorder = _not_none(zorder, 4)
# Parse projection and inherit from the current axes by default
# NOTE: The _parse_proj method also accepts axes classes.
proj = _not_none(proj=proj, projection=projection)
if proj is None:
if self._name in ("cartopy", "basemap"):
proj = copy.copy(self.projection)
else:
proj = self._name
kwargs = self.figure._parse_proj(proj, **kwargs)
# Create axes and apply locator. The locator lets the axes adjust
# automatically if we used data coords. Called by ax.apply_aspect()
cls = mproj.get_projection_class(kwargs.pop("projection"))
ax = cls(self.figure, bounds, zorder=zorder, label=label, **kwargs)
ax.set_axes_locator(locator)
ax._inset_parent = self
ax._inset_bounds = bounds
self.add_child_axes(ax)
# Add zoom indicator (NOTE: requires matplotlib >= 3.0)
zoom_default = self._name == "cartesian" and ax._name == "cartesian"
zoom = ax._inset_zoom = _not_none(zoom, zoom_default)
if zoom:
zoom_kw = zoom_kw or {}
# Check if the inset axes is an Ultraplot axes class.
# Ultraplot axes have a custom indicate_inset_zoom that can be
# called on the inset itself (uses self._inset_parent internally).
# Non-Ultraplot axes (e.g., raw matplotlib/cartopy) require calling
# matplotlib's indicate_inset_zoom on the parent with the inset as first argument.
if isinstance(ax, Axes):
# Ultraplot axes: call on inset (uses self._inset_parent internally)
ax.indicate_inset_zoom(**zoom_kw)
else:
# Non-Ultraplot axes: call matplotlib's parent class method
# with inset as first argument (matplotlib API)
maxes.Axes.indicate_inset_zoom(self, ax, **zoom_kw)
return ax
def _add_queued_guides(self):
"""
Draw the queued-up legends and colorbars. Wrapper funcs and legend func let
user add handles to location lists with successive calls.
"""
# Draw queued colorbars
for (loc, align), colorbar in tuple(self._colorbar_dict.items()):
if not isinstance(colorbar, tuple):
continue
handles, labels, kwargs = colorbar
cb = self._add_colorbar(handles, labels, loc=loc, align=align, **kwargs)
self._colorbar_dict[(loc, align)] = cb
# Draw queued legends
# WARNING: Passing empty list labels=[] to legend causes matplotlib
# _parse_legend_args to search for everything. Ensure None if empty.
for (loc, align), legend in tuple(self._legend_dict.items()):
if not isinstance(legend, tuple) or any(