matplotlib中的交互式线

时间:2016-01-18 12:42:00

标签: python matplotlib

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

我尝试改变poly_editor代码以仅使用Line2D元素,并且我的程序运行没有任何错误,除了它根本不在轴上绘制任何东西。我认为这可能是变量范围内的错误,也可能与matplotlib的绘制调用有关。任何有关错误的指导都将非常感激。

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

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

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()`

先谢谢,凯文。

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

我是新来的,所以希望我不要通过回答这个self.replied问题来犯很多错误。 :)

首先感谢您发布此代码,它节省了一些时间,对我有很大帮助,我几乎完全需要此代码。我做了一些我在此建议的更新,以便可以操纵两个以上的点,并像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()

这实际上会导致我有一个问题/问题,但是会在另一条消息中出现。

克里斯汀