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