Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 37c9b04

Browse files
authored
Merge pull request #21830 from dstansby/polygon-box
Add option of bounding box for PolygonSelector
2 parents 209401c + ba7fdf2 commit 37c9b04

File tree

5 files changed

+200
-25
lines changed

5 files changed

+200
-25
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
PolygonSelector bounding boxes
2+
------------------------------
3+
`~matplotlib.widgets.PolygonSelector` now has a *draw_bounding_box* argument, which
4+
when set to `True` will draw a bounding box around the polygon once it is
5+
complete. The bounding box can be resized and moved, allowing the points of
6+
the polygon to be easily resized.

examples/widgets/polygon_selector_demo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def __init__(self, ax, collection, alpha_other=0.3):
5050
elif len(self.fc) == 1:
5151
self.fc = np.tile(self.fc, (self.Npts, 1))
5252

53-
self.poly = PolygonSelector(ax, self.onselect)
53+
self.poly = PolygonSelector(ax, self.onselect, draw_bounding_box=True)
5454
self.ind = []
5555

5656
def onselect(self, verts):

lib/matplotlib/lines.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,12 @@ def set_picker(self, p):
617617
self.pickradius = p
618618
self._picker = p
619619

620+
def get_bbox(self):
621+
"""Get the bounding box of this line."""
622+
bbox = Bbox([[0, 0], [0, 0]])
623+
bbox.update_from_data_xy(self.get_xydata())
624+
return bbox
625+
620626
def get_window_extent(self, renderer):
621627
bbox = Bbox([[0, 0], [0, 0]])
622628
trans_data_to_xy = self.get_transform().transform

lib/matplotlib/tests/test_widgets.py

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import functools
2+
13
from matplotlib._api.deprecation import MatplotlibDeprecationWarning
24
import matplotlib.colors as mcolors
35
import matplotlib.widgets as widgets
@@ -1112,7 +1114,8 @@ def test_range_slider(orientation):
11121114
assert_allclose(slider.val, [0.1, 0.34])
11131115

11141116

1115-
def check_polygon_selector(event_sequence, expected_result, selections_count):
1117+
def check_polygon_selector(event_sequence, expected_result, selections_count,
1118+
**kwargs):
11161119
"""
11171120
Helper function to test Polygon Selector.
11181121
@@ -1129,6 +1132,8 @@ def check_polygon_selector(event_sequence, expected_result, selections_count):
11291132
selections_count : int
11301133
Wait for the tool to call its `onselect` function `selections_count`
11311134
times, before comparing the result to the `expected_result`
1135+
**kwargs
1136+
Keyword arguments are passed to PolygonSelector.
11321137
"""
11331138
ax = get_ax()
11341139

@@ -1138,7 +1143,7 @@ def onselect(vertices):
11381143
ax._selections_count += 1
11391144
ax._current_result = vertices
11401145

1141-
tool = widgets.PolygonSelector(ax, onselect)
1146+
tool = widgets.PolygonSelector(ax, onselect, **kwargs)
11421147

11431148
for (etype, event_args) in event_sequence:
11441149
do_event(tool, etype, **event_args)
@@ -1159,14 +1164,18 @@ def polygon_remove_vertex(xdata, ydata):
11591164
('release', dict(xdata=xdata, ydata=ydata, button=3))]
11601165

11611166

1162-
def test_polygon_selector():
1167+
@pytest.mark.parametrize('draw_bounding_box', [False, True])
1168+
def test_polygon_selector(draw_bounding_box):
1169+
check_selector = functools.partial(
1170+
check_polygon_selector, draw_bounding_box=draw_bounding_box)
1171+
11631172
# Simple polygon
11641173
expected_result = [(50, 50), (150, 50), (50, 150)]
11651174
event_sequence = (polygon_place_vertex(50, 50)
11661175
+ polygon_place_vertex(150, 50)
11671176
+ polygon_place_vertex(50, 150)
11681177
+ polygon_place_vertex(50, 50))
1169-
check_polygon_selector(event_sequence, expected_result, 1)
1178+
check_selector(event_sequence, expected_result, 1)
11701179

11711180
# Move first vertex before completing the polygon.
11721181
expected_result = [(75, 50), (150, 50), (50, 150)]
@@ -1180,7 +1189,7 @@ def test_polygon_selector():
11801189
('on_key_release', dict(key='control'))]
11811190
+ polygon_place_vertex(50, 150)
11821191
+ polygon_place_vertex(75, 50))
1183-
check_polygon_selector(event_sequence, expected_result, 1)
1192+
check_selector(event_sequence, expected_result, 1)
11841193

