Revision: 3889 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3889&view=rev Author: mdboom Date: 2007-09-25 10:04:51 -0700 (Tue, 25 Sep 2007)
Log Message: ----------- Automaticall separate affine from non-affine transforms Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/backends/backend_agg.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/scale.py branches/transforms/lib/matplotlib/ticker.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -545,7 +545,7 @@ "move this out of __init__ because non-separable axes don't use it" self.xaxis = maxis.XAxis(self) self.yaxis = maxis.YAxis(self) - self._update_transAxisXY() + self._update_transScale() def sharex_foreign(self, axforeign): """ @@ -631,12 +631,28 @@ self.viewLim = mtransforms.Bbox.unit() self.transAxes = mtransforms.BboxTransform( mtransforms.Bbox.unit(), self.bbox) - self.transAxisXY = mtransforms.TransformWrapper() - self.transData = self.transAxisXY + self.transAxes - def _update_transAxisXY(self): - self.transAxisXY.set(mtransforms.blended_transform_factory( + # Transforms the x and y axis separately by a scale factor + # It is assumed that this part will have non-linear components + self.transScale = mtransforms.TransformWrapper(mtransforms.IdentityTransform()) + + # A (possibly non-linear) projection on the (already scaled) data + self.transProjection = mtransforms.IdentityTransform() + + # An affine transformation on the data, generally to limit the + # range of the axes + self.transLimits = mtransforms.BboxTransform( + mtransforms.TransformedBbox(self.viewLim, self.transScale), mtransforms.Bbox.unit()) + + self.transData = self.transScale + self.transProjection + self.transLimits + self.transAxes + + + def _update_transScale(self): + self.transScale.set( + mtransforms.blended_transform_factory( self.xaxis.get_transform(), self.yaxis.get_transform())) + + self.transData.make_graphviz(open("trans.dot", "w")) def get_position(self, original=False): 'Return the axes rectangle left, bottom, width, height' @@ -1537,7 +1553,7 @@ ACCEPTS: ['log' | 'linear' ] """ self.xaxis.set_scale(value, **kwargs) - self._update_transAxisXY() + self._update_transScale() def get_xticks(self): 'Return the x ticks as a list of locations' @@ -1647,7 +1663,7 @@ ACCEPTS: ['log' | 'linear'] """ self.yaxis.set_scale(value, basey, subsy) - self._update_transAxisXY() + self._update_transScale() def get_yticks(self): 'Return the y ticks as a list of locations' Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -508,16 +508,15 @@ self.majorTicks = [] self.minorTicks = [] self.pickradius = pickradius - self._transform = LinearScale(self.axes.viewLim, self.axis).get_transform() - self._scale = 'linear' + self._scale = LinearScale() self.cla() def get_transform(self): - return self._transform - + return self._scale.get_transform() + def get_scale(self): - return self._scale + return self._scale.name def set_scale(self, value, base=10, subs=None): # MGDTODO: Move these settings (ticker etc.) into the scale class itself @@ -528,17 +527,16 @@ self.set_major_formatter(ScalarFormatter()) self.set_minor_locator(NullLocator()) self.set_minor_formatter(NullFormatter()) - self._transform = LinearScale(self.axes.viewLim, self.axis).get_transform() + self._scale = LinearScale() elif value == 'log': self.set_major_locator(LogLocator(base)) self.set_major_formatter(LogFormatterMathtext(base)) self.set_minor_locator(LogLocator(base,subs)) # MGDTODO: Pass base along - self._transform = LogScale(self.axes.viewLim, self.axis).get_transform() + self._scale = LogScale() miny, maxy = getattr(self.axes.viewLim, 'interval' + self.axis) if min(miny, maxy)<=0: self.axes.autoscale_view() - self._scale = value def get_children(self): children = [self.label] Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -132,14 +132,14 @@ # MGDTODO: This is a hack for now to allow for arbitrary transformations def draw_path(self, gc, path, trans, rgbFace=None): - new_path, affine = path.transformed_without_affine(trans) - self._renderer.draw_path(gc, new_path, affine, rgbFace) + assert trans.is_affine() + self._renderer.draw_path(gc, path, trans, rgbFace) # MGDTODO: This is a hack for now to allow for arbitrary transformations def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): assert marker_trans.is_affine() - new_path, affine = path.transformed_without_affine(trans) - self._renderer.draw_markers(gc, marker_path, marker_trans, new_path, affine, rgbFace) + assert trans.is_affine() + self._renderer.draw_markers(gc, marker_path, marker_trans, path, trans, rgbFace) def draw_mathtext(self, gc, x, y, s, prop, angle): """ Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -17,7 +17,7 @@ from cbook import iterable, is_string_like, is_numlike from colors import colorConverter from path import Path -from transforms import Affine2D, Bbox +from transforms import Affine2D, Bbox, TransformedPath from matplotlib import rcParams @@ -416,8 +416,18 @@ self._logcache = None # Masked arrays are now handled by the Path class itself self._path = Path(self._xy, closed=False) + self._transformed_path = TransformedPath(self._path, self.get_transform()) # MGDTODO: If _draw_steps is removed, remove the following line also self._step_path = None + + def set_transform(self, t): + """ + set the Transformation instance used by this artist + + ACCEPTS: a matplotlib.transform transformation instance + """ + Artist.set_transform(self, t) + self._transformed_path = TransformedPath(self._path, self.get_transform()) def _is_sorted(self, x): "return true if x is sorted" @@ -486,10 +496,10 @@ funcname = self._lineStyles.get(self._linestyle, '_draw_nothing') lineFunc = getattr(self, funcname) - lineFunc(renderer, gc, self._path) + lineFunc(renderer, gc, *self._transformed_path.get_path_and_affine()) # MGDTODO: Deal with markers - if self._marker is not None: + if self._marker is not None and False: gc = renderer.new_gc() self._set_gc_clip(gc) gc.set_foreground(self.get_markeredgecolor()) @@ -678,7 +688,7 @@ self.set_linestyle('--') self._dashSeq = seq # TODO: offset ignored for now - def _draw_nothing(self, renderer, gc, path): + def _draw_nothing(self, *args, **kwargs): pass @@ -704,191 +714,191 @@ renderer.draw_path(gc, self._step_path, self.get_transform()) - def _draw_solid(self, renderer, gc, path): + def _draw_solid(self, renderer, gc, path, trans): gc.set_linestyle('solid') - renderer.draw_path(gc, path, self.get_transform()) + renderer.draw_path(gc, path, trans) - def _draw_dashed(self, renderer, gc, path): + def _draw_dashed(self, renderer, gc, path, trans): gc.set_linestyle('dashed') if self._dashSeq is not None: gc.set_dashes(0, self._dashSeq) - renderer.draw_path(gc, path, self.get_transform()) + renderer.draw_path(gc, path, trans) - def _draw_dash_dot(self, renderer, gc, path): + def _draw_dash_dot(self, renderer, gc, path, trans): gc.set_linestyle('dashdot') - renderer.draw_path(gc, path, self.get_transform()) + renderer.draw_path(gc, path, trans) - def _draw_dotted(self, renderer, gc, path): + def _draw_dotted(self, renderer, gc, path, trans): gc.set_linestyle('dotted') - renderer.draw_path(gc, path, self.get_transform()) + renderer.draw_path(gc, path, trans) - def _draw_point(self, renderer, gc, path): + def _draw_point(self, renderer, gc, path, path_trans): w = renderer.points_to_pixels(self._markersize) * \ self._point_size_reduction * 0.5 rgbFace = self._get_rgb_face() transform = Affine2D().scale(w) renderer.draw_markers( - gc, Path.unit_circle(), transform, path, self.get_transform(), + gc, Path.unit_circle(), transform, path, path_trans, rgbFace) - def _draw_pixel(self, renderer, gc, path): + def _draw_pixel(self, renderer, gc, path, path_trans): rgbFace = self._get_rgb_face() transform = Affine2D().translate(-0.5, -0.5) renderer.draw_markers(gc, Path.unit_rectangle, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_circle(self, renderer, gc, path): + def _draw_circle(self, renderer, gc, path, path_trans): w = renderer.points_to_pixels(self._markersize) * 0.5 rgbFace = self._get_rgb_face() transform = Affine2D().scale(w, w) renderer.draw_markers( - gc, Path.unit_circle(), transform, path, self.get_transform(), + gc, Path.unit_circle(), transform, path, path_trans, rgbFace) _triangle_path = Path([[0.0, 1.0], [-1.0, -1.0], [1.0, -1.0]]) - def _draw_triangle_up(self, renderer, gc, path): + def _draw_triangle_up(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset, offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, self._triangle_path, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_triangle_down(self, renderer, gc, path): + def _draw_triangle_down(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset, -offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, self._triangle_path, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_triangle_left(self, renderer, gc, path): + def _draw_triangle_left(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset, offset).rotate_deg(90) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, self._triangle_path, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_triangle_right(self, renderer, gc, path): + def _draw_triangle_right(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset, offset).rotate_deg(-90) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, self._triangle_path, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_square(self, renderer, gc, path): + def _draw_square(self, renderer, gc, path, path_trans): side = renderer.points_to_pixels(self._markersize) transform = Affine2D().translate(-0.5, -0.5).scale(side) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_rectangle(), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_diamond(self, renderer, gc, path): + def _draw_diamond(self, renderer, gc, path, path_trans): side = renderer.points_to_pixels(self._markersize) transform = Affine2D().translate(0.5, 0.5).rotate_deg(45).scale(side) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_rectangle(), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_thin_diamond(self, renderer, gc, path): + def _draw_thin_diamond(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) transform = Affine2D().translate(0.5, 0.5) \ .rotate_deg(45).scale(offset * 0.8, offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_rectangle(), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_pentagon(self, renderer, gc, path): + def _draw_pentagon(self, renderer, gc, path, path_trans): offset = 0.5 * renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_regular_polygon(5), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_hexagon1(self, renderer, gc, path): + def _draw_hexagon1(self, renderer, gc, path, path_trans): offset = 0.5 * renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_regular_polygon(6), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_hexagon2(self, renderer, gc, path): + def _draw_hexagon2(self, renderer, gc, path, path_trans): offset = 0.5 * renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(30) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_regular_polygon(6), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]], closed=False) - def _draw_vline(self, renderer, gc, path): + def _draw_vline(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._line_marker_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_hline(self, renderer, gc, path): + def _draw_hline(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(90) renderer.draw_markers(gc, self._line_marker_path, transform, - path, self.get_transform()) + path, path_trans) _tickhoriz_path = Path([[0.0, 0.5], [1.0, 0.5]], closed=False) - def _draw_tickleft(self, renderer, gc, path): + def _draw_tickleft(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) marker_transform = Affine2D().scale(-offset, 1.0) renderer.draw_markers(gc, self._tickhoriz_path, marker_transform, - path, self.get_transform()) + path, path_trans) - def _draw_tickright(self, renderer, gc, path): + def _draw_tickright(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) marker_transform = Affine2D().scale(offset, 1.0) renderer.draw_markers(gc, self._tickhoriz_path, marker_transform, - path, self.get_transform()) + path, path_trans) _tickvert_path = Path([[-0.5, 0.0], [-0.5, 1.0]], closed=False) - def _draw_tickup(self, renderer, gc, path): + def _draw_tickup(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) marker_transform = Affine2D().scale(1.0, offset) renderer.draw_markers(gc, self._tickvert_path, marker_transform, - path, self.get_transform()) + path, path_trans) - def _draw_tickdown(self, renderer, gc, path): + def _draw_tickdown(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) marker_transform = Affine2D().scale(1.0, -offset) renderer.draw_markers(gc, self._tickvert_path, marker_transform, - path, self.get_transform()) + path, path_trans) _plus_path = Path([[-1.0, 0.0], [1.0, 0.0], [0.0, -1.0], [0.0, 1.0]], [Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO]) - def _draw_plus(self, renderer, gc, path): + def _draw_plus(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._plus_path, transform, - path, self.get_transform()) + path, path_trans) _tri_path = Path([[0.0, 0.0], [0.0, -1.0], @@ -897,61 +907,61 @@ [Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO]) - def _draw_tri_down(self, renderer, gc, path): + def _draw_tri_down(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._tri_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_tri_up(self, renderer, gc, path): + def _draw_tri_up(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(180) renderer.draw_markers(gc, self._tri_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_tri_left(self, renderer, gc, path): + def _draw_tri_left(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(90) renderer.draw_markers(gc, self._tri_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_tri_right(self, renderer, gc, path): + def _draw_tri_right(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(270) renderer.draw_markers(gc, self._tri_path, transform, - path, self.get_transform()) + path, path_trans) _caret_path = Path([[-1.0, 1.5], [0.0, 0.0], [1.0, 1.5]], closed=False) - def _draw_caretdown(self, renderer, gc, path): + def _draw_caretdown(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._caret_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_caretup(self, renderer, gc, path): + def _draw_caretup(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(180) renderer.draw_markers(gc, self._caret_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_caretleft(self, renderer, gc, path): + def _draw_caretleft(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(90) renderer.draw_markers(gc, self._caret_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_caretright(self, renderer, gc, path): + def _draw_caretright(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(270) renderer.draw_markers(gc, self._caret_path, transform, - path, self.get_transform()) + path, path_trans) _x_path = Path([[-1.0, -1.0], [1.0, 1.0], @@ -962,7 +972,7 @@ offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._x_path, transform, - path, self.get_transform()) + path, path_trans) def update_from(self, other): Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/path.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -88,10 +88,6 @@ def transformed(self, transform): return Path(transform.transform(self.vertices), self.codes) - - def transformed_without_affine(self, transform): - vertices, affine = transform.transform_without_affine(self.vertices) - return Path(vertices, self.codes), affine _unit_rectangle = None [EMAIL PROTECTED] @@ -152,6 +148,3 @@ cls._unit_circle = Path(vertices, codes) return cls._unit_circle unit_circle = classmethod(unit_circle) - -# MGDTODO: Add a transformed path that would automatically invalidate -# itself when its transform changes Modified: branches/transforms/lib/matplotlib/scale.py =================================================================== --- branches/transforms/lib/matplotlib/scale.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/scale.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -1,66 +1,54 @@ import numpy as npy from numpy import ma +from numpy.linalg import inv -from transforms import Affine1D, IntervalTransform, Transform +from transforms import Affine1DBase, IntervalTransform, Transform, \ + composite_transform_factory, IdentityTransform class ScaleBase(object): pass class LinearScale(ScaleBase): - def __init__(self, viewLim, direction): - direction = 'interval' + direction - self._transform = IntervalTransform(viewLim, direction) - def get_transform(self): - return self._transform + return IdentityTransform() class LogScale(ScaleBase): class LogTransform(Transform): input_dims = 1 output_dims = 1 - def __init__(self, viewLim, direction, base): + def __init__(self, base): Transform.__init__(self) self._base = base - self._viewLim = viewLim - self._direction = direction - self.set_children(['_viewLim']) + def is_separable(self): + return True + def transform(self, a): - a, affine = self.transform_without_affine(a) - return affine.transform(a) + if len(a) > 10: + print "Log Transforming..." + return ma.log10(ma.masked_where(a <= 0.0, a * 10.0)) - def transform_without_affine(self, a): - # MGDTODO: Support different bases - base = self._base - marray = ma.masked_where(a <= 0.0, a * 10.0) - marray = npy.log10(marray) - minimum, maximum = npy.log10(getattr(self._viewLim, self._direction) * 10.0) - return marray, Affine1D.from_values(maximum - minimum, minimum).inverted() - def inverted(self): - return LogScale.InvertedLogTransform(self._viewLim, self._direction, self._base) + return LogScale.InvertedLogTransform(self._base) class InvertedLogTransform(Transform): input_dims = 1 output_dims = 1 - def __init__(self, viewLim, direction, base): + def __init__(self, base): Transform.__init__(self) self._base = base - self._viewLim = viewLim - self._direction = direction - self.set_children(['_viewLim']) + + def is_separable(self): + return True def transform(self, a): - minimum, maximum = npy.log10(getattr(self._viewLim, self._direction) * 10.0) - a = Affine1D.from_values(maximum - minimum, minimum).transform(a) return ma.power(10.0, a) / 10.0 def inverted(self): - return LogScale.LogTransform(self._viewLim, self._direction, self._base) + return LogScale.LogTransform(self._base) - def __init__(self, viewLim, direction, base=10): - direction = 'interval' + direction - self._transform = self.LogTransform(viewLim, direction, base) + def __init__(self, base=10): + self._transform = self.LogTransform(base) def get_transform(self): return self._transform Modified: branches/transforms/lib/matplotlib/ticker.py =================================================================== --- branches/transforms/lib/matplotlib/ticker.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/ticker.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -937,9 +937,7 @@ if vmin==vmax: vmin = decade_down(vmin,self._base) vmax = decade_up(vmax,self._base) - print vmin, vmax result = mtransforms.nonsingular(vmin, vmax) - print result return result class AutoLocator(MaxNLocator): Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -8,7 +8,10 @@ from numpy import ma as ma from numpy.linalg import inv from sets import Set +from weakref import WeakKeyDictionary +from path import Path + DEBUG = False # MGDTODO: This creates a ton of cyclical references. We may want to @@ -22,45 +25,54 @@ self._parents = Set() self._children = [] - def invalidate(self): - self._do_invalidation() + def invalidate(self, which_child=None, affine_only=[]): + if which_child is None: + which_child = self + self._do_invalidation(which_child, affine_only) + # affine_only = affine_only and (self.is_affine() or self.is_bbox()) for parent in self._parents: - parent.invalidate() + parent.invalidate(self, affine_only + [self]) - def _do_invalidation(self): - return False - + def _do_invalidation(self, which_child, affine_only): + pass + def set_children(self, children): for child in children: getattr(self, child)._parents.add(self) self._children = children def make_graphviz(self, fobj): + seen = Set() + def recurse(root): + if root in seen: + return + seen.add(root) fobj.write('%s [label="%s"];\n' % (hash(root), root.__class__.__name__)) - if isinstance(root, Affine2DBase): + if root.is_affine(): fobj.write('%s [style=filled, color=".7 .7 .9"];\n' % hash(root)) - elif isinstance(root, BboxBase): + elif root.is_bbox(): fobj.write('%s [style=filled, color=".9 .9 .7"];\n' % hash(root)) for child_name in root._children: child = getattr(root, child_name) - fobj.write("%s -> %s;\n" % ( + fobj.write('%s -> %s [label="%s"];\n' % ( hash(root), - hash(child))) + hash(child), + child_name)) recurse(child) - + fobj.write("digraph G {\n") recurse(self) fobj.write("}\n") def is_affine(self): - return isinstance(self, Affine2DBase) + return False def is_bbox(self): - return isinstance(self, BboxBase) + return False class BboxBase(TransformNode): @@ -70,7 +82,10 @@ def __init__(self): TransformNode.__init__(self) - + + def is_bbox(self): + return True + def __array__(self): return self.get_points() @@ -125,63 +140,81 @@ height = property(_get_height) def _get_bounds(self): - return (self.xmin, self.ymin, - self.xmax - self.xmin, self.ymax - self.ymin) + ((xmin, ymin), (xmax, ymax)) = self.get_points() + return (xmin, ymin, xmax - xmin, ymax - ymin) bounds = property(_get_bounds) + def _get_lbrt(self): + return self.get_points().flatten().copy() + lbrt = property(_get_lbrt) + def get_points(self): return NotImplementedError() - + # MGDTODO: Optimize def containsx(self, x): - return x >= self.xmin and x <= self.xmax + xmin, xmax = self.intervalx + return x >= xmin and x <= xmax def containsy(self, y): - return y >= self.ymin and y <= self.ymax + ymin, ymax = self.intervaly + return y >= ymin and y <= ymax def contains(self, x, y): return self.containsx(x) and self.containsy(y) def overlapsx(self, other): - return self.containsx(other.xmin) \ - or self.containsx(other.xmax) + xmin, xmax = other.intervalx + return self.containsx(xmin) \ + or self.containsx(xmax) def overlapsy(self, other): - return self.containsy(other.ymin) \ - or self.containsx(other.ymax) + ymin, ymax = other.intervaly + return self.containsy(ymin) \ + or self.containsx(ymax) def overlaps(self, other): return self.overlapsx(other) \ and self.overlapsy(other) def fully_containsx(self, x): - return x > self.xmin and x < self.xmax + xmin, xmax = self.intervalx + return x > xmin and x < xmax def fully_containsy(self, y): - return y > self.ymin and y < self.ymax + ymin, ymax = self.intervaly + return y > ymin and y < ymax def fully_contains(self, x, y): return self.fully_containsx(x) \ and self.fully_containsy(y) def fully_overlapsx(self, other): - return self.fully_containsx(other.xmin) \ - or self.fully_containsx(other.xmax) + xmin, xmax = other.intervalx + return self.fully_containsx(xmin) \ + or self.fully_containsx(xmax) def fully_overlapsy(self, other): - return self.fully_containsy(other.ymin) \ - or self.fully_containsx(other.ymax) + ymin, ymax = other.intervaly + return self.fully_containsy(ymin) \ + or self.fully_containsx(ymax) def fully_overlaps(self, other): return self.fully_overlapsx(other) and \ self.fully_overlapsy(other) + def transformed(self, transform): + return Bbox(transform.transform(self.get_points())) + + def inverse_transformed(self, transform): + return Bbox(transform.inverted().transform(self.get_points())) + class Bbox(BboxBase): def __init__(self, points): BboxBase.__init__(self) self._points = npy.asarray(points, npy.float_) - + [EMAIL PROTECTED] def unit(): return Bbox.from_lbrt(0., 0., 1., 1.) @@ -198,12 +231,6 @@ return Bbox(points) from_lbrt = staticmethod(from_lbrt) - def __cmp__(self, other): - # MGDTODO: Totally suboptimal - if isinstance(other, Bbox) and (self._points == other._points).all(): - return 0 - return -1 - def __repr__(self): return 'Bbox(%s)' % repr(self._points) __str__ = __repr__ @@ -219,8 +246,7 @@ [max(x.max(), self.xmax), max(y.max(), self.ymax)]], npy.float_) self.invalidate() - - # MGDTODO: Probably a more efficient ways to do this... + def _set_xmin(self, val): self._points[0, 0] = val self.invalidate() @@ -278,12 +304,6 @@ self._points = other.get_points() self.invalidate() - def transformed(self, transform): - return Bbox(transform.transform(self._points)) - - def inverse_transformed(self, transform): - return Bbox(transform.inverted().transform(self._points)) - def expanded(self, sw, sh): width = self.width height = self.height @@ -324,6 +344,8 @@ def __init__(self, bbox, transform): assert bbox.is_bbox() assert isinstance(transform, Transform) + assert transform.input_dims == 2 + assert transform.output_dims == 2 BboxBase.__init__(self) self.bbox = bbox @@ -335,7 +357,7 @@ return "TransformedBbox(%s, %s)" % (self.bbox, self.transform) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._points = None def get_points(self): @@ -347,13 +369,10 @@ class Transform(TransformNode): def __init__(self): TransformNode.__init__(self) - - def transform(self, points): - raise NotImplementedError() - def transform_without_affine(self, points): - return self.transform(points), IDENTITY - + def is_separable(self): + return False + def __add__(self, other): if isinstance(other, Transform): return composite_transform_factory(self, other) @@ -366,39 +385,69 @@ raise TypeError( "Can not add Transform to object of type '%s'" % type(other)) + def transform(self, points): + raise NotImplementedError + + def transform_affine(self, points): + raise NotImplementedError + + def transform_non_affine(self, points): + raise NotImplementedError + + def get_affine(self): + raise NotImplementedError + def transform_point(self, point): return self.transform(npy.asarray([point]))[0] - + def has_inverse(self): raise NotImplementedError() def inverted(self): raise NotImplementedError() - def is_separable(self): - return False +class TransformWrapper(Transform): + def __init__(self, child): + assert isinstance(child, Transform) + + Transform.__init__(self) + self.input_dims = child.input_dims + self.output_dims = child.output_dims + self._child = child + self.set_children(['_child']) -class TransformWrapper(Transform): - input_dims = 2 - output_dims = 2 - + def __repr__(self): + return "TransformWrapper(%r)" % self._child + __str__ = __repr__ + def set(self, child): - self.child = child - self.child._parents.add(self) + assert child.input_dims == self.input_dims + assert child.output_dims == self.output_dims + self._child = child + self.set_children(['_child']) self.invalidate() + + def is_separable(self): + return self._child.is_separable() + def is_affine(self): + return self._child.is_affine() + def transform(self, points): - return self.child.transform(points) + return self._child.transform(points) - def transform_without_affine(points): - return self.child.transform_without_affine(points) + def transform_affine(self, points): + return self._child.transform_affine(points) + + def transform_non_affine(self, points): + return self._child.transform_non_affine(points) + + def get_affine(self): + return self._child.get_affine() def inverted(self): - return self.child.inverted() - - def is_separable(self): - return self.child.is_separable() + return self._child.inverted() class AffineBase(Transform): @@ -406,10 +455,13 @@ Transform.__init__(self) self._inverted = None + def is_affine(self): + return True + def __array__(self, *args, **kwargs): return self.get_matrix() - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._inverted = None [EMAIL PROTECTED] @@ -425,10 +477,14 @@ def get_matrix(self): raise NotImplementedError() - def transform_without_affine(self, points): - # MGDTODO: Should we copy the points here? I'd like to avoid it, - # if possible - return points, self + def transform_affine(self, points): + return self.transform(points) + + def transform_non_affine(self, points): + return points + + def get_affine(self): + return self class Affine1DBase(AffineBase): @@ -437,13 +493,16 @@ def __init__(self): AffineBase.__init__(self) - + + def is_separable(self): + return True + def __array__(self, *args, **kwargs): return self.get_matrix() - + def to_values(self): mtx = self.get_matrix() - return tuple(mtx[0]) + return mtx[0] [EMAIL PROTECTED] def matrix_from_values(a, b): @@ -472,9 +531,6 @@ points = ma.asarray(values, npy.float_) return points * mtx[0,0] + mtx[0,1] - def is_separable(self): - return True - def inverted(self): if self._inverted is None: mtx = self.get_matrix() @@ -551,9 +607,12 @@ class IntervalTransform(Affine1DBase): def __init__(self, bbox, direction): + assert direction in ('x', 'y') + assert bbox.is_bbox() + Affine1DBase.__init__(self) self._bbox = bbox - self._direction = direction + self._direction = "interval" + direction self.set_children(['_bbox']) self._mtx = None @@ -561,10 +620,9 @@ return "IntervalTransform(%s)" % (getattr(self._bbox, self._direction)) __str__ = __repr__ - def _do_invalidation(self): - print "IntervalTransform.invalidation", self._bbox + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine1DBase._do_invalidation(self) + Affine1DBase._do_invalidation(self, which_child, affine_only) def get_matrix(self): if self._mtx is None: @@ -581,6 +639,10 @@ def __init__(self): AffineBase.__init__(self) + def is_separable(self): + mtx = self.get_matrix() + return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + def __array__(self, *args, **kwargs): return self.get_matrix() @@ -627,10 +689,6 @@ mtx = self.get_matrix() self._inverted = Affine2D(inv(mtx)) return self._inverted - - def is_separable(self): - mtx = self.get_matrix() - return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 class Affine2D(Affine2DBase): @@ -728,6 +786,10 @@ """ _mtx = npy.identity(3) + def __repr__(self): + return "IdentityTransform()" + __str__ = __repr__ + def __cmp__(self, other): if (isinstance(other, Affine2D) and (other == IDENTITY)): @@ -735,34 +797,44 @@ return -1 def get_matrix(self): - return _mtx + return self._mtx def transform(self, points): return points - def transform_without_affine(self, points): - return points, self + def transform_affine(self, points): + return points + def transform_non_affine(self, points): + return points + + def get_affine(self): + return self + def inverted(self): return self + -IDENTITY = Affine2D() - class BlendedGenericTransform(Transform): input_dims = 2 output_dims = 2 def __init__(self, x_transform, y_transform): # Here we ask: "Does it blend?" - # MGDTODO: Turn these checks back on - # assert x_transform.is_separable() - # assert y_transform.is_separable() + assert x_transform.is_separable() + assert y_transform.is_separable() Transform.__init__(self) self._x = x_transform self._y = y_transform self.set_children(['_x', '_y']) + def is_affine(self): + return self._x.is_affine() and self._y.is_affine() + + def is_separable(self): + return True + def __repr__(self): return "BlendedGenericTransform(%s,%s)" % (self._x, self._y) __str__ = __repr__ @@ -787,33 +859,17 @@ return ma.concatenate((x_points, y_points), 1) - def transform_without_affine(self, points): - x = self._x - y = self._y - if x == y and x.input_dims == 2: - return self._x.transform_without_affine(points) - - if x.input_dims == 2: - x_points, x_affine = x.transform_without_affine(points) - x_points = x_points[:, 0:1] - else: - x_points, x_affine = x.transform_without_affine(points[:, 0]) - x_points = x_points.reshape((len(x_points), 1)) - - if y.input_dims == 2: - y_points, y_affine = y.transform_without_affine(points) - y_points = y_points[:, 1:] - else: - y_points, y_affine = y.transform_without_affine(points[:, 1]) - y_points = y_points.reshape((len(y_points), 1)) - - return ma.concatenate((x_points, y_points), 1), blended_transform_factory(x_affine, y_affine) + def transform_affine(self, points): + return points + + def transform_non_affine(self, points): + return self.transform(points) + + def get_affine(self): + return IdentityTransform() def inverted(self): return BlendedGenericTransform(self._x.inverted(), self._y.inverted()) - - def is_separable(self): - return True class BlendedAffine1D(Affine2DBase, Transform): @@ -829,17 +885,17 @@ Affine2DBase.__init__(self) self._mtx = None + def is_separable(self): + return True + def __repr__(self): return "BlendedAffine1D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine2DBase._do_invalidation(self) + Affine2DBase._do_invalidation(self, which_child, affine_only) - def is_separable(self): - return True - def get_matrix(self): if self._mtx is None: x_mtx = self._x.get_matrix() @@ -854,9 +910,9 @@ def __init__(self, x_transform, y_transform): assert x_transform.is_affine() assert y_transform.is_affine() - # MGDTODO: Turn these checks back on - # assert x_transform.is_separable() - # assert y_transform.is_separable() + assert x_transform.is_separable() + assert y_transform.is_separable() + Transform.__init__(self) self._x = x_transform self._y = y_transform @@ -865,17 +921,17 @@ Affine2DBase.__init__(self) self._mtx = None + def is_separable(self): + return True + def __repr__(self): return "BlendedAffine2D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine2DBase._do_invalidation(self) + Affine2DBase._do_invalidation(self, which_child, affine_only) - def is_separable(self): - return True - def get_matrix(self): if self._mtx is None: if self._x == self._y: @@ -909,8 +965,12 @@ self._b = b self.set_children(['_a', '_b']) - self.take_shortcut = b.is_affine() - + def is_affine(self): + return self._a.is_affine() and self._b.is_affine() + + def is_separable(self): + return self._a.is_separable() and self._b.is_separable() + def __repr__(self): return "CompositeGenericTransform(%s, %s)" % (self._a, self._b) __str__ = __repr__ @@ -918,20 +978,24 @@ def transform(self, points): return self._b.transform(self._a.transform(points)) - def transform_without_affine(self, points): - if self.take_shortcut: - return self._a.transform(points), self._b - return self.transform(points), IDENTITY + def transform_affine(self, points): + return self._b.transform_affine(self._a.transform_affine(points)) + + def transform_non_affine(self, points): + return self._b.transform_non_affine(self._a.transform_non_affine(points)) + + def get_affine(self): + return self._a.get_affine() + self._b.get_affine() def inverted(self): return CompositeGenericTransform(self._b.inverted(), self._a.inverted()) - - def is_separable(self): - return self._a.is_separable() and self._b.is_separable() class CompositeAffine2D(Affine2DBase): def __init__(self, a, b): + assert a.output_dims == b.input_dims + self.input_dims = a.input_dims + self.output_dims = b.output_dims assert a.is_affine() assert b.is_affine() @@ -945,9 +1009,9 @@ return "CompositeAffine2D(%s, %s)" % (self._a, self._b) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine2DBase._do_invalidation(self) + Affine2DBase._do_invalidation(self, which_child, affine_only) def get_matrix(self): if self._mtx is None: @@ -958,7 +1022,9 @@ def composite_transform_factory(a, b): - if a.is_affine() and b.is_affine(): + if isinstance(a, BboxTransform) and isinstance(b, BboxTransform): + return BboxTransform(a._boxin, b._boxout) + if isinstance(a, AffineBase) and isinstance(b, AffineBase): return CompositeAffine2D(a, b) return CompositeGenericTransform(a, b) @@ -972,33 +1038,6 @@ return npy.log10(m) -class TestLogTransform(Transform): - input_dims = 1 - output_dims = 1 - def transform(self, a): - marray = ma.masked_where(a <= 0.0, a * 10.0) - return (npy.log10(marray) * 0.5) + 0.5 - - def inverted(self): - return TestInvertLogTransform() - - def is_separable(self): - return True - - -class TestInvertLogTransform(Transform): - input_dims = 1 - output_dims = 1 - def transform(self, a): - return ma.power(10, (a - 0.5) * 2.0) / 10.0 - - def inverted(self): - return TestLogTransform() - - def is_separable(self): - return True - - class TestPolarTransform(Transform): input_dims = 2 output_dims = 2 @@ -1078,9 +1117,9 @@ return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine2DBase._do_invalidation(self) + Affine2DBase._do_invalidation(self, which_child, affine_only) def is_separable(self): return True @@ -1101,7 +1140,38 @@ self._mtx = affine._mtx return self._mtx + +class TransformedPath(TransformNode): + def __init__(self, path, transform): + assert isinstance(transform, Transform) + TransformNode.__init__(self) + + self._path = path + self._transform = transform + self.set_children(['_transform']) + self._transformed_path = None + + def _do_invalidation(self, which_child, affine_only): + if not (affine_only[0].is_affine() or affine_only[0].is_bbox()): + self._transformed_path = None + + def get_path_and_affine(self): + if self._transformed_path is None: + vertices = self._transform.transform_non_affine(self._path.vertices) + self._transformed_path = Path(vertices, self._path.codes) + return self._transformed_path, self._transform.get_affine() + + def get_path(self): + if self._transformed_path is None: + vertices = self._tranform.transform_non_affine(self._path.vertices) + self._transformed_path = Path(vertices, self._path.codes) + vertices = self._transform.transform_affine(self._transformed_path.vertices) + return Path(vertices, self._transformed_path.codes) + + def get_affine(self): + return self._transform.get_affine() + def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): ''' Ensure the endpoints of a range are not too close together. @@ -1128,6 +1198,7 @@ vmin, vmax = vmax, vmin return vmin, vmax + # MGDTODO: Optimize (perhaps in an extension) def interval_contains(interval, val): return interval[0] <= val and interval[1] >= val @@ -1173,8 +1244,8 @@ bbox = Bbox.from_lbwh(10, 11, 12, 13) assert bbox.bounds == (10, 11, 12, 13) - bbox_copy = copy.copy(bbox) - assert bbox == bbox_copy + bbox_copy = copy.deepcopy(bbox) + assert (bbox.lbrt == bbox_copy.lbrt).all() bbox_copy.max = (14, 15) assert bbox.bounds == (10, 11, 12, 13) assert bbox_copy.bounds == (10, 11, 4, 4) @@ -1183,7 +1254,7 @@ bbox2 = Bbox([[30., 35.], [40., 45.]]) trans = BboxTransform(bbox1, bbox2) bbox3 = bbox1.transformed(trans) - assert bbox3 == bbox2 + assert (bbox3.lbrt == bbox2.lbrt).all() translation = Affine2D().translate(10, 20) assert translation.to_values() == (1, 0, 0, 1, 10, 20) @@ -1210,12 +1281,6 @@ print points - comp = TestLogTransform() + Affine2D().rotate_deg(15) - tpoints = comp.transform(points) - itpoints = comp.inverted().transform(tpoints) - print tpoints, itpoints - assert (points.round() == itpoints.round()).all() - # Here are some timing tests points = npy.asarray([(random(), random()) for i in xrange(10000)]) t = timeit.Timer("trans_sum.transform(points)", "from __main__ import trans_sum, points") This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------- This SF.net email is sponsored by: Microsoft Defy all challenges. Microsoft(R) Visual Studio 2005. http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/ _______________________________________________ Matplotlib-checkins mailing list Matplotlib-checkins@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins