Revision: 4286
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=4286&view=rev
Author:   mdboom
Date:     2007-11-14 10:44:39 -0800 (Wed, 14 Nov 2007)

Log Message:
-----------
New path-related utilities (used for an aborted attempt at fixing
contouring -- may be useful in other contexts in the future).

Modified Paths:
--------------
    branches/transforms/lib/matplotlib/patches.py
    branches/transforms/lib/matplotlib/path.py
    branches/transforms/src/_path.cpp

Modified: branches/transforms/lib/matplotlib/patches.py
===================================================================
--- branches/transforms/lib/matplotlib/patches.py       2007-11-14 18:43:35 UTC 
(rev 4285)
+++ branches/transforms/lib/matplotlib/patches.py       2007-11-14 18:44:39 UTC 
(rev 4286)
@@ -152,7 +152,7 @@
         ACCEPTS: any matplotlib color
         """
         self._facecolor = color
-
+        
     def set_linewidth(self, w):
         """
         Set the patch linewidth in points
@@ -522,6 +522,28 @@
     def get_patch_transform(self):
         return self._poly_transform
        
+class PathPatch(Patch):
+    """
+    A general polycurve path patch.
+    """
+    def __str__(self):
+        return "Poly((%g, %g) ...)" % tuple(self._path.vertices[0])
+
+    def __init__(self, path, **kwargs):
+        """
+        path is a Path object
+        
+        Valid kwargs are:
+        %(Patch)s
+        See Patch documentation for additional kwargs
+        """
+        Patch.__init__(self, **kwargs)
+       self._path = path
+    __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd
+
+    def get_path(self):
+       return self._path
+
 class Polygon(Patch):
     """
     A general polygon patch.
@@ -549,7 +571,7 @@
     def _set_xy(self, vertices):
         self._path = Path(vertices)
     xy = property(_get_xy, _set_xy)
-        
+    
 class Wedge(Patch):
     def __str__(self):
         return "Wedge(%g,%g)"%self.xy[0]

Modified: branches/transforms/lib/matplotlib/path.py
===================================================================
--- branches/transforms/lib/matplotlib/path.py  2007-11-14 18:43:35 UTC (rev 
4285)
+++ branches/transforms/lib/matplotlib/path.py  2007-11-14 18:44:39 UTC (rev 
4286)
@@ -11,8 +11,8 @@
 from matplotlib.numerix import npyma as ma
 
 from matplotlib._path import point_in_path, get_path_extents, \
-    point_in_path_collection
-import matplotlib._path as _path
+    point_in_path_collection, get_path_collection_extents, \
+    path_in_path
 from matplotlib.cbook import simple_linear_interpolation
 
 KAPPA = 4.0 * (npy.sqrt(2) - 1) / 3.0
@@ -128,6 +128,30 @@
         self.codes = codes
        self.vertices = vertices
 
+    [EMAIL PROTECTED]
+    def make_compound_path(*args):
+        """
+        Make a compound path from a list of Path objects.  Only
+        polygons (not curves) are supported.
+        """
+        for p in args:
+            assert p.codes is None
+
+        lengths = [len(x) for x in args]
+        total_length = sum(lengths)
+
+        vertices = npy.vstack([x.vertices for x in args])
+        vertices.reshape((total_length, 2))
+        
+        codes = Path.LINETO * npy.ones(total_length)
+        i = 0
+        for length in lengths:
+            codes[i] = Path.MOVETO
+            i += length
+            
+        return Path(vertices, codes)
+    make_compound_path = staticmethod(make_compound_path)
+    
     def __repr__(self):
        return "Path(%s, %s)" % (self.vertices, self.codes)
 
@@ -186,9 +210,18 @@
         """
         if transform is None:
             from transforms import IdentityTransform
-            transform = IdentityTransform
+            transform = IdentityTransform()
         return point_in_path(point[0], point[1], self, transform.frozen())
 
