Revision: 3872
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3872&view=rev
Author:   mdboom
Date:     2007-09-21 09:52:50 -0700 (Fri, 21 Sep 2007)

Log Message:
-----------
Further progress on arbitrary transformations -- zooming and panning
now works without any log-scale-specific hacks.  (Though the
underlying model is slightly wrong.)
Added graphviz output support for debugging transformation trees.
Masked array handling much more robust.

Modified Paths:
--------------
    branches/transforms/lib/matplotlib/axes.py
    branches/transforms/lib/matplotlib/backend_bases.py
    branches/transforms/lib/matplotlib/lines.py
    branches/transforms/lib/matplotlib/path.py
    branches/transforms/lib/matplotlib/transforms.py

Modified: branches/transforms/lib/matplotlib/axes.py
===================================================================
--- branches/transforms/lib/matplotlib/axes.py  2007-09-21 15:33:18 UTC (rev 
3871)
+++ branches/transforms/lib/matplotlib/axes.py  2007-09-21 16:52:50 UTC (rev 
3872)
@@ -637,10 +637,14 @@
 #             self.viewLim, self.bbox)
         self.preDataTransform = mtransforms.BboxTransform(
             self.viewLim, mtransforms.Bbox.unit())
-        self.dataTransform = mtransforms.TestLogTransform()
-        # self.dataTransform = mtransforms.Affine2D().scale(1.5)
+#        self.dataTransform = mtransforms.TestPolarTransform()
+#         self.dataTransform = mtransforms.blended_transform_factory(
+#             mtransforms.TestLogTransform(),
+#             mtransforms.Affine2D())
+        self.dataTransform = mtransforms.Affine2D()
         self.transData = self.preDataTransform + self.dataTransform + 
mtransforms.BboxTransform(
             mtransforms.Bbox.unit(), self.bbox)
+        self.transData.make_graphviz(open("trans.dot", "w"))
         
            
     def get_position(self, original=False):
@@ -1523,7 +1527,7 @@
         'return the xaxis scale string: log or linear'
        # MGDTODO
         # return self.scaled[self.transData.get_funcx().get_type()]
