Revision: 7144
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=7144&view=rev
Author:   astraw
Date:     2009-05-27 16:25:15 +0000 (Wed, 27 May 2009)

Log Message:
-----------
Arbitrary spine placement

Modified Paths:
--------------
    trunk/matplotlib/CHANGELOG
    trunk/matplotlib/doc/api/api_changes.rst
    trunk/matplotlib/doc/users/whats_new.rst
    trunk/matplotlib/examples/api/custom_projection_example.py
    trunk/matplotlib/examples/tests/backend_driver.py
    trunk/matplotlib/lib/matplotlib/axes.py
    trunk/matplotlib/lib/matplotlib/axis.py
    trunk/matplotlib/lib/matplotlib/projections/geo.py
    trunk/matplotlib/lib/matplotlib/projections/polar.py

Added Paths:
-----------
    trunk/matplotlib/examples/pylab_examples/spine_placement_demo.py
    trunk/matplotlib/lib/matplotlib/spines.py

Modified: trunk/matplotlib/CHANGELOG
===================================================================
--- trunk/matplotlib/CHANGELOG  2009-05-27 16:24:53 UTC (rev 7143)
+++ trunk/matplotlib/CHANGELOG  2009-05-27 16:25:15 UTC (rev 7144)
@@ -1,3 +1,5 @@
+2009-05-26 Add support for "axis spines" to have arbitrary location. -ADS
+
 2009-05-20 Add an empty matplotlibrc to the tests/ directory so that running
            tests will use the default set of rcparams rather than the user's
            config. - RMM

Modified: trunk/matplotlib/doc/api/api_changes.rst
===================================================================
--- trunk/matplotlib/doc/api/api_changes.rst    2009-05-27 16:24:53 UTC (rev 
7143)
+++ trunk/matplotlib/doc/api/api_changes.rst    2009-05-27 16:25:15 UTC (rev 
7144)
@@ -20,6 +20,13 @@
 Changes beyond 0.98.x
 =====================
 
+* Axes instanaces no longer have a "frame" attribute. Instead, use the
+  new "spines" attribute. Spines is a dictionary where the keys are
+  the names of the spines (e.g. 'left','right' and so on) and the
+  values are the artists that draw the spines. For normal
+  (rectilinear) axes, these artists are Line2D instances. For other
+  axes (such as polar axes), these artists may be Patch instances.
+
 * Polar plots no longer accept a resolution kwarg.  Instead, each Path
   must specify its own number of interpolation steps.  This is
   unlikely to be a user-visible change -- if interpolation of data is

Modified: trunk/matplotlib/doc/users/whats_new.rst
===================================================================
--- trunk/matplotlib/doc/users/whats_new.rst    2009-05-27 16:24:53 UTC (rev 
7143)
+++ trunk/matplotlib/doc/users/whats_new.rst    2009-05-27 16:25:15 UTC (rev 
7144)
@@ -4,6 +4,18 @@
 What's new in matplotlib
 ***************************
 
+.. _whats-new-svn:
+
+What new in svn
+===============
+
+Axis spine placement
+--------------------
+
+Andrew Straw has added the ability to place "axis spines" -- the lines
+that denote the data limits -- in various arbitrary locations. See
+:class:`matplotlib.spines.Spine`.
+
 .. _whats-new-0-98-4:
 
 What new in 0.98.4

Modified: trunk/matplotlib/examples/api/custom_projection_example.py
===================================================================
--- trunk/matplotlib/examples/api/custom_projection_example.py  2009-05-27 
16:24:53 UTC (rev 7143)
+++ trunk/matplotlib/examples/api/custom_projection_example.py  2009-05-27 
16:25:15 UTC (rev 7144)
@@ -6,6 +6,8 @@
 from matplotlib.transforms import Affine2D, Affine2DBase, Bbox, \
     BboxTransformTo, IdentityTransform, Transform, TransformWrapper
 from matplotlib.projections import register_projection
+import matplotlib.spines as mspines
+import matplotlib.axis as maxis
 
 import numpy as np
 
@@ -32,6 +34,14 @@
         self.set_aspect(0.5, adjustable='box', anchor='C')
         self.cla()
 