11851194
# Move first two vertices at once before completing the polygon.
11861195
expected_result = [(50, 75), (150, 75), (50, 150)]
@@ -1194,7 +1203,7 @@ def test_polygon_selector():
11941203
('on_key_release', dict(key='shift'))]
11951204
+ polygon_place_vertex(50, 150)
11961205
+ polygon_place_vertex(50, 75))
1197-
check_polygon_selector(event_sequence, expected_result, 1)
1206+
check_selector(event_sequence, expected_result, 1)
11981207

11991208
# Move first vertex after completing the polygon.
12001209
expected_result = [(75, 50), (150, 50), (50, 150)]
@@ -1206,7 +1215,7 @@ def test_polygon_selector():
12061215
('press', dict(xdata=50, ydata=50)),
12071216
('onmove', dict(xdata=75, ydata=50)),
12081217
('release', dict(xdata=75, ydata=50))])
1209-
check_polygon_selector(event_sequence, expected_result, 2)
1218+
check_selector(event_sequence, expected_result, 2)
12101219

12111220
# Move all vertices after completing the polygon.
12121221
expected_result = [(75, 75), (175, 75), (75, 175)]
@@ -1220,7 +1229,7 @@ def test_polygon_selector():
12201229
('onmove', dict(xdata=125, ydata=125)),
12211230
('release', dict(xdata=125, ydata=125)),
12221231
('on_key_release', dict(key='shift'))])
1223-
check_polygon_selector(event_sequence, expected_result, 2)
1232+
check_selector(event_sequence, expected_result, 2)
12241233

12251234
# Try to move a vertex and move all before placing any vertices.
12261235
expected_result = [(50, 50), (150, 50), (50, 150)]
@@ -1240,7 +1249,7 @@ def test_polygon_selector():
12401249
+ polygon_place_vertex(150, 50)
12411250
+ polygon_place_vertex(50, 150)
12421251
+ polygon_place_vertex(50, 50))
1243-
check_polygon_selector(event_sequence, expected_result, 1)
1252+
check_selector(event_sequence, expected_result, 1)
12441253

12451254
# Try to place vertex out-of-bounds, then reset, and start a new polygon.
12461255
expected_result = [(50, 50), (150, 50), (50, 150)]
@@ -1252,10 +1261,11 @@ def test_polygon_selector():
12521261
+ polygon_place_vertex(150, 50)
12531262
+ polygon_place_vertex(50, 150)
12541263
+ polygon_place_vertex(50, 50))
1255-
check_polygon_selector(event_sequence, expected_result, 1)
1264+
check_selector(event_sequence, expected_result, 1)
12561265

12571266

1258-
def test_polygon_selector_set_props_handle_props():
1267+
@pytest.mark.parametrize('draw_bounding_box', [False, True])
1268+
def test_polygon_selector_set_props_handle_props(draw_bounding_box):
12591269
ax = get_ax()
12601270

12611271
ax._selections_count = 0
@@ -1266,7 +1276,8 @@ def onselect(vertices):
12661276

12671277
tool = widgets.PolygonSelector(ax, onselect,
12681278
props=dict(color='b', alpha=0.2),
1269-
handle_props=dict(alpha=0.5))
1279+
handle_props=dict(alpha=0.5),
1280+
draw_bounding_box=draw_bounding_box)
12701281