-       return 'linear'
+       return 'log'
 
     def set_xscale(self, value, basex = 10, subsx=None):
         """

Modified: branches/transforms/lib/matplotlib/backend_bases.py
===================================================================
--- branches/transforms/lib/matplotlib/backend_bases.py 2007-09-21 15:33:18 UTC 
(rev 3871)
+++ branches/transforms/lib/matplotlib/backend_bases.py 2007-09-21 16:52:50 UTC 
(rev 3872)
@@ -1655,60 +1655,30 @@
             #multiple button can get pressed during motion...
             if self._button_pressed==1:
                inverse = trans.inverted()
-                lastx, lasty = inverse.transform_point((lastx, lasty))
-                x, y = inverse.transform_point( (event.x, event.y) )
-                if a.get_xscale()=='log':
-                    dx=1-lastx/x
-                else:
-                    dx=x-lastx
-                if a.get_yscale()=='log':
-                    dy=1-lasty/y
-                else:
-                    dy=y-lasty
-
-                dx,dy=format_deltas(event,dx,dy)
-
-                if a.get_xscale()=='log':
-                    xmin *= 1-dx
-                    xmax *= 1-dx
-                else:
-                    xmin -= dx
-                    xmax -= dx
-                if a.get_yscale()=='log':
-                    ymin *= 1-dy
-                    ymax *= 1-dy
-                else:
-                    ymin -= dy
-                    ymax -= dy
+                dx, dy = event.x - lastx, event.y - lasty
+                dx, dy = format_deltas(event, dx, dy)
+                delta = npy.array([[dx, dy], [dx, dy]], npy.float_)
+                bbox = transforms.Bbox(a.bbox.get_points() - delta)
+                result = bbox.transformed(inverse)
             elif self._button_pressed==3:
                 try:
+                    inverse = trans.inverted()
                     dx=(lastx-event.x)/float(a.bbox.width)
                     dy=(lasty-event.y)/float(a.bbox.height)
-                    dx,dy=format_deltas(event,dx,dy)
-                    if a.get_aspect() != 'auto':
-                        dx = 0.5*(dx + dy)
-                        dy = dx
-                    alphax = pow(10.0,dx)
-                    alphay = pow(10.0,dy)#use logscaling, avoid singularities 
and smother scaling...
-                   inverse = trans.inverted()
-                    lastx, lasty = inverse.transform_point( (lastx, lasty) )
-                    if a.get_xscale()=='log':
-                        xmin = lastx*(xmin/lastx)**alphax
-                        xmax = lastx*(xmax/lastx)**alphax
-                    else:
-                        xmin = lastx+alphax*(xmin-lastx)
-                        xmax = lastx+alphax*(xmax-lastx)
-                    if a.get_yscale()=='log':
-                        ymin = lasty*(ymin/lasty)**alphay
-                        ymax = lasty*(ymax/lasty)**alphay
-                    else:
-                        ymin = lasty+alphay*(ymin-lasty)
-                        ymax = lasty+alphay*(ymax-lasty)
+                    alphax = pow(10.0, dx)
+                    alphay = pow(10.0, dy)
+                    # MGDTODO: Make better use of numpy
+                    lastx, lasty = inverse.transform_point((lastx, lasty))
+                    xmin = (lastx + alphax * (xmin - lastx))
+                    xmax = (lastx + alphax * (xmax - lastx))
+                    ymin = (lasty + alphay * (ymin - lasty))
+                    ymax = (lasty + alphay * (ymax - lasty))
+                    result = transforms.Bbox.from_lbrt(xmin, ymin, xmax, ymax)
                 except OverflowError:
                     warnings.warn('Overflow while panning')
                     return
-            a.set_xlim(xmin, xmax)
-            a.set_ylim(ymin, ymax)
+            a.set_xlim(*result.intervalx)
+            a.set_ylim(*result.intervaly)
 
         self.dynamic_update()
 

Modified: branches/transforms/lib/matplotlib/lines.py
===================================================================
--- branches/transforms/lib/matplotlib/lines.py 2007-09-21 15:33:18 UTC (rev 
3871)
+++ branches/transforms/lib/matplotlib/lines.py 2007-09-21 16:52:50 UTC (rev 
3872)
@@ -25,6 +25,53 @@
 (TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN,
     CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN) = range(8)
 
+def unmasked_index_ranges(mask, compressed = True):
+    '''
+    Calculate the good data ranges in a masked 1-D npy.array, based on mask.
+
+    Returns Nx2 npy.array with each row the start and stop indices
+    for slices of the compressed npy.array corresponding to each of N
+    uninterrupted runs of unmasked values.
+    If optional argument compressed is False, it returns the
+    start and stop indices into the original npy.array, not the
+    compressed npy.array.
+    Returns None if there are no unmasked values.
+
+    Example:
+
+    y = ma.array(npy.arange(5), mask = [0,0,1,0,0])
+    #ii = unmasked_index_ranges(y.mask())
+    ii = unmasked_index_ranges(ma.getmask(y))
+        # returns [[0,2,] [2,4,]]
+
+    y.compressed().filled()[ii[1,0]:ii[1,1]]
+        # returns npy.array [3,4,]
+        # (The 'filled()' method converts the masked npy.array to a numerix 
npy.array.)
+
+    #i0, i1 = unmasked_index_ranges(y.mask(), compressed=False)
+    i0, i1 = unmasked_index_ranges(ma.getmask(y), compressed=False)
+        # returns [[0,3,] [2,5,]]
+
+    y.filled()[ii[1,0]:ii[1,1]]
+        # returns npy.array [3,4,]
+
+    '''
+    m = npy.concatenate(((1,), mask, (1,)))
+    indices = npy.arange(len(mask) + 1)
+    mdif = m[1:] - m[:-1]
+    i0 = npy.compress(mdif == -1, indices)
+    i1 = npy.compress(mdif == 1, indices)
+    assert len(i0) == len(i1)
+    if len(i1) == 0:
+        return None
+    if not compressed:
+        return npy.concatenate((i0[:, npy.newaxis], i1[:, npy.newaxis]), 
axis=1)
+    seglengths = i1 - i0
+    breakpoints = npy.cumsum(seglengths)
+    ic0 = npy.concatenate(((0,), breakpoints[:-1]))
+    ic1 = breakpoints
+    return npy.concatenate((ic0[:, npy.newaxis], ic1[:, npy.newaxis]), axis=1)
+
 def segment_hits(cx,cy,x,y,radius):
     """Determine if any line segments are within radius of a point. Returns
     the list of line segments that are within that radius.
@@ -302,7 +349,7 @@
         self._picker = p
 
     def get_window_extent(self, renderer):
-        xy = self.get_transform()(self._xy)
+        xy = self.get_transform().transform(self._xy)
 
        x = xy[:, 0]
        y = xy[:, 1]
@@ -343,9 +390,6 @@
         self._yorig = y
         self.recache()
 
