我正在开发一个带有可拖动线条的简单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'
。我不确定这是否最有效的计算时间。
答案 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'