+    def _init_axis(self):
+        self.xaxis = maxis.XAxis(self)
+        self.yaxis = maxis.YAxis(self)
+        # Do not register xaxis or yaxis with spines -- as done in
+        # Axes._init_axis() -- until HammerAxes.xaxis.cla() works.
+        # self.spines['hammer'].register_axis(self.yaxis)
+        self._update_transScale()
+
     def cla(self):
         """
         Override to set up some reasonable defaults.
@@ -163,11 +173,12 @@
             yaxis_text_base + \
             Affine2D().translate(8.0, 0.0)
 
-    def get_xaxis_transform(self):
+    def get_xaxis_transform(self,which=None):
         """
         Override this method to provide a transformation for the
         x-axis grid and ticks.
         """
+        assert which in ['tick1','tick2','grid']
         return self._xaxis_transform
 
     def get_xaxis_text1_transform(self, pixelPad):
@@ -188,11 +199,12 @@
         """
         return self._xaxis_text2_transform, 'top', 'center'
 
-    def get_yaxis_transform(self):
+    def get_yaxis_transform(self,which=None):
         """
         Override this method to provide a transformation for the
         y-axis grid and ticks.
         """
+        assert which in ['tick1','tick2','grid']
         return self._yaxis_transform
 
     def get_yaxis_text1_transform(self, pixelPad):