-    # MGDTODO: Masked data arrays are broken
-    _masked_array_to_path_code_mapping = npy.array(
-        [Path.LINETO, Path.MOVETO, Path.MOVETO], Path.code_type)
     def recache(self):
         #if self.axes is None: print 'recache no axes'
         #else: print 'recache units', self.axes.xaxis.units, 
self.axes.yaxis.units
@@ -363,24 +407,15 @@
         if len(x) != len(y):
             raise RuntimeError('xdata and ydata must be the same length')
 
-       self._xy = npy.vstack((npy.asarray(x, npy.float_),
-                              npy.asarray(y, npy.float_))).transpose()
+        x = x.reshape((len(x), 1))
+        y = y.reshape((len(y), 1))
+            
+       self._xy = ma.concatenate((x, y), 1)
        self._x = self._xy[:, 0] # just a view
        self._y = self._xy[:, 1] # just a view
         self._logcache = None
-
-        mx = ma.getmask(x)
-        my = ma.getmask(y)
-        mask = ma.mask_or(mx, my)
-        codes = None
-        if mask is not ma.nomask:
-            m = npy.concatenate(((1,), mask, (1,)))
-            mdif = m[1:] - m[:-1]
-            mdif = npy.maximum((mdif[:-1] * -2), mask)
-            codes = npy.take(
-                self._masked_array_to_path_code_mapping,
-                mdif)
-        self._path = Path(self._xy, codes, closed=False)
+        # Masked arrays are now handled by the Path class itself
+        self._path = Path(self._xy, closed=False)
         # MGDTODO: If _draw_steps is removed, remove the following line also
         self._step_path = None
         

Modified: branches/transforms/lib/matplotlib/path.py
===================================================================
--- branches/transforms/lib/matplotlib/path.py  2007-09-21 15:33:18 UTC (rev 
3871)
+++ branches/transforms/lib/matplotlib/path.py  2007-09-21 16:52:50 UTC (rev 
3872)
@@ -1,4 +1,5 @@
 import numpy as npy
+from numpy import ma as ma
 
 class Path(object):
     # Path codes
@@ -21,10 +22,8 @@
     code_type = npy.uint8
     
     def __init__(self, vertices, codes=None, closed=True):
