From 36ce4a7f3afd775dfb8b91fe4997a2ee0ab929fc Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 14 Dec 2015 09:15:10 -0500 Subject: [PATCH 1/3] Fix #5670. No double endpoints in Path.to_polygon --- lib/matplotlib/tests/test_path.py | 37 +++++++++++++++++++++++++ src/_path.h | 45 ++++++++++++++++++++++++------- src/_path_wrapper.cpp | 23 ++++++++++++---- 3 files changed, 90 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 156cf4ad7419..6a4f6628868b 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -10,6 +10,7 @@ from nose.tools import assert_raises, assert_equal from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt +from matplotlib import transforms def test_readonly_path(): @@ -113,6 +114,42 @@ def test_marker_paths_pdf(): plt.ylim(-1, 7) +def test_path_no_doubled_point_in_to_polygon(): + hand = np.array( + [[ 1.64516129, 1.16145833 ], + [ 1.64516129, 1.59375 ], + [ 1.35080645, 1.921875 ], + [ 1.375 , 2.18229167 ], + [ 1.68548387, 1.9375 ], + [ 1.60887097, 2.55208333 ], + [ 1.68548387, 2.69791667 ], + [ 1.76209677, 2.56770833 ], + [ 1.83064516, 1.97395833 ], + [ 1.89516129, 2.75 ], + [ 1.9516129 , 2.84895833 ], + [ 2.01209677, 2.76041667 ], + [ 1.99193548, 1.99479167 ], + [ 2.11290323, 2.63020833 ], + [ 2.2016129 , 2.734375 ], + [ 2.25403226, 2.60416667 ], + [ 2.14919355, 1.953125 ], + [ 2.30645161, 2.36979167 ], + [ 2.39112903, 2.36979167 ], + [ 2.41532258, 2.1875 ], + [ 2.1733871 , 1.703125 ], + [ 2.07782258, 1.16666667 ]]) + + (r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5) + + poly = Path(np.vstack((hand[:, 1], hand[:, 0])).T, closed=True) + clip_rect = transforms.Bbox([[r0, c0], [r1, c1]]) + poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0] + + assert np.all(poly_clipped[-2] != poly_clipped[-1]) + assert np.all(poly_clipped[-1] == poly_clipped[0]) + + + if __name__ == '__main__': import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/src/_path.h b/src/_path.h index 55dfd2f72be7..85032b3bf836 100644 --- a/src/_path.h +++ b/src/_path.h @@ -27,6 +27,16 @@ struct XY XY(double x_, double y_) : x(x_), y(y_) { } + + bool operator==(const XY& o) + { + return (x == o.x && y == o.y); + } + + bool operator!=(const XY& o) + { + return (x != o.x || y != o.y); + } }; // @@ -838,6 +848,25 @@ bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2) return false; } +void _finalize_polygon(std::vector &result) +{ + Polygon &polygon = result.back(); + + if (result.size() == 0) { + return; + } + + /* Clean up the last polygon in the result. If less than a + triangle, remove it. */ + if (polygon.size() < 3) { + result.pop_back(); + } else { + if (polygon.front() != polygon.back()) { + polygon.push_back(polygon.front()); + } + } +} + template void convert_path_to_polygons(PathIterator &path, agg::trans_affine &trans, @@ -867,14 +896,12 @@ void convert_path_to_polygons(PathIterator &path, while ((code = curve.vertex(&x, &y)) != agg::path_cmd_stop) { if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { - if (polygon->size() >= 1) { - polygon->push_back((*polygon)[0]); - result.push_back(Polygon()); - polygon = &result.back(); - } + _finalize_polygon(result); + result.push_back(Polygon()); + polygon = &result.back(); } else { - if (code == agg::path_cmd_move_to && polygon->size() >= 1) { - polygon->push_back((*polygon)[0]); + if (code == agg::path_cmd_move_to) { + _finalize_polygon(result); result.push_back(Polygon()); polygon = &result.back(); } @@ -882,9 +909,7 @@ void convert_path_to_polygons(PathIterator &path, } } - if (polygon->size() == 0) { - result.pop_back(); - } + _finalize_polygon(result); } template diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 98947e24f1c4..2ef17ecc573b 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -8,16 +8,29 @@ PyObject *convert_polygon_vector(std::vector &polygons) { PyObject *pyresult = PyList_New(polygons.size()); + bool fix_endpoints; for (size_t i = 0; i < polygons.size(); ++i) { Polygon poly = polygons[i]; - npy_intp dims[] = {(npy_intp)poly.size() + 1, 2 }; - numpy::array_view subresult(dims); + npy_intp dims[2]; + dims[1] = 2; + + if (poly.front() != poly.back()) { + /* Make last point same as first, if not already */ + dims[0] = (npy_intp)poly.size() + 1; + fix_endpoints = true; + } else { + dims[0] = (npy_intp)poly.size(); + fix_endpoints = false; + } - /* Make last point same as first. */ + numpy::array_view subresult(dims); memcpy(subresult.data(), &poly[0], sizeof(double) * poly.size() * 2); - subresult(poly.size(), 0) = poly[0].x; - subresult(poly.size(), 1) = poly[0].y; + + if (fix_endpoints) { + subresult(poly.size(), 0) = poly.front().x; + subresult(poly.size(), 1) = poly.front().y; + } if (PyList_SetItem(pyresult, i, subresult.pyobj())) { Py_DECREF(pyresult); From 66a4f889f58cf1a35a27a05ece869523a0425ceb Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 14 Dec 2015 10:19:12 -0500 Subject: [PATCH 2/3] Fix unit tests --- lib/matplotlib/tests/test_widgets.py | 2 +- lib/matplotlib/widgets.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index c9b100e4d933..c954355eb1e4 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -162,7 +162,7 @@ def onselect(epress, erelease): extents = [int(e) for e in tool.extents] assert extents == [70, 129, 70, 130], extents - assert tool.geometry.shape == (2, 74) + assert tool.geometry.shape == (2, 73) assert_allclose(tool.geometry[:, 0], [70., 100]) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 611b3dab2859..8b37b006541b 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -245,7 +245,7 @@ def on_clicked(self, func): """ When the button is clicked, call this *func* with event. - A connection id is returned. It can be used to disconnect + A connection id is returned. It can be used to disconnect the button from its callback. """ cid = self.cnt @@ -265,7 +265,7 @@ class Slider(AxesWidget): """ A slider representing a floating point range. - For the slider to remain responsive you must maintain a + For the slider to remain responsive you must maintain a reference to it. The following attributes are defined @@ -2036,7 +2036,7 @@ def geometry(self): if hasattr(self.to_draw, 'get_verts'): xfm = self.ax.transData.inverted() y, x = xfm.transform(self.to_draw.get_verts()).T - return np.array([x[:-1], y[:-1]]) + return np.array([x, y]) else: return np.array(self.to_draw.get_data()) From bf230fd3073405854965fbf78a114ae388a1dab7 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 14 Dec 2015 11:46:35 -0500 Subject: [PATCH 3/3] PEP8 --- lib/matplotlib/tests/test_path.py | 45 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 6a4f6628868b..5f606461916e 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -116,28 +116,28 @@ def test_marker_paths_pdf(): def test_path_no_doubled_point_in_to_polygon(): hand = np.array( - [[ 1.64516129, 1.16145833 ], - [ 1.64516129, 1.59375 ], - [ 1.35080645, 1.921875 ], - [ 1.375 , 2.18229167 ], - [ 1.68548387, 1.9375 ], - [ 1.60887097, 2.55208333 ], - [ 1.68548387, 2.69791667 ], - [ 1.76209677, 2.56770833 ], - [ 1.83064516, 1.97395833 ], - [ 1.89516129, 2.75 ], - [ 1.9516129 , 2.84895833 ], - [ 2.01209677, 2.76041667 ], - [ 1.99193548, 1.99479167 ], - [ 2.11290323, 2.63020833 ], - [ 2.2016129 , 2.734375 ], - [ 2.25403226, 2.60416667 ], - [ 2.14919355, 1.953125 ], - [ 2.30645161, 2.36979167 ], - [ 2.39112903, 2.36979167 ], - [ 2.41532258, 2.1875 ], - [ 2.1733871 , 1.703125 ], - [ 2.07782258, 1.16666667 ]]) + [[1.64516129, 1.16145833], + [1.64516129, 1.59375], + [1.35080645, 1.921875], + [1.375, 2.18229167], + [1.68548387, 1.9375], + [1.60887097, 2.55208333], + [1.68548387, 2.69791667], + [1.76209677, 2.56770833], + [1.83064516, 1.97395833], + [1.89516129, 2.75], + [1.9516129, 2.84895833], + [2.01209677, 2.76041667], + [1.99193548, 1.99479167], + [2.11290323, 2.63020833], + [2.2016129, 2.734375], + [2.25403226, 2.60416667], + [2.14919355, 1.953125], + [2.30645161, 2.36979167], + [2.39112903, 2.36979167], + [2.41532258, 2.1875], + [2.1733871, 1.703125], + [2.07782258, 1.16666667]]) (r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5) @@ -149,7 +149,6 @@ def test_path_no_doubled_point_in_to_polygon(): assert np.all(poly_clipped[-1] == poly_clipped[0]) - if __name__ == '__main__': import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False)