Skip to content

Commit a82ca89

Browse files
committed
Merge pull request #5672 from mdboom/double-endpoints
Fix #5670. No double endpoints in Path.to_polygon
2 parents 5904e2f + bf230fd commit a82ca89

File tree

5 files changed

+93
-19
lines changed

5 files changed

+93
-19
lines changed

lib/matplotlib/tests/test_path.py

+36
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from nose.tools import assert_raises, assert_equal
1111
from matplotlib.testing.decorators import image_comparison
1212
import matplotlib.pyplot as plt
13+
from matplotlib import transforms
1314

1415

1516
def test_readonly_path():
@@ -113,6 +114,41 @@ def test_marker_paths_pdf():
113114
plt.ylim(-1, 7)
114115

115116

117+
def test_path_no_doubled_point_in_to_polygon():
118+
hand = np.array(
119+
[[1.64516129, 1.16145833],
120+
[1.64516129, 1.59375],
121+
[1.35080645, 1.921875],
122+
[1.375, 2.18229167],
123+
[1.68548387, 1.9375],
124+
[1.60887097, 2.55208333],
125+
[1.68548387, 2.69791667],
126+
[1.76209677, 2.56770833],
127+
[1.83064516, 1.97395833],
128+
[1.89516129, 2.75],
129+
[1.9516129, 2.84895833],
130+
[2.01209677, 2.76041667],
131+
[1.99193548, 1.99479167],
132+
[2.11290323, 2.63020833],
133+
[2.2016129, 2.734375],
134+
[2.25403226, 2.60416667],
135+
[2.14919355, 1.953125],
136+
[2.30645161, 2.36979167],
137+
[2.39112903, 2.36979167],
138+
[2.41532258, 2.1875],
139+
[2.1733871, 1.703125],
140+
[2.07782258, 1.16666667]])
141+
142+
(r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5)
143+
144+
poly = Path(np.vstack((hand[:, 1], hand[:, 0])).T, closed=True)
145+
clip_rect = transforms.Bbox([[r0, c0], [r1, c1]])
146+
poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]
147+
148+
assert np.all(poly_clipped[-2] != poly_clipped[-1])
149+
assert np.all(poly_clipped[-1] == poly_clipped[0])
150+
151+
116152
if __name__ == '__main__':
117153
import nose
118154
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)

lib/matplotlib/tests/test_widgets.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def onselect(epress, erelease):
162162
extents = [int(e) for e in tool.extents]
163163
assert extents == [70, 129, 70, 130], extents
164164

165-
assert tool.geometry.shape == (2, 74)
165+
assert tool.geometry.shape == (2, 73)
166166
assert_allclose(tool.geometry[:, 0], [70., 100])
167167

168168

lib/matplotlib/widgets.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def on_clicked(self, func):
245245
"""
246246
When the button is clicked, call this *func* with event.
247247
248-
A connection id is returned. It can be used to disconnect
248+
A connection id is returned. It can be used to disconnect
249249
the button from its callback.
250250
"""
251251
cid = self.cnt
@@ -265,7 +265,7 @@ class Slider(AxesWidget):
265265
"""
266266
A slider representing a floating point range.
267267
268-
For the slider to remain responsive you must maintain a
268+
For the slider to remain responsive you must maintain a
269269
reference to it.
270270
271271
The following attributes are defined
@@ -2036,7 +2036,7 @@ def geometry(self):
20362036
if hasattr(self.to_draw, 'get_verts'):
20372037
xfm = self.ax.transData.inverted()
20382038
y, x = xfm.transform(self.to_draw.get_verts()).T
2039-
return np.array([x[:-1], y[:-1]])
2039+
return np.array([x, y])
20402040
else:
20412041
return np.array(self.to_draw.get_data())
20422042

src/_path.h

+35-10
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ struct XY
2727
XY(double x_, double y_) : x(x_), y(y_)
2828
{
2929
}
30+
31+
bool operator==(const XY& o)
32+
{
33+
return (x == o.x && y == o.y);
34+
}
35+
36+
bool operator!=(const XY& o)
37+
{
38+
return (x != o.x || y != o.y);
39+
}
3040
};
3141

3242
//
@@ -838,6 +848,25 @@ bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2)
838848
return false;
839849
}
840850

851+
void _finalize_polygon(std::vector<Polygon> &result)
852+
{
853+
Polygon &polygon = result.back();
854+
855+
if (result.size() == 0) {
856+
return;
857+
}
858+
859+
/* Clean up the last polygon in the result. If less than a
860+
triangle, remove it. */
861+
if (polygon.size() < 3) {
862+
result.pop_back();
863+
} else {
864+
if (polygon.front() != polygon.back()) {
865+
polygon.push_back(polygon.front());
866+
}
867+
}
868+
}
869+
841870
template <class PathIterator>
842871
void convert_path_to_polygons(PathIterator &path,
843872
agg::trans_affine &trans,
@@ -867,24 +896,20 @@ void convert_path_to_polygons(PathIterator &path,
867896

868897
while ((code = curve.vertex(&x, &y)) != agg::path_cmd_stop) {
869898
if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) {
870-
if (polygon->size() >= 1) {
871-
polygon->push_back((*polygon)[0]);
872-
result.push_back(Polygon());
873-
polygon = &result.back();
874-
}
899+
_finalize_polygon(result);
900+
result.push_back(Polygon());
901+
polygon = &result.back();
875902
} else {
876-
if (code == agg::path_cmd_move_to && polygon->size() >= 1) {
877-
polygon->push_back((*polygon)[0]);
903+
if (code == agg::path_cmd_move_to) {
904+
_finalize_polygon(result);
878905
result.push_back(Polygon());
879906
polygon = &result.back();
880907
}
881908
polygon->push_back(XY(x, y));
882909
}
883910
}
884911

885-
if (polygon->size() == 0) {
886-
result.pop_back();
887-
}
912+
_finalize_polygon(result);
888913
}
889914

890915
template <class VertexSource>

src/_path_wrapper.cpp

+18-5
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,29 @@
88
PyObject *convert_polygon_vector(std::vector<Polygon> &polygons)
99
{
1010
PyObject *pyresult = PyList_New(polygons.size());
11+
bool fix_endpoints;
1112

1213
for (size_t i = 0; i < polygons.size(); ++i) {
1314
Polygon poly = polygons[i];
14-
npy_intp dims[] = {(npy_intp)poly.size() + 1, 2 };
15-
numpy::array_view<double, 2> subresult(dims);
15+
npy_intp dims[2];
16+
dims[1] = 2;
17+
18+
if (poly.front() != poly.back()) {
19+
/* Make last point same as first, if not already */
20+
dims[0] = (npy_intp)poly.size() + 1;
21+
fix_endpoints = true;
22+
} else {
23+
dims[0] = (npy_intp)poly.size();
24+
fix_endpoints = false;
25+
}
1626

17-
/* Make last point same as first. */
27+
numpy::array_view<double, 2> subresult(dims);
1828
memcpy(subresult.data(), &poly[0], sizeof(double) * poly.size() * 2);
19-
subresult(poly.size(), 0) = poly[0].x;
20-
subresult(poly.size(), 1) = poly[0].y;
29+
30+
if (fix_endpoints) {
31+
subresult(poly.size(), 0) = poly.front().x;
32+
subresult(poly.size(), 1) = poly.front().y;
33+
}
2134

2235
if (PyList_SetItem(pyresult, i, subresult.pyobj())) {
2336
Py_DECREF(pyresult);

0 commit comments

Comments
 (0)