12711282
event_sequence = (polygon_place_vertex(50, 50)
12721283
+ polygon_place_vertex(150, 50)
@@ -1308,7 +1319,8 @@ def onselect(verts):
13081319

13091320
# Change the order that the extra point is inserted in
13101321
@pytest.mark.parametrize('idx', [1, 2, 3])
1311-
def test_polygon_selector_remove(idx):
1322+
@pytest.mark.parametrize('draw_bounding_box', [False, True])
1323+
def test_polygon_selector_remove(idx, draw_bounding_box):
13121324
verts = [(50, 50), (150, 50), (50, 150)]
13131325
event_sequence = [polygon_place_vertex(*verts[0]),
13141326
polygon_place_vertex(*verts[1]),
@@ -1321,20 +1333,24 @@ def test_polygon_selector_remove(idx):
13211333
event_sequence.append(polygon_remove_vertex(200, 200))
13221334
# Flatten list of lists
13231335
event_sequence = sum(event_sequence, [])
1324-
check_polygon_selector(event_sequence, verts, 2)
1336+
check_polygon_selector(event_sequence, verts, 2,
1337+
draw_bounding_box=draw_bounding_box)
13251338

13261339

1327-
def test_polygon_selector_remove_first_point():
1340+
@pytest.mark.parametrize('draw_bounding_box', [False, True])
1341+
def test_polygon_selector_remove_first_point(draw_bounding_box):
13281342
verts = [(50, 50), (150, 50), (50, 150)]
13291343
event_sequence = (polygon_place_vertex(*verts[0]) +
13301344
polygon_place_vertex(*verts[1]) +
13311345
polygon_place_vertex(*verts[2]) +
13321346
polygon_place_vertex(*verts[0]) +
13331347
polygon_remove_vertex(*verts[0]))
1334-
check_polygon_selector(event_sequence, verts[1:], 2)
1348+
check_polygon_selector(event_sequence, verts[1:], 2,
1349+
draw_bounding_box=draw_bounding_box)
13351350

13361351

1337-
def test_polygon_selector_redraw():
1352+
@pytest.mark.parametrize('draw_bounding_box', [False, True])
1353+
def test_polygon_selector_redraw(draw_bounding_box):
13381354
verts = [(50, 50), (150, 50), (50, 150)]
13391355
event_sequence = (polygon_place_vertex(*verts[0]) +
13401356
polygon_place_vertex(*verts[1]) +
@@ -1352,7 +1368,8 @@ def test_polygon_selector_redraw():
13521368
def onselect(vertices):
13531369
pass
13541370

1355-
tool = widgets.PolygonSelector(ax, onselect)
1371+
tool = widgets.PolygonSelector(ax, onselect,
1372+
draw_bounding_box=draw_bounding_box)
13561373
for (etype, event_args) in event_sequence:
13571374
do_event(tool, etype, **event_args)
13581375
# After removing two verts, only one remains, and the
@@ -1382,6 +1399,56 @@ def onselect(vertices):
13821399
do_event(tool_ref, etype, **event_args)
13831400

13841401

1402+
def test_polygon_selector_box():
1403+
# Create a diamond shape
1404+
verts = [(20, 0), (0, 20), (20, 40), (40, 20)]
1405+
event_sequence = (polygon_place_vertex(*verts[0]) +
1406+
polygon_place_vertex(*verts[1]) +
1407+
polygon_place_vertex(*verts[2]) +
1408+
polygon_place_vertex(*verts[3]) +
1409+
polygon_place_vertex(*verts[0]))
1410+
1411+
ax = get_ax()
1412+
1413+
def onselect(vertices):
1414+
pass
1415+
1416+
# Create selector
1417+
tool = widgets.PolygonSelector(ax, onselect, draw_bounding_box=True)
1418+
for (etype, event_args) in event_sequence:
1419+
do_event(tool, etype, **event_args)
1420+
1421+
# In order to trigger the correct callbacks, trigger events on the canvas
1422+
# instead of the individual tools
1423+
t = ax.transData
1424+
canvas = ax.figure.canvas
1425+
1426+
# Scale to half size using the top right corner of the bounding box
1427+
canvas.button_press_event(*t.transform((40, 40)), 1)
1428+
canvas.motion_notify_event(*t.transform((20, 20)))
1429+
canvas.button_release_event(*t.transform((20, 20)), 1)
1430+
np.testing.assert_allclose(
1431+
tool.verts, [(10, 0), (0, 10), (10, 20), (20, 10)])
1432+
1433+
# Move using the center of the bounding box
1434+
canvas.button_press_event(*t.transform((10, 10)), 1)
1435+
canvas.motion_notify_event(*t.transform((30, 30)))
1436+
canvas.button_release_event(*t.transform((30, 30)), 1)
1437+
np.testing.assert_allclose(
1438+
tool.verts, [(30, 20), (20, 30), (30, 40), (40, 30)])
1439+
1440+
# Remove a point from the polygon and check that the box extents update
1441+
np.testing.assert_allclose(
1442+
tool._box.extents, (20.0, 40.0, 20.0, 40.0))
1443+
1444+
canvas.button_press_event(*t.transform((30, 20)), 3)
1445+
canvas.button_release_event(*t.transform((30, 20)), 3)
1446+
np.testing.assert_allclose(
1447+
tool.verts, [(20, 30), (30, 40), (40, 30)])
1448+
np.testing.assert_allclose(
1449+
tool._box.extents, (20.0, 40.0, 30.0, 40.0))
1450+
1451+
13851452
@pytest.mark.parametrize(
13861453
"horizOn, vertOn",
13871454
[(True, True), (True, False), (False, True)],

0 commit comments

Comments
 (0)