Matplotlib drag overlapping points interactively

前端 未结 2 1598
时光取名叫无心
时光取名叫无心 2020-12-06 07:29

In my case, I only want to drag one point each time. However, since the two points are heavily overlapping, dragging one point would cause another point to be dragged. How c

相关标签:
2条回答
  • 2020-12-06 08:18

    Joe's method works fine, but it makes a set of draggablepoints as a class instead of a single draggablepoint class. I just came across an alternative method to solve the above problem using animation blit techniques. It not only makes the dragging faster and smoother, but also only one point can be dragged. See the following code.

    import matplotlib.pyplot as plt
    import matplotlib.patches as patches
    class DraggablePoint:
        lock = None #only one can be animated at a time
        def __init__(self, point):
            self.point = point
            self.press = None
            self.background = None
    
        def connect(self):
            'connect to all the events we need'
            self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', self.on_press)
            self.cidrelease = self.point.figure.canvas.mpl_connect('button_release_event', self.on_release)
            self.cidmotion = self.point.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)
    
        def on_press(self, event):
            if event.inaxes != self.point.axes: return
            if DraggablePoint.lock is not None: return
            contains, attrd = self.point.contains(event)
            if not contains: return
            self.press = (self.point.center), event.xdata, event.ydata
            DraggablePoint.lock = self
    
            # draw everything but the selected rectangle and store the pixel buffer
            canvas = self.point.figure.canvas
            axes = self.point.axes
            self.point.set_animated(True)
            canvas.draw()
            self.background = canvas.copy_from_bbox(self.point.axes.bbox)
    
            # now redraw just the rectangle
            axes.draw_artist(self.point)
    
            # and blit just the redrawn area
            canvas.blit(axes.bbox)
    
        def on_motion(self, event):
            if DraggablePoint.lock is not self:
                return
            if event.inaxes != self.point.axes: return
            self.point.center, xpress, ypress = self.press
            dx = event.xdata - xpress
            dy = event.ydata - ypress
            self.point.center = (self.point.center[0]+dx, self.point.center[1]+dy)
    
            canvas = self.point.figure.canvas
            axes = self.point.axes
            # restore the background region
            canvas.restore_region(self.background)
    
            # redraw just the current rectangle
            axes.draw_artist(self.point)
    
            # blit just the redrawn area
            canvas.blit(axes.bbox)
    
        def on_release(self, event):
            'on release we reset the press data'
            if DraggablePoint.lock is not self:
                return
    
            self.press = None
            DraggablePoint.lock = None
    
            # turn off the rect animation property and reset the background
            self.point.set_animated(False)
            self.background = None
    
            # redraw the full figure
            self.point.figure.canvas.draw()
    
        def disconnect(self):
            'disconnect all the stored connection ids'
            self.point.figure.canvas.mpl_disconnect(self.cidpress)
            self.point.figure.canvas.mpl_disconnect(self.cidrelease)
            self.point.figure.canvas.mpl_disconnect(self.cidmotion)
    
    fig = plt.figure()
    ax = fig.add_subplot(111)
    drs = []
    circles = [patches.Circle((0.32, 0.3), 0.03, fc='r', alpha=0.5),
                   patches.Circle((0.3,0.3), 0.03, fc='g', alpha=0.5)]
    
    for circ in circles:
        ax.add_patch(circ)
        dr = DraggablePoint(circ)
        dr.connect()
        drs.append(dr)
    
    plt.show()
    
    0 讨论(0)
  • 2020-12-06 08:19

    I would synchronize which artist is currently active through a single class that works with multiple artists.

    It's easiest to use the pick_event for this. It also makes it easier to generalize to other artists. As an example:

    import matplotlib.pyplot as plt
    import matplotlib.patches as patches
    
    class DraggablePoints(object):
        def __init__(self, artists, tolerance=5):
            for artist in artists:
                artist.set_picker(tolerance)
            self.artists = artists
            self.currently_dragging = False
            self.current_artist = None
            self.offset = (0, 0)
    
            for canvas in set(artist.figure.canvas for artist in self.artists):
                canvas.mpl_connect('button_press_event', self.on_press)
                canvas.mpl_connect('button_release_event', self.on_release)
                canvas.mpl_connect('pick_event', self.on_pick)
                canvas.mpl_connect('motion_notify_event', self.on_motion)
    
        def on_press(self, event):
            self.currently_dragging = True
    
        def on_release(self, event):
            self.currently_dragging = False
            self.current_artist = None
    
        def on_pick(self, event):
            if self.current_artist is None:
                self.current_artist = event.artist
                x0, y0 = event.artist.center
                x1, y1 = event.mouseevent.xdata, event.mouseevent.ydata
                self.offset = (x0 - x1), (y0 - y1)
    
        def on_motion(self, event):
            if not self.currently_dragging:
                return
            if self.current_artist is None:
                return
            dx, dy = self.offset
            self.current_artist.center = event.xdata + dx, event.ydata + dy
            self.current_artist.figure.canvas.draw()
    
    if __name__ == '__main__':
        fig, ax = plt.subplots()
        ax.set(xlim=[-1, 2], ylim=[-1, 2])
    
        circles = [patches.Circle((0.32, 0.3), 0.2, fc='r', alpha=0.5),
                   patches.Circle((0.3, 0.3), 0.2, fc='b', alpha=0.5)]
        for circ in circles:
            ax.add_patch(circ)
    
        dr = DraggablePoints(circles)
        plt.show()
    
    0 讨论(0)
提交回复
热议问题