+    def contains_path(self, path, transform=None):
+        """
+        Returns True if this path completely contains the given path.
+        """
+        if transform is None:
+            from transforms import IdentityTransform
+            transform = IdentityTransform()
+        return path_in_path(self, IdentityTransform(), path, transform)
+        
     def get_extents(self, transform=None):
         """
         Returns the extents (xmin, ymin, xmax, ymax) of the path.
@@ -408,8 +441,9 @@
         return cls.arc(theta1, theta2, True)
     wedge = classmethod(wedge)
 
+_get_path_collection_extents = get_path_collection_extents
 def get_path_collection_extents(*args):
     from transforms import Bbox
     if len(args[1]) == 0:
         raise ValueError("No paths provided")
-    return Bbox.from_extents(*_path.get_path_collection_extents(*args))
+    return Bbox.from_extents(*_get_path_collection_extents(*args))

Modified: branches/transforms/src/_path.cpp
===================================================================
--- branches/transforms/src/_path.cpp   2007-11-14 18:43:35 UTC (rev 4285)
+++ branches/transforms/src/_path.cpp   2007-11-14 18:44:39 UTC (rev 4286)
@@ -9,6 +9,8 @@
 #include "agg_path_storage.h"
 #include "agg_trans_affine.h"
 
+// MGDTODO: Un-CXX-ify this module
+
 // the extension module
 class _path_module : public Py::ExtensionModule<_path_module>
 {
@@ -26,6 +28,10 @@
                       "get_path_collection_extents(trans, paths, transforms, 
offsets, offsetTrans)");
     add_varargs_method("point_in_path_collection", 
&_path_module::point_in_path_collection,
                       "point_in_path_collection(x, y, r, trans, paths, 
transforms, offsets, offsetTrans, filled)");
+    add_varargs_method("path_in_path", &_path_module::path_in_path,
+                      "point_in_path_collection(a, atrans, b, btrans)");
+    add_varargs_method("clip_path_to_rect", &_path_module::clip_path_to_rect,
+                      "clip_path_to_rect(path, bbox, inside)");
 
     initialize("Helper functions for paths");
   }
@@ -39,6 +45,8 @@
   Py::Object get_path_extents(const Py::Tuple& args);
   Py::Object get_path_collection_extents(const Py::Tuple& args);
   Py::Object point_in_path_collection(const Py::Tuple& args);
+  Py::Object path_in_path(const Py::Tuple& args);
+  Py::Object clip_path_to_rect(const Py::Tuple& args);
 };
 
 //
@@ -85,11 +93,14 @@
   double x, y;
 
   path.rewind(0);
-  unsigned code = path.vertex(&x, &y);
-  if (code == agg::path_cmd_stop)
-    return false;
 
+  inside_flag = 0;
+
+  unsigned code = 0;
   while (true) {
+    if (code != agg::path_cmd_move_to)
+      code = path.vertex(&x, &y);
+
     sx = vtx0 = x;
     sy = vty0 = y;
 
@@ -104,11 +115,11 @@
       code = path.vertex(&x, &y);
 
       // The following cases denote the beginning on a new subpath
-      if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) {
+      if (code == agg::path_cmd_stop || (code & agg::path_cmd_end_poly) == 
agg::path_cmd_end_poly) {
        x = sx; y = sy;
       } else if (code == agg::path_cmd_move_to)
        break;
-      
+
       yflag1 = (vty1 >= ty);
       // Check if endpoints straddle (are on opposite sides) of X axis
       // (i.e. the Y's differ); if so, +X ray could intersect this edge.
@@ -146,26 +157,37 @@
        break;
     }
 
+    yflag1 = (vty1 >= ty);
+    if (yflag0 != yflag1) {
+      if ( ((vty1-ty) * (vtx0-vtx1) >=
+           (vtx1-tx) * (vty0-vty1)) == yflag1 ) {
+       inside_flag ^= 1;
+      }
+    }
+
     if (inside_flag != 0)
       return true;
 
     if (code == agg::path_cmd_stop)
-      return false;
+      break;
   }
 
-  return false;
+  return (inside_flag != 0);
 }
 
-bool point_in_path(double x, double y, PathIterator& path, agg::trans_affine& 
trans) {
+inline bool point_in_path(double x, double y, PathIterator& path, const 
agg::trans_affine& trans) {
   typedef agg::conv_transform<PathIterator> transformed_path_t;
   typedef agg::conv_curve<transformed_path_t> curve_t;
   
+  if (path.total_vertices() < 3)
+    return false;
+
   transformed_path_t trans_path(path, trans);
   curve_t curved_path(trans_path);
   return point_in_path_impl(x, y, curved_path);
 }
 
-bool point_on_path(double x, double y, double r, PathIterator& path, 
agg::trans_affine& trans) {
+inline bool point_on_path(double x, double y, double r, PathIterator& path, 
const agg::trans_affine& trans) {
   typedef agg::conv_transform<PathIterator> transformed_path_t;
   typedef agg::conv_curve<transformed_path_t> curve_t;
   typedef agg::conv_stroke<curve_t> stroke_t;
@@ -204,7 +226,7 @@
   return Py::Int(0);
 }
 
-void get_path_extents(PathIterator& path, agg::trans_affine& trans, 
+void get_path_extents(PathIterator& path, const agg::trans_affine& trans, 
                      double* x0, double* y0, double* x1, double* y1) {
   typedef agg::conv_transform<PathIterator> transformed_path_t;
   typedef agg::conv_curve<transformed_path_t> curve_t;
@@ -393,6 +415,248 @@
   return result;
 }
 
+bool path_in_path(PathIterator& a, const agg::trans_affine& atrans,
+                 PathIterator& b, const agg::trans_affine& btrans) {
+  typedef agg::conv_transform<PathIterator> transformed_path_t;
+  typedef agg::conv_curve<transformed_path_t> curve_t;
+
+  if (a.total_vertices() < 3)
+    return false;
+  
+  transformed_path_t b_path_trans(b, btrans);
+  curve_t b_curved(b_path_trans);
+
+  double x, y;
+  b_curved.rewind(0);
+  while (b_curved.vertex(&x, &y) != agg::path_cmd_stop) {
+    if (!::point_in_path(x, y, a, atrans))
+      return false;
+  }
+  
+  return true;
+}
+
+Py::Object _path_module::path_in_path(const Py::Tuple& args) {
+  args.verify_length(4);
+
+  PathIterator a(args[0]);
+  agg::trans_affine atrans = py_to_agg_transformation_matrix(args[1]);
+  PathIterator b(args[2]);
+  agg::trans_affine btrans = py_to_agg_transformation_matrix(args[3]);
+
+  return Py::Int(::path_in_path(a, atrans, b, btrans));
+}
+
+/** The clip_path_to_rect code here is a clean-room implementation of the
+    Sutherland-Hodgman clipping algorithm described here:
+  
+  http://en.wikipedia.org/wiki/Sutherland-Hodgman_clipping_algorithm
+*/
+
+typedef std::vector<std::pair<double, double> > Polygon;
+
+namespace clip_to_rect_filters {
+  /* There are four different passes needed to create/remove vertices
+     (one for each side of the rectangle).  The differences between those
+     passes are encapsulated in these functor classes.
+  */
+  struct bisectx {
+    double m_x;
+
+    bisectx(double x) : m_x(x) {}
+
+    void bisect(double sx, double sy, double px, double py, double* bx, 
double* by) const {
+      *bx = m_x;
+      double dx = px - sx;
+      double dy = py - sy;
+      *by = sy + dy * ((m_x - sx) / dx);
+    }
+  };
+
+  struct xlt : public bisectx {
+    xlt(double x) : bisectx(x) {}
+
+    bool operator()(double x, double y) const {
+      return x <= m_x;
+    }
+  };
+
+  struct xgt : public bisectx {
+    xgt(double x) : bisectx(x) {}
+    
+    bool operator()(double x, double y) const {
+      return x >= m_x;
+    }
+  };
+
+  struct bisecty {
+    double m_y;
+
+    bisecty(double y) : m_y(y) {}
+
+    void bisect(double sx, double sy, double px, double py, double* bx, 
double* by) const {
+      *by = m_y;
+      double dx = px - sx;
+      double dy = py - sy;
+      *bx = sx + dx * ((m_y - sy) / dy);
+    }
+  };
+
+  struct ylt : public bisecty {
+    ylt(double y) : bisecty(y) {}
+
+    bool operator()(double x, double y) const {
+      return y <= m_y;
+    }
+  };
+
+  struct ygt : public bisecty {
+    ygt(double y) : bisecty(y) {}
+
+    bool operator()(double x, double y) const {
+      return y >= m_y;
+    }
+  };
+}
+
+template<class Filter>
+void clip_to_rect_one_step(const Polygon& polygon, Polygon& result, const 
Filter& filter) {
+  double sx, sy, px, py, bx, by;
+  bool sinside, pinside;
+  result.clear();
+
+  if (polygon.size() == 0)
+    return;
+
+  sx = polygon.back().first;
+  sy = polygon.back().second;
+  for (Polygon::const_iterator i = polygon.begin(); i != polygon.end(); ++i) {
+    px = i->first;
+    py = i->second;
+    
+    sinside = filter(sx, sy);
+    pinside = filter(px, py);
+    
+    if (sinside) {
+      if (pinside) {
+       result.push_back(std::make_pair(px, py));
+      } else {
+       filter.bisect(sx, sy, px, py, &bx, &by);
+       result.push_back(std::make_pair(bx, by));
+      }
+    } else {
+      if (pinside) {
+       filter.bisect(sx, sy, px, py, &bx, &by);
+       result.push_back(std::make_pair(bx, by));
+       result.push_back(std::make_pair(px, py));
+      }
+    }
+    
+    sx = px; sy = py;
+  }
+}   
+
+void clip_to_rect(PathIterator& path, 
+                 double x0, double y0, double x1, double y1, 
+                 bool inside, std::vector<Polygon>& results) {
+  double xmin, ymin, xmax, ymax;
+  if (x0 < x1) {
+    xmin = x0; xmax = x1;
+  } else {
+    xmin = x1; xmax = x0;
+  }
+
+  if (y0 < y1) {
+    ymin = y0; ymax = y1;
+  } else {
+    ymin = y1; ymax = y0;
+  }
+
+  if (!inside) {
+    std::swap(xmin, xmax);
+    std::swap(ymin, ymax);
+  }
+
+  Polygon polygon1, polygon2;
+  double x, y;
+  unsigned code = 0;
+  path.rewind(0);
+
+  while (true) {
+    polygon1.clear();
+    while (true) {
+      if (code == agg::path_cmd_move_to)
+       polygon1.push_back(std::make_pair(x, y));
+
+      code = path.vertex(&x, &y);
+
+      if (code == agg::path_cmd_stop)
+       break;
+
+      if (code != agg::path_cmd_move_to)
+       polygon1.push_back(std::make_pair(x, y));
+
+      if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) {
+       break;
+      } else if (code == agg::path_cmd_move_to) {
+       break;
+      }
+    }
+
+    // The result of each step is fed into the next (note the
+    // swapping of polygon1 and polygon2 at each step).
+    clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::xlt(xmax));
+    clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::xgt(xmin));
+    clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::ylt(ymax));
+    clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::ygt(ymin));
+
+    if (polygon1.size())
+      results.push_back(polygon1);
+
+    if (code == agg::path_cmd_stop)
+      break;
+  }
+}
+
+Py::Object _path_module::clip_path_to_rect(const Py::Tuple &args) {
+  args.verify_length(3);
+  
+  PathIterator path(args[0]);
+  Py::Object bbox_obj = args[1];
+  bool inside = Py::Int(args[2]);
+
+  double x0, y0, x1, y1;
+  if (!py_convert_bbox(bbox_obj.ptr(), x0, y0, x1, y1))
+    throw Py::TypeError("Argument 2 to clip_to_rect must be a Bbox object.");
+
+  std::vector<Polygon> results;
+
+  ::clip_to_rect(path, x0, y0, x1, y1, inside, results);
+
+  // MGDTODO: Not exception safe
+  int dims[2];
+  dims[1] = 2;
+  PyObject* py_results = PyList_New(results.size());
+  for (std::vector<Polygon>::const_iterator p = results.begin(); p != 
results.end(); ++p) {
+    size_t size = p->size();
+    dims[0] = p->size();
+    PyArrayObject* pyarray = (PyArrayObject*)PyArray_FromDims(2, dims, 
PyArray_DOUBLE);
+    for (size_t i = 0; i < size; ++i) {
+      ((double *)pyarray->data)[2*i] = (*p)[i].first;
+      ((double *)pyarray->data)[2*i+1] = (*p)[i].second;
+    }
+    // MGDTODO: Error check
+    PyList_SetItem(py_results, p - results.begin(), (PyObject *)pyarray);
+  }
+
+  return Py::Object(py_results, true);
+}
+
+struct XY {
+  double x;
+  double y;
+};
+
 extern "C"
 DL_EXPORT(void)
   init_path(void)


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: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to