Python matplotlib中的交互行

Python matplotlib中的交互行,python,matplotlib,Python,Matplotlib,我正在尝试使用matplotlib创建一个交互式绘图,该绘图在端点处创建一个具有两个控制柄的线段。您可以单击并拖动控制柄,直线将刷新以匹配以这种方式指定的位置,方式与此matplotlib示例类似:(如果您看到此示例,请想象我想要相同的东西,但只有多边形的一条边) 我已经尝试修改poly_编辑器代码,使其只处理Line2D元素,我的程序运行时没有任何错误,只是它根本没有在轴上绘制任何东西。我认为这可能是变量范围中的错误,或者与matplotlib的draw调用有关。如能提供有关错误的任何指导,我

我正在尝试使用matplotlib创建一个交互式绘图,该绘图在端点处创建一个具有两个控制柄的线段。您可以单击并拖动控制柄,直线将刷新以匹配以这种方式指定的位置,方式与此matplotlib示例类似:(如果您看到此示例,请想象我想要相同的东西,但只有多边形的一条边)

我已经尝试修改poly_编辑器代码,使其只处理Line2D元素,我的程序运行时没有任何错误,只是它根本没有在轴上绘制任何东西。我认为这可能是变量范围中的错误,或者与matplotlib的draw调用有关。如能提供有关错误的任何指导,我们将不胜感激

编辑:我进一步改进了一些,简化了代码,现在我可以让它画线并打印epsilon距离内最近顶点的索引,但线保持静止,不动。更新后的代码如下

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.lines import Line2D

class LineBuilder(object):

    epsilon = 0.5

    def __init__(self, line):
        canvas = line.figure.canvas
        self.canvas = canvas
        self.line = line
        self.axes = line.axes
        self.xs = list(line.get_xdata())
        self.ys = list(line.get_ydata())
    
        self.ind = None
    
        canvas.mpl_connect('button_press_event', self.button_press_callback)
        canvas.mpl_connect('button_release_event', self.button_release_callback)
        canvas.mpl_connect('motion_notify_event', self.motion_notify_callback)

    def get_ind(self, event):
        x = np.array(self.line.get_xdata())
        y = np.array(self.line.get_ydata())
        d = np.sqrt((x-event.xdata)**2 + (y - event.ydata)**2)
        if min(d) > self.epsilon:
            return None
        if d[0] < d[1]:
            return 0
        else:
            return 1

    def button_press_callback(self, event):
        if event.button != 1:
            return
        self.ind = self.get_ind(event)
        print(self.ind)
    
        self.line.set_animated(True)
        self.canvas.draw()
        self.background = self.canvas.copy_from_bbox(self.line.axes.bbox)
    
        self.axes.draw_artist(self.line)
        self.canvas.blit(self.axes.bbox)

    def button_release_callback(self, event):
        if event.button != 1:
            return
        self.ind = None
        self.line.set_animated(False)
        self.background = None
        self.line.figure.canvas.draw()

    def motion_notify_callback(self, event):
        if event.inaxes != self.line.axes:
            return
        if event.button != 1:
            return
        if self.ind is None:
            return
        self.xs[self.ind] = event.xdata
        self.ys[self.ind] = event.ydata
        self.line.set_data(self.xs, self.ys)
    
        self.canvas.restore_region(self.background)
        self.axes.draw_artist(self.line)
        self.canvas.blit(self.axes.bbox)


if __name__ == '__main__':
    fig, ax = plt.subplots()
    line = Line2D([0,1], [0,1], marker='o', markerfacecolor='red')
    ax.add_line(line)

    linebuilder = LineBuilder(line)

    ax.set_title('click to create lines')
    ax.set_xlim(-2,2)
    ax.set_ylim(-2,2)
    plt.show()
导入matplotlib.pyplot作为plt
将numpy作为np导入
从matplotlib.lines导入Line2D
类LineBuilder(对象):
ε=0.5
定义初始化(自身,行):
canvas=line.figure.canvas
self.canvas=画布
self.line=line
self.axes=line.axes
self.xs=list(line.get_xdata())
self.ys=list(line.get_ydata())
self.ind=None
canvas.mpl\u connect('button\u press\u event',self.button\u press\u callback)
canvas.mpl\u connect('button\u release\u event',self.button\u release\u回调)
canvas.mpl\u connect('motion\u notify\u event',self.motion\u notify\u回调)
def get_ind(自身、事件):
x=np.array(self.line.get_xdata())
y=np.array(self.line.get_ydata())
d=np.sqrt((x-event.xdata)**2+(y-event.ydata)**2)
如果最小值(d)>self.epsilon:
一无所获
如果d[0]

事先谢谢你,凯文。

好的,我解决了这个问题。新的代码(上面)实际上是有效的,其中有一个错误。motion notify事件的mpl_connect调用具有错误的事件类型,现在它正在按预期工作。

我是新来的,希望通过回答此自我回答的问题不会犯很多错误。:)

