Revision: 8009 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=8009&view=rev Author: leejjoon Date: 2009-12-07 01:16:44 +0000 (Mon, 07 Dec 2009)
Log Message: ----------- support FloatingAxes Added Paths: ----------- trunk/matplotlib/lib/mpl_toolkits/axes_grid/floating_axes.py Added: trunk/matplotlib/lib/mpl_toolkits/axes_grid/floating_axes.py =================================================================== --- trunk/matplotlib/lib/mpl_toolkits/axes_grid/floating_axes.py (rev 0) +++ trunk/matplotlib/lib/mpl_toolkits/axes_grid/floating_axes.py 2009-12-07 01:16:44 UTC (rev 8009) @@ -0,0 +1,691 @@ +""" +An experimental support for curvelinear grid. +""" + + +# TODO : + +# *. see if tick_iterator method can be simplified by reusing the parent method. + +from itertools import chain +from mpl_toolkits.axes_grid.grid_finder import GridFinder + +from mpl_toolkits.axes_grid.axislines import AxisArtistHelper, GridHelperBase +from mpl_toolkits.axes_grid.axis_artist import AxisArtist +from matplotlib.transforms import Affine2D +import numpy as np + + +import mpl_toolkits.axes_grid.grid_helper_curvelinear as grid_helper_curvelinear + +class FloatingAxisArtistHelper(grid_helper_curvelinear.FloatingAxisArtistHelper): + pass + +class FixedAxisArtistHelper(grid_helper_curvelinear.FloatingAxisArtistHelper): + + + def __init__(self, grid_helper, side, nth_coord_ticks=None): + """ + nth_coord = along which coordinate value varies. + nth_coord = 0 -> x axis, nth_coord = 1 -> y axis + """ + + value, nth_coord = grid_helper.get_data_boundary(side) # return v= 0 , nth=1, extremes of the other coordinate. + super(FixedAxisArtistHelper, self).__init__(grid_helper, + nth_coord, + value, + axis_direction=side, + ) + #self.grid_helper = grid_helper + if nth_coord_ticks is None: + nth_coord_ticks = nth_coord + self.nth_coord_ticks = nth_coord_ticks + + self.value = value + self.grid_helper = grid_helper + self._side = side + + + def update_lim(self, axes): + self.grid_helper.update_lim(axes) + + self.grid_info = self.grid_helper.grid_info + + + + def get_axislabel_pos_angle(self, axes): + + extremes = self.grid_info["extremes"] + + if self.nth_coord == 0: + xx0 = self.value + yy0 = (extremes[2]+extremes[3])/2. + dxx, dyy = 0., abs(extremes[2]-extremes[3])/1000. + elif self.nth_coord == 1: + xx0 = (extremes[0]+extremes[1])/2. + yy0 = self.value + dxx, dyy = abs(extremes[0]-extremes[1])/1000., 0. + + grid_finder = self.grid_helper.grid_finder + xx1, yy1 = grid_finder.transform_xy([xx0], [yy0]) + + trans_passingthrough_point = axes.transData + axes.transAxes.inverted() + p = trans_passingthrough_point.transform_point([xx1[0], yy1[0]]) + + + if (0. <= p[0] <= 1.) and (0. <= p[1] <= 1.): + xx1c, yy1c = axes.transData.transform_point([xx1[0], yy1[0]]) + xx2, yy2 = grid_finder.transform_xy([xx0+dxx], [yy0+dyy]) + xx2c, yy2c = axes.transData.transform_point([xx2[0], yy2[0]]) + + return (xx1c, yy1c), np.arctan2(yy2c-yy1c, xx2c-xx1c)/np.pi*180. + else: + return None, None + + + + def get_tick_transform(self, axes): + return axes.transData + + def get_tick_iterators(self, axes): + """tick_loc, tick_angle, tick_label, (optionally) tick_label""" + + + grid_finder = self.grid_helper.grid_finder + + lat_levs, lat_n, lat_factor = self.grid_info["lat_info"] + lon_levs, lon_n, lon_factor = self.grid_info["lon_info"] + + lon_levs, lat_levs = np.asarray(lon_levs), np.asarray(lat_levs) + if lat_factor is not None: + yy0 = lat_levs / lat_factor + dy = 0.01 / lat_factor + else: + yy0 = lat_levs + dy = 0.01 + + if lon_factor is not None: + xx0 = lon_levs / lon_factor + dx = 0.01 / lon_factor + else: + xx0 = lon_levs + dx = 0.01 + + _extremes = self.grid_helper._extremes + xmin, xmax = sorted(_extremes[:2]) + ymin, ymax = sorted(_extremes[2:]) + if self.nth_coord == 0: + mask = (ymin <= yy0) & (yy0 <= ymax) + yy0 = yy0[mask] + elif self.nth_coord == 1: + mask = (xmin <= xx0) & (xx0 <= xmax) + xx0 = xx0[mask] + + # find angles + if self.nth_coord == 0: + xx0 = np.empty_like(yy0) + xx0.fill(self.value) + xx1, yy1 = grid_finder.transform_xy(xx0, yy0) + xx2, yy2 = grid_finder.transform_xy(xx0+dx, yy0) + xx3, yy3 = grid_finder.transform_xy(xx0, yy0+dy) + labels = self.grid_info["lat_labels"] + labels = [l for l, m in zip(labels, mask) if m] + + elif self.nth_coord == 1: + yy0 = np.empty_like(xx0) + yy0.fill(self.value) + xx1, yy1 = grid_finder.transform_xy(xx0, yy0) + xx2, yy2 = grid_finder.transform_xy(xx0, yy0+dy) + xx3, yy3 = grid_finder.transform_xy(xx0+dx, yy0) + labels = self.grid_info["lon_labels"] + labels = [l for l, m in zip(labels, mask) if m] + + + def f1(): + dd = np.arctan2(yy2-yy1, xx2-xx1) # angle normal + dd2 = np.arctan2(yy3-yy1, xx3-xx1) # angle tangent + mm = ((yy2-yy1)==0.) & ((xx2-xx1)==0.) # mask where dd1 is not defined + dd[mm] = dd2[mm]+3.14159/2. + + #dd += 3.14159 + #dd = np.arctan2(xx2-xx1, angle_tangent-yy1) + trans_tick = self.get_tick_transform(axes) + tr2ax = trans_tick + axes.transAxes.inverted() + for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels): + c2 = tr2ax.transform_point((x, y)) + delta=0.00001 + if (0. -delta<= c2[0] <= 1.+delta) and \ + (0. -delta<= c2[1] <= 1.+delta): + d1 = d/3.14159*180. + d2 = d2/3.14159*180. + #_mod = (d2-d1+180)%360 + #if _mod < 180: + # d1 += 180 + ##_div, _mod = divmod(d2-d1, 360) + yield [x, y], d1, d2, lab + #, d2/3.14159*180.+da) + + return f1(), iter([]) + + def get_line_transform(self, axes): + return axes.transData + + def get_line(self, axes): + + self.update_lim(axes) + from matplotlib.path import Path + k, v = dict(left=("lon_lines0", 0), + right=("lon_lines0", 1), + bottom=("lat_lines0", 0), + top=("lat_lines0", 1))[self._side] + + xx, yy = self.grid_info[k][v] + return Path(zip(xx, yy)) + + + +from mpl_toolkits.axes_grid.grid_finder import ExtremeFinderSimple + +class ExtremeFinderFixed(ExtremeFinderSimple): + def __init__(self, extremes): + self._extremes = extremes + + def __call__(self, transform_xy, x1, y1, x2, y2): + """ + get extreme values. + + x1, y1, x2, y2 in image coordinates (0-based) + nx, ny : number of dvision in each axis + """ + #lon_min, lon_max, lat_min, lat_max = self._extremes + return self._extremes + + + +class GridHelperCurveLinear(grid_helper_curvelinear.GridHelperCurveLinear): + + def __init__(self, aux_trans, extremes, + grid_locator1=None, + grid_locator2=None, + tick_formatter1=None, + tick_formatter2=None): + """ + aux_trans : a transform from the source (curved) coordinate to + target (rectlinear) coordinate. An instance of MPL's Transform + (inverse transform should be defined) or a tuple of two callable + objects which defines the transform and its inverse. The callables + need take two arguments of array of source coordinates and + should return two target coordinates: + e.g. x2, y2 = trans(x1, y1) + """ + + self._old_values = None + + self._extremes = extremes + extreme_finder = ExtremeFinderFixed(extremes) + + super(GridHelperCurveLinear, self).__init__(aux_trans, + extreme_finder, + grid_locator1=grid_locator1, + grid_locator2=grid_locator2, + tick_formatter1=tick_formatter1, + tick_formatter2=tick_formatter2) + + + # def update_grid_finder(self, aux_trans=None, **kw): + + # if aux_trans is not None: + # self.grid_finder.update_transform(aux_trans) + + # self.grid_finder.update(**kw) + # self.invalidate() + + + # def _update(self, x1, x2, y1, y2): + # "bbox in 0-based image coordinates" + # # update wcsgrid + + # if self.valid() and self._old_values == (x1, x2, y1, y2): + # return + + # self._update_grid(x1, y1, x2, y2) + + # self._old_values = (x1, x2, y1, y2) + + # self._force_update = False + + + def get_data_boundary(self, side): + """ + return v= 0 , nth=1 + """ + lon1, lon2, lat1, lat2 = self._extremes + return dict(left=(lon1, 0), + right=(lon2, 0), + bottom=(lat1, 1), + top=(lat2, 1))[side] + + + def new_fixed_axis(self, loc, + nth_coord=None, + axis_direction=None, + offset=None, + axes=None): + + if axes is None: + axes = self.axes + + if axis_direction is None: + axis_direction = loc + + _helper = FixedAxisArtistHelper(self, loc, + nth_coord_ticks=nth_coord) + + + axisline = AxisArtist(axes, _helper, axis_direction=axis_direction) + axisline.line.set_clip_on(True) + axisline.line.set_clip_box(axisline.axes.bbox) + + + return axisline + + + # new_floating_axis will inheirt the grid_helper's extremes. + + # def new_floating_axis(self, nth_coord, + # value, + # axes=None, + # axis_direction="bottom" + # ): + + # axis = super(GridHelperCurveLinear, + # self).new_floating_axis(nth_coord, + # value, axes=axes, + # axis_direction=axis_direction) + + # # set extreme values of the axis helper + # if nth_coord == 1: + # axis.get_helper().set_extremes(*self._extremes[:2]) + # elif nth_coord == 0: + # axis.get_helper().set_extremes(*self._extremes[2:]) + + # return axis + + + def _update_grid(self, x1, y1, x2, y2): + + #self.grid_info = self.grid_finder.get_grid_info(x1, y1, x2, y2) + + if self.grid_info is None: + self.grid_info = dict() + + grid_info = self.grid_info + + grid_finder = self.grid_finder + extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy, + x1, y1, x2, y2) + + lon_min, lon_max = sorted(extremes[:2]) + lat_min, lat_max = sorted(extremes[2:]) + lon_levs, lon_n, lon_factor = \ + grid_finder.grid_locator1(lon_min, lon_max) + lat_levs, lat_n, lat_factor = \ + grid_finder.grid_locator2(lat_min, lat_max) + grid_info["extremes"] = lon_min, lon_max, lat_min, lat_max #extremes + + grid_info["lon_info"] = lon_levs, lon_n, lon_factor + grid_info["lat_info"] = lat_levs, lat_n, lat_factor + + grid_info["lon_labels"] = grid_finder.tick_formatter1("bottom", + lon_factor, + lon_levs) + + grid_info["lat_labels"] = grid_finder.tick_formatter2("bottom", + lat_factor, + lat_levs) + + if lon_factor is None: + lon_values = np.asarray(lon_levs[:lon_n]) + else: + lon_values = np.asarray(lon_levs[:lon_n]/lon_factor) + if lat_factor is None: + lat_values = np.asarray(lat_levs[:lat_n]) + else: + lat_values = np.asarray(lat_levs[:lat_n]/lat_factor) + + lon_values0 = lon_values[(lon_min<lon_values) & (lon_values<lon_max)] + lat_values0 = lat_values[(lat_min<lat_values) & (lat_values<lat_max)] + lon_lines, lat_lines = grid_finder._get_raw_grid_lines(lon_values0, + lat_values0, + lon_min, lon_max, + lat_min, lat_max) + + + grid_info["lon_lines"] = lon_lines + grid_info["lat_lines"] = lat_lines + + + lon_lines, lat_lines = grid_finder._get_raw_grid_lines(extremes[:2], + extremes[2:], + *extremes) + #lon_min, lon_max, + # lat_min, lat_max) + + + grid_info["lon_lines0"] = lon_lines + grid_info["lat_lines0"] = lat_lines + + + + def get_gridlines(self): + grid_lines = [] + for gl in self.grid_info["lat_lines"]: + grid_lines.extend([gl]) + for gl in self.grid_info["lon_lines"]: + grid_lines.extend([gl]) + + return grid_lines + + + def get_boundary(self): + """ + return Nx2 array of x,y coordinate of the boundary + """ + x0, x1, y0, y1 = self._extremes + tr = self._aux_trans + xx = np.linspace(x0, x1, 100) + yy0, yy1 = np.empty_like(xx), np.empty_like(xx) + yy0.fill(y0) + yy1.fill(y1) + + yy = np.linspace(y0, y1, 100) + xx0, xx1 = np.empty_like(yy), np.empty_like(yy) + xx0.fill(x0) + xx1.fill(x1) + + xxx = np.concatenate([xx[:-1], xx1[:-1], xx[-1:0:-1], xx0]) + yyy = np.concatenate([yy0[:-1], yy[:-1], yy1[:-1], yy[::-1]]) + t = tr.transform(np.array([xxx, yyy]).transpose()) + + return t + + + + + + + +#from mpl_toolkits.axes_grid.axislines import Axes + +from mpl_toolkits.axes_grid.parasite_axes import HostAxes, ParasiteAxesAuxTrans + + + + +class FloatingAxesBase(object): + + + def __init__(self, *kl, **kwargs): + grid_helper = kwargs.get("grid_helper", None) + if grid_helper is None: + raise ValueError("FloatingAxes requires grid_helper argument") + if not hasattr(grid_helper, "get_boundary"): + raise ValueError("grid_helper must implement get_boundary method") + + self._axes_class_floating.__init__(self, *kl, **kwargs) + + self.set_aspect(1.) + self.adjust_axes_lim() + + + def _gen_axes_patch(self): + """ + Returns the patch used to draw the background of the axes. It + is also used as the clipping path for any data elements on the + axes. + + In the standard axes, this is a rectangle, but in other + projections it may not be. + + .. note:: + Intended to be overridden by new projection types. + """ + import matplotlib.patches as mpatches + grid_helper = self.get_grid_helper() + t = grid_helper.get_boundary() + return mpatches.Polygon(t) + + def cla(self): + self._axes_class_floating.cla(self) + #HostAxes.cla(self) + self.patch.set_transform(self.transData) + + + patch = self._axes_class_floating._gen_axes_patch(self) + patch.set_figure(self.figure) + patch.set_visible(False) + patch.set_transform(self.transAxes) + + self.patch.set_clip_path(patch) + self.gridlines.set_clip_path(patch) + + self._original_patch = patch + + + def adjust_axes_lim(self): + + #t = self.get_boundary() + grid_helper = self.get_grid_helper() + t = grid_helper.get_boundary() + x, y = t[:,0], t[:,1] + + xmin, xmax = min(x), max(x) + ymin, ymax = min(y), max(y) + + dx = (xmax-xmin)/100. + dy = (ymax-ymin)/100. + + self.set_xlim(xmin-dx, xmax+dx) + self.set_ylim(ymin-dy, ymax+dy) + + + +import new + +_floatingaxes_classes = {} + +def floatingaxes_class_factory(axes_class): + + new_class = _floatingaxes_classes.get(axes_class) + if new_class is None: + new_class = new.classobj("Floating %s" % (axes_class.__name__), + (FloatingAxesBase, axes_class), + {'_axes_class_floating': axes_class}) + _floatingaxes_classes[axes_class] = new_class + + return new_class + +FloatingAxes = floatingaxes_class_factory(HostAxes) + + + +import matplotlib.axes as maxes +FloatingSubplot = maxes.subplot_class_factory(FloatingAxes) + +# def test(fig): +# from mpl_toolkits.axes_grid.axislines import Subplot +# ax = Subplot(fig, 111) + +# fig.add_subplot(ax) + +# plt.draw() + + +def curvelinear_test3(fig): + """ + polar projection, but in a rectangular box. + """ + global ax1, axis + import numpy as np + import mpl_toolkits.axes_grid.angle_helper as angle_helper + from matplotlib.projections import PolarAxes + + # PolarAxes.PolarTransform takes radian. However, we want our coordinate + # system in degree + tr = Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform() + + # polar projection, which involves cycle, and also has limits in + # its coordinates, needs a special method to find the extremes + # (min, max of the coordinate within the view). + + + grid_locator1 = angle_helper.LocatorDMS(15) + # Find a grid values appropriate for the coordinate (degree, + # minute, second). + + tick_formatter1 = angle_helper.FormatterDMS() + # And also uses an appropriate formatter. Note that,the + # acceptable Locator and Formatter class is a bit different than + # that of mpl's, and you cannot directly use mpl's Locator and + # Formatter here (but may be possible in the future). + + from mpl_toolkits.axes_grid.grid_finder import FixedLocator + grid_locator2 = FixedLocator([2, 4, 6, 8, 10]) + + + grid_helper = GridHelperCurveLinear(tr, + extremes=(0, 360, 10, 3), + grid_locator1=grid_locator1, + grid_locator2=grid_locator2, + tick_formatter1=tick_formatter1, + tick_formatter2=None, + ) + + ax1 = FloatingSubplot(fig, 111, grid_helper=grid_helper) + + + #ax1.axis["top"].set_visible(False) + #ax1.axis["bottom"].major_ticklabels.set_axis_direction("top") + + fig.add_subplot(ax1) + + + #ax1.grid(True) + + + r_scale = 10. + tr2 = Affine2D().scale(1., 1./r_scale) + tr + grid_locator2 = FixedLocator([30, 60, 90]) + grid_helper2 = GridHelperCurveLinear(tr2, + extremes=(0, 360, + 10.*r_scale, 3.*r_scale), + grid_locator2=grid_locator2, + ) + + ax1.axis["right"] = axis = grid_helper2.new_fixed_axis("right", axes=ax1) + + ax1.axis["left"].label.set_text("Test 1") + ax1.axis["right"].label.set_text("Test 2") + + + for an in [ "left", "right"]: + ax1.axis[an].set_visible(False) + + + #grid_helper2 = ax1.get_grid_helper() + ax1.axis["z"] = axis = grid_helper.new_floating_axis(1, 7, + axes=ax1, + axis_direction="bottom") + axis.toggle(all=True, label=True) + #axis.label.set_axis_direction("top") + axis.label.set_text("z = ?") + axis.label.set_visible(True) + axis.line.set_color("0.5") + #axis.label.set_visible(True) + + + ax2 = ax1.get_aux_axes(tr) + + xx, yy = [67, 90, 75, 30], [2, 5, 8, 4] + ax2.scatter(xx, yy) + l, = ax2.plot(xx, yy, "k-") + l.set_clip_path(ax1.patch) + + +def curvelinear_test4(fig): + """ + polar projection, but in a rectangular box. + """ + global ax1, axis + import numpy as np + import mpl_toolkits.axes_grid.angle_helper as angle_helper + from matplotlib.projections import PolarAxes + + tr = Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform() + + grid_locator1 = angle_helper.LocatorDMS(5) + tick_formatter1 = angle_helper.FormatterDMS() + + from mpl_toolkits.axes_grid.grid_finder import FixedLocator + grid_locator2 = FixedLocator([2, 4, 6, 8, 10]) + + grid_helper = GridHelperCurveLinear(tr, + extremes=(120, 30, 10, 0), + grid_locator1=grid_locator1, + grid_locator2=grid_locator2, + tick_formatter1=tick_formatter1, + tick_formatter2=None, + ) + + ax1 = FloatingSubplot(fig, 111, grid_helper=grid_helper) + + + #ax1.axis["top"].set_visible(False) + #ax1.axis["bottom"].major_ticklabels.set_axis_direction("top") + + fig.add_subplot(ax1) + + + #ax1.grid(True) + + + ax1.axis["left"].label.set_text("Test 1") + ax1.axis["right"].label.set_text("Test 2") + + + for an in [ "top"]: + ax1.axis[an].set_visible(False) + + + #grid_helper2 = ax1.get_grid_helper() + ax1.axis["z"] = axis = grid_helper.new_floating_axis(1, 70, + axes=ax1, + axis_direction="bottom") + axis.toggle(all=True, label=True) + axis.label.set_axis_direction("top") + axis.label.set_text("z = ?") + axis.label.set_visible(True) + axis.line.set_color("0.5") + #axis.label.set_visible(True) + + + ax2 = ax1.get_aux_axes(tr) + + xx, yy = [67, 90, 75, 30], [2, 5, 8, 4] + ax2.scatter(xx, yy) + l, = ax2.plot(xx, yy, "k-") + l.set_clip_path(ax1.patch) + +if __name__ == "__main__": + import matplotlib.pyplot as plt + fig = plt.figure(1, figsize=(5, 5)) + fig.clf() + + #test(fig) + #curvelinear_test1(fig) + curvelinear_test4(fig) + + #plt.draw() + plt.show() + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ Join us December 9, 2009 for the Red Hat Virtual Experience, a free event focused on virtualization and cloud computing. Attend in-depth sessions from your desk. Your couch. Anywhere. http://p.sf.net/sfu/redhat-sfdev2dev _______________________________________________ Matplotlib-checkins mailing list Matplotlib-checkins@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins