Skip to content

Commit ece1102

Browse files
committed
Hide 'registered' axes names from public API
1 parent 6d7c35c commit ece1102

File tree

10 files changed

+89
-85
lines changed

10 files changed

+89
-85
lines changed

proplot/axes/__init__.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55
import matplotlib.projections as mproj
66

7+
from ..internals import context
78
from .base import Axes # noqa: F401
89
from .cartesian import CartesianAxes
910
from .geo import GeoAxes # noqa: F401
@@ -13,13 +14,6 @@
1314
from .shared import _SharedAxes # noqa: F401
1415
from .three import ThreeAxes # noqa: F401
1516

16-
# Register projections
17-
mproj.register_projection(CartesianAxes)
18-
mproj.register_projection(PolarAxes)
19-
mproj.register_projection(_BasemapAxes)
20-
mproj.register_projection(_CartopyAxes)
21-
mproj.register_projection(ThreeAxes)
22-
2317
# Prevent importing module names and set order of appearance for objects
2418
__all__ = [
2519
'Axes',
@@ -29,3 +23,11 @@
2923
'GeoAxes',
3024
'ThreeAxes',
3125
]
26+
27+
# Register projections with package prefix to avoid conflicts
28+
# NOTE: We integrate with cartopy and basemap rather than using matplotlib's
29+
# native projection system. Therefore axes names are not part of public API.
30+
for _cls in (CartesianAxes, PolarAxes, _BasemapAxes, _CartopyAxes, ThreeAxes):
31+
for _name in (_cls._name, *_cls._name_aliases):
32+
with context._state_context(_cls, name='proplot_' + _name):
33+
mproj.register_projection(_cls)

proplot/axes/base.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,9 @@ class Axes(maxes.Axes):
561561
The lowest-level `~matplotlib.axes.Axes` subclass used by proplot.
562562
Implements basic universal features.
563563
"""
564+
_name = None # derived must override
565+
_name_aliases = ()
566+
564567
def __repr__(self): # override matplotlib
565568
# Show the position in the geometry excluding panels. Panels are
566569
# indicated by showing their parent geometry plus a 'side' argument.
@@ -577,11 +580,9 @@ def __repr__(self): # override matplotlib
577580
if self._panel_side:
578581
name = name.replace('Subplot', 'Panel') # e.g. CartesianAxesPanel
579582
params['side'] = self._panel_side
580-
for package in ('cartopy', 'basemap'):
581-
head = '_' + package.title()
582-
if head in name: # e.g. _CartopyAxesSubplot to GeoAxesSubplot
583-
name = name.replace(head, 'Geo')
584-
params['backend'] = package
583+
if self._name in ('cartopy', 'basemap'):
584+
name = name.replace('_' + self._name.title(), 'Geo')
585+
params['backend'] = self._name
585586
params = ', '.join(f'{key}={value!r}' for key, value in params.items())
586587
return f'{name}({params})'
587588

@@ -1474,23 +1475,25 @@ def inset_axes(
14741475
label = kwargs.pop('label', 'inset_axes')
14751476

14761477
# Get projection, inherit from current axes by default
1478+
# NOTE: The _parse_proj method also accepts axes classes.
14771479
proj = _not_none(proj=proj, projection=projection)
14781480
if proj is None:
1479-
proj = self.name # will have 'proplot_' prefix
1480-
if proj in ('proplot_cartopy', 'proplot_basemap'):
1481+
if self._name in ('cartopy', 'basemap'):
14811482
proj = copy.copy(self.projection)
1483+
else:
1484+
proj = self._name
14821485
kwargs = self.figure._parse_proj(proj, **kwargs)
14831486
cls = mprojections.get_projection_class(kwargs.pop('projection'))
14841487

14851488
# Create axes and apply locator. The locator lets the axes adjust
1486-
# automatically if we used data coords. Gets called by ax.apply_aspect()
1489+
# automatically if we used data coords. Called by ax.apply_aspect()
14871490
ax = cls(self.figure, bb.bounds, zorder=zorder, label=label, **kwargs)
14881491
ax.set_axes_locator(locator)
14891492
ax._inset_parent = self
14901493
self.add_child_axes(ax)
14911494

1492-
# Add zoom indicator (NOTE: requires version >=3.0)
1493-
zoom = _not_none(zoom, self.name == ax.name) # only zoom when same projection
1495+
# Add zoom indicator (NOTE: requires matplotlib >= 3.0)
1496+
zoom = _not_none(zoom, self._name == 'cartesian' and ax._name == 'cartesian')
14941497
ax._inset_zoom = zoom
14951498
if zoom:
14961499
zoom_kw = zoom_kw or {}
@@ -2920,7 +2923,7 @@ def text(
29202923
# Translate positional args
29212924
# Audo-redirect to text2D for 3D axes if not enough arguments passed
29222925
# NOTE: The transform must be passed positionally for 3D axes with 2D coords
2923-
keys = tuple('xyz' if self.name == 'proplot_three' else 'xy')
2926+
keys = tuple('xyz' if self._name == 'three' else 'xy')
29242927
keys += (('s', 'text'),) # interpret both 's' and 'text'
29252928
args, kwargs = _keyword_to_positional(keys, *args, **kwargs)
29262929
if len(args) == len(keys) + 1:
@@ -2929,13 +2932,13 @@ def text(
29292932
elif len(args) == len(keys):
29302933
add_text = super().text
29312934
transform = kwargs.pop('transform', None)
2932-
elif len(args) == len(keys) - 1 and self.name == 'proplot_three':
2935+
elif len(args) == len(keys) - 1 and self._name == 'three':
29332936
add_text = self.text2D
29342937
transform = kwargs.pop('transform', None)
29352938
else:
29362939
raise TypeError(
2937-
f'Expected {len(keys) - 1} to {len(keys)} positional '
2938-
f'arguments but got {len(args)}.'
2940+
f'Expected {len(keys) - 1} to {len(keys)} '
2941+
f'positional arguments but got {len(args)}.'
29392942
)
29402943

29412944
# Translate keyword args

proplot/axes/cartesian.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ class CartesianAxes(shared._SharedAxes, plot.PlotAxes):
109109
Adds the `~CartesianAxes.format` method and overrides several existing
110110
methods.
111111
"""
112-
# The registered projection name.
113-
name = 'proplot_cartesian'
112+
_name = 'cartesian'
113+
_name_aliases = ('cart', 'rect', 'rectilinar') # include matplotlib name
114114