@@ -224,6 +236,9 @@
         """
         return Circle((0.5, 0.5), 0.5)
 
+    def _gen_axes_spines(self):
+        return {'hammer':mspines.Spine(self,'hammer',Circle((0.5, 0.5), 0.5))}
+
     # Prevent the user from applying scales to one or both of the
     # axes.  In this particular case, scaling the axes wouldn't make
     # sense, so we don't allow it.

Added: trunk/matplotlib/examples/pylab_examples/spine_placement_demo.py
===================================================================
--- trunk/matplotlib/examples/pylab_examples/spine_placement_demo.py            
                (rev 0)
+++ trunk/matplotlib/examples/pylab_examples/spine_placement_demo.py    
2009-05-27 16:25:15 UTC (rev 7144)
@@ -0,0 +1,116 @@
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib.pyplot import show
+
+fig = plt.figure()
+x = np.linspace(0,2*np.pi,100)
+y = 2*np.sin(x)
+ax = fig.add_subplot(1,2,1)
+ax.set_title('dropped spines')
+ax.plot(x,y)
+for loc, spine in ax.spines.iteritems():
+    if loc in ['left','bottom']:
+        spine.set_position(('outward',10)) # outward by 10 points
+    elif loc in ['right','top']:
+        spine.set_color('none') # don't draw spine
+    else:
+        raise ValueError('unknown spine location: %s'%loc)
+
+# turn off ticks where there is no spine
+ax.xaxis.set_ticks_position('bottom')
+ax.yaxis.set_ticks_position('left')
+
+ax = fig.add_subplot(1,2,2,sharex=ax)
+ax.plot(x,y)
+ax.set_title('normal spines')
+
+# ----------------------------------------------------
+
+fig = plt.figure()
+x = np.linspace(-np.pi,np.pi,100)
+y = 2*np.sin(x)
+
+ax = fig.add_subplot(2,2,1)
+ax.set_title('centered spines')
+ax.plot(x,y)
+ax.spines['left'].set_position('center')
+ax.spines['right'].set_color('none')
+ax.spines['bottom'].set_position('center')
+ax.spines['top'].set_color('none')
+ax.xaxis.set_ticks_position('bottom')
+ax.yaxis.set_ticks_position('left')
+
+ax = fig.add_subplot(2,2,2)
+ax.set_title('zeroed spines')
+ax.plot(x,y)
+ax.spines['left'].set_position('zero')
+ax.spines['right'].set_color('none')
+ax.spines['bottom'].set_position('zero')
+ax.spines['top'].set_color('none')
+ax.xaxis.set_ticks_position('bottom')
+ax.yaxis.set_ticks_position('left')
+
+ax = fig.add_subplot(2,2,3)
+ax.set_title('spines at axes (0.6, 0.1)')
+ax.plot(x,y)
+ax.spines['left'].set_position(('axes',0.6))
+ax.spines['right'].set_color('none')
+ax.spines['bottom'].set_position(('axes',0.1))
+ax.spines['top'].set_color('none')
+ax.xaxis.set_ticks_position('bottom')
+ax.yaxis.set_ticks_position('left')
+
+ax = fig.add_subplot(2,2,4)
+ax.set_title('spines at data (1,2)')
+ax.plot(x,y)
+ax.spines['left'].set_position(('data',1))
+ax.spines['right'].set_color('none')
+ax.spines['bottom'].set_position(('data',2))
+ax.spines['top'].set_color('none')
+ax.xaxis.set_ticks_position('bottom')
+ax.yaxis.set_ticks_position('left')
+
+# ----------------------------------------------------
+
+def adjust_spines(ax,spines):
+    for loc, spine in ax.spines.iteritems():
+        if loc in spines:
+            spine.set_position(('outward',10)) # outward by 10 points
+        else:
+            spine.set_color('none') # don't draw spine
+
+    # turn off ticks where there is no spine
+    if 'left' in spines:
+        ax.yaxis.set_ticks_position('left')
+    else:
+        # no yaxis ticks
+        ax.yaxis.set_ticks([])
+
+    if 'bottom' in spines:
+        ax.xaxis.set_ticks_position('bottom')
+    else:
+        # no xaxis ticks
+        ax.xaxis.set_ticks([])
+
+fig = plt.figure()
+
+x = np.linspace(0,2*np.pi,100)
+y = 2*np.sin(x)
+
+ax = fig.add_subplot(2,2,1)
+ax.plot(x,y)
+adjust_spines(ax,['left'])
+
+ax = fig.add_subplot(2,2,2)
+ax.plot(x,y)
+adjust_spines(ax,[])
+
+ax = fig.add_subplot(2,2,3)
+ax.plot(x,y)
+adjust_spines(ax,['left','bottom'])
+
+ax = fig.add_subplot(2,2,4)
+ax.plot(x,y)
+adjust_spines(ax,['bottom'])
+
+show()

Modified: trunk/matplotlib/examples/tests/backend_driver.py
===================================================================
--- trunk/matplotlib/examples/tests/backend_driver.py   2009-05-27 16:24:53 UTC 
(rev 7143)
+++ trunk/matplotlib/examples/tests/backend_driver.py   2009-05-27 16:25:15 UTC 
(rev 7144)
@@ -173,6 +173,7 @@
     'simple_plot.py',
     'simplification_clipping_test.py',
     'specgram_demo.py',
+    'spine_placement_demo.py',
     'spy_demos.py',
     'stem_plot.py',
     'step_demo.py',

Modified: trunk/matplotlib/lib/matplotlib/axes.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/axes.py     2009-05-27 16:24:53 UTC (rev 
7143)
+++ trunk/matplotlib/lib/matplotlib/axes.py     2009-05-27 16:25:15 UTC (rev 
7144)
@@ -21,6 +21,7 @@
 import matplotlib.lines as mlines
 import matplotlib.mlab as mlab
 import matplotlib.patches as mpatches
+import matplotlib.spines as mspines
 import matplotlib.quiver as mquiver
 import matplotlib.scale as mscale
 import matplotlib.table as mtable
@@ -526,6 +527,8 @@
 
         self.set_axes_locator(kwargs.get("axes_locator", None))
 
+        self.spines = self._gen_axes_spines()
+
         # this call may differ for non-sep axes, eg polar
         self._init_axis()
 
@@ -576,7 +579,11 @@
     def _init_axis(self):
         "move this out of __init__ because non-separable axes don't use it"
         self.xaxis = maxis.XAxis(self)
+        self.spines['bottom'].register_axis(self.xaxis)
+        self.spines['top'].register_axis(self.xaxis)
         self.yaxis = maxis.YAxis(self)
+        self.spines['left'].register_axis(self.yaxis)
+        self.spines['right'].register_axis(self.yaxis)
         self._update_transScale()
 
     def set_figure(self, fig):
@@ -634,7 +641,7 @@
         self._yaxis_transform = mtransforms.blended_transform_factory(
                 self.transAxes, self.transData)
 
-    def get_xaxis_transform(self):
+    def get_xaxis_transform(self,which=None):
         """
         Get the transformation used for drawing x-axis labels, ticks
         and gridlines.  The x-direction is in data coordinates and the
@@ -646,7 +653,16 @@
             overridden by new kinds of projections that may need to
             place axis elements in different locations.
         """
-        return self._xaxis_transform
+        if which=='grid':
+            return self._xaxis_transform
+        elif which=='tick1':
+            # for cartesian projection, this is bottom spine
+            return self.spines['bottom'].get_spine_transform()
+        elif which=='tick2':
+            # for cartesian projection, this is top spine
+            return self.spines['top'].get_spine_transform()
+        else:
+            raise ValueError('unknown value for which')
 
     def get_xaxis_text1_transform(self, pad_points):
         """
