Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1e98373

Browse files
author
Phil Elson
committed
Merge pull request #923 from pelson/class_projections_rebased
Support for non string projections, exposing the _as_mpl_axes external API.
2 parents 606ca15 + cdce7af commit 1e98373

File tree

8 files changed

+267
-73
lines changed

8 files changed

+267
-73
lines changed

doc/api/api_changes.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,36 @@ Changes in 1.2.x
2222
now set the radius of the pie; setting the *radius* to 'None' (the default
2323
value), will result in a pie with a radius of 1 as before.
2424

25+
* Use of :func:`~matplotlib.projections.projection_factory` is now deprecated
26+
in favour of axes class identification using
27+
:func:`~matplotlib.projections.process_projection_requirements` followed by
28+
direct axes class invocation (at the time of writing, functions which do this
29+
are: :meth:`~matplotlib.figure.Figure.add_axes`,
30+
:meth:`~matplotlib.figure.Figure.add_subplot` and
31+
:meth:`~matplotlib.figure.Figure.gca`). Therefore::
32+
33+
34+
key = figure._make_key(*args, **kwargs)
35+
ispolar = kwargs.pop('polar', False)
36+
projection = kwargs.pop('projection', None)
37+
if ispolar:
38+
if projection is not None and projection != 'polar':
39+
raise ValuError('polar and projection args are inconsistent')
40+
projection = 'polar'
41+
ax = projection_factory(projection, self, rect, **kwargs)
42+
key = self._make_key(*args, **kwargs)
43+
44+
# is now
45+
46+
projection_class, kwargs, key = \
47+
process_projection_requirements(self, *args, **kwargs)
48+
ax = projection_class(self, rect, **kwargs)
49+
50+
This change means that third party objects can expose themselves as
51+
matplotlib axes by providing a ``_as_mpl_axes`` method. See
52+
:ref:`adding-new-scales` for more detail.
53+
54+
2555
Changes in 1.1.x
2656
================
2757

doc/devel/add_new_projection.rst

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ in :mod:`matplotlib.scale` that may be used as starting points.
7171
Creating a new projection
7272
=========================
7373

74-
Adding a new projection consists of defining a subclass of
75-
:class:`matplotlib.axes.Axes`, that includes the following elements:
74+
Adding a new projection consists of defining a projection axes which
75+
subclasses :class:`matplotlib.axes.Axes` and includes the following
76+
elements:
7677

7778
- A transformation from data coordinates into display coordinates.
7879

@@ -101,8 +102,25 @@ Adding a new projection consists of defining a subclass of
101102

102103
- Any additional methods for additional convenience or features.
103104

104-
Once the class is defined, it must be registered with matplotlib
105-
so that the user can select it.
105+
Once the projection axes is defined, it can be used in one of two ways:
106+
107+
- By defining the class attribute ``name``, the projection axes can be
108+
registered with :func:`matplotlib.projections.register_projection`
109+
and subsequently simply invoked by name::
110+
111+
plt.axes(projection='my_proj_name')
112+
113+
- For more complex, parameterisable projections, a generic "projection"
114+
object may be defined which includes the method ``_as_mpl_axes``.
115+
``_as_mpl_axes`` should take no arguments and return the projection's
116+
axes subclass and a dictionary of additional arguments to pass to the
117+
subclass' ``__init__`` method. Subsequently a parameterised projection
118+
can be initialised with::
119+
120+
plt.axes(projection=MyProjection(param1=param1_value))
121+
122+
where MyProjection is an object which implements a ``_as_mpl_axes`` method.
123+
106124

107125
A full-fledged and heavily annotated example is in
108126
:file:`examples/api/custom_projection_example.py`. The polar plot

lib/matplotlib/figure.py

Lines changed: 101 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727

2828
from legend import Legend
2929
from transforms import Affine2D, Bbox, BboxTransformTo, TransformedBbox
30-
from projections import projection_factory, get_projection_names, \
31-
get_projection_class
30+
from projections import get_projection_names, get_projection_class, \
31+
process_projection_requirements
3232
from matplotlib.blocking_input import BlockingMouseInput, BlockingKeyMouseInput
3333

3434
import matplotlib.cbook as cbook
@@ -41,11 +41,18 @@
4141

