简体   繁体   English

matplotlib 中的交互线

[英]Interactive Line in matplotlib

I'm trying to make an interactive plot using matplotlib that creates a line segment with two handles at the endpoints.我正在尝试使用 matplotlib 制作一个交互式绘图,该绘图创建一个在端点处有两个手柄的线段。 You can click and drag the handles and the line will refresh to match the positions specified in this way, in a similar fashion to this matplotlib example poly_editor : (if you see the example, imagine that I want the same thing but with just one edge of the polygon).您可以单击并拖动手柄,线条将刷新以匹配以这种方式指定的位置,其方式类似于此 matplotlib 示例poly_editor :的多边形)。

I have tried altering the poly_editor code to work with just the Line2D element, and my program runs without any errors, except that it doesn't draw anything on the axis at all.我已经尝试更改 poly_editor 代码以仅使用 Line2D 元素,并且我的程序运行时没有任何错误,只是它根本没有在轴上绘制任何东西。 I think it might be an error in the scope of the variables or something to do with the draw calls from matplotlib.我认为这可能是变量范围的错误或与来自 matplotlib 的绘图调用有关。 Any guidance as to what the errors are would be greatly appreciated.任何关于错误是什么的指导将不胜感激。

Edit: I advanced some more, simplified the code and now I can get it to draw the line and print the index of the nearest vertex within epsilon distance, but the line stays stationary and does not animate.编辑:我进一步改进了一些,简化了代码,现在我可以让它画线并打印 epsilon 距离内最近顶点的索引,但线保持静止并且没有动画。 The updated code is bellow更新后的代码如下

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

Thanks in advance, Kevin.提前致谢,凯文。

Okay, I solved the problem.好的,我解决了问题。 The new code (above) actually works, there was a mistake in it.新代码(上面)确实有效,但其中有一个错误。 The mpl_connect call for the motion notify event had the wrong event type, now it is working as intended.运动通知事件的 mpl_connect 调用有错误的事件类型,现在它按预期工作。

I am new here so hope I don't make to many mistakes by replying to this self.replied question.我是新来的,所以希望我在回答这个 self.replied 问题时不会犯很多错误。 :) :)

First thanks for posting this, it helped me a lot by saving some time, I wanted almost exactly this code.首先感谢您发布这个,它通过节省一些时间帮助了我很多,我几乎想要这个代码。 I did some updates that I propose here, so that it's possible to manipulate more than two points, and use the key handling events to create or delete points in the line, as the PolygonInteractor does.我做了一些我在这里建议的更新,这样就可以操纵两个以上的点,并使用键处理事件来创建或删除线中的点,就像 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()

This actually leads to a question/problem i have but it will be in another message.这实际上导致了我遇到的问题/问题,但它将在另一条消息中。

Kristen克里斯汀

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM