I'm trying to create a class of draggable lines using matplotlib handling and picking. The aim is to set different thresholds and intervals on a graph. Here is the code:
import matplotlib.pyplot as plt
import matplotlib.lines as lines
import numpy as np
class draggable_lines:
def __init__(self, ax, kind, XorY):
self.ax = ax
self.c = ax.get_figure().canvas
self.o = kind
self.XorY = XorY
if kind == "h":
x = [-1, 1]
y = [XorY, XorY]
elif kind == "v":
x = [XorY, XorY]
y = [-1, 1]
else:
print("choose h or v line")
self.line = lines.Line2D(x, y, picker=5)
self.ax.add_line(self.line)
self.c.draw()
sid = self.c.mpl_connect('pick_event', self.clickonline)
# pick line when I select it
def clickonline(self, event):
self.active_line = event.artist
print("line selected ", event.artist)
self.follower = self.c.mpl_connect("motion_notify_event", self.followmouse)
self.releaser = self.c.mpl_connect("button_press_event", self.releaseonclick)
# The selected line must follow the mouse
def followmouse(self, event):
if self.o == "h":
self.line.set_ydata([event.ydata, event.ydata])
else:
self.line.set_xdata([event.xdata, event.xdata])
self.c.draw()
# release line on click
def releaseonclick(self, event):
if self.o == "h":
self.XorY = self.line.get_ydata()[0]
else:
self.XorY = self.line.get_xdata()[0]
print (self.XorY)
self.c.mpl_disconnect(self.releaser)
self.c.mpl_disconnect(self.follower)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
Vline = draggable_lines(ax, "h", 0.5)
Tline = draggable_lines(ax, "v", 0.5)
Tline2 = draggable_lines(ax, "v", 0.1)
The behavior is what I expected when using only 1 line (even if it notify the selection also when I release the line).
When I'm using more than one line it selects all of them at the same time!
I think I'm misunderstanding the event manager functionality, but I cannot understand why different objects, well distinguished (as I can see in the print("line selected ", event.artist)
) should select themselves and another!
One could ask differently: How would matplotlib know which line to drag if you click on any of them? Answer: it wouldn't, because it has three callbacks, one for each line and will execute them all.
The solution is hence to first check if the line clicked is actually the line to be moved inside the 'pick_event'
callback:
if event.artist == self.line:
# register other callbacks
(On a different note: You would benefit from not calling canvas.draw()
so often, but instead canvas.draw_idle()
)
import matplotlib.pyplot as plt
import matplotlib.lines as lines
class draggable_lines:
def __init__(self, ax, kind, XorY):
self.ax = ax
self.c = ax.get_figure().canvas
self.o = kind
self.XorY = XorY
if kind == "h":
x = [-1, 1]
y = [XorY, XorY]
elif kind == "v":
x = [XorY, XorY]
y = [-1, 1]
self.line = lines.Line2D(x, y, picker=5)
self.ax.add_line(self.line)
self.c.draw_idle()
self.sid = self.c.mpl_connect('pick_event', self.clickonline)
def clickonline(self, event):
if event.artist == self.line:
print("line selected ", event.artist)
self.follower = self.c.mpl_connect("motion_notify_event", self.followmouse)
self.releaser = self.c.mpl_connect("button_press_event", self.releaseonclick)
def followmouse(self, event):
if self.o == "h":
self.line.set_ydata([event.ydata, event.ydata])
else:
self.line.set_xdata([event.xdata, event.xdata])
self.c.draw_idle()
def releaseonclick(self, event):
if self.o == "h":
self.XorY = self.line.get_ydata()[0]
else:
self.XorY = self.line.get_xdata()[0]
print (self.XorY)
self.c.mpl_disconnect(self.releaser)
self.c.mpl_disconnect(self.follower)
fig = plt.figure()
ax = fig.add_subplot(111)
Vline = draggable_lines(ax, "h", 0.5)
Tline = draggable_lines(ax, "v", 0.5)
Tline2 = draggable_lines(ax, "v", 0.1)
plt.show()