4242
class AxesStack(Stack):
4343
"""
44-
Specialization of the Stack to handle all
45-
tracking of Axes in a Figure. This requires storing
46-
key, (ind, axes) pairs. The key is based on the args and kwargs
47-
used in generating the Axes. ind is a serial number for tracking
48-
the order in which axes were added.
44+
Specialization of the Stack to handle all tracking of Axes in a Figure.
45+
This stack stores ``key, (ind, axes)`` pairs, where:
46+
47+
* **key** should be a hash of the args and kwargs
48+
used in generating the Axes.
49+
* **ind** is a serial number for tracking the order
50+
in which axes were added.
51+
52+
The AxesStack is a callable, where ``ax_stack()`` returns
53+
the current axes. Alternatively the :meth:`current_key_axes` will
54+
return the current key and associated axes.
55+
4956
"""
5057
def __init__(self):
5158
Stack.__init__(self)
@@ -74,9 +81,14 @@ def _entry_from_axes(self, e):
7481
return (k, (ind, e))
7582

7683
def remove(self, a):
84+
"""Remove the axes from the stack."""
7785
Stack.remove(self, self._entry_from_axes(a))
7886

7987
def bubble(self, a):
88+
"""
89+
Move the given axes, which must already exist in the
90+
stack, to the top.
91+
"""
8092
return Stack.bubble(self, self._entry_from_axes(a))
8193

8294
def add(self, key, a):
@@ -107,11 +119,21 @@ def add(self, key, a):
107119
self._ind += 1
108120
return Stack.push(self, (key, (self._ind, a)))
109121

110-
def __call__(self):
122+
def current_key_axes(self):
123+
"""
124+
Return a tuple of ``(key, axes)`` for the active axes.
125+
126+
If no axes exists on the stack, then returns ``(None, None)``.
127+
128+
"""
111129
if not len(self._elements):
112-
return self._default
130+
return self._default, self._default
113131
else:
114-
return self._elements[self._pos][1][1]
132+
key, (index, axes) = self._elements[self._pos]
133+
return key, axes
134+
135+
def __call__(self):
136+
return self.current_key_axes()[1]
115137