115115
def __init__(self, *args, **kwargs):
116116
"""

proplot/axes/geo.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ def format(
533533
# drawing gridlines before basemap map boundary will call set_axes_limits()
534534
# which initializes a boundary hidden from external access. So we must call
535535
# it here. Must do this between matplotlib.Axes.__init__() and Axes.format().
536-
if self.name == 'proplot_basemap' and self._map_boundary is None:
536+
if self._name == 'basemap' and self._map_boundary is None:
537537
if self.projection.projection in self._proj_non_rectangular:
538538
patch = self.projection.drawmapboundary(ax=self)
539539
self._map_boundary = patch
@@ -653,8 +653,8 @@ class _CartopyAxes(GeoAxes, _GeoAxes):
653653
"""
654654
Axes subclass for plotting cartopy projections.
655655
"""
656-
# The registered projection name.
657-
name = 'proplot_cartopy'
656+
_name = 'cartopy'
657+
_name_aliases = ('geo', 'geographic') # default 'geographic' axes
658658
_proj_class = Projection
659659
_proj_north = (
660660
pcrs.NorthPolarStereo,
@@ -684,7 +684,9 @@ def __init__(
684684
# NOTE: Initial extent is configured in _update_extent
685685
import cartopy # noqa: F401 verify package is available
686686
if not isinstance(map_projection, ccrs.Projection):
687-
raise ValueError('CartopyAxes requires map_projection=cartopy.crs.Projection.') # noqa: E501
687+
raise ValueError(
688+
'Cartopy axes require map_projection=cartopy.crs.Projection.'
689+
)
688690
latmax = 90
689691
boundinglat = None
690692
polar = isinstance(map_projection, self._proj_polar)
@@ -1121,8 +1123,7 @@ class _BasemapAxes(GeoAxes):
11211123
"""
11221124
Axes subclass for plotting basemap projections.
11231125
"""
1124-
# The registered projection name.
1125-
name = 'proplot_basemap'
1126+
_name = 'basemap'
11261127
_proj_class = Basemap
11271128
_proj_north = ('npaeqd', 'nplaea', 'npstere')
11281129
_proj_south = ('spaeqd', 'splaea', 'spstere')
@@ -1149,7 +1150,9 @@ def __init__(self, *args, map_projection=None, **kwargs):
11491150
# and python immmediately crashes. Do not try again.
11501151
import mpl_toolkits.basemap # noqa: F401 verify package is available
11511152
if not isinstance(map_projection, mbasemap.Basemap):
1152-
raise ValueError('BasemapAxes requires map_projection=mpl_toolkits.basemap.Basemap.') # noqa: E501
1153+
raise ValueError(
1154+
'Basemap axes require map_projection=mpl_toolkits.basemap.Basemap.'
1155+
)
11531156
map_projection = self._map_projection = copy.copy(map_projection)
11541157
lon0 = self._get_lon0()
11551158
if map_projection.projection in self._proj_polar:

proplot/axes/plot.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,7 +1200,7 @@ def _plot_native(self, name, *args, **kwargs):
12001200
# Now prevent internal calls from running through overrides using preprocessor
12011201
kwargs.pop('distribution', None) # remove stat distributions
12021202
with context._state_context(self, _internal_call=True):
1203-
if getattr(self, 'name', None) == 'proplot_basemap':
1203+
if self._name == 'basemap':
12041204
obj = getattr(self.projection, name)(*args, ax=self, **kwargs)
12051205
else:
12061206
obj = getattr(super(), name)(*args, **kwargs)
@@ -1605,7 +1605,7 @@ def _inbounds_vlim(self, x, y, z, *, to_centers=False):
16051605
# keep this in a try-except clause for now. However *internally* we should
16061606
# not reach this block unless everything is an array so raise that error.
16071607
xmask = ymask = None
1608-
if self.name != 'proplot_cartesian':
1608+
if self._name != 'cartesian':
16091609
return z # TODO: support geographic projections when input is PlateCarree()
16101610
if not all(getattr(a, 'ndim', None) in (1, 2) for a in (x, y, z)):
16111611
raise ValueError('Invalid input coordinates. Must be 1D or 2D arrays.')
@@ -1629,7 +1629,7 @@ def _inbounds_vlim(self, x, y, z, *, to_centers=False):
16291629
return z
16301630
except Exception as err:
16311631
warnings._warn_proplot(
1632-
'Failed to restrict automatic colormap normalization algorithm '
1632+
'Failed to restrict automatic colormap normalization '
16331633
f'to in-bounds data only. Error message: {err}'
16341634
)
16351635
return z
@@ -1643,11 +1643,13 @@ def _inbounds_xylim(self, extents, x, y, **kwargs):
16431643
# WARNING: This feature is still experimental. But seems obvious. Matplotlib
16441644
# updates data limits in ad hoc fashion differently for each plotting command
16451645
# but since proplot standardizes inputs we can easily use them for dataLim.
1646-
kwargs, vert = _get_vert(**kwargs)
1647-
if extents is None or self.name != 'proplot_cartesian':
1646+
if extents is None:
1647+
return
1648+
if self._name != 'cartesian':
16481649
return
16491650
if not x.size or not y.size:
16501651
return
1652+
kwargs, vert = _get_vert(**kwargs)
16511653
if not vert:
16521654
x, y = y, x
16531655
trans = self.dataLim
@@ -1767,8 +1769,8 @@ def _parse_format1d(
17671769
_guide_kw_to_arg('colorbar', kwargs, label=title)
17681770

17691771
# Apply the basic x and y settings
1770-
autox = autox and self.name == 'proplot_cartesian'
1771-
autoy = autoy and self.name == 'proplot_cartesian'
1772+
autox = autox and self._name == 'cartesian'
1773+
autoy = autoy and self._name == 'cartesian'
17721774
sx, sy = 'xy' if vert else 'yx'
17731775
kw_format = {}
17741776
if autox and autoformat: # 'x' axis
@@ -1828,9 +1830,9 @@ def _parse_plot1d(self, x, *ys, **kwargs):
18281830
x, *ys, kwargs = self._parse_format1d(x, *ys, zerox=zerox, **kwargs)
18291831

18301832
# Geographic corrections
1831-
if self.name == 'proplot_cartopy' and isinstance(kwargs.get('transform'), PlateCarree): # noqa: E501
1833+
if self._name == 'cartopy' and isinstance(kwargs.get('transform'), PlateCarree): # noqa: E501
18321834
x, *ys = data._geo_cartopy_1d(x, *ys)
1833-
elif self.name == 'proplot_basemap' and kwargs.get('latlon', None):
1835+
elif self._name == 'basemap' and kwargs.get('latlon', None):
18341836
xmin, xmax = self._lonaxis.get_view_interval()
18351837
x, *ys = data._geo_basemap_1d(x, *ys, xmin=xmin, xmax=xmax)
18361838

@@ -1853,7 +1855,7 @@ def _parse_format2d(self, x, y, *zs, autoformat=None, autoguide=True, **kwargs):
18531855
y = data._meta_labels(z, axis=0)
18541856

18551857
# Apply labels and XY axis settings
1856-
if self.name == 'proplot_cartesian':
1858+
if self._name == 'cartesian':
18571859
# Apply labels
18581860
# NOTE: Do not overwrite existing labels!
18591861
kw_format = {}
@@ -1927,9 +1929,9 @@ def _parse_plot2d(
19271929
# Geographic corrections
19281930
if allow1d:
19291931
pass
1930-
elif self.name == 'proplot_cartopy' and isinstance(kwargs.get('transform'), PlateCarree): # noqa: E501
1932+
elif self._name == 'cartopy' and isinstance(kwargs.get('transform'), PlateCarree): # noqa: E501
19311933
x, y, *zs = data._geo_cartopy_2d(x, y, *zs, globe=globe)
1932-
elif self.name == 'proplot_basemap' and kwargs.get('latlon', None):
1934+
elif self._name == 'basemap' and kwargs.get('latlon', None):
19331935
xmin, xmax = self._lonaxis.get_view_interval()
19341936
x, y, *zs = data._geo_basemap_2d(x, y, *zs, xmin=xmin, xmax=xmax, globe=globe) # noqa: E501
19351937
x, y = np.meshgrid(x, y) # WARNING: required always
@@ -3707,12 +3709,7 @@ def heatmap(self, *args, aspect=None, **kwargs):
37073709
"""
37083710
obj = self.pcolormesh(*args, default_discrete=False, **kwargs)
37093711
aspect = _not_none(aspect, rc['image.aspect'])
3710-
if self.name != 'proplot_cartesian':
3711-
warnings._warn_proplot(
3712-
'The heatmap() command is meant for CartesianAxes. '
3713-
'Please use pcolor() or pcolormesh() instead.'
3714-
)
3715-
else:
3712+
if self._name == 'cartesian':
37163713
coords = getattr(obj, '_coordinates', None)
37173714
xlocator = ylocator = None
37183715
if coords is not None:
@@ -3729,6 +3726,11 @@ def heatmap(self, *args, aspect=None, **kwargs):
37293726
if self.yaxis.isDefault_minloc:
37303727
kw['ytickminor'] = False
37313728
self.format(**kw)
3729+
else:
3730+
warnings._warn_proplot(
3731+
'The heatmap() command is meant for CartesianAxes. '
3732+
'Please use pcolor() or pcolormesh() instead.'
3733+
)
37323734
return obj
37333735

