Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2fd6129

Browse filesBrowse files
authored
Merge pull request #8224 from MaartenBaert/faster-legend-loc-best-squashed
Implement Path.intersects_bbox in C++ to speed up legend positioning.
2 parents 355695a + b048fc4 commit 2fd6129
Copy full SHA for 2fd6129

File tree

Expand file treeCollapse file tree

5 files changed

+1810
-7
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+1810
-7
lines changed
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Path.intersects_bbox always treats the bounding box as filled
2+
`````````````````````````````````````````````````````````````
3+
4+
Previously, when ``Path.intersects_bbox`` was called with ``filled`` set to
5+
``False``, it would treat both the path and the bounding box as unfilled. This
6+
behavior was not well documented and it is usually not the desired behavior,
7+
since bounding boxes are used to represent more complex shapes located inside
8+
the bounding box. This behavior has now been changed: when ``filled`` is
9+
``False``, the path will be treated as unfilled, but the bounding box is still
10+
treated as filled. The old behavior was arguably an implementation bug.
11+
12+
When ``Path.intersects_bbox`` is called with ``filled`` set to ``True``
13+
(the default value), there is no change in behavior. For those rare cases where
14+
``Path.intersects_bbox`` was called with ``filled`` set to ``False`` and where
15+
the old behavior is actually desired, the suggested workaround is to call
16+
``Path.intersects_path`` with a rectangle as the path::
17+
18+
from matplotlib.path import Path
19+
from matplotlib.transforms import Bbox, BboxTransformTo
20+
rect = Path.unit_rectangle().transformed(BboxTransformTo(bbox))
21+
result = path.intersects_path(rect, filled=False)

‎lib/matplotlib/path.py

Copy file name to clipboardExpand all lines: lib/matplotlib/path.py
+6-7Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -558,14 +558,13 @@ def intersects_bbox(self, bbox, filled=True):
558558
:class:`~matplotlib.transforms.Bbox`.
559559
560560
*filled*, when True, treats the path as if it was filled.
561-
That is, if one path completely encloses the other,
562-
:meth:`intersects_path` will return True.
561+
That is, if the path completely encloses the bounding box,
562+
:meth:`intersects_bbox` will return True.
563+
564+
The bounding box is always considered filled.
563565
"""
564-
from .transforms import BboxTransformTo
565-
rectangle = self.unit_rectangle().transformed(
566-
BboxTransformTo(bbox))
567-
result = self.intersects_path(rectangle, filled)
568-
return result
566+
return _path.path_intersects_rectangle(self,
567+
bbox.x0, bbox.y0, bbox.x1, bbox.y1, filled)
569568

570569
def interpolated(self, steps):
571570
"""

‎src/_path.h

Copy file name to clipboardExpand all lines: src/_path.h
+60Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,66 @@ bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2)
872872
return false;
873873
}
874874

875+
// returns whether the segment from (x1,y1) to (x2,y2)
876+
// intersects the rectangle centered at (cx,cy) with size (w,h)
877+
// see doc/segment_intersects_rectangle.svg for a more detailed explanation
878+
inline bool segment_intersects_rectangle(double x1, double y1,
879+
double x2, double y2,
880+
double cx, double cy,
881+
double w, double h)
882+
{
883+
return fabs(x1 + x2 - 2.0 * cx) < fabs(x1 - x2) + w &&
884+
fabs(y1 + y2 - 2.0 * cy) < fabs(y1 - y2) + h &&
885+
2.0 * fabs((x1 - cx) * (y1 - y2) - (y1 - cy) * (x1 - x2)) <
886+
w * fabs(y1 - y2) + h * fabs(x1 - x2);
887+
}
888+
889+
template <class PathIterator>
890+
bool path_intersects_rectangle(PathIterator &path,
891+
double rect_x1, double rect_y1,
892+
double rect_x2, double rect_y2,
893+
bool filled)
894+
{
895+
typedef PathNanRemover<py::PathIterator> no_nans_t;
896+
typedef agg::conv_curve<no_nans_t> curve_t;
897+
898+
if (path.total_vertices() == 0) {
899+
return false;
900+
}
901+
902+
no_nans_t no_nans(path, true, path.has_curves());
903+
curve_t curve(no_nans);
904+
905+
double cx = (rect_x1 + rect_x2) * 0.5, cy = (rect_y1 + rect_y2) * 0.5;
906+
double w = fabs(rect_x1 - rect_x2), h = fabs(rect_y1 - rect_y2);
907+
double xmin = std::min(rect_x1, rect_x2), xmax = std::max(rect_x1, rect_x2);
908+
double ymin = std::min(rect_x1, rect_x2), ymax = std::max(rect_x1, rect_x2);
909+
910+
double x1, y1, x2, y2;
911+
912+
curve.vertex(&x1, &y1);
913+
if (2.0 * fabs(x1 - cx) <= w && 2.0 * fabs(y1 - cy) <= h) {
914+
return true;
915+
}
916+
917+
while (curve.vertex(&x2, &y2) != agg::path_cmd_stop) {
918+
if (segment_intersects_rectangle(x1, y1, x2, y2, cx, cy, w, h)) {
919+
return true;
920+
}
921+
x1 = x2;
922+
y1 = y2;
923+
}
924+
925+
if (filled) {
926+
agg::trans_affine trans;
927+
if (point_in_path(cx, cy, 0.0, path, trans)) {
928+
return true;
929+
}
930+
}
931+
932+
return false;
933+
}
934+
875935
template <class PathIterator>
876936
void convert_path_to_polygons(PathIterator &path,
877937
agg::trans_affine &trans,

‎src/_path_wrapper.cpp

Copy file name to clipboardExpand all lines: src/_path_wrapper.cpp
+34Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,39 @@ static PyObject *Py_path_intersects_path(PyObject *self, PyObject *args, PyObjec
521521
}
522522
}
523523

524+
const char *Py_path_intersects_rectangle__doc__ = "path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled=False)";
525+
526+
static PyObject *Py_path_intersects_rectangle(PyObject *self, PyObject *args, PyObject *kwds)
527+
{
528+
py::PathIterator path;
529+
double rect_x1, rect_y1, rect_x2, rect_y2;
530+
int filled = 0;
531+
const char *names[] = { "path", "rect_x1", "rect_y1", "rect_x2", "rect_y2", "filled", NULL };
532+
bool result;
533+
534+
if (!PyArg_ParseTupleAndKeywords(args,
535+
kwds,
536+
"O&dddd|i:path_intersects_rectangle",
537+
(char **)names,
538+
&convert_path,
539+
&path,
540+
&rect_x1,
541+
&rect_y1,
542+
&rect_x2,
543+
&rect_y2,
544+
&filled)) {
545+
return NULL;
546+
}
547+
548+
CALL_CPP("path_intersects_rectangle", (result = path_intersects_rectangle(path, rect_x1, rect_y1, rect_x2, rect_y2, filled)));
549+
550+
if (result) {
551+
Py_RETURN_TRUE;
552+
} else {
553+
Py_RETURN_FALSE;
554+
}
555+
}
556+
524557
const char *Py_convert_path_to_polygons__doc__ =
525558
"convert_path_to_polygons(path, trans, width=0, height=0)";
526559

@@ -819,6 +852,7 @@ extern "C" {
819852
{"affine_transform", (PyCFunction)Py_affine_transform, METH_VARARGS, Py_affine_transform__doc__},
820853
{"count_bboxes_overlapping_bbox", (PyCFunction)Py_count_bboxes_overlapping_bbox, METH_VARARGS, Py_count_bboxes_overlapping_bbox__doc__},
821854
{"path_intersects_path", (PyCFunction)Py_path_intersects_path, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_path__doc__},
855+
{"path_intersects_rectangle", (PyCFunction)Py_path_intersects_rectangle, METH_VARARGS|METH_KEYWORDS, Py_path_intersects_rectangle__doc__},
822856
{"convert_path_to_polygons", (PyCFunction)Py_convert_path_to_polygons, METH_VARARGS|METH_KEYWORDS, Py_convert_path_to_polygons__doc__},
823857
{"cleanup_path", (PyCFunction)Py_cleanup_path, METH_VARARGS, Py_cleanup_path__doc__},
824858
{"convert_to_string", (PyCFunction)Py_convert_to_string, METH_VARARGS, Py_convert_to_string__doc__},

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.