116138
def __contains__(self, a):
117139
return a in self.as_list()
@@ -692,6 +714,8 @@ def add_axes(self, *args, **kwargs):
692714
"""
693715
if not len(args): return
694716

717+
# shortcut the projection "key" modifications later on, if an axes
718+
# with the exact args/kwargs exists, return it immediately.
695719
key = self._make_key(*args, **kwargs)
696720
ax = self._axstack.get(key)
697721
if ax is not None:
@@ -703,17 +727,18 @@ def add_axes(self, *args, **kwargs):
703727
assert(a.get_figure() is self)
704728
else:
705729
rect = args[0]
706-
ispolar = kwargs.pop('polar', False)
707-
projection = kwargs.pop('projection', None)
708-
if ispolar:
709-
if projection is not None and projection != 'polar':
710-
raise ValueError(
711-
"polar=True, yet projection='%s'. " +
712-
"Only one of these arguments should be supplied." %
713-
projection)
714-
projection = 'polar'
715-
716-
a = projection_factory(projection, self, rect, **kwargs)
730+
projection_class, kwargs, key = \
731+
process_projection_requirements(self, *args, **kwargs)
732+
733+
# check that an axes of this type doesn't already exist, if it
734+
# does, set it as active and return it
735+
ax = self._axstack.get(key)
736+
if ax is not None and isinstance(ax, projection_class):
737+
self.sca(ax)
738+
return ax
739+
740+
# create the new axes using the axes class given
741+
a = projection_class(self, rect, **kwargs)
717742

718743
self._axstack.add(key, a)
719744
self.sca(a)
@@ -725,10 +750,18 @@ def add_subplot(self, *args, **kwargs):
725750
Add a subplot. Examples::
726751
727752
fig.add_subplot(111)
728-
fig.add_subplot(1,1,1) # equivalent but more general
729-
fig.add_subplot(212, axisbg='r') # add subplot with red background
730-
fig.add_subplot(111, polar=True) # add a polar subplot
731-
fig.add_subplot(sub) # add Subplot instance sub
753+
754+
# equivalent but more general
755+
fig.add_subplot(1,1,1)
756+
757+
# add subplot with red background
758+
fig.add_subplot(212, axisbg='r')
759+
760+
# add a polar subplot
761+
fig.add_subplot(111, projection='polar')
762+
763+
# add Subplot instance sub
764+
fig.add_subplot(sub)
732765
733766
*kwargs* are legal :class:`~matplotlib.axes.Axes` kwargs plus
734767
*projection*, which chooses a projection type for the axes.
@@ -755,39 +788,32 @@ def add_subplot(self, *args, **kwargs):
755788
args = tuple([int(c) for c in str(args[0])])
756789

757790
if isinstance(args[0], SubplotBase):
791+
758792
a = args[0]
759793
assert(a.get_figure() is self)
760-
key = self._make_key(*args, **kwargs)
794+
key = self._make_key(*args[1:], **kwargs)
761795
else:
762-
kwargs = kwargs.copy()
763-
ispolar = kwargs.pop('polar', False)
764-
projection = kwargs.pop('projection', None)
765-
if ispolar:
766-
if projection is not None and projection != 'polar':
767-
raise ValueError(
768-
"polar=True, yet projection='%s'. " +
769-
"Only one of these arguments should be supplied." %
770-
projection)
771-
projection = 'polar'
772-
773-
projection_class = get_projection_class(projection)
774-
775-
# Remake the key without projection kwargs:
776-
key = self._make_key(*args, **kwargs)
796+
projection_class, kwargs, key = \
797+
process_projection_requirements(self, *args, **kwargs)
798+
799+
# try to find the axes with this key in the stack
777800
ax = self._axstack.get(key)
801+
778802
if ax is not None:
779803
if isinstance(ax, projection_class):
804+
# the axes already existed, so set it as active & return
780805
self.sca(ax)
781806
return ax
782807
else:
783-
self._axstack.remove(ax)
784808
# Undocumented convenience behavior:
785809
# subplot(111); subplot(111, projection='polar')
786810
# will replace the first with the second.
787811
# Without this, add_subplot would be simpler and
788812
# more similar to add_axes.
813+
self._axstack.remove(ax)
789814

790815
a = subplot_class_factory(projection_class)(self, *args, **kwargs)
816+
791817
self._axstack.add(key, a)
792818
self.sca(a)
793819
return a
@@ -1046,24 +1072,40 @@ def gca(self, **kwargs):
10461072
"""
10471073
Return the current axes, creating one if necessary
10481074
1049-
The following kwargs are supported
1075+
The following kwargs are supported for ensuring the returned axes
1076+
adheres to the given projection etc., and for axes creation if
1077+
the active axes does not exist:
10501078
%(Axes)s
1079+
1080+
.. note::
1081+
When specifying kwargs to ``gca`` to find the pre-created active
1082+
axes, they should be equivalent in every way to the kwargs which
1083+
were used in its creation.
1084+
10511085
"""
1052-
ax = self._axstack()
1053-
if ax is not None:
1054-
ispolar = kwargs.get('polar', False)
1055-
projection = kwargs.get('projection', None)
1056-
if ispolar:
1057-
if projection is not None and projection != 'polar':
1058-
raise ValueError(
1059-
"polar=True, yet projection='%s'. " +
1060-
"Only one of these arguments should be supplied." %
1061-
projection)
1062-
projection = 'polar'
1063-
1064-
projection_class = get_projection_class(projection)
1065-
if isinstance(ax, projection_class):
1066-
return ax
1086+
ckey, cax = self._axstack.current_key_axes()
1087+
# if there exists an axes on the stack see if it maches
1088+
# the desired axes configuration
1089+
if cax is not None:
1090+
1091+
# if no kwargs are given just return the current axes
1092+
# this is a convenience for gca() on axes such as polar etc.
1093+
if not kwargs:
1094+
return cax
1095+
1096+
# if the user has specified particular projection detail
1097+
# then build up a key which can represent this
1098+
else:
1099+
# we don't want to modify the original kwargs
1100+
# so take a copy so that we can do what we like to it
1101+
kwargs_copy = kwargs.copy()
1102+
projection_class, _, key = \
1103+
process_projection_requirements(self, **kwargs_copy)
1104+
# if the cax matches this key then return the axes, otherwise
1105+
# continue and a new axes will be created
1106+
if key == ckey and isinstance(cax, projection_class):
1107+
return cax
1108+
10671109
return self.add_subplot(111, **kwargs)
10681110

10691111
def sca(self, a):
@@ -1094,7 +1136,7 @@ def savefig(self, *args, **kwargs):
10941136
10951137
savefig(fname, dpi=None, facecolor='w', edgecolor='w',
10961138
orientation='portrait', papertype=None, format=None,
1097-
transparent=False, bbox_inches=None, pad_inches=0.1):
1139+
transparent=False, bbox_inches=None, pad_inches=0.1)
10981140
10991141
Save the current figure.
11001142

0 commit comments

Comments
 (0)