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