From: <as...@us...> - 2009-05-27 16:25:21
|
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. |