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!
Advertisement
Answer
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()