【问题标题】:Matplotlib: responding to click eventsMatplotlib:响应点击事件
【发布时间】:2015-11-14 16:54:59
【问题描述】:

我想使用 matplotlib 执行以下操作:

  1. 通过执行以下操作在两点之间创建一条线: 一世。使用左键双击画布(创建的第一个点) ii.将鼠标拖动到(或简单地单击)第二个点 ii.在第一点和第二点之间画线

  2. 通过以下操作在画布上放置一个绿色(或任何其他颜色)圆圈: 一世。双击画布,使用右按钮

  3. 由于我在双击时可能会出错,所以我希望能够选择一个绘制的圆(或线),然后按删除按钮删除所选项目。

    李>

在 VB 的好日子里,这是一个 15 分钟的工作。在这上面浪费了几个小时之后,我的想法已经用完了。

这是我目前所拥有的:

import matplotlib.pyplot as plt


class LineDrawer(object):
    lines = []
    def draw_line(self):
        ax = plt.gca()
        xy = plt.ginput(2)

        x = [p[0] for p in xy]
        y = [p[1] for p in xy]
        line = plt.plot(x,y)
        ax.figure.canvas.draw()

        self.lines.append(line)


def onclick(event):
    if event.dblclick:
        if event.button == 1:
            # Draw line
            ld = LineDrawer()
            ld.draw_line() # here you click on the plot
        elif event.button == 3:
            # Write to figure
            plt.figtext(3, 8, 'boxed italics text in data coords', style='italic', bbox={'facecolor':'red', 'alpha':0.5, 'pad':10})
            circ = plt.Circle((event.x, event.y), radius=0.07, color='g')
            ax.add_patch(circ)
            plt.draw()
        else:
            pass # Do nothing


def onpick(event):
    thisline = event.artist
    xdata = thisline.get_xdata()
    ydata = thisline.get_ydata()
    ind = event.ind
    print ('onpick points:', zip(xdata[ind], ydata[ind]))



fig, ax = plt.subplots()

connection_id = fig.canvas.mpl_connect('button_press_event', onclick)
fig.canvas.mpl_connect('pick_event', onpick)


plt.tight_layout()

plt.show()

除了删除功能,我什至还没有完成,为什么我的代码没有执行要求 1 和 2?

我做错了什么?更重要的是,如何修复代码以获得所需的功能?

【问题讨论】:

  • 我只能说这在 Tkinter 中会容易得多。我知道这可能没有帮助,但如果您有一定的灵活性,请考虑一下。
  • @sunny:我并不反对使用 TKinter(尽管我以前从未使用过它)。如果你能提供一些代码让我开始,那可能就足够了......(虽然我需要缩放和平移功能 - 因为 matplotlib 提供了“开箱即用”)

标签: python matplotlib


【解决方案1】:

您快到了,但您的逻辑发送代码以在双击时绘制一条线,而不存储双击的位置,因此它需要两次单击来绘制一条线。此外,您需要在圆形代码中绘制画布。这是满足要求 1 和 2 的最低限度修订版本:

import matplotlib.pyplot as plt


class LineDrawer(object):
    lines = []
    def draw_line(self, startx,starty):
        ax = plt.gca()
        xy = plt.ginput(1)
        x = [startx,xy[0][0]]
        y = [starty,xy[0][1]]
        line = plt.plot(x,y)
        ax.figure.canvas.draw()

        self.lines.append(line)


def onclick(event):
    if event.dblclick:
        if event.button == 1:
            # Draw line
            ld = LineDrawer()
            ld.draw_line(event.xdata,event.ydata) # here you click on the plot
        elif event.button == 3:
            # Write to figure
            plt.figtext(3, 8, 'boxed italics text in data coords', style='italic', bbox={'facecolor':'red', 'alpha':0.5, 'pad':10})
            circ = plt.Circle((event.xdata, event.ydata), radius=0.07, color='g')
            ax.add_patch(circ)
            ax.figure.canvas.draw()
        else:
            pass # Do nothing


def onpick(event):
    thisline = event.artist
    xdata = thisline.get_xdata()
    ydata = thisline.get_ydata()
    ind = event.ind
    print ('onpick points:', zip(xdata[ind], ydata[ind]))



fig, ax = plt.subplots()

connection_id = fig.canvas.mpl_connect('button_press_event', onclick)
fig.canvas.mpl_connect('pick_event', onpick)


plt.tight_layout()

plt.show()

请注意,matplotlib 可能不是实现这些要求的最佳或最简单的方法 - 轴也会在绘制第一条线时自动重新缩放。您可以通过修复xlimylim 来更改此设置。例如如下:

ax.set_xlim([0,2])
ax.set_ylim([0,2])

要实现要求 3,您将必须存储拾取的对象并侦听匹配删除的按键以将其删除。这是一个结合了以上所有内容的版本。我尽量坚持你的设计。我将对拾取对象的引用存储在相关的轴对象中。如果您不喜欢将拾取的对象插入当前轴,您可能需要实现自己的数据结构来存储它。我已经对其进行了一些测试,但可能存在可能会混淆逻辑的单击/按键序列。

import matplotlib.pyplot as plt

# function to draw lines - from matplotlib examples.  Note you don't need
# to keep a reference to the lines drawn, so I've removed the class as it
# is overkill for your purposes
def draw_line(startx,starty):
        ax = plt.gca()
        xy = plt.ginput(1)
        x = [startx,xy[0][0]]
        y = [starty,xy[0][1]]
        line = ax.plot(x,y, picker=5) # note that picker=5 means a click within 5 pixels will "pick" the Line2D object
        ax.figure.canvas.draw()        

def onclick(event):
    """
    This implements click functionality.  If it's a double click do something,
    else ignore.
    Once in the double click block, if its a left click, wait for a further 
    click and draw a line between the double click co-ordinates and that click
    (using ginput(1) - the 1 means wait for one mouse input - a higher number
    is used to get multiple clicks to define a polyline)
    If the double click was a right click, draw the fixed radius circle

    """
    if event.dblclick:
        if event.button == 1:
            # Draw line
            draw_line(event.xdata,event.ydata) # here you click on the plot
        elif event.button == 3:
            # Write to figure
            plt.figtext(3, 8, 'boxed italics text in data coords', style='italic', bbox={'facecolor':'red', 'alpha':0.5, 'pad':10})
            circ = plt.Circle((event.xdata, event.ydata), radius=0.07, color='g', picker = True)
            ax.add_patch(circ)
            ax.figure.canvas.draw()
        else:
            pass # Do nothing


def onpick(event):    
    """
    Handles the pick event - if an object has been picked, store a
    reference to it.  We do this by simply adding a reference to it
    named 'stored_pick' to the axes object.  Note that in python we
    can dynamically add an attribute variable (stored_pick) to an 
    existing object - even one that is produced by a library as in this
    case
    """
    this_artist = event.artist #the picked object is available as event.artist
    # print(this_artist) #For debug just to show you which object is picked
    plt.gca().picked_object = this_artist

def on_key(event):
    """
    Function to be bound to the key press event
    If the key pressed is delete and there is a picked object,
    remove that object from the canvas
    """
    if event.key == u'delete':
        ax = plt.gca()
        if ax.picked_object:
            ax.picked_object.remove()
            ax.picked_object = None
            ax.figure.canvas.draw()


fig, ax = plt.subplots()

#First we need to catch three types of event, clicks, "picks" (a specialised
#type of click to select an object on a matplotlib canvas) and key presses.
#The logic is - if it's a right double click, wait for the next click and draw
#a line, if its a right double click draw a fixed radius circle.  If it's a
#pick, store a reference to the picked item until the next keypress.  If it's
#a keypress - test if it's delete and if so, remove the picked object.
#The functions (defined above) bound to the events implement this logic
connection_id = fig.canvas.mpl_connect('button_press_event', onclick)
fig.canvas.mpl_connect('pick_event', onpick)
cid = fig.canvas.mpl_connect('key_press_event', on_key)

#set the size of the matplotlib figure in data units, so that it doesn't
#auto-resize (which it will be default on the first drawn item)
ax.set_xlim([0,2])
ax.set_ylim([0,2])
ax.aspect = 1
plt.tight_layout()

plt.show()

【讨论】:

  • 谢谢 - 这似乎做了我想做的大部分事情,因为我可以用它来实际执行我想做的工作。唯一的小问题是我真的不明白它在做什么,所以几乎不可能维护甚至扩展它(我原来的 sn-p 主要是从 matplotlib 站点复制粘贴)。您能否评论一下您的 sn-p,这样可以理解,以便我接受您的回答?谢谢
  • @HomunculusReticulli 好的 - 我没有意识到这是一个拼凑在一起的例子。我将注释代码示例
  • @HomunculusReticulli 完成。我已经添加了评论并简化了设计,因为我知道您的原始结构不是特定设计,而是复制粘贴。如果还有什么不清楚的,请告诉我
猜你喜欢
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-07
相关资源
最近更新 更多