@@ -667,7 +683,7 @@
             overridden by new kinds of projections that may need to
             place axis elements in different locations.
         """
-        return (self._xaxis_transform +
+        return (self.get_xaxis_transform(which='tick1') +
                 mtransforms.ScaledTranslation(0, -1 * pad_points / 72.0,
                                               self.figure.dpi_scale_trans),
                 "top", "center")
@@ -691,12 +707,12 @@
             overridden by new kinds of projections that may need to
             place axis elements in different locations.
         """
-        return (self._xaxis_transform +
+        return (self.get_xaxis_transform(which='tick2') +
                 mtransforms.ScaledTranslation(0, pad_points / 72.0,
                                               self.figure.dpi_scale_trans),
                 "bottom", "center")
 
-    def get_yaxis_transform(self):
+    def get_yaxis_transform(self,which=None):
         """
         Get the transformation used for drawing y-axis labels, ticks
         and gridlines.  The x-direction is in axis coordinates and the
@@ -708,7 +724,16 @@
             overridden by new kinds of projections that may need to
             place axis elements in different locations.
         """
-        return self._yaxis_transform
+        if which=='grid':
+            return self._yaxis_transform
+        elif which=='tick1':
+            # for cartesian projection, this is bottom spine
+            return self.spines['left'].get_spine_transform()
+        elif which=='tick2':
+            # for cartesian projection, this is top spine
+            return self.spines['right'].get_spine_transform()
+        else:
+            raise ValueError('unknown value for which')
 
     def get_yaxis_text1_transform(self, pad_points):
         """
@@ -729,7 +754,7 @@
             overridden by new kinds of projections that may need to
             place axis elements in different locations.
         """
-        return (self._yaxis_transform +
+        return (self.get_yaxis_transform(which='tick1') +
                 mtransforms.ScaledTranslation(-1 * pad_points / 72.0, 0,
                                                self.figure.dpi_scale_trans),
                 "center", "right")
@@ -754,7 +779,7 @@
             overridden by new kinds of projections that may need to
             place axis elements in different locations.
         """
-        return (self._yaxis_transform +
+        return (self.get_yaxis_transform(which='tick2') +
                 mtransforms.ScaledTranslation(pad_points / 72.0, 0,
                                                self.figure.dpi_scale_trans),
                 "center", "left")
@@ -853,6 +878,29 @@
         """
         return mpatches.Rectangle((0.0, 0.0), 1.0, 1.0)
 
+    def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'):
+        """
+        Returns a dict whose keys are spine names and values are
+        Line2D or Patch instances. Each element is used to draw a
+        spine of the axes.
+
+        In the standard axes, this is a single line segment, but in
+        other projections it may not be.
+
+        .. note::
+            Intended to be overridden by new projection types.
+        """
+        return {
+            'left':mspines.Spine(self,'left',
+                                 mlines.Line2D((0.0, 0.0), (0.0, 1.0))),
+            'right':mspines.Spine(self,'right',
+                                  mlines.Line2D((1.0, 1.0), (0.0, 1.0))),
+            'bottom':mspines.Spine(self,'bottom',
+                                   mlines.Line2D((0.0, 1.0), (0.0, 0.0))),
+            'top':mspines.Spine(self,'top',
+                                mlines.Line2D((0.0, 1.0), (1.0, 1.0))),
+            }
+
     def cla(self):
         'Clear the current axes'
         # Note: this is called by Axes.__init__()
@@ -928,17 +976,6 @@
         self.patch.set_linewidth(0)
         self.patch.set_transform(self.transAxes)
 
-        # the frame draws the border around the axes and we want this
-        # above.  this is a place holder for a more sophisticated
-        # artist that might just draw a left, bottom frame, or a
-        # centered frame, etc the axesFrame name is deprecated
-        self.frame = self.axesFrame = self._gen_axes_patch()
-        self.frame.set_figure(self.figure)
-        self.frame.set_facecolor('none')
-        self.frame.set_edgecolor(rcParams['axes.edgecolor'])
-        self.frame.set_linewidth(rcParams['axes.linewidth'])
-        self.frame.set_transform(self.transAxes)
-        self.frame.set_zorder(2.5)
         self.axison = True
 
         self.xaxis.set_clip_path(self.patch)
@@ -947,6 +984,10 @@
         self._shared_x_axes.clean()
         self._shared_y_axes.clean()
 
+    def get_frame(self):
+        raise AttributeError('Axes.frame was removed in favor of Axes.spines')
+    frame = property(get_frame)
+
     def clear(self):
         'clear the axes'
         self.cla()
@@ -1724,7 +1765,7 @@
         # decouple these so the patch can be in the background and the
         # frame in the foreground.
         if self.axison and self._frameon:
-            artists.append(self.frame)
+            artists.extend(self.spines.itervalues())
 
 
         dsu = [ (a.zorder, i, a) for i, a in enumerate(artists)
@@ -2645,7 +2686,7 @@
         children.extend(self.collections)
         children.append(self.title)
         children.append(self.patch)
-        children.append(self.frame)
+        children.extend(self.spines.itervalues())
         return children
 
     def contains(self,mouseevent):

Modified: trunk/matplotlib/lib/matplotlib/axis.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/axis.py     2009-05-27 16:24:53 UTC (rev 
7143)
+++ trunk/matplotlib/lib/matplotlib/axis.py     2009-05-27 16:25:15 UTC (rev 
7144)
@@ -282,7 +282,7 @@
                    marker = self._xtickmarkers[0],
                    markersize=self._size,
                    )
-        l.set_transform(self.axes.get_xaxis_transform())
+        l.set_transform(self.axes.get_xaxis_transform(which='tick1'))
         self._set_artist_props(l)
         return l
 
@@ -296,7 +296,7 @@
                        markersize=self._size,
                        )
 
-        l.set_transform(self.axes.get_xaxis_transform())
+        l.set_transform(self.axes.get_xaxis_transform(which='tick2'))
         self._set_artist_props(l)
         return l
 
@@ -308,7 +308,7 @@
                    linestyle=rcParams['grid.linestyle'],
                    linewidth=rcParams['grid.linewidth'],
                    )