-        vertices = npy.asarray(vertices, npy.float_)
-       assert vertices.ndim == 2
-       assert vertices.shape[1] == 2
-        
+        vertices = ma.asarray(vertices, npy.float_)
+
        if codes is None:
            if closed:
                codes = self.LINETO * npy.ones(
@@ -41,10 +40,27 @@
             assert codes.ndim == 1
             assert len(codes) == len(vertices)
 
+        # The path being passed in may have masked values.  However,
+        # the backends are not expected to deal with masked arrays, so
+        # we must remove them from the array (using compressed), and
+        # add MOVETO commands to the codes array accordingly.
+        mask = ma.getmask(vertices)
+        if mask is not ma.nomask:
+            mask1d = ma.mask_or(mask[:, 0], mask[:, 1])
+            vertices = ma.compress(npy.invert(mask1d), vertices, 0)
+            codes = npy.where(npy.concatenate((mask1d[-1:], mask1d[:-1])),
+                              self.MOVETO, codes)
+            codes = ma.masked_array(codes, mask=mask1d).compressed()
+            codes = npy.asarray(codes, self.code_type)
+
+        vertices = npy.asarray(vertices, npy.float_)
+
+        assert vertices.ndim == 2
+        assert vertices.shape[1] == 2
+       assert codes.ndim == 1
+        
         self._codes = codes
        self._vertices = vertices
-        
-       assert self._codes.ndim == 1
 
     def __repr__(self):
        return "Path(%s, %s)" % (self.vertices, self.codes)
@@ -91,10 +107,11 @@
     def unit_regular_polygon(cls, numVertices):
        path = cls._unit_regular_polygons.get(numVertices)
        if path is None:
-           theta = 2*npy.pi/numVertices * npy.arange(numVertices)
-           # This is to make sure the polygon always "points-up"
+           theta = 2*npy.pi/numVertices * 
npy.arange(numVertices).reshape((numVertices, 1))
+           # This initial rotation is to make sure the polygon always
+            # "points-up"
            theta += npy.pi / 2.0
-           verts = npy.vstack((npy.cos(theta), npy.sin(theta))).transpose()
+           verts = npy.concatenate((npy.cos(theta), npy.sin(theta)))
            path = Path(verts)
            cls._unit_regular_polygons[numVertices] = path
        return path

Modified: branches/transforms/lib/matplotlib/transforms.py
===================================================================
--- branches/transforms/lib/matplotlib/transforms.py    2007-09-21 15:33:18 UTC 
(rev 3871)
+++ branches/transforms/lib/matplotlib/transforms.py    2007-09-21 16:52:50 UTC 
(rev 3872)
@@ -5,6 +5,7 @@
 """
 
 import numpy as npy
+from numpy import ma as ma
 from numpy.linalg import inv
 from sets import Set
 
@@ -19,6 +20,7 @@
 class TransformNode(object):
     def __init__(self):
         self._parents = Set()
+        self._children = []
         
     def invalidate(self):
         self._do_invalidation()
@@ -33,7 +35,34 @@
             getattr(self, child)._parents.add(self)
         self._children = children
 
+    def make_graphviz(self, fobj):
+        def recurse(root):
+            fobj.write('%s [label="%s"];\n' %
+                       (hash(root), root.__class__.__name__))
+            if isinstance(root, Affine2DBase):
+                fobj.write('%s [style=filled, color=".7 .7 .9"];\n' %
+                           hash(root))
+            elif isinstance(root, BboxBase):
+                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" % (
+                        hash(root),
+                        hash(child)))
+                recurse(child)
         
+        fobj.write("digraph G {\n")
+        recurse(self)
+        fobj.write("}\n")
+        
+    def is_affine(self):
+        return isinstance(self, Affine2DBase)
+
+    def is_bbox(self):
+        return isinstance(self, BboxBase)
+    
+        
 class BboxBase(TransformNode):
     '''
     This is the read-only part of a bounding-box
@@ -169,12 +198,6 @@
         return Bbox(points)
     from_lbrt = staticmethod(from_lbrt)
     
-    def __copy__(self):
-        return Bbox(self._points.copy())
-
-    def __deepcopy__(self, memo):
-        return Bbox(self._points.copy())
-    
     def __cmp__(self, other):
         # MGDTODO: Totally suboptimal
         if isinstance(other, Bbox) and (self._points == other._points).all():
@@ -274,6 +297,8 @@
         """
         Return the Bbox that bounds all bboxes
         """
+        # MGDTODO: There's got to be a way to utilize numpy here
+        # to make this faster...
         assert(len(bboxes))
 
         if len(bboxes) == 1:
@@ -297,7 +322,7 @@
     
 class TransformedBbox(BboxBase):
     def __init__(self, bbox, transform):
-        assert isinstance(bbox, Bbox)
+        assert bbox.is_bbox()
         assert isinstance(transform, Transform)
 
         BboxBase.__init__(self)
@@ -353,9 +378,6 @@
     def is_separable(self):
         return False
 
-    def is_affine(self):
-        return False
-
     
 class Affine2DBase(Transform):
     input_dims = 2
@@ -416,9 +438,9 @@
 #             print "".join(traceback.format_stack())
 #             print points
         mtx = self.get_matrix()
-        points = npy.asarray(points, npy.float_)
+        points = ma.asarray(points, npy.float_)
         points = points.transpose()
-        points = npy.dot(mtx[0:2, 0:2], points)
+        points = ma.dot(mtx[0:2, 0:2], points)
         points = points + mtx[0:2, 2:]
         return points.transpose()
 
@@ -437,9 +459,6 @@
         mtx = self.get_matrix()
         return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0
 
-    def is_affine(self):
-        return True
-
         
 class Affine2D(Affine2DBase):
     def __init__(self, matrix = None):
@@ -469,12 +488,6 @@
             return 0
         return -1
     
-    def __copy__(self):
-        return Affine2D(self._mtx.copy())
-    
-    def __deepcopy__(self, memo):
-        return Affine2D(self._mtx.copy())
-    
     [EMAIL PROTECTED]
     def from_values(a, b, c, d, e, f):
         return Affine2D(Affine2D.matrix_from_values(a, b, c, d, e, f))
@@ -542,9 +555,31 @@
         mtx = self.get_matrix()
         return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0
 
-    def is_affine(self):
-        return True
 
+class IdentityTransform(Affine2DBase):
+    """
+    A special class that does the identity transform quickly.
+    """
+    _mtx = npy.identity(3)
+
+    def __cmp__(self, other):
+        if (isinstance(other, Affine2D) and
+            (other == IDENTITY)):
+            return 0
+        return -1
+
+    def get_matrix(self):
+        return _mtx
+    
+    def transform(self, points):
+        return points
+
+    def transform_without_affine(self, points):
+        return points, self
+
+    def inverted(self):
+        return self
+    
 IDENTITY = Affine2D()
     
 class BlendedGenericTransform(Transform):
@@ -553,10 +588,9 @@
 
     def __init__(self, x_transform, y_transform):
        # Here we ask: "Does it blend?"
-        assert x_transform.is_separable()
-        assert y_transform.is_separable()
-        assert x_transform.input_dims == x_transform.output_dims == 2
-        assert y_transform.input_dims == y_transform.output_dims == 2
+        # MGDTODO: Turn these checks back on
+        # assert x_transform.is_separable()
+        # assert y_transform.is_separable()
         
         Transform.__init__(self)
         self._x = x_transform
@@ -576,16 +610,18 @@
             return self._x(points)
 
         if x.input_dims == 2:
-            x_points = x.transform(points)[:, 0]
+            x_points = x.transform(points)[:, 0:1]
         else:
             x_points = x.transform(points[:, 0])
-
+            x_points = x_points.reshape((len(x_points), 1))
+            
         if y.input_dims == 2:
-            y_points = y.transform(points)[:, 1]
+            y_points = y.transform(points)[:, 1:]
         else:
             y_points = y.transform(points[:, 1])
+            y_points = y_points.reshape((len(y_points), 1))
 
-        return npy.vstack((x_points, y_points)).transpose()
+        return ma.concatenate((x_points, y_points), 1)
 
     def inverted(self):
         return BlendedGenericTransform(self._x.inverted(), self._y.inverted())
@@ -598,6 +634,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()
         Transform.__init__(self)
         self._x = x_transform
         self._y = y_transform
@@ -649,6 +688,8 @@
         self._b = b
         self.set_children(['_a', '_b'])
 
+        self.take_shortcut = b.is_affine()
+
     def __repr__(self):
         return "CompositeGenericTransform(%s, %s)" % (self._a, self._b)
     __str__ = __repr__
@@ -656,11 +697,15 @@
     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 inverted(self):
         return CompositeGenericTransform(self._b.inverted(), 
self._a.inverted())
     
     def is_separable(self):
-        return True
         return self._a.is_separable() and self._b.is_separable()
 
     
@@ -702,35 +747,81 @@
     output_dims = 1
     
     def transform(self, a):
-        m = npy.ma.masked_where(a < 0, a)
+        m = ma.masked_where(a < 0, a)
         return npy.log10(m)
 
 
 class TestLogTransform(Transform):
-    input_dims = 2
-    output_dims = 2
+    input_dims = 1
+    output_dims = 1
     def transform(self, xy):
-        marray = npy.ma.masked_where(xy <= 0.0, xy * 10.0)
-        return npy.log10(marray)
+        marray = ma.masked_where(xy <= 0.0, xy * 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 = 2
-    output_dims = 2
+    input_dims = 1
+    output_dims = 1
     def transform(self, xy):
-        return npy.power(10, xy) / 10.0
+        return ma.power(10, (xy - 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
+
+    def transform(self, xy):
+        debug = len(xy) > 4
+        x = xy[:, 0:1]
+        y = xy[:, 1:]
+        x, y = ((y * npy.cos(x)) + 1.0) * 0.5, ((y * npy.sin(x)) + 1.0) * 0.5
+        if debug:
+            print npy.min(xy[:, 0:1]), npy.max(xy[:, 0:1]), npy.min(xy[:, 
1:]), npy.max(xy[:, 1:])
+            print x.min(), x.max(), y.min(), y.max()
+        return ma.concatenate((x, y), 1)
+
+    def inverted(self):
+        return TestInvertPolarTransform()
     
+    def is_separable(self):
+        return False
+
+
+class TestInvertPolarTransform(Transform):
+    input_dims = 2
+    output_dims = 2
+
+    def transform(self, xy):
+        x = xy[:, 0:1]
+        y = xy[:, 1:]
+        r = ma.sqrt(ma.power(x, 2) + ma.power(y, 2))
+        theta = ma.arccos(x / r)
+        theta = ma.where(y < 0, 2 * npy.pi - theta, theta)
+        return ma.concatenate((theta / (npy.pi * 2), r), 1)
+
+    def inverted(self):
+        return TestInvertPolarTransform()
+    
+    def is_separable(self):
+        return False
+    
+    
 class BboxTransform(Affine2DBase):
     def __init__(self, boxin, boxout):
-        assert isinstance(boxin, BboxBase)
-        assert isinstance(boxout, BboxBase)
+        assert boxin.is_bbox()
+        assert boxout.is_bbox()
 
         Affine2DBase.__init__(self)
         self._boxin = boxin


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

Reply via email to