37343736
@data._preprocess('x', 'y', 'u', 'v', ('c', 'color', 'colors'))

proplot/axes/polar.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ class PolarAxes(shared._SharedAxes, plot.PlotAxes, mproj.PolarAxes):
2020
Axes subclass for plotting in polar coordinates. Adds the `~PolarAxes.format`
2121
method and overrides several existing methods.
2222
"""
23-
# The registered projection name.
24-
name = 'proplot_polar'
23+
_name = 'polar'
2524

2625
def __init__(self, *args, **kwargs):
2726
"""

proplot/axes/three.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ class ThreeAxes(shared._SharedAxes, plot.PlotAxes, Axes3D):
1414
"""
1515
Simple mix-in of `proplot.axes.PlotAxes` with `~mpl_toolkits.mplot3d.axes3d.Axes3D`.
1616
"""
17-
# The registered projection name.
18-
name = 'proplot_three'
17+
_name = 'three'
18+
_name_aliases = ('3d',)
1919

2020
def __init__(self, *args, **kwargs):
2121
import mpl_toolkits.mplot3d # noqa: F401 verify package is available

proplot/constructor.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import cycler
1919
import matplotlib.colors as mcolors
2020
import matplotlib.dates as mdates
21-
import matplotlib.projections.polar as mpolar
21+
import matplotlib.projections as mproj
2222
import matplotlib.scale as mscale
2323
import matplotlib.ticker as mticker
2424
import numpy as np
@@ -108,8 +108,8 @@
108108
LOCATORS['dms'] = partial(pticker.DegreeLocator, dms=True)
109109
LOCATORS['dmslon'] = partial(pticker.LongitudeLocator, dms=True)
110110
LOCATORS['dmslat'] = partial(pticker.LatitudeLocator, dms=True)
111-
if hasattr(mpolar, 'ThetaLocator'):
112-
LOCATORS['theta'] = mpolar.ThetaLocator
111+
if hasattr(mproj.polar, 'ThetaLocator'):
112+
LOCATORS['theta'] = mproj.polar.ThetaLocator
113113