-        l.set_transform(self.axes.get_xaxis_transform())
+        l.set_transform(self.axes.get_xaxis_transform(which='grid'))
         l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
         self._set_artist_props(l)
 
@@ -412,7 +412,7 @@
                     linestyle = 'None',
                     markersize=self._size,
                        )
-        l.set_transform(self.axes.get_yaxis_transform())
+        l.set_transform(self.axes.get_yaxis_transform(which='tick1'))
         self._set_artist_props(l)
         return l
 
@@ -425,7 +425,7 @@
                     markersize=self._size,
                     )
 
-        l.set_transform(self.axes.get_yaxis_transform())
+        l.set_transform(self.axes.get_yaxis_transform(which='tick2'))
         self._set_artist_props(l)
         return l
 
@@ -438,7 +438,7 @@
                     linewidth=rcParams['grid.linewidth'],
                     )
 
-        l.set_transform(self.axes.get_yaxis_transform())
+        l.set_transform(self.axes.get_yaxis_transform(which='grid'))
         l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
         self._set_artist_props(l)
         return l

Modified: trunk/matplotlib/lib/matplotlib/projections/geo.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/projections/geo.py  2009-05-27 16:24:53 UTC 
(rev 7143)
+++ trunk/matplotlib/lib/matplotlib/projections/geo.py  2009-05-27 16:25:15 UTC 
(rev 7144)
@@ -10,6 +10,8 @@
 from matplotlib import cbook
 from matplotlib.patches import Circle
 from matplotlib.path import Path
+import matplotlib.spines as mspines
+import matplotlib.axis as maxis
 from matplotlib.ticker import Formatter, Locator, NullLocator, FixedLocator, 
NullFormatter
 from matplotlib.transforms import Affine2D, Affine2DBase, Bbox, \
     BboxTransformTo, IdentityTransform, Transform, TransformWrapper
@@ -36,6 +38,14 @@
 
     RESOLUTION = 75
 
+    def _init_axis(self):
+        self.xaxis = maxis.XAxis(self)
+        self.yaxis = maxis.YAxis(self)
+        # Do not register xaxis or yaxis with spines -- as done in
+        # Axes._init_axis() -- until GeoAxes.xaxis.cla() works.
+        # self.spines['geo'].register_axis(self.yaxis)
+        self._update_transScale()
+
     def cla(self):
         Axes.cla(self)
 
