Following a tutorial Poly Editor I’ve created 2 polygons, added to the figure, and then instantiated 2 interactive polygons. I expected that when I adjusted the individual polygon it would work separately. However they were conflicting with each other.
Then I have tried some changes in the original code yet still not produce a successful result. If you’re familiar I would like to have your help. I really appreciate in advance.
Here is my following adjusted code:
""" =========== Poly Editor =========== This is an example to show how to build cross-GUI applications using Matplotlib event handling to interact with objects on the canvas. """ import numpy as np from matplotlib.lines import Line2D from matplotlib.artist import Artist def dist(x, y): """ Return the distance between two points. """ d = x - y return np.sqrt(np.dot(d, d)) def dist_point_to_segment(p, s0, s1): """ Get the distance of a point to a segment. *p*, *s0*, *s1* are *xy* sequences This algorithm from http://geomalgorithms.com/a02-_lines.html """ v = s1 - s0 w = p - s0 c1 = np.dot(w, v) if c1 <= 0: return dist(p, s0) c2 = np.dot(v, v) if c2 <= c1: return dist(p, s1) b = c1 / c2 pb = s0 + b * v return dist(p, pb) class PolygonInteractor: """ A polygon editor. Key-bindings 't' toggle vertex markers on and off. When vertex markers are on, you can move them, delete them 'd' delete the vertex under point 'i' insert a vertex at point. You must be within epsilon of the line connecting two existing vertices """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit def __init__(self, ax, poly): if poly.figure is None: raise RuntimeError('You must first add the polygon to a figure ' 'or canvas before defining the interactor') self.ax = ax self.poly = poly x, y = zip(*self.poly.xy) self.line = Line2D(x, y, marker='o', markerfacecolor='r', animated=True) self.ax.add_line(self.line) self._ind = None # the active vert def connect(self): 'connect to all the events we need' self.cid = self.poly.add_callback(self.poly_changed) self.ciddraw = self.poly.figure.canvas.mpl_connect('draw_event', self.on_draw) self.cidpress = self.poly.figure.canvas.mpl_connect('button_press_event', self.on_button_press) self.cidrelease = self.poly.figure.canvas.mpl_connect('button_release_event', self.on_button_release) self.cidkeypress = self.poly.figure.canvas.mpl_connect('key_press_event', self.on_key_press) self.cidmotion = self.poly.figure.canvas.mpl_connect('motion_notify_event', self.on_mouse_move) def on_draw(self, event): self.background = self.poly.figure.canvas.copy_from_bbox(self.poly.figure.bbox) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) # do not need to blit here, this will fire before the screen is # updated def poly_changed(self, poly): """This method is called whenever the pathpatch object is called.""" # only copy the artist props to the line (except visibility) vis = self.line.get_visible() Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state def get_ind_under_point(self, event): """ Return the index of the point closest to the event position or *None* if no point is within ``self.epsilon`` to the event position. """ # display coords xy = np.asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = np.hypot(xt - event.x, yt - event.y) indseq, = np.nonzero(d == d.min()) ind = indseq[0] if d[ind] >= self.epsilon: ind = None return ind def on_button_press(self, event): """Callback for mouse button presses.""" if not self.showverts: return if event.inaxes is None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) def on_button_release(self, event): """Callback for mouse button releases.""" if not self.showverts: return if event.button != 1: return self._ind = None def on_key_press(self, event): """Callback for key presses.""" if not event.inaxes: return if event.key == 't': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key == 'd': ind = self.get_ind_under_point(event) if ind is not None: self.poly.xy = np.delete(self.poly.xy, ind, axis=0) self.line.set_data(zip(*self.poly.xy)) elif event.key == 'i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # display coords for i in range(len(xys) - 1): s0 = xys[i] s1 = xys[i + 1] d = dist_point_to_segment(p, s0, s1) if d <= self.epsilon: self.poly.xy = np.insert( self.poly.xy, i+1, [event.xdata, event.ydata], axis=0) self.line.set_data(zip(*self.poly.xy)) break if self.line.stale: self.poly.figure.canvas.draw_idle() def on_mouse_move(self, event): """Callback for mouse movements.""" if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata self.poly.xy[self._ind] = x, y if self._ind == 0: self.poly.xy[-1] = x, y elif self._ind == len(self.poly.xy) - 1: self.poly.xy[0] = x, y self.line.set_data(zip(*self.poly.xy)) self.poly.figure.canvas.restore_region(self.background) self.poly.figure.draw_artist(self.poly) self.poly.figure.draw_artist(self.line) self.poly.figure.canvas.blit(self.poly.figure.bbox) def disconnect(self): 'disconnect all the stored connection ids' self.poly.figure.canvas.mpl_disconnect(self.ciddraw) self.poly.figure.canvas.mpl_disconnect(self.cidpress) self.poly.figure.canvas.mpl_disconnect(self.cidrelease) self.poly.figure.canvas.mpl_disconnect(self.cidmotion) self.poly.figure.canvas.mpl_disconnect(self.cidkeypress) if __name__ == '__main__': import matplotlib.pyplot as plt from matplotlib.patches import Polygon from matplotlib.collections import PatchCollection import matplotlib # Fixing random state for reproducibility np.random.seed(19680801) fig, ax = plt.subplots() N, LP = (4, 3) poly_4_pts = Polygon(np.random.rand(N, 2), True, fill=None, animated=True) poly_2_pts = Polygon(np.random.rand(LP, 2), True, fill=None, animated=True) ax.add_patch(poly_4_pts) fig.patches.extend([poly_2_pts]) ax.add_patch(poly_2_pts) poly2 = PolygonInteractor(ax, poly_2_pts) poly2.connect() poly1 = PolygonInteractor(ax, poly_4_pts) poly1.connect() ax.set_title('Click and drag a point to move it') ax.set_xlim((-2, 2)) ax.set_ylim((-2, 2)) plt.show()
Advertisement
Answer
I think that the problem is that you don’t restore the animated artists when restoring backgronds. See tutorial on blitting here. I could get it working adding some modifications to restore all the animated object added to the figure, since also the contour lines are animated you should declare them in the main and not in the class then restore all animated inside the class. Here’s my code and result:
""" =========== Poly Editor =========== This is an example to show how to build cross-GUI applications using Matplotlib event handling to interact with objects on the canvas. """ import numpy as np from matplotlib.lines import Line2D from matplotlib.artist import Artist def dist(x, y): """ Return the distance between two points. """ d = x - y return np.sqrt(np.dot(d, d)) def dist_point_to_segment(p, s0, s1): """ Get the distance of a point to a segment. *p*, *s0*, *s1* are *xy* sequences This algorithm from http://geomalgorithms.com/a02-_lines.html """ v = s1 - s0 w = p - s0 c1 = np.dot(w, v) if c1 <= 0: return dist(p, s0) c2 = np.dot(v, v) if c2 <= c1: return dist(p, s1) b = c1 / c2 pb = s0 + b * v return dist(p, pb) class PolygonInteractor: """ A polygon editor. Key-bindings 't' toggle vertex markers on and off. When vertex markers are on, you can move them, delete them 'd' delete the vertex under point 'i' insert a vertex at point. You must be within epsilon of the line connecting two existing vertices """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit def __init__(self, ax, poly, contour, animated_artists=()): if poly.figure is None: raise RuntimeError('You must first add the polygon to a figure ' 'or canvas before defining the interactor') self.ax = ax canvas = poly.figure.canvas self.poly = poly self.line = contour self._ind = None # the active vert canvas.mpl_connect('draw_event', self.on_draw) canvas.mpl_connect('button_press_event', self.on_button_press) canvas.mpl_connect('key_press_event', self.on_key_press) canvas.mpl_connect('button_release_event', self.on_button_release) canvas.mpl_connect('motion_notify_event', self.on_mouse_move) self.canvas = canvas self._artists = [] for a in animated_artists: self.add_artist(a) #self.add_artist(self.line) def add_artist(self, art): if art.figure != self.canvas.figure: raise RuntimeError art.set_animated(True) self._artists.append(art) def on_draw(self, event): self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self._draw_animated() self.canvas.blit(self.ax.bbox) # do not need to blit here, this will fire before the screen is # updated def _draw_animated(self): """Draw all of the animated artists.""" fig = self.canvas.figure for a in self._artists: fig.draw_artist(a) def get_ind_under_point(self, event): """ Return the index of the point closest to the event position or *None* if no point is within ``self.epsilon`` to the event position. """ # display coords xy = np.asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = np.hypot(xt - event.x, yt - event.y) indseq, = np.nonzero(d == d.min()) ind = indseq[0] if d[ind] >= self.epsilon: ind = None return ind def on_button_press(self, event): """Callback for mouse button presses.""" if not self.showverts: return if event.inaxes is None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) def on_button_release(self, event): """Callback for mouse button releases.""" if not self.showverts: return if event.button != 1: return self._ind = None def on_key_press(self, event): """Callback for key presses.""" if not event.inaxes: return if event.key == 't': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key == 'd': ind = self.get_ind_under_point(event) if ind is not None: self.poly.xy = np.delete(self.poly.xy, ind, axis=0) self.line.set_data(zip(*self.poly.xy)) elif event.key == 'i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # display coords for i in range(len(xys) - 1): s0 = xys[i] s1 = xys[i + 1] d = dist_point_to_segment(p, s0, s1) if d <= self.epsilon: self.poly.xy = np.insert( self.poly.xy, i+1, [event.xdata, event.ydata], axis=0) self.line.set_data(zip(*self.poly.xy)) break if self.line.stale: self.canvas.draw_idle() def on_mouse_move(self, event): """Callback for mouse movements.""" if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata self.poly.xy[self._ind] = x, y if self._ind == 0: self.poly.xy[-1] = x, y elif self._ind == len(self.poly.xy) - 1: self.poly.xy[0] = x, y self.line.set_data(zip(*self.poly.xy)) self.canvas.restore_region(self.background) self._draw_animated() self.canvas.draw_idle() # self.ax.draw_artist(self.poly) # self.ax.draw_artist(self.line) self.canvas.blit(self.ax.bbox) if __name__ == '__main__': import matplotlib.pyplot as plt from matplotlib.patches import Polygon theta = np.arange(0, 2*np.pi, 0.1) r = 1.5 xs = r * np.cos(theta) ys = r * np.sin(theta) poly = Polygon(np.column_stack([xs, ys]), animated=True) xc1, yc1 = zip(*poly.xy) poly_contour = Line2D(xc1, yc1, marker='o', markerfacecolor='r', animated=True) poly2 = Polygon(np.column_stack([xs+3, ys+3]), animated=True) xc2, yc2 = zip(*poly2.xy) poly_contour2 = Line2D(xc2, yc2, marker='o', markerfacecolor='r', animated=True) fig, ax = plt.subplots() ax.add_patch(poly) ax.add_line(poly_contour) ax.add_patch(poly2) ax.add_line(poly_contour2) p = PolygonInteractor(ax, poly, poly_contour, [poly, poly2, poly_contour, poly_contour2]) p2 = PolygonInteractor(ax, poly2, poly_contour2, [poly, poly2, poly_contour, poly_contour2]) ax.set_title('Click and drag a point to move it') ax.set_xlim((-2, 6)) ax.set_ylim((-2, 6)) plt.show()