如何将matplotlib包含限制为一个对象?

时间:2016-09-01 16:48:48

标签: python python-3.x matplotlib

我正在开发一个带有可拖动线条的简单GUI,以允许用户直观地显示一些绘制的数据。

使用matplotlib's event handling documentation我已经能够实现可拖动窗口行的初始版本:

import numpy as np
import matplotlib.pyplot as plt

class DraggableLine:
    def __init__(self, orientation, ax, position):
        if orientation.lower() == 'horizontal':
            self.myline, = ax.plot(ax.get_xlim(), np.array([1, 1])*position)
            self.orientation = orientation.lower()
        elif orientation.lower() == 'vertical':
            self.myline, = ax.plot(np.array([1, 1])*position, ax.get_ylim())
            self.orientation = orientation.lower()
        else:
            # throw an error
            pass

        self.parentfig = self.myline.figure.canvas
        self.parentax = ax

        self.clickpress = self.parentfig.mpl_connect('button_press_event', self.on_click)  # Execute on mouse click
        self.clicked = False

    def on_click(self, event):
        # Executed on mouse click
        if event.inaxes != self.parentax: return  # See if the mouse is over the parent axes object

        # See if the click is on top of this line object
        contains, attrs = self.myline.contains(event)
        if not contains: return

        self.mousemotion = self.parentfig.mpl_connect('motion_notify_event', self.on_motion)
        self.clickrelease = self.parentfig.mpl_connect('button_release_event', self.on_release)
        self.clicked = True

    def on_motion(self, event):
        # Executed on mouse motion
        if not self.clicked: return  # See if we've clicked yet
        if event.inaxes != self.parentax: return # See if we're moving over the parent axes object

        if self.orientation == 'vertical':
            self.myline.set_xdata(np.array([1, 1])*event.xdata)
            self.myline.set_ydata(self.parentax.get_ylim())
        elif self.orientation == 'horizontal':
            self.myline.set_xdata(self.parentax.get_xlim())
            self.myline.set_ydata(np.array([1, 1])*event.ydata)

        self.parentfig.draw()

    def on_release(self, event):
        self.clicked = False

        self.parentfig.mpl_disconnect(self.mousemotion)
        self.parentfig.mpl_disconnect(self.clickrelease)
        self.parentfig.draw()

生成符合预期的行:

fig = plt.figure()
ax = fig.add_subplot(111)

vl1 = DraggableLine('vertical', ax, 3)
vl2 = DraggableLine('vertical', ax, 6)

ax.set_xlim([0, 10])
plt.show()

但是,当线条堆叠时,移动单行的能力会丢失,因为matplotlib.lines.Line2D.contains()不知道一个对象被另一个对象遮挡。所以我们左边拖着一大块物体,直到情节结束。

是否有已实施的方法可以缓解此问题?如果没有,我认为一种方法可能是查询父轴的子项以获取鼠标释放时DraggableLine类的实例,检查它们的位置,并在必要时连接/断开'button_press_event'。我不确定这是否最有效的计算时间。

1 个答案:

答案 0 :(得分:2)

一种方法可能是检查轴的子项以查找将触发各自“移动”回调的对象,查看哪一个被渲染到最顶层并且只移动那个。

对于上面的例子,我已经定义了另一种方法:

def shouldthismove(self, event):
    # Check to see if this object has been clicked on
    contains, attrs = self.myline.contains(event)
    if not contains:
        # We haven't been clicked
        timetomove = False
    else:
        # See how many draggable objects contains this event
        firingobjs = []
        for child in self.parentax.get_children():
            if child._label == 'dragobj':
                contains, attrs = child.contains(event)
                if contains:
                    firingobjs.append(child)

        # Assume the last child object is the topmost rendered object, only move if we're it
        if firingobjs[-1] == self.myline:
            timetomove = True
        else:
            timetomove = False

    return timetomove

重新定义了我的on_click方法:

def on_click(self, event):
    # Executed on mouse click
    if event.inaxes != self.parentax: return  # See if the mouse is over the parent axes object

    # Check for overlaps, make sure we only fire for one object per click
    timetomove = self.shouldthismove(event)
    if not timetomove: return

    self.mousemotion = self.parentfig.canvas.mpl_connect('motion_notify_event', self.on_motion)
    self.clickrelease = self.parentfig.canvas.mpl_connect('button_release_event', self.on_release)
    self.clicked = True

并在我__init__的线对象中添加了一个通用标签,以便以后加快对子轴的过滤:

self.myline._label = 'dragobj'
相关问题