@@ -111,7 +121,8 @@
             .scale(0.5 / xscale, 0.5 / yscale) \
             .translate(0.5, 0.5)
 
-    def get_xaxis_transform(self):
+    def get_xaxis_transform(self,which=None):
+        assert which in ['tick1','tick2','grid']
         return self._xaxis_transform
 
     def get_xaxis_text1_transform(self, pad):
@@ -120,7 +131,8 @@
     def get_xaxis_text2_transform(self, pad):
         return self._xaxis_text2_transform, 'top', 'center'
 
-    def get_yaxis_transform(self):
+    def get_yaxis_transform(self,which=None):
+        assert which in ['tick1','tick2','grid']
         return self._yaxis_transform
 
     def get_yaxis_text1_transform(self, pad):
@@ -132,6 +144,9 @@
     def _gen_axes_patch(self):
         return Circle((0.5, 0.5), 0.5)
 
+    def _gen_axes_spines(self):
+        return {'geo':mspines.Spine(self,'geo',Circle((0.5, 0.5), 0.5))}
+
     def set_yscale(self, *args, **kwargs):
         if args[0] != 'linear':
             raise NotImplementedError

Modified: trunk/matplotlib/lib/matplotlib/projections/polar.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/projections/polar.py        2009-05-27 
16:24:53 UTC (rev 7143)
+++ trunk/matplotlib/lib/matplotlib/projections/polar.py        2009-05-27 
16:25:15 UTC (rev 7144)
@@ -7,12 +7,14 @@
 rcParams = matplotlib.rcParams
 from matplotlib.artist import kwdocd
 from matplotlib.axes import Axes
+import matplotlib.axis as maxis
 from matplotlib import cbook
 from matplotlib.patches import Circle
 from matplotlib.path import Path
 from matplotlib.ticker import Formatter, Locator
 from matplotlib.transforms import Affine2D, Affine2DBase, Bbox, \
     BboxTransformTo, IdentityTransform, Transform, TransformWrapper
