Skip to content

Commit 93d1434

Browse files
authored
Merge pull request #19660 from dstansby/remove-poly-points
Allow PolygonSelector points to be removed
2 parents 842f1e6 + 0554f6e commit 93d1434

File tree

3 files changed

+111
-5
lines changed

3 files changed

+111
-5
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Removing points on a PolygonSelector
2+
------------------------------------
3+
After completing a `~matplotlib.widgets.PolygonSelector`, individual
4+
points can now be removed by right-clicking on them.

lib/matplotlib/tests/test_widgets.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,12 @@ def polygon_place_vertex(xdata, ydata):
416416
('release', dict(xdata=xdata, ydata=ydata))]
417417

418418

419+
def polygon_remove_vertex(xdata, ydata):
420+
return [('onmove', dict(xdata=xdata, ydata=ydata)),
421+
('press', dict(xdata=xdata, ydata=ydata, button=3)),
422+
('release', dict(xdata=xdata, ydata=ydata, button=3))]
423+
424+
419425
def test_polygon_selector():
420426
# Simple polygon
421427
expected_result = [(50, 50), (150, 50), (50, 150)]
@@ -564,3 +570,57 @@ def onselect(verts):
564570
tool = widgets.RectangleSelector(ax_test, onselect,
565571
rectprops={'visible': False})
566572
tool.extents = (0.2, 0.8, 0.3, 0.7)
573+
574+
575+
# Change the order that the extra point is inserted in
576+
@pytest.mark.parametrize('idx', [1, 2, 3])
577+
def test_polygon_selector_remove(idx):
578+
verts = [(50, 50), (150, 50), (50, 150)]
579+
event_sequence = [polygon_place_vertex(*verts[0]),
580+
polygon_place_vertex(*verts[1]),
581+
polygon_place_vertex(*verts[2]),
582+
# Finish the polygon
583+
polygon_place_vertex(*verts[0])]
584+
# Add an extra point
585+
event_sequence.insert(idx, polygon_place_vertex(200, 200))
586+
# Remove the extra point
587+
event_sequence.append(polygon_remove_vertex(200, 200))
588+
# Flatten list of lists
589+
event_sequence = sum(event_sequence, [])
590+
check_polygon_selector(event_sequence, verts, 2)
591+
592+
593+
def test_polygon_selector_remove_first_point():
594+
verts = [(50, 50), (150, 50), (50, 150)]
595+
event_sequence = (polygon_place_vertex(*verts[0]) +
596+
polygon_place_vertex(*verts[1]) +
597+
polygon_place_vertex(*verts[2]) +
598+
polygon_place_vertex(*verts[0]) +
599+
polygon_remove_vertex(*verts[0]))
600+
check_polygon_selector(event_sequence, verts[1:], 2)
601+
602+
603+
def test_polygon_selector_redraw():
604+
verts = [(50, 50), (150, 50), (50, 150)]
605+
event_sequence = (polygon_place_vertex(*verts[0]) +
606+
polygon_place_vertex(*verts[1]) +
607+
polygon_place_vertex(*verts[2]) +
608+
polygon_place_vertex(*verts[0]) +
609+
# Polygon completed, now remove first two verts
610+
polygon_remove_vertex(*verts[1]) +
611+
polygon_remove_vertex(*verts[2]) +
612+
# At this point the tool should be reset so we can add
613+
# more vertices
614+
polygon_place_vertex(*verts[1]))
615+
616+
ax = get_ax()
617+
618+
def onselect(vertices):
619+
pass
620+
621+
tool = widgets.PolygonSelector(ax, onselect)
622+
for (etype, event_args) in event_sequence:
623+
do_event(tool, etype, **event_args)
624+
# After removing two verts, only one remains, and the
625+
# selector should be automatically resete
626+
assert tool.verts == verts[0:2]

lib/matplotlib/widgets.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2759,11 +2759,17 @@ class PolygonSelector(_SelectorWidget):
27592759
Select a polygon region of an axes.
27602760
27612761
Place vertices with each mouse click, and make the selection by completing
2762-
the polygon (clicking on the first vertex). Hold the *ctrl* key and click
2763-
and drag a vertex to reposition it (the *ctrl* key is not necessary if the
2764-
polygon has already been completed). Hold the *shift* key and click and
2765-
drag anywhere in the axes to move all vertices. Press the *esc* key to
2766-
start a new polygon.
2762+
the polygon (clicking on the first vertex). Once drawn individual vertices
2763+
can be moved by clicking and dragging with the left mouse button, or
2764+
removed by clicking the right mouse button.
2765+
2766+
In addition, the following modifier keys can be used:
2767+
2768+
- Hold *ctrl* and click and drag a vertex to reposition it before the
2769+
polygon has been completed.
2770+
- Hold the *shift* key and click and drag anywhere in the axes to move
2771+
all vertices.
2772+
- Press the *esc* key to start a new polygon.
27672773
27682774
For the selector to remain responsive you must keep a reference to it.
27692775
@@ -2789,6 +2795,12 @@ class PolygonSelector(_SelectorWidget):
27892795
Examples
27902796
--------
27912797
:doc:`/gallery/widgets/polygon_selector_demo`
2798+
2799+
Notes
2800+
-----
2801+
If only one point remains after removing points, the selector reverts to an
2802+
incomplete state and you can start drawing a new polygon from the existing
2803+
point.
27922804
"""
27932805

27942806
def __init__(self, ax, onselect, useblit=False,
@@ -2827,6 +2839,33 @@ def __init__(self, ax, onselect, useblit=False,
28272839
self.artists = [self.line, self._polygon_handles.artist]
28282840
self.set_visible(True)
28292841

2842+
@property
2843+
def _nverts(self):
2844+
return len(self._xs)
2845+
2846+
def _remove_vertex(self, i):
2847+
"""Remove vertex with index i."""
2848+
if (self._nverts > 2 and
2849+
self._polygon_completed and
2850+
i in (0, self._nverts - 1)):
2851+
# If selecting the first or final vertex, remove both first and
2852+
# last vertex as they are the same for a closed polygon
2853+
self._xs.pop(0)
2854+
self._ys.pop(0)
2855+
self._xs.pop(-1)
2856+
self._ys.pop(-1)
2857+
# Close the polygon again by appending the new first vertex to the
2858+
# end
2859+
self._xs.append(self._xs[0])
2860+
self._ys.append(self._ys[0])
2861+
else:
2862+
self._xs.pop(i)
2863+
self._ys.pop(i)
2864+
if self._nverts <= 2:
2865+
# If only one point left, return to incomplete state to let user
2866+
# start drawing again
2867+
self._polygon_completed = False
2868+
28302869
def _press(self, event):
28312870
"""Button press event handler."""
28322871
# Check for selection of a tool handle.
@@ -2843,6 +2882,9 @@ def _release(self, event):
28432882
"""Button release event handler."""
28442883
# Release active tool handle.
28452884
if self._active_handle_idx >= 0:
2885+
if event.button == 3:
2886+
self._remove_vertex(self._active_handle_idx)
2887+
self._draw_polygon()
28462888
self._active_handle_idx = -1
28472889

28482890
# Complete the polygon.

0 commit comments

Comments
 (0)