Skip to content

Commit

Permalink
Merge pull request #5672 from mdboom/double-endpoints
Browse files Browse the repository at this point in the history
Fix #5670. No double endpoints in Path.to_polygon
  • Loading branch information
tacaswell committed Dec 15, 2015
2 parents 5904e2f + bf230fd commit a82ca89
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 19 deletions.
36 changes: 36 additions & 0 deletions lib/matplotlib/tests/test_path.py
Expand Up @@ -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():
Expand Down Expand Up @@ -113,6 +114,41 @@ 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)
2 changes: 1 addition & 1 deletion lib/matplotlib/tests/test_widgets.py
Expand Up @@ -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])


Expand Down
6 changes: 3 additions & 3 deletions lib/matplotlib/widgets.py
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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())

Expand Down
45 changes: 35 additions & 10 deletions src/_path.h
Expand Up @@ -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);
}
};

//
Expand Down Expand Up @@ -838,6 +848,25 @@ bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2)
return false;
}

void _finalize_polygon(std::vector<Polygon> &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 <class PathIterator>
void convert_path_to_polygons(PathIterator &path,
agg::trans_affine &trans,
Expand Down Expand Up @@ -867,24 +896,20 @@ 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();
}
polygon->push_back(XY(x, y));
}
}

if (polygon->size() == 0) {
result.pop_back();
}
_finalize_polygon(result);
}

template <class VertexSource>
Expand Down
23 changes: 18 additions & 5 deletions src/_path_wrapper.cpp
Expand Up @@ -8,16 +8,29 @@
PyObject *convert_polygon_vector(std::vector<Polygon> &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<double, 2> 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<double, 2> 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);
Expand Down

0 comments on commit a82ca89

Please sign in to comment.