114114
# Mapping of strings to `~matplotlib.ticker.Formatter` classes. See
115115
# `Formatter` for a table.
@@ -151,8 +151,8 @@
151151
FORMATTERS['dms'] = partial(pticker.DegreeFormatter, dms=True)
152152
FORMATTERS['dmslon'] = partial(pticker.LongitudeFormatter, dms=True)
153153
FORMATTERS['dmslat'] = partial(pticker.LatitudeFormatter, dms=True)
154-
if hasattr(mpolar, 'ThetaFormatter'):
155-
FORMATTERS['theta'] = mpolar.ThetaFormatter
154+
if hasattr(mproj.polar, 'ThetaFormatter'):
155+
FORMATTERS['theta'] = mproj.polar.ThetaFormatter
156156
if hasattr(mdates, 'ConciseDateFormatter'):
157157
FORMATTERS['concise'] = mdates.ConciseDateFormatter
158158

@@ -1433,7 +1433,7 @@ def Proj(name, basemap=None, **kwargs):
14331433
use_basemap = _not_none(basemap, rc['basemap'])
14341434
is_crs = CRS is not object and isinstance(name, CRS)
14351435
is_basemap = Basemap is not object and isinstance(name, Basemap)
1436-
include_axes_projections = kwargs.pop('include_axes_projections', None)
1436+
include_axes = kwargs.pop('include_axes', None)
14371437
if is_crs or is_basemap:
14381438
proj = name
14391439
proj._proj_package = 'cartopy' if is_crs else 'basemap'
@@ -1520,9 +1520,11 @@ def Proj(name, basemap=None, **kwargs):
15201520
# Unknown
15211521
else:
15221522
options = tuple(PROJS)
1523-
if include_axes_projections:
1524-
from .figure import AXES_PROJS # avoid circular import
1525-
options += tuple(AXES_PROJS)
1523+
if include_axes:
1524+
options += tuple(
1525+
proj.split('proplot_', 1)[1] for proj in mproj.get_projection_names()
1526+
if 'proplot_' in proj
1527+
)
15261528
raise ValueError(
15271529
f'Unknown projection {name!r}. Options are: '
15281530
+ ', '.join(map(repr, options))

0 commit comments

Comments
 (0)