问题描述
如何在Matplotlib中创建一个箭头(其末端(头)可以用鼠标拖动)?
编辑:受Mathematica启发,可能的实现是创建一个“定位器”,然后向其绘制箭头。定位器(而不是箭头)可拖动;移动后,将箭头重新绘制到定位器。
相应的Mathematica代码(请参见Locator的文档):
Manipulate[
Graphics[Line[{{0,0},p}],PlotRange -> 2],{{p,{1,1}},Locator}]
此类对象的用途:我想创建一个交互式图形,以说明基数变化(在2D模式下)如何影响基矢量沿矢量的分量(将在数学课程中使用)。因此,我想将两个基向量实现为具有可拖动末端的箭头(起点固定在原点)。通过拖动箭头末端(头)可以更改它们。
我不熟悉Matplotlib中的事件处理。我尝试阅读related docs,但仍然感到困惑。欢迎答案中的详细解释。
解决方法
这是两个可移动向量的示例。由于我们只想拾取和移动端点,因此仅出于移动端点的目的而添加了两个不可见的点。
这只是一个简单的例子。 motion_notify_callback
可以扩展以更新更多元素。
希望这些函数的名称能帮助您找出正在发生的事情。
from matplotlib import pyplot as plt
from matplotlib.backend_bases import MouseButton
from matplotlib.ticker import MultipleLocator
fig,ax = plt.subplots(figsize=(10,10))
picked_artist = None
vector_x2 = ax.annotate('',(0,0),(2,1),ha="left",va="center",arrowprops=dict(arrowstyle='<|-',fc="k",ec="k",shrinkA=0,shrinkB=0))
vector_y2 = ax.annotate('',(-1,2),shrinkB=0))
point_x2,= ax.plot(2,1,'.',color='none',picker=True)
point_y2,= ax.plot(-1,2,picker=True)
def pick_callback(event):
'called when an element is picked'
global picked_artist
if event.mouseevent.button == MouseButton.LEFT:
picked_artist = event.artist
def button_release_callback(event):
'called when a mouse button is released'
global picked_artist
if event.button == MouseButton.LEFT:
picked_artist = None
def motion_notify_callback(event):
'called when the mouse moves'
global picked_artist
if picked_artist is not None and event.inaxes is not None and event.button == MouseButton.LEFT:
picked_artist.set_data([event.xdata],[event.ydata])
if picked_artist == point_x2:
vector_x2.set_position([event.xdata,event.ydata])
elif picked_artist == point_y2:
vector_y2.set_position([event.xdata,event.ydata])
fig.canvas.draw_idle()
ax.set_xlim(-5,5)
ax.set_ylim(-5,5)
ax.xaxis.set_major_locator(MultipleLocator(1))
ax.xaxis.set_minor_locator(MultipleLocator(0.5))
ax.yaxis.set_major_locator(MultipleLocator(1))
ax.yaxis.set_minor_locator(MultipleLocator(0.5))
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.grid(True,which='major',linestyle='-',lw=1)
ax.grid(True,which='minor',linestyle=':',lw=0.5)
fig.canvas.mpl_connect('button_release_event',button_release_callback)
fig.canvas.mpl_connect('pick_event',pick_callback)
fig.canvas.mpl_connect('motion_notify_event',motion_notify_callback)
plt.show()
,
基于@JohanC的代码,我现在直接操作FancyArrow而不是ax.annotate。更新方法是删除箭头并重新绘制。欢迎使用更有效的更新方法。
from matplotlib import pyplot as plt
from matplotlib.backend_bases import MouseButton
from matplotlib.ticker import MultipleLocator
from matplotlib.patches import FancyArrow
import numpy as np
fig,ax = plt.subplots(figsize=(8,8))
text_kw = dict(fontsize=14)
picked_artist = None
ax.set_xlim(-5,5)
ax.set_aspect(1)
ax.xaxis.set_major_locator(MultipleLocator(1))
ax.xaxis.set_minor_locator(MultipleLocator(0.5))
ax.yaxis.set_major_locator(MultipleLocator(1))
ax.yaxis.set_minor_locator(MultipleLocator(0.5))
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.tick_params(left=False,bottom=False,labelsize=14)
ax.grid(True,lw=0.5)
# locator of vectors
init_v = np.array([3,2])
## the point indicator can be made invisible by setting `color='none'`
ptv,= ax.plot(*init_v,'+',color='blue',ms=10,picker=20)
# draw vector
arrow_kw = {'color': 'blue','head_width': 0.2,'head_length': 0.3,'width': 0.03,'length_includes_head': True}
v = FancyArrow(0,*init_v,**arrow_kw)
ax.add_artist(v)
def on_pick(event):
'called when an element is picked'
global picked_artist
if event.mouseevent.button == MouseButton.LEFT:
picked_artist = event.artist
def on_button_release(event):
'called when a mouse button is released'
global picked_artist
if event.button == MouseButton.LEFT:
picked_artist = None
def on_motion_notify(event):
'called when the mouse moves'
global picked_artist
global init_v,v
if picked_artist is not None and event.inaxes is not None and event.button == MouseButton.LEFT:
picked_artist.set_data([event.xdata],[event.ydata])
if picked_artist == ptv:
# redraw arrow v
init_v = np.array([event.xdata,event.ydata])
v.remove()
v = FancyArrow(0,**arrow_kw)
ax.add_artist(v)
fig.canvas.draw_idle()
fig.canvas.mpl_connect('button_release_event',on_button_release)
fig.canvas.mpl_connect('pick_event',on_pick)
fig.canvas.mpl_connect('motion_notify_event',on_motion_notify)
plt.show()