Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 37e3851

Browse files
Phil Elsonpelson
Phil Elson
authored andcommitted
Squashed class_projections from matplotlib#694
1 parent 67df281 commit 37e3851

File tree

6 files changed

+191
-65
lines changed

6 files changed

+191
-65
lines changed

‎doc/api/api_changes.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ 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, this is done by
29+
:meth:`matplotlib.figure.Figure.add_axes`,
30+
:meth:`matplotlib.figure.Figure.add_subplot` and
31+
:meth:`matplotlib.figure.Figure.gca`.
32+
This change means that third party objects can expose themselves as
33+
matplotlib axes by providing a ``_as_mpl_axes`` method (see
34+
:ref:`adding-new-scales` for more detail).
35+
2536
Changes in 1.1.x
2637
================
2738

‎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=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/axes.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3514,7 +3514,9 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
35143514
* draw a gray rectangle from *y* = 0.25-0.75 that spans the
35153515
horizontal extent of the axes::
35163516
3517-
>>> axhspan(0.25, 0.75, facecolor='0.5', alpha=0.5)
3517+
# draw a gray rectangle from *y* = 0.25-0.75 that spans the
3518+
# horizontal extent of the axes
3519+
axhspan(0.25, 0.75, facecolor='0.5', alpha=0.5)
35183520
35193521
Valid kwargs are :class:`~matplotlib.patches.Polygon` properties:
35203522
@@ -3570,7 +3572,9 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs):
35703572
* draw a vertical green translucent rectangle from x=1.25 to 1.55 that
35713573
spans the yrange of the axes::
35723574
3573-
>>> axvspan(1.25, 1.55, facecolor='g', alpha=0.5)
3575+
# draw a vertical green translucent rectangle from x=1.25 to 1.55
3576+
# that spans the yrange of the axes
3577+
axvspan(1.25, 1.55, facecolor='g', alpha=0.5)
35743578
35753579
Valid kwargs are :class:`~matplotlib.patches.Polygon`
35763580
properties:

‎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)

AltStyle によって変換されたページ (->オリジナル) /