On Tue, Nov 18, 2008 at 9:20 AM, John Hunter <[EMAIL PROTECTED]> wrote:
>
>
> There are two related problems here: one easier, one harder.  We can
> start with the easier one.  The easy one is to have a "detachable
> axis".  Right now we have one on the right and one on the left for the
> x-axis.  We may as well generalize this to allow folks to have as many
> as they want and put them whereever they want (center, offset farther
> to the left, etc.).  It would be nice to support a different set of
> tick locators and formatters for each axis, eg you could have a left
> axis in dollars and a right axis in euros.  Note this is not the same
> thing as having a different scale, in the sense of the two_scales
> example, this is just two different formatting of the same scale.  The
> harder problem is supporting an arbitrary number of axis instances
> each with its own transform, etc.  Eg generalizing the two_scales
> example.  I suggest you punt on that one for now.
>

I spent some time on this a while ago. And I was able to partially
implement what John described above. I'm attaching my code, but note
that it is more like a proof of the concept, than a practical code.
What it can do now is to place an axis at some arbitrary position (can
be either an axes coordinate or a data coordinate).

In brief, the code is consisted of GridHelper and AxisLine. My
intention was an Axes object has a single GridHelper instance and
multiple AxisLine instances.
I wanted to make some framework which can work with any arbitrary
transformation (for example, see
http://www.atnf.csiro.au/people/mcalabre/WCS/PGSBOX/index.html), and
this affected some of my design. I only had a partial success on this,
and then swamped by other things.

So, I'm also willing to help Mike on this. And even though Mike start
with easy things, I wish the design is flexible enough to be extended
to support an arbitrary transformation  in a future.

-JJ
import matplotlib.axes as maxes
import matplotlib.artist as martist
import matplotlib.text as mtext
import matplotlib.font_manager as font_manager

from matplotlib.path import Path
from matplotlib.transforms import Affine2D
import matplotlib.transforms as mtransforms

from matplotlib.pyplot import rcParams

#from simple_path import BezierPath
from matplotlib.lines import Line2D
import numpy as np

class GridHelperRectlinear:

    class FixedAxisLineHelper:

        def __init__(self, axes,
                     nth_coord, passingthrough_point):
            """ nth_coord = along which coordinate value varies
            in 2d, nth_coord = 0 ->  x axis, nth_coord = 1 -> y axis
            """
            self.axes = axes
            self.nth_coord = nth_coord
            self.axis = [self.axes.xaxis, self.axes.yaxis][self.nth_coord]
            
            self.passingthrough_point = passingthrough_point
            
            #self.trans_tick = [self.axes.transData] + \
            #                  self.axes.transAxes.inverted()
            
            _verts = np.array([[0., 0.],
                               [1., 1.]])
            fixed_coord = 1-nth_coord
            _verts[:,fixed_coord] = self.passingthrough_point[fixed_coord]

            self._path = Path(_verts)

        def get_nth_coord(self):
            return self.nth_coord
        
        def get_path(self):
            return self._path
            
        def iter_ticks(self):
            """tick_loc, tick_angle, tick_label"""

            angle = 0 - 90 * self.nth_coord
            if self.passingthrough_point[1 - self.nth_coord] > 0.5:
                angle = 180+angle
            
            majorLocs = self.axis.major.locator()
            self.axis.major.formatter.set_locs(majorLocs)
            majorLabels = [self.axis.major.formatter(val, i) for i, val in enumerate(majorLocs)]

            trans_tick = [self.axes.get_xaxis_transform(),
                          self.axes.get_yaxis_transform()][self.nth_coord] + \
                          self.axes.transAxes.inverted()

            for x, l in zip(majorLocs, majorLabels):

                #p = trans_tick.transform_point([x,x])
                #xcoord_axes = list(self.passingthrough_point) # copy
                #xcoord_axes[self.nth_coord] = p[self.nth_coord]

                #p = trans_tick.transform_point([x,x])
                c = list(self.passingthrough_point) # copy
                c[self.nth_coord] = x
                xcoord_axes = trans_tick.transform_point(c)
                #xcoord_axes[self.nth_coord] = p[self.nth_coord]

                yield xcoord_axes, angle, l



    class FloatingAxisLineHelper:
        def __init__(self, axes,
                     nth_coord, passingthrough_point, transform):

            self.axes = axes
            self.nth_coord = nth_coord
            self.axis = [self.axes.xaxis, self.axes.yaxis][self.nth_coord]
            
            self.passingthrough_point = passingthrough_point
            #self.trans_tick = self.axes.transData + self.axes.transAxes.inverted()
            
            self.transform = transform
            
        def get_nth_coord(self):
            return self.nth_coord

        def get_path(self):
            _verts = np.array([[0., 0.],
                               [1., 1.]])

            fixed_coord = 1-self.nth_coord
            trans_passingthrough_point = self.transform + self.axes.transAxes.inverted()
            p = trans_passingthrough_point.transform_point(self.passingthrough_point)
            _verts[:,fixed_coord] = p[fixed_coord]

            return Path(_verts)

            
        def iter_ticks(self):
            """tick_loc, tick_angle, tick_label"""

            angle = 0 - 90 * self.nth_coord
            #angle = 0
            
            majorLocs = self.axis.major.locator()
            self.axis.major.formatter.set_locs(majorLocs)
            majorLabels = [self.axis.major.formatter(val, i) for i, val in enumerate(majorLocs)]
            
            trans_passingthrough_point = self.transform + self.axes.transAxes.inverted()
            xcoord_axes0 = trans_passingthrough_point.transform_point(self.passingthrough_point) 

            trans_tick = [self.axes.get_xaxis_transform(),
                          self.axes.get_yaxis_transform()][self.nth_coord] + \
                          self.axes.transAxes.inverted()

            for x, l in zip(majorLocs, majorLabels):

                c = list(xcoord_axes0) # copy
                c[self.nth_coord] = x
                xcoord_axes = trans_tick.transform_point(c)

                
                yield xcoord_axes, angle, l


        
    def __init__(self, axes):
        self.axes = axes
        #self.set_major_locator(mticker.AutoLocator())
        
    def get_axisline_helper(self, nth_coord,
                            passingthrough_point, transform=None):
        if transform is None or transform is self.axes.transAxes:
            return self.FixedAxisLineHelper(self.axes,
                                            nth_coord, passingthrough_point)

        else:
            return self.FloatingAxisLineHelper(self.axes,
                                               nth_coord, passingthrough_point,
                                               transform)




class XYEvent:
    def __init__(self, xy):
        self.x, self.y = xy

class AxisGridLineBase(martist.Artist):
    def __init__(self, *kl, **kw):
        martist.Artist.__init__(self, *kl, **kw)
                                

class LabelTransformHelper:
    def __init__(self, *kl, **kw):
        pass
    
    def get_label_transform(self, angle, pad_points):

        if self._helper.get_nth_coord() == 0: # xaxis
            if (angle % 360.) < 90. or 270. < (angle % 360.):
                trans = self.axes.transAxes + \
                        mtransforms.ScaledTranslation(0, -pad_points / 72., self.figure.dpi_scale_trans)
                return trans, "top", "center"
            else:
                trans = self.axes.transAxes + \
                        mtransforms.ScaledTranslation(0, +pad_points / 72., self.figure.dpi_scale_trans)
                return trans, "bottom", "center"
        else: # yaxis
            trans = self.axes.transAxes + \
                    mtransforms.ScaledTranslation(-pad_points / 72., 0, self.figure.dpi_scale_trans)
            return trans, "center", "right"
            
        


class AxisLine(AxisGridLineBase, LabelTransformHelper):
    """ a line along which the n-th axes coord is constant."""


    def __init__(self, axes,
                 _helper,
                 tick_direction="in",
                 label_direction="none", **kw):

        AxisGridLineBase.__init__(self, **kw)
        LabelTransformHelper.__init__(self, **kw)


        self.axes = axes
        self.figure = axes.figure
        
        self._helper = _helper
        #self.line = BezierPath(self._helper.get_path(),
        #                       color="k",
        #                       transform=axes.transAxes)
        x, y = self._helper.get_path().vertices
        self.line = Line2D(x, y, 
                           color="k",
                           transform=axes.transAxes)

        self._markersize = 3
        self._pad = 3

        self.tick_direction = tick_direction

    _tickvert_path = Path([[-0.0, 0.0], [-0.0, 1.0]])
    def _draw_tickup(self, renderer, gc, angle, loc, path_trans):
        offset = renderer.points_to_pixels(self._markersize)
        marker_scale = Affine2D().scale(offset, offset)
        marker_rotation = Affine2D().rotate_deg(angle)
        #marker_translation = Affine2D().translate(10, 20)
        marker_transform = marker_scale + marker_rotation
        renderer.draw_markers(gc, self._tickvert_path, marker_transform,
                              Path([loc]), path_trans)



    
    def _draw_label(self, renderer, gc, angle, loc, label):
        trans, vert, horiz = self.get_label_transform(angle, self._pad)
        size = rcParams['xtick.labelsize']
        t = mtext.Text(
            x=loc[0], y=loc[1],
            #x=0.5, y=0.5, text="123",
            text=label, 
            fontproperties=font_manager.FontProperties(size=size),
            color=rcParams['xtick.color'],
            verticalalignment=vert,
            horizontalalignment=horiz,
            )

        t.set_transform(trans)
        self.axes._set_artist_props(t)

        t.draw(renderer)
        


    def _draw_ticks(self, renderer):

        gc = renderer.new_gc()
        #self._set_gc_clip(gc)

        #gc.set_foreground(self._color)
        #gc.set_antialiased(self._antialiased)
        #gc.set_linewidth(self._linewidth)
        #gc.set_alpha(self._alpha)
        #cap = self._solidcapstyle
        #join = self._solidjoinstyle
        #gc.set_joinstyle(join)
        #gc.set_capstyle(cap)



        tr = self.axes.transAxes
        
        for tick_loc, tick_angle, tick_label in self._helper.iter_ticks():

            # check if tick is inside the patch
            tick_loc_figure = tr.transform_point(tick_loc)

            # contains method returns (boolean, dict)
            if self.axes.patch.contains(XYEvent(tick_loc_figure))[0]:
                self._draw_tickup(renderer, gc, tick_angle,
                                  tick_loc, self.axes.transAxes)
                self._draw_label(renderer, gc, tick_angle,
                                 tick_loc, tick_label)
                

    def _draw_line(self, renderer):
        #self.line.set_path(self._helper.get_path())
        xy = self._helper.get_path().vertices
        self.line.set_data(xy.transpose())
        self.line.draw(renderer)
        
    def draw(self, renderer):
        'Draw the axis lines, tick lines and labels'
        
        if not self.get_visible(): return

        renderer.open_group(__name__)

        self._draw_line(renderer)
        self._draw_ticks(renderer)

        renderer.close_group(__name__)

        
        
class GridLine(AxisGridLineBase):
    """ a line along which the n-th data coord is constant."""
    pass




class Axes(maxes.Axes):
    def __init__(self, *kl, **kw):

        maxes.Axes.__init__(self, *kl, **kw)

        self.frame.set_visible(False)
        self.xaxis.set_visible(False)
        self.yaxis.set_visible(False)

        self._grid_helper = GridHelperRectlinear(self)
        
        self.xaxis_bottom = self._get_axisline(nth_coord=0,
                                               passthrough_point=(0.,0.0001),
                                               tick_direction="in",
                                               label_direction="none")

        self.xaxis_top = self._get_axisline(nth_coord=0,
                                            passthrough_point=(0.,0.9999),
                                            tick_direction="in",
                                            label_direction="none")

        self.xaxis_zero = self._get_axisline(nth_coord=0,
                                              passthrough_point=(0.,0.),
                                              transform=self.transData,
                                              tick_direction="in",
                                              label_direction="none")

        self.xaxis_zero.line.set_clip_path(self.patch)
        #self.xaxis_zero.set_visible(False)

        self.yaxis_zero = self._get_axisline(nth_coord=1,
                                             passthrough_point=(0.,0.),
                                             transform=self.transData,
                                             tick_direction="in",
                                             label_direction="none")

        self.yaxis_zero.line.set_clip_path(self.patch)
        #self.yaxis_zero.set_visible(False)

    def _get_axisline(self, nth_coord,
                      passthrough_point,
                      transform=None,
                      tick_direction="in",
                      label_direction="none"):

        _helper = self._grid_helper.get_axisline_helper(nth_coord,
                                                        passthrough_point,
                                                        transform)

        axisline = AxisLine(self, _helper,
                            tick_direction="in",
                            label_direction="none")

        
        return axisline
    

    def draw(self, renderer):
        maxes.Axes.draw(self, renderer)

        self.xaxis_bottom.draw(renderer)
        self.xaxis_top.draw(renderer)
        self.xaxis_zero.draw(renderer)
        self.yaxis_zero.draw(renderer)

        

Subplot = maxes.subplot_class_factory(Axes)

if __name__ == "__main__":
    fig = figure(1)
    fig.clf()
    ax = Subplot(fig, 1, 1, 1)
    fig.add_subplot(ax)
    #ax.xaxis_zero.set_visible(True)
    ax.set_ylim(-2, 4)
    ax.set_xlim(-2.5, 3)

    draw()
    
    
    
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

Reply via email to