Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 383bc28

Browse files
committed
Major speed improvements for auto-placing of legends.
svn path=/branches/transforms/; revision=4481
1 parent faeba6f commit 383bc28

File tree

5 files changed

+118
-58
lines changed

5 files changed

+118
-58
lines changed

lib/matplotlib/legend.py

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,33 +36,6 @@
3636
from text import Text
3737
from transforms import Affine2D, Bbox, BboxTransformTo
3838

39-
def line_cuts_bbox(line, bbox):
40-
""" Return True if and only if line cuts bbox. """
41-
minx, miny, width, height = bbox.bounds
42-
maxx = minx + width
43-
maxy = miny + height
44-
45-
n = len(line)
46-
if n == 0:
47-
return False
48-
49-
if n == 1:
50-
return bbox.contains(line[0][0], line[0][1])
51-
p1 = line[0]
52-
for p2 in line[1:]:
53-
segment = (p1, p2)
54-
# See if the segment cuts any of the edges of bbox
55-
for edge in (((minx, miny), (minx, maxy)),
56-
((minx, miny), (maxx, miny)),
57-
((maxx, miny), (maxx, maxy)),
58-
((minx, maxy), (maxx, maxy))):
59-
if segments_intersect(segment, edge):
60-
return True
61-
p1=p2
62-
63-
return False
64-
65-
6639
class Legend(Artist):
6740
"""
6841
Place a legend on the axes at location loc. Labels are a
@@ -344,11 +317,11 @@ def _auto_legend_data(self):
344317

345318
for handle in ax.lines:
346319
assert isinstance(handle, Line2D)
347-
data = handle.get_xydata()
320+
path = handle.get_path()
348321
trans = handle.get_transform()
349-
tdata = trans.transform(data)
350-
averts = inverse_transform.transform(tdata)
351-
lines.append(averts)
322+
tpath = trans.transform_path(path)
323+
apath = inverse_transform.transform_path(tpath)
324+
lines.append(apath)
352325

353326
for handle in ax.patches:
354327
assert isinstance(handle, Patch)
@@ -435,7 +408,7 @@ def _find_best_position(self, width, height, consider=None):
435408
badness = legendBox.count_contains(verts)
436409
badness += legendBox.count_overlaps(bboxes)
437410
for line in lines:
438-
if line_cuts_bbox(line, legendBox):
411+
if line.intersects_bbox(legendBox):
439412
badness += 1
440413

441414
ox, oy = l-tx, b-ty

lib/matplotlib/lines.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ def __init__(self, xdata, ydata,
285285

286286
self._xorig = npy.asarray([])
287287
self._yorig = npy.asarray([])
288+
self._invalid = True
288289
self.set_data(xdata, ydata)
289290

290291
def contains(self, mouseevent):
@@ -353,7 +354,7 @@ def set_picker(self,p):
353354

354355
def get_window_extent(self, renderer):
355356
bbox = Bbox.unit()
356-
bbox.update_from_data_xy(self.get_transform().transform(self._xy),
357+
bbox.update_from_data_xy(self.get_transform().transform(self.get_xydata()),
357358
ignore=True)
358359
# correct for marker size, if any
359360
if self._marker is not None:
@@ -394,9 +395,10 @@ def set_data(self, *args):
394395
(y.shape != self._yorig.shape or npy.any(y != self._yorig)))):
395396
self._xorig = x
396397
self._yorig = y
397-
self.recache()
398+
self._invalid = True
398399
else:
399-
self._transformed_path._invalid = self._transformed_path.INVALID_NON_AFFINE
400+
if hasattr(self, "_transformed_path"):
401+
self._transformed_path._invalid = self._transformed_path.INVALID_NON_AFFINE
400402

401403
def recache(self):
402404
#if self.axes is None: print 'recache no axes'
@@ -434,6 +436,7 @@ def recache(self):
434436
self._path = Path(self._xy)
435437
self._transformed_path = TransformedPath(self._path, self.get_transform())
436438

439+
self._invalid = False
437440

438441
def set_transform(self, t):
439442
"""
@@ -442,14 +445,18 @@ def set_transform(self, t):
442445
ACCEPTS: a matplotlib.transforms.Transform instance
443446
"""
444447
Artist.set_transform(self, t)
445-
self._transformed_path = TransformedPath(self._path, self.get_transform())
448+
self._invalid = True
449+
# self._transformed_path = TransformedPath(self._path, self.get_transform())
446450

447451
def _is_sorted(self, x):
448452
"return true if x is sorted"
449453
if len(x)<2: return 1
450454
return npy.alltrue(x[1:]-x[0:-1]>=0)
451455

452456
def draw(self, renderer):
457+
if self._invalid:
458+
self.recache()
459+
453460
renderer.open_group('line2d')
454461

455462
if not self._visible: return
@@ -531,6 +538,8 @@ def get_xdata(self, orig=True):
531538
"""
532539
if orig:
533540
return self._xorig
541+
if self._invalid:
542+
self.recache()
534543
return self._x
535544

