How can I limit matplotlib contains to one object?
<p>I'm working on a simple GUI with draggable lines to allow a user to visually window some plotted data. </p>

<p>Working with <a href="http://matplotlib.org/users/event_handling.html" rel="nofollow">matplotlib's event handling documentation</a> I've been able to implement an initial version of the draggable window lines:</p>

<pre><code>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()
</code></pre>

<p>Which generates the lines that behave as expected:</p>

<pre><code>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()
</code></pre>

<p>However, when the lines are stacked the ability to move a single line is lost because <code>matplotlib.lines.Line2D.contains()</code> does not know that one object is obscured by another. So we're left dragging a chunk of objects around until the plot is closed.</p>

<p>Is there an already implemented method to mitigate this issue? If not, I think one approach could be to query the children of the parent axes for instances of the <code>DraggableLine</code> class on mouse release, check their positions, and connect/disconnect the <code>'button_press_event'</code> where necessary. I'm not sure if that makes the most sense computation time wise.</p>

<p>One approach could be to check the children of the axes for objects that will fire their respective "move" callbacks, see which one is rendered the topmost and only move that one.</p>

<p>For the above example I've defined an additional method:</p>

<pre><code>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
</code></pre>

<p>Redefined my <code>on_click</code> method:</p>

<pre><code>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
</code></pre>

<p>And added a generic label to my line object in <code>__init__</code> to expedite filtering of the axes children later on:</p>

<pre><code>self.myline._label = 'dragobj'
</code></pre>

python python-3.x matplotlib