+import matplotlib.spines as mspines
 
 class PolarAxes(Axes):
     """
@@ -202,6 +204,16 @@
         self.xaxis.set_ticks_position('none')
         self.yaxis.set_ticks_position('none')
 
+    def _init_axis(self):
+        "move this out of __init__ because non-separable axes don't use it"
+        self.xaxis = maxis.XAxis(self)
+        self.yaxis = maxis.YAxis(self)
+        # Calling polar_axes.xaxis.cla() or polar_axes.xaxis.cla()
+        # results in weird artifacts. Therefore we disable this for
+        # now.
+        # self.spines['polar'].register_axis(self.yaxis)
+        self._update_transScale()
+
     def _set_lim_and_transforms(self):
         self.transAxes = BboxTransformTo(self.bbox)
 
@@ -258,7 +270,8 @@
             self._yaxis_transform
             )
 
-    def get_xaxis_transform(self):
+    def get_xaxis_transform(self,which=None):
+        assert which in ['tick1','tick2','grid']
         return self._xaxis_transform
 
     def get_xaxis_text1_transform(self, pad):
@@ -267,7 +280,8 @@
     def get_xaxis_text2_transform(self, pad):
         return self._xaxis_text2_transform, 'center', 'center'
 
-    def get_yaxis_transform(self):
+    def get_yaxis_transform(self,which=None):
+        assert which in ['tick1','tick2','grid']
         return self._yaxis_transform
 
     def get_yaxis_text1_transform(self, pad):
@@ -279,6 +293,9 @@
     def _gen_axes_patch(self):
         return Circle((0.5, 0.5), 0.5)
 
+    def _gen_axes_spines(self):
+        return {'polar':mspines.Spine(self,'polar',Circle((0.5, 0.5), 0.5))}
+
     def set_rmax(self, rmax):
         self.viewLim.y0 = 0
         self.viewLim.y1 = rmax

Added: trunk/matplotlib/lib/matplotlib/spines.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/spines.py                           (rev 0)
+++ trunk/matplotlib/lib/matplotlib/spines.py   2009-05-27 16:25:15 UTC (rev 
7144)
@@ -0,0 +1,232 @@
+from __future__ import division
+
+import matplotlib
+rcParams = matplotlib.rcParams
+
+import matplotlib.artist as martist
+from matplotlib.artist import allow_rasterization
+import matplotlib.transforms as mtransforms
+import matplotlib.lines as mlines
+import matplotlib.patches as mpatches
+import warnings
+
+class Spine(martist.Artist):
+    """an axis spine -- the line noting the data area boundaries
+
+    Spines are the lines connecting the axis tick marks and noting the
+    boundaries of the data area. They can be placed at arbitrary
+    positions. See function:`~matplotlib.spines.Spine.set_position`
+    for more information.
+
+    The default position is ``('outward',0)``.
+    """
+    def __str__(self):
+        return "Spine"
+
+    def __init__(self,axes,spine_type,artist):
+        """
+        - *axes* : the Axes instance containing the spine
+        - *spine_type* : a string specifying the spine type
+        - *artist* : the artist instance used to draw the spine
+        """
+        martist.Artist.__init__(self)
+        self.axes = axes
+        self.set_figure(self.axes.figure)
+        self.spine_type = spine_type
+        self.artist = artist
+        self.color = rcParams['axes.edgecolor']
+        self.axis = None
+
+        if isinstance(self.artist,mlines.Line2D):
+            self.artist.set_color(self.color)
+            self.artist.set_linewidth(rcParams['axes.linewidth'])
+        elif isinstance(self.artist,mpatches.Patch):
+            self.artist.set_facecolor('none')
+            self.artist.set_edgecolor(self.color)
+            self.artist.set_linewidth(rcParams['axes.linewidth'])
+        self.artist.set_zorder(2.5)
+        self.artist.set_transform(self.axes.transAxes) # default transform
+
+        # Defer initial position determination. (Not much support for
+        # non-rectangular axes is currently implemented, and this lets
+        # them pass through the spines machinery without errors.)
+        self._position = None
+
+    def _ensure_position_is_set(self):
+        if self._position is None:
+            # default position
+            self._position = ('outward',0.0) # in points
+            self.set_position(self._position)
+
+    def register_axis(self,axis):
+        """register an axis
+
+        An axis should be registered with its corresponding spine from
+        the Axes instance. This allows the spine to clear any axis
+        properties when needed.
+        """
+        self.axis = axis
+        if self.axis is not None:
+            self.axis.cla()
+
+    @allow_rasterization
+    def draw(self,renderer):
+        "draw everything that belongs to the spine"
+        if self.color=='none':
+            # don't draw invisible spines
+            return
+        self.artist.draw(renderer)
+
+    def _calc_offset_transform(self):
+        """calculate the offset transform performed by the spine"""
+        self._ensure_position_is_set()
+        position = self._position
+        if isinstance(position,basestring):
+            if position=='center':
+                position = ('axes',0.5)
+            elif position=='zero':
+                position = ('data',0)
+        assert len(position)==2, "position should be 2-tuple"
+        position_type, amount = position
+        assert position_type in ('axes','outward','data')
+        if position_type=='outward':
+            if amount == 0:
+                # short circuit commonest case
+                self._spine_transform =  
('identity',mtransforms.IdentityTransform())
+            elif self.spine_type in ['left','right','top','bottom']:
+                offset_vec = {'left':(-1,0),
+                              'right':(1,0),
+                              'bottom':(0,-1),
+                              'top':(0,1),
+                              }[self.spine_type]
+                # calculate x and y offset in dots
+                offset_x = amount*offset_vec[0]/ 72.0
+                offset_y = amount*offset_vec[1]/ 72.0
+                self._spine_transform = ('post',
+                                         
mtransforms.ScaledTranslation(offset_x,offset_y,
+                                                                       
self.figure.dpi_scale_trans))
+            else:
+                warnings.warn('unknown spine type "%s": no spine '
+                              'offset performed'%self.spine_type)
+                self._spine_transform = 
('identity',mtransforms.IdentityTransform())
+        elif position_type=='axes':
+            if self.spine_type in ('left','right'):
+                self._spine_transform = ('pre',
+                                         
mtransforms.Affine2D().translate(amount, 0.0))
+            elif self.spine_type in  ('bottom','top'):
+                self._spine_transform = ('pre',
+                                         mtransforms.Affine2D().translate(0.0, 
amount))
+            else:
+                warnings.warn('unknown spine type "%s": no spine '
+                              'offset performed'%self.spine_type)
+                self._spine_transform = 
('identity',mtransforms.IdentityTransform())
+        elif position_type=='data':
+            if self.spine_type in ('left','right'):
+                self._spine_transform = ('data',
+                                         
mtransforms.Affine2D().translate(amount,0))
+            elif self.spine_type in ('bottom','top'):
+                self._spine_transform = ('data',
+                                         
mtransforms.Affine2D().translate(0,amount))
+            else:
+                warnings.warn('unknown spine type "%s": no spine '
+                              'offset performed'%self.spine_type)
+                self._spine_transform =  
('identity',mtransforms.IdentityTransform())
+
+    def set_position(self,position):
+        """set the position of the spine
+
+        Spine position is specified by a 2 tuple of (position type,
+        amount). The position types are:
+
+        * 'outward' : place the spine out from the data area by the
+          specified number of points. (Negative values specify placing the
+          spine inward.)
+
+        * 'axes' : place the spine at the specified Axes coordinate (from
+          0.0-1.0).
+
+        * 'data' : place the spine at the specified data coordinate.
+
+        Additionally, shorthand notations define a special positions:
+
+        * 'center' -> ('axes',0.5)
+        * 'zero' -> ('data', 0.0)
+
+        """
+        if position in ('center','zero'):
+            # special positions
+            pass
+        else:
+            assert len(position)==2, "position should be 'center' or 2-tuple"
+            assert position[0] in ['outward','axes','data']
+        self._position = position
+        self._calc_offset_transform()
+
+        t = self.get_spine_transform()
+        if self.spine_type in ['left','right']:
+            t2 = mtransforms.blended_transform_factory(t,
+                                                       self.axes.transAxes)
+        elif self.spine_type in ['bottom','top']:
+            t2 = mtransforms.blended_transform_factory(self.axes.transAxes,
+                                                       t)
+        self.artist.set_transform(t2)
+
+        if self.axis is not None:
+            self.axis.cla()
+
+    def get_position(self):
+        """get the spine position"""
+        self._ensure_position_is_set()
+        return self._position
+
+    def get_spine_transform(self):
+        """get the spine transform"""
+        self._ensure_position_is_set()
+        what, how = self._spine_transform
+
+        if what == 'data':
+            # special case data based spine locations
+            if self.spine_type in ['left','right']:
+                data_xform = self.axes.transScale + \
+                             (how+self.axes.transLimits + self.axes.transAxes)
+                result = mtransforms.blended_transform_factory(
+                    data_xform,self.axes.transData)
+            elif self.spine_type in ['top','bottom']:
+                data_xform = self.axes.transScale + \
+                             (how+self.axes.transLimits + self.axes.transAxes)
+                result = mtransforms.blended_transform_factory(
+                    self.axes.transData,data_xform)
+            else:
+                raise ValueError('unknown spine spine_type: 
%s'%self.spine_type)
+            return result
+
+        if self.spine_type in ['left','right']:
+            base_transform = self.axes.get_yaxis_transform(which='grid')
+        elif self.spine_type in ['top','bottom']:
+            base_transform = self.axes.get_xaxis_transform(which='grid')
+        else:
+            raise ValueError('unknown spine spine_type: %s'%self.spine_type)
+
+        if what=='identity':
+            return base_transform
+        elif what=='post':
+            return base_transform+how
+        elif what=='pre':
+            return how+base_transform
+        else:
+            raise ValueError("unknown spine_transform type: %s"%what)
+
+    def set_color(self,value):
+        """set the color of the spine artist
+
+        Note: a value of 'none' will cause the artist not to be drawn.
+        """
+        self.color = value
+        if isinstance(self.artist,mlines.Line2D):
+            self.artist.set_color(self.color)
+        elif isinstance(self.artist,mpatches.Patch):
+            self.artist.set_edgecolor(self.color)
+
+    def get_color(self):
+        """get the color of the spine artist"""
+        return self.color


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------
Register Now for Creativity and Technology (CaT), June 3rd, NYC. CaT 
is a gathering of tech-side developers & brand creativity professionals. Meet
the minds behind Google Creative Lab, Visual Complexity, Processing, & 
iPhoneDevCamp as they present alongside digital heavyweights like Barbarian 
Group, R/GA, & Big Spaceship. http://p.sf.net/sfu/creativitycat-com 
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to