536545
def get_ydata(self, orig=True):
@@ -540,9 +549,21 @@ def get_ydata(self, orig=True):
540549
"""
541550
if orig:
542551
return self._yorig
552+
if self._invalid:
553+
self.recache()
543554
return self._y
544555

556+
def get_path(self):
557+
"""
558+
Return the Path object associated with this line.
559+
"""
560+
if self._invalid:
561+
self.recache()
562+
return self._path
563+
545564
def get_xydata(self):
565+
if self._invalid:
566+
self.recache()
546567
return self._xy
547568

548569
def set_antialiased(self, b):

lib/matplotlib/path.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from matplotlib._path import point_in_path, get_path_extents, \
1414
point_in_path_collection, get_path_collection_extents, \
15-
path_in_path
15+
path_in_path, path_intersects_path
1616
from matplotlib.cbook import simple_linear_interpolation
1717

1818
KAPPA = 4.0 * (npy.sqrt(2) - 1) / 3.0
@@ -237,6 +237,22 @@ def get_extents(self, transform=None):
237237
transform = Affine2D()
238238
return Bbox.from_extents(*get_path_extents(self, transform))
239239

240+
def intersects_path(self, other):
241+
"""
242+
Returns True if this path intersects another given path.
243+
"""
244+
return path_intersects_path(self, other)
245+
246+
def intersects_bbox(self, bbox):
247+
"""
248+
Returns True if this path intersects a given Bbox.
249+
"""
250+
from transforms import BboxTransformTo
251+
rectangle = self.unit_rectangle().transformed(
252+
BboxTransformTo(bbox))
253+
result = self.intersects_path(rectangle)
254+
return result
255+
240256
def interpolated(self, steps):
241257
"""
242258
Returns a new path resampled to length N x steps.

lib/matplotlib/transforms.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -508,25 +508,7 @@ def count_overlaps(self, bboxes):
508508
509509
bboxes is a sequence of Bbox objects
510510
"""
511-
ax1, ay1, ax2, ay2 = self._get_extents()
512-
if ax2 < ax1:
513-
ax2, ax1 = ax1, ax2
514-
if ay2 < ay1:
515-
ay2, ay1 = ay1, ay2
516-
517-
count = 0
518-
for bbox in bboxes:
519-
# bx1, by1, bx2, by2 = bbox._get_extents() ... inlined...
520-
bx1, by1, bx2, by2 = bbox.get_points().flatten()
521-
if bx2 < bx1:
522-
bx2, bx1 = bx1, bx2
523-
if by2 < by1:
524-
by2, by1 = by1, by2
525-
count += (not ((bx2 <= ax1) or
526-
(by2 <= ay1) or
527-
(bx1 >= ax2) or
528-
(by1 >= ay2)))
529-
return count
511+
return count_bboxes_overlapping_bbox(self, bboxes)
530512

531513
def expanded(self, sw, sh):
532514
"""

src/_path.cpp

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ class _path_module : public Py::ExtensionModule<_path_module>
3636
add_varargs_method("point_in_path_collection", &_path_module::point_in_path_collection,
3737
"point_in_path_collection(x, y, r, trans, paths, transforms, offsets, offsetTrans, filled)");
3838
add_varargs_method("path_in_path", &_path_module::path_in_path,
39-
"point_in_path_collection(a, atrans, b, btrans)");
39+
"path_in_path(a, atrans, b, btrans)");
4040
add_varargs_method("clip_path_to_rect", &_path_module::clip_path_to_rect,
4141
"clip_path_to_rect(path, bbox, inside)");
4242
add_varargs_method("affine_transform", &_path_module::affine_transform,
4343
"affine_transform(vertices, transform)");
4444
add_varargs_method("count_bboxes_overlapping_bbox", &_path_module::count_bboxes_overlapping_bbox,
4545
"count_bboxes_overlapping_bbox(bbox, bboxes)");
46+
add_varargs_method("path_intersects_path", &_path_module::path_intersects_path,
47+
"path_intersects_path(p1, p2)");
4648

