From: <md...@us...> - 2007-11-28 13:42:46
|
Revision: 4481 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=4481&view=rev Author: mdboom Date: 2007-11-28 05:42:39 -0800 (Wed, 28 Nov 2007) Log Message: ----------- Major speed improvements for auto-placing of legends. Modified Paths: -------------- branches/transforms/lib/matplotlib/legend.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/transforms.py branches/transforms/src/_path.cpp Modified: branches/transforms/lib/matplotlib/legend.py =================================================================== --- branches/transforms/lib/matplotlib/legend.py 2007-11-28 13:40:54 UTC (rev 4480) +++ branches/transforms/lib/matplotlib/legend.py 2007-11-28 13:42:39 UTC (rev 4481) @@ -36,33 +36,6 @@ from text import Text from transforms import Affine2D, Bbox, BboxTransformTo -def line_cuts_bbox(line, bbox): - """ Return True if and only if line cuts bbox. """ - minx, miny, width, height = bbox.bounds - maxx = minx + width - maxy = miny + height - - n = len(line) - if n == 0: - return False - - if n == 1: - return bbox.contains(line[0][0], line[0][1]) - p1 = line[0] - for p2 in line[1:]: - segment = (p1, p2) - # See if the segment cuts any of the edges of bbox - for edge in (((minx, miny), (minx, maxy)), - ((minx, miny), (maxx, miny)), - ((maxx, miny), (maxx, maxy)), - ((minx, maxy), (maxx, maxy))): - if segments_intersect(segment, edge): - return True - p1=p2 - - return False - - class Legend(Artist): """ Place a legend on the axes at location loc. Labels are a @@ -344,11 +317,11 @@ for handle in ax.lines: assert isinstance(handle, Line2D) - data = handle.get_xydata() + path = handle.get_path() trans = handle.get_transform() - tdata = trans.transform(data) - averts = inverse_transform.transform(tdata) - lines.append(averts) + tpath = trans.transform_path(path) + apath = inverse_transform.transform_path(tpath) + lines.append(apath) for handle in ax.patches: assert isinstance(handle, Patch) @@ -435,7 +408,7 @@ badness = legendBox.count_contains(verts) badness += legendBox.count_overlaps(bboxes) for line in lines: - if line_cuts_bbox(line, legendBox): + if line.intersects_bbox(legendBox): badness += 1 ox, oy = l-tx, b-ty Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-11-28 13:40:54 UTC (rev 4480) +++ branches/transforms/lib/matplotlib/lines.py 2007-11-28 13:42:39 UTC (rev 4481) @@ -285,6 +285,7 @@ self._xorig = npy.asarray([]) self._yorig = npy.asarray([]) + self._invalid = True self.set_data(xdata, ydata) def contains(self, mouseevent): @@ -353,7 +354,7 @@ def get_window_extent(self, renderer): bbox = Bbox.unit() - bbox.update_from_data_xy(self.get_transform().transform(self._xy), + bbox.update_from_data_xy(self.get_transform().transform(self.get_xydata()), ignore=True) # correct for marker size, if any if self._marker is not None: @@ -394,9 +395,10 @@ (y.shape != self._yorig.shape or npy.any(y != self._yorig)))): self._xorig = x self._yorig = y - self.recache() + self._invalid = True else: - self._transformed_path._invalid = self._transformed_path.INVALID_NON_AFFINE + if hasattr(self, "_transformed_path"): + self._transformed_path._invalid = self._transformed_path.INVALID_NON_AFFINE def recache(self): #if self.axes is None: print 'recache no axes' @@ -434,6 +436,7 @@ self._path = Path(self._xy) self._transformed_path = TransformedPath(self._path, self.get_transform()) + self._invalid = False def set_transform(self, t): """ @@ -442,7 +445,8 @@ ACCEPTS: a matplotlib.transforms.Transform instance """ Artist.set_transform(self, t) - self._transformed_path = TransformedPath(self._path, self.get_transform()) + self._invalid = True + # self._transformed_path = TransformedPath(self._path, self.get_transform()) def _is_sorted(self, x): "return true if x is sorted" @@ -450,6 +454,9 @@ return npy.alltrue(x[1:]-x[0:-1]>=0) def draw(self, renderer): + if self._invalid: + self.recache() + renderer.open_group('line2d') if not self._visible: return @@ -531,6 +538,8 @@ """ if orig: return self._xorig + if self._invalid: + self.recache() return self._x def get_ydata(self, orig=True): @@ -540,9 +549,21 @@ """ if orig: return self._yorig + if self._invalid: + self.recache() return self._y + def get_path(self): + """ + Return the Path object associated with this line. + """ + if self._invalid: + self.recache() + return self._path + def get_xydata(self): + if self._invalid: + self.recache() return self._xy def set_antialiased(self, b): Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-11-28 13:40:54 UTC (rev 4480) +++ branches/transforms/lib/matplotlib/path.py 2007-11-28 13:42:39 UTC (rev 4481) @@ -12,7 +12,7 @@ from matplotlib._path import point_in_path, get_path_extents, \ point_in_path_collection, get_path_collection_extents, \ - path_in_path + path_in_path, path_intersects_path from matplotlib.cbook import simple_linear_interpolation KAPPA = 4.0 * (npy.sqrt(2) - 1) / 3.0 @@ -237,6 +237,22 @@ transform = Affine2D() return Bbox.from_extents(*get_path_extents(self, transform)) + def intersects_path(self, other): + """ + Returns True if this path intersects another given path. + """ + return path_intersects_path(self, other) + + def intersects_bbox(self, bbox): + """ + Returns True if this path intersects a given Bbox. + """ + from transforms import BboxTransformTo + rectangle = self.unit_rectangle().transformed( + BboxTransformTo(bbox)) + result = self.intersects_path(rectangle) + return result + def interpolated(self, steps): """ Returns a new path resampled to length N x steps. Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-11-28 13:40:54 UTC (rev 4480) +++ branches/transforms/lib/matplotlib/transforms.py 2007-11-28 13:42:39 UTC (rev 4481) @@ -508,26 +508,8 @@ bboxes is a sequence of Bbox objects """ - ax1, ay1, ax2, ay2 = self._get_extents() - if ax2 < ax1: - ax2, ax1 = ax1, ax2 - if ay2 < ay1: - ay2, ay1 = ay1, ay2 + return count_bboxes_overlapping_bbox(self, bboxes) - count = 0 - for bbox in bboxes: - # bx1, by1, bx2, by2 = bbox._get_extents() ... inlined... - bx1, by1, bx2, by2 = bbox.get_points().flatten() - if bx2 < bx1: - bx2, bx1 = bx1, bx2 - if by2 < by1: - by2, by1 = by1, by2 - count += (not ((bx2 <= ax1) or - (by2 <= ay1) or - (bx1 >= ax2) or - (by1 >= ay2))) - return count - def expanded(self, sw, sh): """ Return a new Bbox which is this Bbox expanded around its Modified: branches/transforms/src/_path.cpp =================================================================== --- branches/transforms/src/_path.cpp 2007-11-28 13:40:54 UTC (rev 4480) +++ branches/transforms/src/_path.cpp 2007-11-28 13:42:39 UTC (rev 4481) @@ -36,13 +36,15 @@ 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)"); + "path_in_path(a, atrans, b, btrans)"); add_varargs_method("clip_path_to_rect", &_path_module::clip_path_to_rect, "clip_path_to_rect(path, bbox, inside)"); add_varargs_method("affine_transform", &_path_module::affine_transform, "affine_transform(vertices, transform)"); add_varargs_method("count_bboxes_overlapping_bbox", &_path_module::count_bboxes_overlapping_bbox, "count_bboxes_overlapping_bbox(bbox, bboxes)"); + add_varargs_method("path_intersects_path", &_path_module::path_intersects_path, + "path_intersects_path(p1, p2)"); initialize("Helper functions for paths"); } @@ -60,6 +62,7 @@ Py::Object clip_path_to_rect(const Py::Tuple& args); Py::Object affine_transform(const Py::Tuple& args); Py::Object count_bboxes_overlapping_bbox(const Py::Tuple& args); + Py::Object path_intersects_path(const Py::Tuple& args); }; // @@ -673,7 +676,8 @@ transform = (PyArrayObject*) PyArray_FromObject (transform_obj.ptr(), PyArray_DOUBLE, 2, 2); - if (!transform || PyArray_NDIM(transform) != 2 || PyArray_DIM(transform, 0) != 3 || PyArray_DIM(transform, 1) != 3) + if (!transform || PyArray_NDIM(transform) != 2 || + PyArray_DIM(transform, 0) != 3 || PyArray_DIM(transform, 1) != 3) throw Py::ValueError("Invalid transform."); double a, b, c, d, e, f; @@ -783,6 +787,70 @@ return Py::Int(count); } +bool segments_intersect(const double& x1, const double &y1, + const double& x2, const double &y2, + const double& x3, const double &y3, + const double& x4, const double &y4) { + double den = ((y4-y3) * (x2-x1)) - ((x4-x3)*(y2-y1)); + if (den == 0.0) + return false; + + double n1 = ((x4-x3) * (y1-y3)) - ((y4-y3)*(x1-x3)); + double n2 = ((x2-x1) * (y1-y3)) - ((y2-y1)*(x1-x3)); + + double u1 = n1/den; + double u2 = n2/den; + + return (u1 >= 0.0 && u1 <= 1.0 && + u2 >= 0.0 && u2 <= 1.0); +} + +bool path_intersects_path(PathIterator& p1, PathIterator& p2) { + typedef agg::conv_curve<PathIterator> curve_t; + + if (p1.total_vertices() < 2 || p2.total_vertices() < 2) + return false; + + curve_t c1(p1); + curve_t c2(p2); + + double x11, y11, x12, y12; + double x21, y21, x22, y22; + + c1.vertex(&x11, &y11); + while (c1.vertex(&x12, &y12) != agg::path_cmd_stop) { + c2.rewind(0); + c2.vertex(&x21, &y21); + while (c2.vertex(&x22, &y22) != agg::path_cmd_stop) { + if (segments_intersect(x11, y11, x12, y12, x21, y21, x22, y22)) + return true; + x21 = x22; y21 = y22; + } + x11 = x12; y11 = y12; + } + + return false; +} + +Py::Object _path_module::path_intersects_path(const Py::Tuple& args) { + args.verify_length(2); + + PathIterator p1(args[0]); + PathIterator p2(args[1]); + + bool intersects = ::path_intersects_path(p1, p2); + if (!intersects) { + intersects = ::path_in_path(p1, agg::trans_affine(), p2, agg::trans_affine()); + if (!intersects) { + intersects = ::path_in_path(p2, agg::trans_affine(), p1, agg::trans_affine()); + if (!intersects) { + return Py::Int(0); + } + } + } + return Py::Int(1); +} + 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. |