1"""
2Non-separable transforms that map from data space to screen space.
3
4Projections are defined as `~.axes.Axes` subclasses. They include the
5following elements:
6
7- A transformation from data coordinates into display coordinates.
8
9- An inverse of that transformation. This is used, for example, to convert
10 mouse positions from screen space back into data space.
11
12- Transformations for the gridlines, ticks and ticklabels. Custom projections
13 will often need to place these elements in special locations, and Matplotlib
14 has a facility to help with doing so.
15
16- Setting up default values (overriding `~.axes.Axes.cla`), since the defaults
17 for a rectilinear Axes may not be appropriate.
18
19- Defining the shape of the Axes, for example, an elliptical Axes, that will be
20 used to draw the background of the plot and for clipping any data elements.
21
22- Defining custom locators and formatters for the projection. For example, in
23 a geographic projection, it may be more convenient to display the grid in
24 degrees, even if the data is in radians.
25
26- Set up interactive panning and zooming. This is left as an "advanced"
27 feature left to the reader, but there is an example of this for polar plots
28 in `matplotlib.projections.polar`.
29
30- Any additional methods for additional convenience or features.
31
32Once the projection Axes is defined, it can be used in one of two ways:
33
34- By defining the class attribute ``name``, the projection Axes can be
35 registered with `matplotlib.projections.register_projection` and subsequently
36 simply invoked by name::
37
38 fig.add_subplot(projection="my_proj_name")
39
40- For more complex, parameterisable projections, a generic "projection" object
41 may be defined which includes the method ``_as_mpl_axes``. ``_as_mpl_axes``
42 should take no arguments and return the projection's Axes subclass and a
43 dictionary of additional arguments to pass to the subclass' ``__init__``
44 method. Subsequently a parameterised projection can be initialised with::
45
46 fig.add_subplot(projection=MyProjection(param1=param1_value))
47
48 where MyProjection is an object which implements a ``_as_mpl_axes`` method.
49
50A full-fledged and heavily annotated example is in
51:doc:`/gallery/misc/custom_projection`. The polar plot functionality in
52`matplotlib.projections.polar` may also be of interest.
53"""
54
55from .. import axes, _docstring
56from .geo import AitoffAxes, HammerAxes, LambertAxes, MollweideAxes
57from .polar import PolarAxes
58
59try:
60 from mpl_toolkits.mplot3d import Axes3D
61except Exception:
62 import warnings
63 warnings.warn("Unable to import Axes3D. This may be due to multiple versions of "
64 "Matplotlib being installed (e.g. as a system package and as a pip "
65 "package). As a result, the 3D projection is not available.")
66 Axes3D = None
67
68
69class ProjectionRegistry:
70 """A mapping of registered projection names to projection classes."""
71
72 def __init__(self):
73 self._all_projection_types = {}
74
75 def register(self, *projections):
76 """Register a new set of projections."""
77 for projection in projections:
78 name = projection.name
79 self._all_projection_types[name] = projection
80
81 def get_projection_class(self, name):
82 """Get a projection class from its *name*."""
83 return self._all_projection_types[name]
84
85 def get_projection_names(self):
86 """Return the names of all projections currently registered."""
87 return sorted(self._all_projection_types)
88
89
90projection_registry = ProjectionRegistry()
91projection_registry.register(
92 axes.Axes,
93 PolarAxes,
94 AitoffAxes,
95 HammerAxes,
96 LambertAxes,
97 MollweideAxes,
98)
99if Axes3D is not None:
100 projection_registry.register(Axes3D)
101else:
102 # remove from namespace if not importable
103 del Axes3D
104
105
106def register_projection(cls):
107 projection_registry.register(cls)
108
109
110def get_projection_class(projection=None):
111 """
112 Get a projection class from its name.
113
114 If *projection* is None, a standard rectilinear projection is returned.
115 """
116 if projection is None:
117 projection = 'rectilinear'
118
119 try:
120 return projection_registry.get_projection_class(projection)
121 except KeyError as err:
122 raise ValueError("Unknown projection %r" % projection) from err
123
124
125get_projection_names = projection_registry.get_projection_names
126_docstring.interpd.update(projection_names=get_projection_names())