4749
initialize("Helper functions for paths");
4850
}
@@ -60,6 +62,7 @@ class _path_module : public Py::ExtensionModule<_path_module>
6062
Py::Object clip_path_to_rect(const Py::Tuple& args);
6163
Py::Object affine_transform(const Py::Tuple& args);
6264
Py::Object count_bboxes_overlapping_bbox(const Py::Tuple& args);
65+
Py::Object path_intersects_path(const Py::Tuple& args);
6366
};
6467

6568
//
@@ -673,7 +676,8 @@ Py::Object _path_module::affine_transform(const Py::Tuple& args) {
673676

674677
transform = (PyArrayObject*) PyArray_FromObject
675678
(transform_obj.ptr(), PyArray_DOUBLE, 2, 2);
676-
if (!transform || PyArray_NDIM(transform) != 2 || PyArray_DIM(transform, 0) != 3 || PyArray_DIM(transform, 1) != 3)
679+
if (!transform || PyArray_NDIM(transform) != 2 ||
680+
PyArray_DIM(transform, 0) != 3 || PyArray_DIM(transform, 1) != 3)
677681
throw Py::ValueError("Invalid transform.");
678682

679683
double a, b, c, d, e, f;
@@ -783,6 +787,70 @@ Py::Object _path_module::count_bboxes_overlapping_bbox(const Py::Tuple& args) {
783787
return Py::Int(count);
784788
}
785789

790+
bool segments_intersect(const double& x1, const double &y1,
791+
const double& x2, const double &y2,
792+
const double& x3, const double &y3,
793+
const double& x4, const double &y4) {
794+
double den = ((y4-y3) * (x2-x1)) - ((x4-x3)*(y2-y1));
795+
if (den == 0.0)
796+
return false;
797+
798+
double n1 = ((x4-x3) * (y1-y3)) - ((y4-y3)*(x1-x3));
799+
double n2 = ((x2-x1) * (y1-y3)) - ((y2-y1)*(x1-x3));
800+
801+
double u1 = n1/den;
802+
double u2 = n2/den;
803+
804+
return (u1 >= 0.0 && u1 <= 1.0 &&
805+
u2 >= 0.0 && u2 <= 1.0);
806+
}
807+
808+
bool path_intersects_path(PathIterator& p1, PathIterator& p2) {
809+
typedef agg::conv_curve<PathIterator> curve_t;
810+
811+
if (p1.total_vertices() < 2 || p2.total_vertices() < 2)
812+
return false;
813+
814+
curve_t c1(p1);
815+
curve_t c2(p2);
816+
817+
double x11, y11, x12, y12;
818+
double x21, y21, x22, y22;
819+
820+
c1.vertex(&x11, &y11);
821+
while (c1.vertex(&x12, &y12) != agg::path_cmd_stop) {
822+
c2.rewind(0);
823+
c2.vertex(&x21, &y21);
824+
while (c2.vertex(&x22, &y22) != agg::path_cmd_stop) {
825+
if (segments_intersect(x11, y11, x12, y12, x21, y21, x22, y22))
826+
return true;
827+
x21 = x22; y21 = y22;
828+
}
829+
x11 = x12; y11 = y12;
830+
}
831+
832+
return false;
833+
}
834+
835+
Py::Object _path_module::path_intersects_path(const Py::Tuple& args) {
836+
args.verify_length(2);
837+
838+
PathIterator p1(args[0]);
839+
PathIterator p2(args[1]);
840+
841+
bool intersects = ::path_intersects_path(p1, p2);
842+
if (!intersects) {
843+
intersects = ::path_in_path(p1, agg::trans_affine(), p2, agg::trans_affine());
844+
if (!intersects) {
845+
intersects = ::path_in_path(p2, agg::trans_affine(), p1, agg::trans_affine());
846+
if (!intersects) {
847+
return Py::Int(0);
848+
}
849+
}
850+
}
851+
return Py::Int(1);
852+
}
853+
786854
extern "C"
787855
DL_EXPORT(void)
788856
init_path(void)

0 commit comments

Comments
 (0)