diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 6c09ee9e474b..2a7e97814928 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -3643,21 +3643,20 @@ def __init__(self, ax, onselect, useblit=False, super().__init__(ax, onselect, useblit=useblit, state_modifier_keys=state_modifier_keys) - self._xs, self._ys = [0], [0] + self._xys = [(0, 0)] if props is None: props = dict(color='k', linestyle='-', linewidth=2, alpha=0.5) props['animated'] = self.useblit self._props = props - line = Line2D(self._xs, self._ys, **self._props) + self._selection_artist = line = Line2D([], [], **self._props) self.ax.add_line(line) - self._selection_artist = line if handle_props is None: handle_props = dict(markeredgecolor='k', markerfacecolor=self._props.get('color', 'k')) self._handle_props = handle_props - self._polygon_handles = ToolHandles(self.ax, self._xs, self._ys, + self._polygon_handles = ToolHandles(self.ax, [], [], useblit=self.useblit, marker_props=self._handle_props) @@ -3728,10 +3727,9 @@ def _scale_polygon(self, event): .scale(w1, h1) .translate(x1, y1)) - # Update polygon verts - new_verts = t.transform(np.array(self.verts)) - self._xs = list(np.append(new_verts[:, 0], new_verts[0, 0])) - self._ys = list(np.append(new_verts[:, 1], new_verts[0, 1])) + # Update polygon verts. Must be a list of tuples for consistency. + new_verts = [(x, y) for x, y in t.transform(np.array(self.verts))] + self._xys = [*new_verts, new_verts[0]] self._draw_polygon() self._old_box_extents = self._box.extents @@ -3745,33 +3743,25 @@ def _scale_polygon(self, event): lambda self, value: setattr(self, "grab_range", value)) ) - @property - def _nverts(self): - return len(self._xs) - @property def _handles_artists(self): return self._polygon_handles.artists def _remove_vertex(self, i): """Remove vertex with index i.""" - if (self._nverts > 2 and + if (len(self._xys) > 2 and self._selection_completed and - i in (0, self._nverts - 1)): + i in (0, len(self._xys) - 1)): # If selecting the first or final vertex, remove both first and # last vertex as they are the same for a closed polygon - self._xs.pop(0) - self._ys.pop(0) - self._xs.pop(-1) - self._ys.pop(-1) + self._xys.pop(0) + self._xys.pop(-1) # Close the polygon again by appending the new first vertex to the # end - self._xs.append(self._xs[0]) - self._ys.append(self._ys[0]) + self._xys.append(self._xys[0]) else: - self._xs.pop(i) - self._ys.pop(i) - if self._nverts <= 2: + self._xys.pop(i) + if len(self._xys) <= 2: # If only one point left, return to incomplete state to let user # start drawing again self._selection_completed = False @@ -3781,13 +3771,13 @@ def _press(self, event): """Button press event handler.""" # Check for selection of a tool handle. if ((self._selection_completed or 'move_vertex' in self._state) - and len(self._xs) > 0): + and len(self._xys) > 0): h_idx, h_dist = self._polygon_handles.closest(event.x, event.y) if h_dist < self.grab_range: self._active_handle_idx = h_idx # Save the vertex positions at the time of the press event (needed to # support the 'move_all' state modifier). - self._xs_at_press, self._ys_at_press = self._xs.copy(), self._ys.copy() + self._xys_at_press = self._xys.copy() def _release(self, event): """Button release event handler.""" @@ -3799,9 +3789,7 @@ def _release(self, event): self._active_handle_idx = -1 # Complete the polygon. - elif (len(self._xs) > 3 - and self._xs[-1] == self._xs[0] - and self._ys[-1] == self._ys[0]): + elif len(self._xys) > 3 and self._xys[-1] == self._xys[0]: self._selection_completed = True if self._draw_box and self._box is None: self._add_box() @@ -3810,8 +3798,7 @@ def _release(self, event): elif (not self._selection_completed and 'move_all' not in self._state and 'move_vertex' not in self._state): - self._xs.insert(-1, event.xdata) - self._ys.insert(-1, event.ydata) + self._xys.insert(-1, (event.xdata, event.ydata)) if self._selection_completed: self.onselect(self.verts) @@ -3833,19 +3820,19 @@ def _onmove(self, event): # Move the active vertex (ToolHandle). if self._active_handle_idx >= 0: idx = self._active_handle_idx - self._xs[idx], self._ys[idx] = event.xdata, event.ydata + self._xys[idx] = event.xdata, event.ydata # Also update the end of the polygon line if the first vertex is # the active handle and the polygon is completed. if idx == 0 and self._selection_completed: - self._xs[-1], self._ys[-1] = event.xdata, event.ydata + self._xys[-1] = event.xdata, event.ydata # Move all vertices. elif 'move_all' in self._state and self._eventpress: dx = event.xdata - self._eventpress.xdata dy = event.ydata - self._eventpress.ydata - for k in range(len(self._xs)): - self._xs[k] = self._xs_at_press[k] + dx - self._ys[k] = self._ys_at_press[k] + dy + for k in range(len(self._xys)): + x_at_press, y_at_press = self._xys_at_press[k] + self._xys[k] = x_at_press + dx, y_at_press + dy # Do nothing if completed or waiting for a move. elif (self._selection_completed @@ -3855,15 +3842,14 @@ def _onmove(self, event): # Position pending vertex. else: # Calculate distance to the start vertex. - x0, y0 = self._selection_artist.get_transform().transform( - (self._xs[0], self._ys[0]) - ) + x0, y0 = \ + self._selection_artist.get_transform().transform(self._xys[0]) v0_dist = np.hypot(x0 - event.x, y0 - event.y) # Lock on to the start vertex if near it and ready to complete. - if len(self._xs) > 3 and v0_dist < self.grab_range: - self._xs[-1], self._ys[-1] = self._xs[0], self._ys[0] + if len(self._xys) > 3 and v0_dist < self.grab_range: + self._xys[-1] = self._xys[0] else: - self._xs[-1], self._ys[-1] = event.xdata, event.ydata + self._xys[-1] = event.xdata, event.ydata self._draw_polygon() @@ -3874,7 +3860,7 @@ def _on_key_press(self, event): if (not self._selection_completed and ('move_vertex' in self._state or 'move_all' in self._state)): - self._xs, self._ys = self._xs[:-1], self._ys[:-1] + self._xys.pop() self._draw_polygon() def _on_key_release(self, event): @@ -3885,37 +3871,36 @@ def _on_key_release(self, event): and (event.key == self._state_modifier_keys.get('move_vertex') or event.key == self._state_modifier_keys.get('move_all'))): - self._xs.append(event.xdata) - self._ys.append(event.ydata) + self._xys.append((event.xdata, event.ydata)) self._draw_polygon() # Reset the polygon if the released key is the 'clear' key. elif event.key == self._state_modifier_keys.get('clear'): event = self._clean_event(event) - self._xs, self._ys = [event.xdata], [event.ydata] + self._xys = [(event.xdata, event.ydata)] self._selection_completed = False self._remove_box() self.set_visible(True) def _draw_polygon(self): """Redraw the polygon based on the new vertex positions.""" - self._selection_artist.set_data(self._xs, self._ys) + xs, ys = zip(*self._xys) if self._xys else ([], []) + self._selection_artist.set_data(xs, ys) self._update_box() # Only show one tool handle at the start and end vertex of the polygon # if the polygon is completed or the user is locked on to the start # vertex. if (self._selection_completed - or (len(self._xs) > 3 - and self._xs[-1] == self._xs[0] - and self._ys[-1] == self._ys[0])): - self._polygon_handles.set_data(self._xs[:-1], self._ys[:-1]) + or (len(self._xys) > 3 + and self._xys[-1] == self._xys[0])): + self._polygon_handles.set_data(xs[:-1], ys[:-1]) else: - self._polygon_handles.set_data(self._xs, self._ys) + self._polygon_handles.set_data(xs, ys) self.update() @property def verts(self): """The polygon vertices, as a list of ``(x, y)`` pairs.""" - return list(zip(self._xs[:-1], self._ys[:-1])) + return self._xys[:-1] @verts.setter def verts(self, xys): @@ -3925,10 +3910,7 @@ def verts(self, xys): This will remove any pre-existing vertices, creating a complete polygon with the new vertices. """ - self._xs = [xy[0] for xy in xys] - self._xs.append(self._xs[0]) - self._ys = [xy[1] for xy in xys] - self._ys.append(self._ys[0]) + self._xys = [*xys, xys[0]] self._selection_completed = True self.set_visible(True) self._draw_polygon()