From 1a0021b9b9af3ffba3eff4c5932c4c289276345d Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Tue, 31 Jan 2012 16:08:58 +0000 Subject: [PATCH 01/16] Class projection support added. --- lib/matplotlib/figure.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 87d819c1260e..dde32b14805b 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -758,7 +758,12 @@ def add_subplot(self, *args, **kwargs): projection) projection = 'polar' - projection_class = get_projection_class(projection) + if isinstance(projection, basestring) or projection is None: + projection_class = get_projection_class(projection) + else: + projection_class, extra_kwargs = projection._as_mpl_axes() + # XXX Do the extra arguments need to be hashable??? + kwargs.update(**extra_kwargs) # Remake the key without projection kwargs: key = self._make_key(*args, **kwargs) From ab0615cba29fe3af55c294256b46864d8bf3fd40 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Tue, 31 Jan 2012 16:57:50 +0000 Subject: [PATCH 02/16] Tweaked. --- lib/matplotlib/figure.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index dde32b14805b..996c5f688e25 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -762,7 +762,6 @@ def add_subplot(self, *args, **kwargs): projection_class = get_projection_class(projection) else: projection_class, extra_kwargs = projection._as_mpl_axes() - # XXX Do the extra arguments need to be hashable??? kwargs.update(**extra_kwargs) # Remake the key without projection kwargs: From 756dd8c1c6a5d9b2c725f5cac72e202c30a12d4e Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 6 Feb 2012 16:48:42 +0000 Subject: [PATCH 03/16] Added documentation, supported add_axes regarding _as_mpl_axes. --- doc/devel/add_new_projection.rst | 31 ++++++++++++++++++++++---- lib/matplotlib/figure.py | 27 +++++++++++++++------- lib/matplotlib/projections/__init__.py | 17 ++++++++++---- lib/matplotlib/projections/polar.py | 12 +++++++++- 4 files changed, 70 insertions(+), 17 deletions(-) diff --git a/doc/devel/add_new_projection.rst b/doc/devel/add_new_projection.rst index 5efbead7fc40..fcac5756d26a 100644 --- a/doc/devel/add_new_projection.rst +++ b/doc/devel/add_new_projection.rst @@ -71,8 +71,9 @@ in :mod:`matplotlib.scale` that may be used as starting points. Creating a new projection ========================= -Adding a new projection consists of defining a subclass of -:class:`matplotlib.axes.Axes`, that includes the following elements: +Adding a new projection consists of defining a projection axes which +subclasses :class:`matplotlib.axes.Axes` and includes the following +elements: - A transformation from data coordinates into display coordinates. @@ -98,11 +99,33 @@ Adding a new projection consists of defining a subclass of - Set up interactive panning and zooming. This is left as an "advanced" feature left to the reader, but there is an example of this for polar plots in :mod:`matplotlib.projections.polar`. + + - Optionally define the class attribute ``NAME`` which can be + registered with :func:`matplotlib.projections.register_projection` + and used for simple projection instantiation such as + ``plt.axes(projection=NAME)``, where ``NAME`` is a string. - Any additional methods for additional convenience or features. -Once the class is defined, it must be registered with matplotlib -so that the user can select it. +Once the projection axes is defined, it can be used in one of two ways: + + - By defining the class attribute ``NAME``, the projection axes can be + registered with :func:`matplotlib.projections.register_projection` + and subsequently simply invoked by name:: + + plt.axes(projection=NAME) + + - For more complex, parameterisable projections, a generic "projection" + object may be defined which includes the method ``_as_mpl_axes``. + ``_as_mpl_axes`` should take no arguments and return the projection's + axes subclass and a dictionary of additional arguments to pass to the + subclass' ``__init__`` method. Subsequently a parameterised projection + can be initialised with:: + + plt.axes(projection=MyProjection(param1=param1_value)) + + where MyProjection is an object which implements a ``_as_mpl_axes`` method. + A full-fledged and heavily annotated example is in :file:`examples/api/custom_projection_example.py`. The polar plot diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 996c5f688e25..0be8ef13f380 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -702,8 +702,16 @@ def add_axes(self, *args, **kwargs): projection) projection = 'polar' + if isinstance(projection, basestring) or projection is None: + projection_class = get_projection_class(projection) + elif hasattr(projection, '_as_mpl_axes'): + projection_class, extra_kwargs = projection._as_mpl_axes() + kwargs.update(**extra_kwargs) + else: + TypeError('projection must be a string, None or implement a _as_mpl_axes method. Got %r' % projection) + a = projection_factory(projection, self, rect, **kwargs) - + self._axstack.add(key, a) self.sca(a) return a @@ -714,10 +722,11 @@ def add_subplot(self, *args, **kwargs): Add a subplot. Examples: fig.add_subplot(111) - fig.add_subplot(1,1,1) # equivalent but more general - fig.add_subplot(212, axisbg='r') # add subplot with red background - fig.add_subplot(111, polar=True) # add a polar subplot - fig.add_subplot(sub) # add Subplot instance sub + fig.add_subplot(1,1,1) # equivalent but more general + fig.add_subplot(212, axisbg='r') # add subplot with red background + fig.add_subplot(111, projection='polar') # add a polar subplot + fig.add_subplot(111, polar=True) # add a polar subplot + fig.add_subplot(sub) # add Subplot instance sub *kwargs* are legal :class:`!matplotlib.axes.Axes` kwargs plus *projection*, which chooses a projection type for the axes. @@ -751,18 +760,20 @@ def add_subplot(self, *args, **kwargs): ispolar = kwargs.pop('polar', False) projection = kwargs.pop('projection', None) if ispolar: - if projection is not None and projection != 'polar': + if projection is not None: raise ValueError( - "polar=True, yet projection='%s'. " + + "polar=True, yet projection=%r. " + "Only one of these arguments should be supplied." % projection) projection = 'polar' if isinstance(projection, basestring) or projection is None: projection_class = get_projection_class(projection) - else: + elif hasattr(projection, '_as_mpl_axes'): projection_class, extra_kwargs = projection._as_mpl_axes() kwargs.update(**extra_kwargs) + else: + TypeError('projection must be a string, None or implement a _as_mpl_axes method. Got %r' % projection) # Remake the key without projection kwargs: key = self._make_key(*args, **kwargs) diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index cbdd114d524f..3c89f50bdd60 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -56,10 +56,19 @@ def get_projection_class(projection=None): if projection is None: projection = 'rectilinear' - try: - return projection_registry.get_projection_class(projection) - except KeyError: - raise ValueError("Unknown projection '%s'" % projection) + if isinstance(projection, basestring): + try: + return projection_registry.get_projection_class(projection) + except KeyError: + raise ValueError("Unknown projection '%s'" % projection) + + elif hasattr(projection, '_as_mpl_axes'): + projection_class, extra_kwargs = projection._as_mpl_axes() + kwargs.update(**extra_kwargs) + else: + TypeError('projection must be a string, None or implement a _as_mpl_axes method. Got %r' % projection) + + def projection_factory(projection, figure, rect, **kwargs): """ diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index e9d3a677fbac..fb9023d9f251 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -757,4 +757,14 @@ def drag_pan(self, button, key, x, y): # return mpath.Path(result, codes) # transform_path_non_affine = transform_path - +class Polar(object): + """ + Represents the polar projection, a Polar instance can be used to initialise + a polar plot in the standard ways, for example:: + + plt.axes(projection=Polar()) + + """ + def _as_mpl_axes(self): + # implement the matplotlib axes interface + return PolarAxes, {} \ No newline at end of file From 40a0ec9e5db7ddb075907d869a38f111f3ccb5e5 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 6 Feb 2012 16:51:33 +0000 Subject: [PATCH 04/16] reverted projections change. --- lib/matplotlib/projections/__init__.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 3c89f50bdd60..72d07acbff18 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -56,18 +56,10 @@ def get_projection_class(projection=None): if projection is None: projection = 'rectilinear' - if isinstance(projection, basestring): - try: - return projection_registry.get_projection_class(projection) - except KeyError: - raise ValueError("Unknown projection '%s'" % projection) - - elif hasattr(projection, '_as_mpl_axes'): - projection_class, extra_kwargs = projection._as_mpl_axes() - kwargs.update(**extra_kwargs) - else: - TypeError('projection must be a string, None or implement a _as_mpl_axes method. Got %r' % projection) - + try: + return projection_registry.get_projection_class(projection) + except KeyError: + raise ValueError("Unknown projection '%s'" % projection) def projection_factory(projection, figure, rect, **kwargs): From 53cbea654dbdd2023b69f38e5556e9c5686c80b0 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 6 Feb 2012 16:55:25 +0000 Subject: [PATCH 05/16] Tidied up branch a little. --- doc/devel/add_new_projection.rst | 5 ----- lib/matplotlib/projections/__init__.py | 3 ++- lib/matplotlib/projections/polar.py | 11 ----------- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/doc/devel/add_new_projection.rst b/doc/devel/add_new_projection.rst index fcac5756d26a..8704a2542cdc 100644 --- a/doc/devel/add_new_projection.rst +++ b/doc/devel/add_new_projection.rst @@ -100,11 +100,6 @@ elements: "advanced" feature left to the reader, but there is an example of this for polar plots in :mod:`matplotlib.projections.polar`. - - Optionally define the class attribute ``NAME`` which can be - registered with :func:`matplotlib.projections.register_projection` - and used for simple projection instantiation such as - ``plt.axes(projection=NAME)``, where ``NAME`` is a string. - - Any additional methods for additional convenience or features. Once the projection axes is defined, it can be used in one of two ways: diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 72d07acbff18..bd91d75b48dd 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -60,7 +60,8 @@ def get_projection_class(projection=None): return projection_registry.get_projection_class(projection) except KeyError: raise ValueError("Unknown projection '%s'" % projection) - + + def projection_factory(projection, figure, rect, **kwargs): """ diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index fb9023d9f251..4166dc29b675 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -757,14 +757,3 @@ def drag_pan(self, button, key, x, y): # return mpath.Path(result, codes) # transform_path_non_affine = transform_path -class Polar(object): - """ - Represents the polar projection, a Polar instance can be used to initialise - a polar plot in the standard ways, for example:: - - plt.axes(projection=Polar()) - - """ - def _as_mpl_axes(self): - # implement the matplotlib axes interface - return PolarAxes, {} \ No newline at end of file From bd0fd6e690184db72ee5a968cfb3745bd5ba7ff5 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 6 Feb 2012 16:56:34 +0000 Subject: [PATCH 06/16] Matching up diffs with github's diff viewer (newlines). --- lib/matplotlib/projections/__init__.py | 2 -- lib/matplotlib/projections/polar.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index bd91d75b48dd..cbdd114d524f 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -61,8 +61,6 @@ def get_projection_class(projection=None): except KeyError: raise ValueError("Unknown projection '%s'" % projection) - - def projection_factory(projection, figure, rect, **kwargs): """ Get a new projection instance. diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 4166dc29b675..e9d3a677fbac 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -757,3 +757,4 @@ def drag_pan(self, button, key, x, y): # return mpath.Path(result, codes) # transform_path_non_affine = transform_path + From eb37499b3f005da393778a3b6deaf8ca32a892f6 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 6 Feb 2012 17:53:51 +0000 Subject: [PATCH 07/16] Fixed error messages substitution. --- lib/matplotlib/figure.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 0be8ef13f380..feb962593499 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -697,9 +697,8 @@ def add_axes(self, *args, **kwargs): if ispolar: if projection is not None and projection != 'polar': raise ValueError( - "polar=True, yet projection='%s'. " + - "Only one of these arguments should be supplied." % - projection) + "polar=True, yet projection='%s'. " % projection + + "Only one of these arguments should be supplied.") projection = 'polar' if isinstance(projection, basestring) or projection is None: @@ -708,7 +707,8 @@ def add_axes(self, *args, **kwargs): projection_class, extra_kwargs = projection._as_mpl_axes() kwargs.update(**extra_kwargs) else: - TypeError('projection must be a string, None or implement a _as_mpl_axes method. Got %r' % projection) + TypeError('projection must be a string, None or implement a ' + '_as_mpl_axes method. Got %r' % projection) a = projection_factory(projection, self, rect, **kwargs) @@ -762,9 +762,8 @@ def add_subplot(self, *args, **kwargs): if ispolar: if projection is not None: raise ValueError( - "polar=True, yet projection=%r. " + - "Only one of these arguments should be supplied." % - projection) + "polar=True, yet projection=%r. " % projection + + "Only one of these arguments should be supplied.") projection = 'polar' if isinstance(projection, basestring) or projection is None: @@ -773,7 +772,8 @@ def add_subplot(self, *args, **kwargs): projection_class, extra_kwargs = projection._as_mpl_axes() kwargs.update(**extra_kwargs) else: - TypeError('projection must be a string, None or implement a _as_mpl_axes method. Got %r' % projection) + TypeError('projection must be a string, None or implement a ' + '_as_mpl_axes method. Got %r' % projection) # Remake the key without projection kwargs: key = self._make_key(*args, **kwargs) @@ -1058,9 +1058,8 @@ def gca(self, **kwargs): if ispolar: if projection is not None and projection != 'polar': raise ValueError( - "polar=True, yet projection='%s'. " + - "Only one of these arguments should be supplied." % - projection) + "polar=True, yet projection='%s'. " % projection + + "Only one of these arguments should be supplied.") projection = 'polar' projection_class = get_projection_class(projection) From 1fa7d390dd15c371a8ccb83870ad71640371b645 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 6 Feb 2012 17:56:09 +0000 Subject: [PATCH 08/16] Corrected having not hit git add before committing. --- lib/matplotlib/figure.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index feb962593499..6cb37daef56e 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -697,8 +697,9 @@ def add_axes(self, *args, **kwargs): if ispolar: if projection is not None and projection != 'polar': raise ValueError( - "polar=True, yet projection='%s'. " % projection + - "Only one of these arguments should be supplied.") + "polar=True, yet projection=%r. " + "Only one of these arguments should be supplied." + % projection) projection = 'polar' if isinstance(projection, basestring) or projection is None: @@ -762,8 +763,9 @@ def add_subplot(self, *args, **kwargs): if ispolar: if projection is not None: raise ValueError( - "polar=True, yet projection=%r. " % projection + - "Only one of these arguments should be supplied.") + "polar=True, yet projection=%r. " + "Only one of these arguments should be supplied." + % projection) projection = 'polar' if isinstance(projection, basestring) or projection is None: From 89053c5bcd2f2be565a597292d6064f75168911f Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 6 Feb 2012 18:03:49 +0000 Subject: [PATCH 09/16] Reverted polar check. --- lib/matplotlib/figure.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 6cb37daef56e..565d0c5d50ab 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -698,8 +698,8 @@ def add_axes(self, *args, **kwargs): if projection is not None and projection != 'polar': raise ValueError( "polar=True, yet projection=%r. " - "Only one of these arguments should be supplied." - % projection) + "Only one of these arguments should be supplied." % + projection) projection = 'polar' if isinstance(projection, basestring) or projection is None: @@ -761,11 +761,11 @@ def add_subplot(self, *args, **kwargs): ispolar = kwargs.pop('polar', False) projection = kwargs.pop('projection', None) if ispolar: - if projection is not None: + if projection is not None and projection != 'polar': raise ValueError( "polar=True, yet projection=%r. " - "Only one of these arguments should be supplied." - % projection) + "Only one of these arguments should be supplied." % + projection) projection = 'polar' if isinstance(projection, basestring) or projection is None: From 7674dcdc0aa745de9bc995a01abda774b0a3e6ca Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 6 Feb 2012 18:14:24 +0000 Subject: [PATCH 10/16] Further tweaks re add_axes. --- lib/matplotlib/axes.py | 2 +- lib/matplotlib/figure.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index 3f418a30aed6..ebb5f5ae1382 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -8439,7 +8439,7 @@ def label_outer(self): _subplot_classes = {} def subplot_class_factory(axes_class=None): - # This makes a new class that inherits from SubclassBase and the + # This makes a new class that inherits from SubplotBase and the # given axes_class (which is assumed to be a subclass of Axes). # This is perhaps a little bit roundabout to make a new class on # the fly like this, but it means that a new Subplot class does diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 565d0c5d50ab..5dd674d1f2dc 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -703,16 +703,15 @@ def add_axes(self, *args, **kwargs): projection = 'polar' if isinstance(projection, basestring) or projection is None: - projection_class = get_projection_class(projection) + a = projection_factory(projection, self, rect, **kwargs) elif hasattr(projection, '_as_mpl_axes'): projection_class, extra_kwargs = projection._as_mpl_axes() kwargs.update(**extra_kwargs) + a = projection_class(self, rect, **kwargs) else: TypeError('projection must be a string, None or implement a ' '_as_mpl_axes method. Got %r' % projection) - a = projection_factory(projection, self, rect, **kwargs) - self._axstack.add(key, a) self.sca(a) return a From f8021a206a703703a3a98c350b8a59e909f34617 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 20 Feb 2012 13:36:11 +0000 Subject: [PATCH 11/16] Review actions --- lib/matplotlib/axes.py | 36 +++--- lib/matplotlib/figure.py | 147 +++++++++++++------------ lib/matplotlib/projections/__init__.py | 49 ++++++--- 3 files changed, 128 insertions(+), 104 deletions(-) diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index ebb5f5ae1382..db1f570c1ecc 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -938,15 +938,11 @@ def hold(self, b=None): Set the hold state. If *hold* is *None* (default), toggle the *hold* state. Else set the *hold* state to boolean value *b*. - Examples: - - * toggle hold: - >>> hold() - * turn hold on: - >>> hold(True) - * turn hold off - >>> hold(False) - + Examples:: + + hold() # toggle hold + hold(True) # turn hold on + hold(False) # turn hold off When hold is True, subsequent plot commands will be added to the current axes. When hold is False, the current axes and @@ -3461,12 +3457,11 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): Return value is a :class:`matplotlib.patches.Polygon` instance. - Examples: - - * draw a gray rectangle from *y* = 0.25-0.75 that spans the - horizontal extent of the axes + Examples:: - >>> axhspan(0.25, 0.75, facecolor='0.5', alpha=0.5) + # draw a gray rectangle from *y* = 0.25-0.75 that spans the + # horizontal extent of the axes + axhspan(0.25, 0.75, facecolor='0.5', alpha=0.5) Valid kwargs are :class:`~matplotlib.patches.Polygon` properties: @@ -3519,10 +3514,9 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs): Examples: - * draw a vertical green translucent rectangle from x=1.25 to 1.55 that - spans the yrange of the axes - - >>> axvspan(1.25, 1.55, facecolor='g', alpha=0.5) + # draw a vertical green translucent rectangle from x=1.25 to 1.55 + # that spans the yrange of the axes + axvspan(1.25, 1.55, facecolor='g', alpha=0.5) Valid kwargs are :class:`~matplotlib.patches.Polygon` properties: @@ -6872,7 +6866,7 @@ def pcolor(self, *args, **kwargs): y = np.arange(3) X, Y = meshgrid(x,y) - is equivalent to: + is equivalent to:: X = array([[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], @@ -7412,11 +7406,11 @@ def hist(self, x, bins=10, range=None, normed=False, weights=None, """ call signature:: - def hist(x, bins=10, range=None, normed=False, weights=None, + hist(x, bins=10, range=None, normed=False, weights=None, cumulative=False, bottom=None, histtype='bar', align='mid', orientation='vertical', rwidth=None, log=False, color=None, label=None, - **kwargs): + **kwargs) Compute and draw the histogram of *x*. The return value is a tuple (*n*, *bins*, *patches*) or ([*n0*, *n1*, ...], *bins*, diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 5dd674d1f2dc..9ea12b4f2ebe 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -27,8 +27,8 @@ from legend import Legend from transforms import Affine2D, Bbox, BboxTransformTo, TransformedBbox -from projections import projection_factory, get_projection_names, \ - get_projection_class +from projections import get_projection_names, get_projection_class, \ + process_projection_requirements from matplotlib.blocking_input import BlockingMouseInput, BlockingKeyMouseInput import matplotlib.cbook as cbook @@ -107,12 +107,17 @@ def add(self, key, a): self._ind += 1 return Stack.push(self, (key, (self._ind, a))) - def __call__(self): + def current_key_axes(self): + """Return a tuple of (key, axes) for the last axes added.""" if not len(self._elements): - return self._default + return self._default, self._default else: - return self._elements[self._pos][1][1] + key, (index, axes) = self._elements[self._pos] + return key, axes + def __call__(self): + return self.current_key_axes()[1] + def __contains__(self, a): return a in self.as_list() @@ -681,6 +686,8 @@ def add_axes(self, *args, **kwargs): """ if not len(args): return + # shortcut the projection "key" modifications later on, if an axes + # with the exact args/kwargs exists, return it immediately. key = self._make_key(*args, **kwargs) ax = self._axstack.get(key) if ax is not None: @@ -692,25 +699,18 @@ def add_axes(self, *args, **kwargs): assert(a.get_figure() is self) else: rect = args[0] - ispolar = kwargs.pop('polar', False) - projection = kwargs.pop('projection', None) - if ispolar: - if projection is not None and projection != 'polar': - raise ValueError( - "polar=True, yet projection=%r. " - "Only one of these arguments should be supplied." % - projection) - projection = 'polar' - - if isinstance(projection, basestring) or projection is None: - a = projection_factory(projection, self, rect, **kwargs) - elif hasattr(projection, '_as_mpl_axes'): - projection_class, extra_kwargs = projection._as_mpl_axes() - kwargs.update(**extra_kwargs) - a = projection_class(self, rect, **kwargs) - else: - TypeError('projection must be a string, None or implement a ' - '_as_mpl_axes method. Got %r' % projection) + projection_class, kwargs, key = \ + process_projection_requirements(self, **kwargs) + + # check that an axes of this type doesn't already exist, if it + # does, set it as active and return it + ax = self._axstack.get(key) + if ax is not None and isinstance(ax, projection_class): + self.sca(ax) + return ax + + # create the new axes using the axes class given + a = projection_class(self, rect, **kwargs) self._axstack.add(key, a) self.sca(a) @@ -719,14 +719,21 @@ def add_axes(self, *args, **kwargs): @docstring.dedent_interpd def add_subplot(self, *args, **kwargs): """ - Add a subplot. Examples: + Add a subplot. Examples:: fig.add_subplot(111) - fig.add_subplot(1,1,1) # equivalent but more general - fig.add_subplot(212, axisbg='r') # add subplot with red background - fig.add_subplot(111, projection='polar') # add a polar subplot - fig.add_subplot(111, polar=True) # add a polar subplot - fig.add_subplot(sub) # add Subplot instance sub + + # equivalent but more general + fig.add_subplot(1,1,1) + + # add subplot with red background + fig.add_subplot(212, axisbg='r') + + # add a polar subplot + fig.add_subplot(111, projection='polar') + + # add Subplot instance sub + fig.add_subplot(sub) *kwargs* are legal :class:`!matplotlib.axes.Axes` kwargs plus *projection*, which chooses a projection type for the axes. @@ -756,42 +763,27 @@ def add_subplot(self, *args, **kwargs): assert(a.get_figure() is self) key = self._make_key(*args, **kwargs) else: - kwargs = kwargs.copy() - ispolar = kwargs.pop('polar', False) - projection = kwargs.pop('projection', None) - if ispolar: - if projection is not None and projection != 'polar': - raise ValueError( - "polar=True, yet projection=%r. " - "Only one of these arguments should be supplied." % - projection) - projection = 'polar' - - if isinstance(projection, basestring) or projection is None: - projection_class = get_projection_class(projection) - elif hasattr(projection, '_as_mpl_axes'): - projection_class, extra_kwargs = projection._as_mpl_axes() - kwargs.update(**extra_kwargs) - else: - TypeError('projection must be a string, None or implement a ' - '_as_mpl_axes method. Got %r' % projection) - - # Remake the key without projection kwargs: - key = self._make_key(*args, **kwargs) + projection_class, kwargs, key = \ + process_projection_requirements(self, **kwargs) + + # try to find the axes with this key in the stack ax = self._axstack.get(key) + if ax is not None: if isinstance(ax, projection_class): + # the axes already existed, so set it as active & return self.sca(ax) return ax else: - self._axstack.remove(ax) # Undocumented convenience behavior: # subplot(111); subplot(111, projection='polar') # will replace the first with the second. # Without this, add_subplot would be simpler and # more similar to add_axes. + self._axstack.remove(ax) a = subplot_class_factory(projection_class)(self, *args, **kwargs) + self._axstack.add(key, a) self.sca(a) return a @@ -1049,23 +1041,40 @@ def gca(self, **kwargs): """ Return the current axes, creating one if necessary - The following kwargs are supported + The following kwargs are supported for ensuring the returned axes + adheres to the given projection etc., and for axes creation if + the active axes does not exist: %(Axes)s + + .. note:: + When specifying kwargs to ``gca`` to find the pre-created active + axes, they should be equivalent in every way to the kwargs which + were used in its creation. + """ - ax = self._axstack() - if ax is not None: - ispolar = kwargs.get('polar', False) - projection = kwargs.get('projection', None) - if ispolar: - if projection is not None and projection != 'polar': - raise ValueError( - "polar=True, yet projection='%s'. " % projection + - "Only one of these arguments should be supplied.") - projection = 'polar' - - projection_class = get_projection_class(projection) - if isinstance(ax, projection_class): - return ax + ckey, kax = self._axstack.current_key_axes() + # if there exists an axes on the stack see if it maches + # the desired axes configuration + if kax is not None: + + # if no kwargs are given just return the current axes + # this is a convenience for gca() on axes such as polar etc. + if not kwargs: + return kax + + # if the user has specified particular projection detail + # then build up a key which can represent this + else: + # we don't want to modify the original kwargs + # so take a copy so that we can do what we like to it + kwargs_copy = kwargs.copy() + projection_class, _, key = \ + process_projection_requirements(self, **kwargs_copy) + # if the kax matches this key then return the axes, otherwise + # continue and a new axes will be created + if key == ckey and isinstance(kax, projection_class): + return kax + return self.add_subplot(111, **kwargs) def sca(self, a): @@ -1096,7 +1105,7 @@ def savefig(self, *args, **kwargs): savefig(fname, dpi=None, facecolor='w', edgecolor='w', orientation='portrait', papertype=None, format=None, - transparent=False, bbox_inches=None, pad_inches=0.1): + transparent=False, bbox_inches=None, pad_inches=0.1) Save the current figure. diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index cbdd114d524f..4ca557b97239 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -46,6 +46,7 @@ def get_projection_names(self): def register_projection(cls): projection_registry.register(cls) + def get_projection_class(projection=None): """ Get a projection class from its name. @@ -61,22 +62,42 @@ def get_projection_class(projection=None): except KeyError: raise ValueError("Unknown projection '%s'" % projection) -def projection_factory(projection, figure, rect, **kwargs): - """ - Get a new projection instance. - - *projection* is a projection name. - - *figure* is a figure to add the axes to. - *rect* is a :class:`~matplotlib.transforms.Bbox` object specifying - the location of the axes within the figure. - - Any other kwargs are passed along to the specific projection - constructor being used. - """ +def process_projection_requirements(figure, *args, **kwargs): + """ + Handle the args/kwargs to for add_axes/add_subplot/gca, + returning:: + + (axes_proj_class, proj_class_kwargs, proj_stack_key) + + Which can be used for new axes initialization/identification. + + """ + ispolar = kwargs.pop('polar', False) + projection = kwargs.pop('projection', None) + if ispolar: + if projection is not None and projection != 'polar': + raise ValueError( + "polar=True, yet projection=%r. " + "Only one of these arguments should be supplied." % + projection) + projection = 'polar' + + if isinstance(projection, basestring) or projection is None: + projection_class = get_projection_class(projection) + elif hasattr(projection, '_as_mpl_axes'): + projection_class, extra_kwargs = projection._as_mpl_axes() + kwargs.update(**extra_kwargs) + else: + raise TypeError('projection must be a string, None or implement a ' + '_as_mpl_axes method. Got %r' % projection) + + # Make the key without projection kwargs, this is used as a unique + # lookup for axes instances + key = figure._make_key(*args, **kwargs) + + return projection_class, kwargs, key - return get_projection_class(projection)(figure, rect, **kwargs) def get_projection_names(): """ From 677a0edbb54c0ed1e48fdaa6ebbc404169b49b6a Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 20 Feb 2012 13:44:07 +0000 Subject: [PATCH 12/16] Minor doc changes --- lib/matplotlib/figure.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 9ea12b4f2ebe..d27a6c50c937 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -108,7 +108,7 @@ def add(self, key, a): return Stack.push(self, (key, (self._ind, a))) def current_key_axes(self): - """Return a tuple of (key, axes) for the last axes added.""" + """Return a tuple of (key, axes) for the active axes.""" if not len(self._elements): return self._default, self._default else: @@ -1052,15 +1052,15 @@ def gca(self, **kwargs): were used in its creation. """ - ckey, kax = self._axstack.current_key_axes() + ckey, cax = self._axstack.current_key_axes() # if there exists an axes on the stack see if it maches # the desired axes configuration - if kax is not None: + if cax is not None: # if no kwargs are given just return the current axes # this is a convenience for gca() on axes such as polar etc. if not kwargs: - return kax + return cax # if the user has specified particular projection detail # then build up a key which can represent this @@ -1070,10 +1070,10 @@ def gca(self, **kwargs): kwargs_copy = kwargs.copy() projection_class, _, key = \ process_projection_requirements(self, **kwargs_copy) - # if the kax matches this key then return the axes, otherwise + # if the cax matches this key then return the axes, otherwise # continue and a new axes will be created - if key == ckey and isinstance(kax, projection_class): - return kax + if key == ckey and isinstance(cax, projection_class): + return cax return self.add_subplot(111, **kwargs) From 836a45961f75442c987c1efb4bd426a0d8da4b22 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 20 Feb 2012 15:40:06 +0000 Subject: [PATCH 13/16] Fixed error raising unittest case --- lib/matplotlib/figure.py | 11 ++++++----- lib/matplotlib/tests/test_tightlayout.py | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index d27a6c50c937..d959aa230e7d 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -700,7 +700,7 @@ def add_axes(self, *args, **kwargs): else: rect = args[0] projection_class, kwargs, key = \ - process_projection_requirements(self, **kwargs) + process_projection_requirements(self, *args, **kwargs) # check that an axes of this type doesn't already exist, if it # does, set it as active and return it @@ -757,14 +757,15 @@ def add_subplot(self, *args, **kwargs): if len(args) == 1 and isinstance(args[0], int): args = tuple([int(c) for c in str(args[0])]) - + if isinstance(args[0], SubplotBase): + a = args[0] assert(a.get_figure() is self) - key = self._make_key(*args, **kwargs) + key = self._make_key(*args[1:], **kwargs) else: projection_class, kwargs, key = \ - process_projection_requirements(self, **kwargs) + process_projection_requirements(self, *args, **kwargs) # try to find the axes with this key in the stack ax = self._axstack.get(key) @@ -781,7 +782,7 @@ def add_subplot(self, *args, **kwargs): # Without this, add_subplot would be simpler and # more similar to add_axes. self._axstack.remove(ax) - + a = subplot_class_factory(projection_class)(self, *args, **kwargs) self._axstack.add(key, a) diff --git a/lib/matplotlib/tests/test_tightlayout.py b/lib/matplotlib/tests/test_tightlayout.py index 3e6d7861d109..a846b56bb21b 100644 --- a/lib/matplotlib/tests/test_tightlayout.py +++ b/lib/matplotlib/tests/test_tightlayout.py @@ -121,3 +121,6 @@ def test_tight_layout6(): h_pad=0.5) +if __name__=='__main__': + import nose + nose.runmodule(argv=['-s','--with-doctest'], exit=False) From 564e042721e4a17bc4c8fcc93ab80f0f19059561 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 27 Feb 2012 17:18:46 +0000 Subject: [PATCH 14/16] Review actions #2 --- lib/matplotlib/axes.py | 2 +- lib/matplotlib/figure.py | 31 ++++++++++++++++++++++++------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index db1f570c1ecc..545ff665aaa5 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -3512,7 +3512,7 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs): Return value is the :class:`matplotlib.patches.Polygon` instance. - Examples: + Examples:: # draw a vertical green translucent rectangle from x=1.25 to 1.55 # that spans the yrange of the axes diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index d959aa230e7d..dbc08c447734 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -41,11 +41,18 @@ class AxesStack(Stack): """ - Specialization of the Stack to handle all - tracking of Axes in a Figure. This requires storing - key, (ind, axes) pairs. The key is based on the args and kwargs - used in generating the Axes. ind is a serial number for tracking - the order in which axes were added. + Specialization of the Stack to handle all tracking of Axes in a Figure. + This stack stores ``key, (ind, axes)`` pairs, where: + + * **key** should be a hash of the args and kwargs + used in generating the Axes. + * **ind** is a serial number for tracking the order + in which axes were added. + + The AxesStack is a callable, where ``ax_stack()`` returns + the current axes. Alternatively the :meth:`current_key_axes` will + return the current key and associated axes. + """ def __init__(self): Stack.__init__(self) @@ -74,9 +81,14 @@ def _entry_from_axes(self, e): return (k, (ind, e)) def remove(self, a): + """Remove the axes from the stack.""" Stack.remove(self, self._entry_from_axes(a)) - + def bubble(self, a): + """ + Move the given axes, which must already exist in the + stack, to the top. + """ return Stack.bubble(self, self._entry_from_axes(a)) def add(self, key, a): @@ -108,7 +120,12 @@ def add(self, key, a): return Stack.push(self, (key, (self._ind, a))) def current_key_axes(self): - """Return a tuple of (key, axes) for the active axes.""" + """ + Return a tuple of ``(key, axes)`` for the active axes. + + If no axes exists on the stack, then returns ``(None, None)``. + + """ if not len(self._elements): return self._default, self._default else: From 270f2919e287c29de42daf5d697eb28d2667aabe Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 29 Feb 2012 13:22:03 +0000 Subject: [PATCH 15/16] Review actions #3 --- doc/api/api_changes.rst | 10 +++ lib/matplotlib/projections/__init__.py | 91 +++++++++++++++++--------- 2 files changed, 69 insertions(+), 32 deletions(-) diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index a1f6682170fc..d0b74a2f0f64 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -13,6 +13,16 @@ For new features that were added to matplotlib, please see Changes in 1.1.x ================ +* Use of :func:`matplotlib.projections.projection_factory` is now deprecated + in favour of axes class identification using + :func:`matplotlib.projections.process_projection_requirements` followed by + direct axes class invocation (at the time of writing, this is done by + :meth:`matplotlib.figure.Figure.add_axes`, + :meth:`matplotlib.figure.Figure.add_subplot` and + :meth:`matplotlib.figure.Figure.gca`. + This change means that third party objects can expose themselves as + matplotlib axes by providing a ``_as_mpl_axes`` method (see + :ref:`adding-new-scales` for more detail). * Added new :class:`matplotlib.sankey.Sankey` for generating Sankey diagrams. diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 4ca557b97239..7b999a39cddf 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -63,40 +63,67 @@ def get_projection_class(projection=None): raise ValueError("Unknown projection '%s'" % projection) +def projection_factory(projection, figure, rect, **kwargs): + """ + Get a new projection instance. + + *projection* is a projection name. + + *figure* is a figure to add the axes to. + + *rect* is a :class:`~matplotlib.transforms.Bbox` object specifying + the location of the axes within the figure. + + Any other kwargs are passed along to the specific projection + constructor being used. + + .. deprecated:: + + This routine is deprecated in favour of getting the projection + class directly with :func:`get_projection_class` and initialising it + directly. Will be removed in version 1.3. + + """ + + return get_projection_class(projection)(figure, rect, **kwargs) + + def process_projection_requirements(figure, *args, **kwargs): - """ - Handle the args/kwargs to for add_axes/add_subplot/gca, - returning:: - - (axes_proj_class, proj_class_kwargs, proj_stack_key) - - Which can be used for new axes initialization/identification. - - """ - ispolar = kwargs.pop('polar', False) - projection = kwargs.pop('projection', None) - if ispolar: - if projection is not None and projection != 'polar': - raise ValueError( - "polar=True, yet projection=%r. " - "Only one of these arguments should be supplied." % - projection) - projection = 'polar' - - if isinstance(projection, basestring) or projection is None: - projection_class = get_projection_class(projection) - elif hasattr(projection, '_as_mpl_axes'): - projection_class, extra_kwargs = projection._as_mpl_axes() - kwargs.update(**extra_kwargs) - else: - raise TypeError('projection must be a string, None or implement a ' - '_as_mpl_axes method. Got %r' % projection) - - # Make the key without projection kwargs, this is used as a unique - # lookup for axes instances - key = figure._make_key(*args, **kwargs) + """ + Handle the args/kwargs to for add_axes/add_subplot/gca, + returning:: + + (axes_proj_class, proj_class_kwargs, proj_stack_key) - return projection_class, kwargs, key + Which can be used for new axes initialization/identification. + + .. note:: **kwargs** is modified in place. + + """ + ispolar = kwargs.pop('polar', False) + projection = kwargs.pop('projection', None) + if ispolar: + if projection is not None and projection != 'polar': + raise ValueError( + "polar=True, yet projection=%r. " + "Only one of these arguments should be supplied." % + projection) + projection = 'polar' + + if isinstance(projection, basestring) or projection is None: + projection_class = get_projection_class(projection) + elif hasattr(projection, '_as_mpl_axes'): + projection_class, extra_kwargs = projection._as_mpl_axes() + kwargs.update(**extra_kwargs) + else: + raise TypeError('projection must be a string, None or implement a ' + '_as_mpl_axes method. Got %r' % projection) + + # Make the key without projection kwargs, this is used as a unique + # lookup for axes instances + key = figure._make_key(*args, **kwargs) + + return projection_class, kwargs, key def get_projection_names(): From 597b6f00615c7108921267acd89a5ebf793ef302 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Fri, 16 Mar 2012 16:35:01 +0000 Subject: [PATCH 16/16] Changed version which these changes take effect. --- doc/api/api_changes.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index d0b74a2f0f64..2b118428fad4 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -11,7 +11,7 @@ help figure out possible sources of the changes you are experiencing. For new features that were added to matplotlib, please see :ref:`whats-new`. -Changes in 1.1.x +Changes in 1.2.x ================ * Use of :func:`matplotlib.projections.projection_factory` is now deprecated in favour of axes class identification using @@ -24,6 +24,10 @@ Changes in 1.1.x matplotlib axes by providing a ``_as_mpl_axes`` method (see :ref:`adding-new-scales` for more detail). + +Changes in 1.1.x +================ + * Added new :class:`matplotlib.sankey.Sankey` for generating Sankey diagrams. * In :meth:`~matplotlib.pyplot.imshow`, setting *interpolation* to 'nearest'