目标

我想同时:

1)通过窗口选择在matplotlib画布上选择多个艺术家。

就是说,如果我按住鼠标按钮,请拖动鼠标,
然后释放,由x,y位置定义的矩形中的所有艺术家
按下按钮并松开按钮。

2)拖动多个选定的艺术家,然后将鼠标向下按
选定的艺术家之一,然后移动鼠标并释放。

这与普通文件浏览器中的预期行为完全相同。

先前的尝试和遗留问题

为此,我已经开始编写两个类,WindowSelect
Draggable,如下所示。

WindowSelect实现了(1)的逻辑,除了我
不知道如何手动触发选择器功能
matplotlib documentation中引用。
相反,我调用了占位符函数func

Draggable实现(2)的逻辑,除了
一次选拔一位艺术家(从
另一个指示的答案)。 matplotlibdocumentation
表示应同时挑选多个艺术家
可能(网页上的最后一个示例)。但是,如果我
例如,将选择器的公差设置得很高,只有一个
艺术家似乎被选中/然后可以在
画布,所以我不确定代码需要如何更改以适应
多位艺术家。



import numpy as np
import matplotlib.pyplot as plt; plt.ion()
import matplotlib.patches as patches


class WindowSelect(object):

    def __init__(self, artists):
        self.artists = artists
        self.canvases = set(artist.figure.canvas for artist in self.artists)

        for canvas in self.canvases:
            canvas.mpl_connect('button_press_event', self.on_press)
            canvas.mpl_connect('button_release_event', self.on_release)

        self.currently_dragging = False

    def on_press(self, event):
        if not self.currently_dragging:
            self.x0 = event.xdata
            self.y0 = event.ydata
            self.currently_dragging = True

    def on_release(self, event):
        if self.currently_dragging:
            self.x1 = event.xdata
            self.y1 = event.ydata

            for artist in self.artists:
                if self.is_inside_rect(*artist.center):
                    self.func(artist)

            for canvas in self.canvases:
                canvas.draw()

            self.currently_dragging = False

    def is_inside_rect(self, x, y):
        xlim = np.sort([self.x0, self.x1])
        ylim = np.sort([self.y0, self.y1])
        if (xlim[0]<=x) and (x<xlim[1]) and (ylim[0]<=y) and (y<ylim[1]):
            return True
        else:
            return False

    def func(self, artist):
        artist.set_color('k') # <- just an example operation; would like to pick artist instead


class Draggable(object):
    """
    https://stackoverflow.com/questions/21654008/matplotlib-drag-overlapping-points-interactively
    """
    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()


def demo(TestClass):

    fig, ax = plt.subplots(1,1)
    xlim = [-5, 5]
    ylim = [-5, 5]
    ax.set(xlim=xlim, ylim=ylim)

    circles = [patches.Circle((3.0, 3.0), 0.5, fc='r', alpha=1.0),
               patches.Circle((0.0, 0.0), 0.5, fc='b', alpha=1.0),
               patches.Circle((0.0, 3.0), 0.5, fc='g', alpha=1.0)]

    for circle in circles:
        ax.add_patch(circle)

    return TestClass(circles)


if __name__ == '__main__':

    out1 = demo(Draggable)
    out2 = demo(WindowSelect)

最佳答案

很难分开选择和拖动。这是因为各个事件需要根据您当前正在选择还是拖动来执行不同的任务。因此,在每个时间点上,您都需要知道当前正在执行哪个操作,最好是在一个类中完成操作,在该类中您可能有两个不同的标志,分别是self.currently_selectingself.currently_dragging。然后,您还需要一个选定艺术家的列表,self.selected_artists,可以在需要时将其拖动。

我将摆脱pick_event,因为无论如何都很难与button_press_event区分开(它们将同时触发,并且不确定性首先出现)。相反,单个button_press_event可以处理所有逻辑。即找出艺术家是否发生点击,如果发生,则开始拖动,否则开始选择。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches


class WindowSelect(object):

    def __init__(self, artists):
        self.artists = artists
        self.colors = [a.get_facecolor() for a in self.artists]
        # assume all artists are in the same figure, otherwise selection is meaningless
        self.fig = self.artists[0].figure
        self.ax = self.artists[0].axes

        self.fig.canvas.mpl_connect('button_press_event', self.on_press)
        self.fig.canvas.mpl_connect('button_release_event', self.on_release)
        self.fig.canvas.mpl_connect('motion_notify_event', self.on_motion)

        self.currently_selecting = False
        self.currently_dragging = False
        self.selected_artists = []
        self.offset = np.zeros((1,2))
        self.rect = plt.Rectangle((0,0),1,1, linestyle="--",
                                  edgecolor="crimson", fill=False)
        self.ax.add_patch(self.rect)
        self.rect.set_visible(False)

    def on_press(self, event):
        # is the press over some artist
        isonartist = False
        for artist in self.artists:
            if artist.contains(event)[0]:
                isonartist = artist
        self.x0 = event.xdata
        self.y0 = event.ydata
        if isonartist:
            # add clicked artist to selection
            self.select_artist(isonartist)
            # start dragging
            self.currently_dragging = True
            ac = np.array([a.center for a in self.selected_artists])
            ec = np.array([event.xdata, event.ydata])
            self.offset = ac - ec
        else:
            #start selecting
            self.currently_selecting = True
            self.deseclect_artists()

    def on_release(self, event):
        if self.currently_selecting:

            for artist in self.artists:
                if self.is_inside_rect(*artist.center):
                    self.select_artist(artist)
            self.fig.canvas.draw_idle()
            self.currently_selecting = False
            self.rect.set_visible(False)

        elif self.currently_dragging:
            self.currently_dragging = False


    def on_motion(self, event):
        if self.currently_dragging:
            newcenters = np.array([event.xdata, event.ydata])+self.offset
            for i, artist in enumerate(self.selected_artists):
                artist.center = newcenters[i]
            self.fig.canvas.draw_idle()
        elif self.currently_selecting:
            self.x1 = event.xdata
            self.y1 = event.ydata
            #add rectangle for selection here
            self.selector_on()
            self.fig.canvas.draw_idle()

    def is_inside_rect(self, x, y):
        xlim = np.sort([self.x0, self.x1])
        ylim = np.sort([self.y0, self.y1])
        if (xlim[0]<=x) and (x<xlim[1]) and (ylim[0]<=y) and (y<ylim[1]):
            return True
        else:
            return False

    def select_artist(self, artist):
        artist.set_color('k')
        if artist not in self.selected_artists:
            self.selected_artists.append(artist)

    def deseclect_artists(self):
        for artist,color in zip(self.artists, self.colors):
            artist.set_color(color)
        self.selected_artists = []

    def selector_on(self):
        self.rect.set_visible(True)
        xlim = np.sort([self.x0, self.x1])
        ylim = np.sort([self.y0, self.y1])
        self.rect.set_xy((xlim[0],ylim[0] ) )
        self.rect.set_width(np.diff(xlim))
        self.rect.set_height(np.diff(ylim))


def demo():

    fig, ax = plt.subplots(1,1)
    xlim = [-5, 5]
    ylim = [-5, 5]
    ax.set(xlim=xlim, ylim=ylim)

    circles = [patches.Circle((3.0, 3.0), 0.5, fc='r', alpha=1.0),
               patches.Circle((0.0, 0.0), 0.5, fc='b', alpha=1.0),
               patches.Circle((0.0, 3.0), 0.5, fc='g', alpha=1.0)]

    for circle in circles:
        ax.add_patch(circle)

    w = WindowSelect(circles)
    plt.show()

if __name__ == '__main__':
    demo()


python - 窗口中选择多个艺术家并将其拖动到 Canvas 上-LMLPHP

关于python - 窗口中选择多个艺术家并将其拖动到 Canvas 上,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47293499/

10-13 00:30