首先感谢你发布这篇文章,它帮了我很多,节省了一些时间,我想要的几乎就是这段代码。我做了一些我在这里提出的更新,这样就可以操纵两个以上的点,并使用键处理事件来创建或删除线中的点,就像PolygonInteractor所做的那样

from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import numpy as np

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 LineBuilder(object):

    epsilon = 30 #in pixels

    def __init__(self, line):
        canvas = line.figure.canvas
        self.canvas = canvas
        self.line = line
        self.axes = line.axes
        self.xs = list(line.get_xdata())
        self.ys = list(line.get_ydata())

        self.ind = None

        canvas.mpl_connect('button_press_event', self.button_press_callback)
        canvas.mpl_connect('button_release_event', self.button_release_callback)
        canvas.mpl_connect('key_press_event', self.key_press_callback)
        canvas.mpl_connect('motion_notify_event', self.motion_notify_callback)

    def get_ind(self, event):
        xy = np.asarray(self.line._xy)
        xyt = self.line.get_transform().transform(xy)
        x, y = xyt[:, 0], xyt[:, 1]
        d = np.sqrt((x-event.x)**2 + (y - event.y)**2)
        indseq, = np.nonzero(d == d.min())
        ind = indseq[0]

        if d[ind] >= self.epsilon:
            ind = None

        return ind

    def button_press_callback(self, event):
        if event.button != 1:
            return
        if event.inaxes is None:
            return
        self.ind = self.get_ind(event)
        print(self.ind)

        self.line.set_animated(True)
        self.canvas.draw()
        self.background = self.canvas.copy_from_bbox(self.line.axes.bbox)

        self.axes.draw_artist(self.line)
        self.canvas.blit(self.axes.bbox)

    def button_release_callback(self, event):
        if event.button != 1:
            return
        self.ind = None
        self.line.set_animated(False)
        self.background = None
        self.line.figure.canvas.draw()

    def motion_notify_callback(self, event):
        if event.inaxes != self.line.axes:
            return
        if event.button != 1:
            return
        if self.ind is None:
            return
        self.xs[self.ind] = event.xdata
        self.ys[self.ind] = event.ydata
        self.line.set_data(self.xs, self.ys)

        self.canvas.restore_region(self.background)
        self.axes.draw_artist(self.line)
        self.canvas.blit(self.axes.bbox)

    def key_press_callback(self, event):
        """Callback for key presses."""

        if not event.inaxes:
            return
        elif event.key == 'd':
            ind = self.get_ind(event)
            if ind is not None and len(self.xs) > 2:
                self.xs = np.delete(self.xs, ind)
                self.ys = np.delete(self.ys, ind)
                self.line.set_data(self.xs, self.ys)
                self.axes.draw_artist(self.line)
                self.canvas.draw_idle()
        elif event.key == 'i':
            p = np.array([event.x, event.y])  # display coords
            xy = np.asarray(self.line._xy)
            xyt = self.line.get_transform().transform(xy)
            for i in range(len(xyt) - 1):
                s0 = xyt[i]
                s1 = xyt[i+1]
                d = dist_point_to_segment(p, s0, s1)
                if d <= self.epsilon:
                    self.xs = np.insert(self.xs, i+1, event.xdata)
                    self.ys = np.insert(self.ys, i+1, event.ydata)
                    self.line.set_data(self.xs, self.ys)
                    self.axes.draw_artist(self.line)
                    self.canvas.draw_idle()
                    break

if __name__ == '__main__':

    fig, ax = plt.subplots()
    line = Line2D([0,0.5,1], [0,0.5,1], marker = 'o', markerfacecolor = 'red')
    ax.add_line(line)

    linebuilder = LineBuilder(line)

    ax.set_title('click to create lines')
    ax.set_xlim(-2,2)
    ax.set_ylim(-2,2)
    plt.show()
从matplotlib.lines导入Line2D
将matplotlib.pyplot作为plt导入
将numpy作为np导入
def距离(x,y):
"""
返回两点之间的距离。
"""
d=x-y
返回np.sqrt(np.dot(d,d))
def dist_point_至_段(p、s0、s1):
"""
获取点到线段的距离。
*p*、*s0*、*s1*是*xy*序列
此算法来自
http://geomalgorithms.com/a02-_lines.html
"""
v=s1-s0
w=p-s0
c1=np.点(w,v)
如果c1 2:
self.xs=np.delete(self.xs,ind)
self.ys=np.delete(self.ys,ind)
self.line.set_数据(self.xs、self.ys)
self.axes.draw_艺术家(self.line)
self.canvas.draw_idle()
elif event.key==“i”:
p=np.array([event.x,event.y])#显示坐标
xy=np.asarray(自连线)
xyt=self.line.get_transform().transform(xy)
对于范围内的i(len(xyt)-1):
s0=xyt[i]
s1=xyt[i+1]
d=从点到段的距离(p、s0、s1)
如果d