当光标悬停在matplotlib图上时,是否可以绑定滚轮进行放大/缩小?
答案 0 :(得分:22)
这应该有效。滚动时,它会将图形重新定位在指针位置上。
import matplotlib.pyplot as plt
def zoom_factory(ax,base_scale = 2.):
def zoom_fun(event):
# get the current x and y limits
cur_xlim = ax.get_xlim()
cur_ylim = ax.get_ylim()
cur_xrange = (cur_xlim[1] - cur_xlim[0])*.5
cur_yrange = (cur_ylim[1] - cur_ylim[0])*.5
xdata = event.xdata # get event x location
ydata = event.ydata # get event y location
if event.button == 'up':
# deal with zoom in
scale_factor = 1/base_scale
elif event.button == 'down':
# deal with zoom out
scale_factor = base_scale
else:
# deal with something that should never happen
scale_factor = 1
print event.button
# set new limits
ax.set_xlim([xdata - cur_xrange*scale_factor,
xdata + cur_xrange*scale_factor])
ax.set_ylim([ydata - cur_yrange*scale_factor,
ydata + cur_yrange*scale_factor])
plt.draw() # force re-draw
fig = ax.get_figure() # get the figure of interest
# attach the call back
fig.canvas.mpl_connect('scroll_event',zoom_fun)
#return the function
return zoom_fun
假设您有一个轴对象ax
ax.plot(range(10))
scale = 1.5
f = zoom_factory(ax,base_scale = scale)
可选参数base_scale
允许您将比例因子设置为您想要的。
确保您保留f
的副本。回调使用弱引用,所以如果你不保留f
的副本,它可能是垃圾回收。
在写完这个答案后,我认为这实际上非常有用,并把它放在gist
中答案 1 :(得分:13)
谢谢大家,这些例子非常有帮助。我不得不做一些改变来使用散点图,我添加了一个左按钮拖动平移。希望有人会觉得这很有用。
from matplotlib.pyplot import figure, show
import numpy
class ZoomPan:
def __init__(self):
self.press = None
self.cur_xlim = None
self.cur_ylim = None
self.x0 = None
self.y0 = None
self.x1 = None
self.y1 = None
self.xpress = None
self.ypress = None
def zoom_factory(self, ax, base_scale = 2.):
def zoom(event):
cur_xlim = ax.get_xlim()
cur_ylim = ax.get_ylim()
xdata = event.xdata # get event x location
ydata = event.ydata # get event y location
if event.button == 'down':
# deal with zoom in
scale_factor = 1 / base_scale
elif event.button == 'up':
# deal with zoom out
scale_factor = base_scale
else:
# deal with something that should never happen
scale_factor = 1
print event.button
new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor
relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])
rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])
ax.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)])
ax.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)])
ax.figure.canvas.draw()
fig = ax.get_figure() # get the figure of interest
fig.canvas.mpl_connect('scroll_event', zoom)
return zoom
def pan_factory(self, ax):
def onPress(event):
if event.inaxes != ax: return
self.cur_xlim = ax.get_xlim()
self.cur_ylim = ax.get_ylim()
self.press = self.x0, self.y0, event.xdata, event.ydata
self.x0, self.y0, self.xpress, self.ypress = self.press
def onRelease(event):
self.press = None
ax.figure.canvas.draw()
def onMotion(event):
if self.press is None: return
if event.inaxes != ax: return
dx = event.xdata - self.xpress
dy = event.ydata - self.ypress
self.cur_xlim -= dx
self.cur_ylim -= dy
ax.set_xlim(self.cur_xlim)
ax.set_ylim(self.cur_ylim)
ax.figure.canvas.draw()
fig = ax.get_figure() # get the figure of interest
# attach the call back
fig.canvas.mpl_connect('button_press_event',onPress)
fig.canvas.mpl_connect('button_release_event',onRelease)
fig.canvas.mpl_connect('motion_notify_event',onMotion)
#return the function
return onMotion
fig = figure()
ax = fig.add_subplot(111, xlim=(0,1), ylim=(0,1), autoscale_on=False)
ax.set_title('Click to zoom')
x,y,s,c = numpy.random.rand(4,200)
s *= 200
ax.scatter(x,y,s,c)
scale = 1.1
zp = ZoomPan()
figZoom = zp.zoom_factory(ax, base_scale = scale)
figPan = zp.pan_factory(ax)
show()
答案 2 :(得分:5)
def zoom(self, event, factor):
curr_xlim = self.ax.get_xlim()
curr_ylim = self.ax.get_ylim()
new_width = (curr_xlim[1]-curr_ylim[0])*factor
new_height= (curr_xlim[1]-curr_ylim[0])*factor
relx = (curr_xlim[1]-event.xdata)/(curr_xlim[1]-curr_xlim[0])
rely = (curr_ylim[1]-event.ydata)/(curr_ylim[1]-curr_ylim[0])
self.ax.set_xlim([event.xdata-new_width*(1-relx),
event.xdata+new_width*(relx)])
self.ax.set_ylim([event.ydata-new_width*(1-rely),
event.ydata+new_width*(rely)])
self.draw()
这个稍微改动的代码的目的是跟踪光标相对于新变焦中心的位置。这样,如果您在中心以外的点放大和缩小图片,则会保持在同一点上。
答案 3 :(得分:2)
非常感谢。这很有效。但是,对于比例不再是线性的图(例如,对数图),这会破坏。我为此写了一个新版本。我希望它有所帮助。
基本上,我放大了标准化为[0,1]的轴坐标。所以,如果我在x中放大两个,我想现在处于[.25,.75]范围内。 我还添加了一个功能,只有当您直接位于x轴上方或下方时,才能放大x,如果您直接向左或向右移动到y轴,则仅在y中放大。如果你不需要这个,只需设置zoomx = True和zoomy = True并忽略if语句。
此参考对于想要了解matplotlib如何在不同坐标系之间进行转换的人非常有用:http://matplotlib.org/users/transforms_tutorial.html
此函数位于包含指向轴的指针(self.ax)的对象中。
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{HTTP_HOST} ^sub.domain\.com$ [NC]
RewriteRule !^sub/ /sub%{REQUEST_URI} [L,NC]
答案 4 :(得分:2)
我非常喜欢图中的“仅x”或“仅y”模式。您可以绑定x和y键,以便仅在一个方向上进行缩放。请注意,如果单击“输入框”或其他内容,您可能还必须将焦点放回画布上 -
QRectF QTextLine::rect() const
修改后的代码的其余部分如下:
canvas.mpl_connect('button_press_event', lambda event:canvas._tkcanvas.focus_set())
答案 5 :(得分:1)
这是对上面代码进行略微修改的建议 - 它可以使缩放居中更容易管理。
cur_xrange = (cur_xlim[1] - cur_xlim[0])*.5
cur_yrange = (cur_ylim[1] - cur_ylim[0])*.5
xmouse = event.xdata # get event x location
ymouse = event.ydata # get event y location
cur_xcentre = (cur_xlim[1] + cur_xlim[0])*.5
cur_ycentre = (cur_ylim[1] + cur_ylim[0])*.5
xdata = cur_xcentre+ 0.25*(xmouse-cur_xcentre)
ydata = cur_ycentre+ 0.25*(ymouse-cur_ycentre)
答案 6 :(得分:1)
据我所知,还有另一种方法。我偶然遇到了Axis.zoom方法。我不知道这是不是更快或更一般的好方法,但是它可以工作并且代码肯定更少:
def __init(self):
...
self.cid_zoom = self.canvas.mpl_connect('scroll_event', self.zoom)
def zoom(self, event):
if event.inaxes == self.ax:
scale_factor = np.power(self.zoom_factor, -event.step)*event.step
self.ax.get_xaxis().zoom(scale_factor)
self.ax.get_yaxis().zoom(scale_factor)
self.ax.invert_yaxis()
self.canvas.draw_idle()
如果由于某些原因绘制图像,则必须再次反转y轴。
您也可以通过这种方式来实现panning,但效果并不理想。我不确定为什么:
def __init(self):
...
self.cid_motion = self.canvas.mpl_connect(
'motion_notify_event', self.pan_move
)
self.cid_button = self.canvas.mpl_connect(
'button_press_event', self.pan_press
)
def pan_press(self, event):
if event.inaxes == self.ax:
self.x_press = event.xdata
self.y_press = event.ydata
def pan_move(self, event):
if event.button == 1 and event.inaxes == self.ax:
xdata = event.xdata
ydata = event.ydata
dx = (xdata - self.x_press)/np.diff(self.ax.get_xlim())
dy = (ydata - self.y_press)/np.diff(self.ax.get_ylim())
self.ax.get_xaxis().pan(-dx)
self.ax.get_yaxis().pan(-dy)
self.ax.drag_pan(event.button, event.key, dx, dy)
self.canvas.draw()
答案 7 :(得分:0)
使tacaswell的答案“顺畅”
def zoom_factory(ax, base_scale=2.):
prex = 0
prey = 0
prexdata = 0
preydata = 0
def zoom_fun(event):
nonlocal prex, prey, prexdata, preydata
curx = event.x
cury = event.y
# if not changed mouse position(or changed so little)
# remain the pre scale center
if abs(curx - prex) < 10 and abs(cury - prey) < 10:
# remain same
xdata = prexdata
ydata = preydata
# if changed mouse position ,also change the cur scale center
else:
# change
xdata = event.xdata # get event x location
ydata = event.ydata # get event y location
# update previous location data
prex = event.x
prey = event.y
prexdata = xdata
preydata = ydata
# get the current x and y limits
cur_xlim = ax.get_xlim()
cur_ylim = ax.get_ylim()
cur_xrange = (cur_xlim[1] - cur_xlim[0]) * .5
cur_yrange = (cur_ylim[1] - cur_ylim[0]) * .5
# log.debug((xdata, ydata))
if event.button == 'up':
# deal with zoom in
scale_factor = 1 / base_scale
elif event.button == 'down':
# deal with zoom out
scale_factor = base_scale
else:
# deal with something that should never happen
scale_factor = 1
print(event.button)
# set new limits
ax.set_xlim([
xdata - cur_xrange * scale_factor,
xdata + cur_xrange * scale_factor
])
ax.set_ylim([
ydata - cur_yrange * scale_factor,
ydata + cur_yrange * scale_factor
])
plt.draw() # force re-draw
fig = ax.get_figure() # get the figure of interest
# attach the call back
fig.canvas.mpl_connect('scroll_event', zoom_fun)
# return the function
return zoom_fun
答案 8 :(得分:0)
对于设置轴较慢的图形,使用ax.set_xlim()
和ax.set_ylim()
的其他答案不能提供令人满意的用户体验。 (对我来说,这是一个带有pcolormesh的轴)ax.drag_pan()
方法要快得多,我相信它更适合大多数情况:
def mousewheel_move( event):
ax=event.inaxes
ax._pan_start = types.SimpleNamespace(
lim=ax.viewLim.frozen(),
trans=ax.transData.frozen(),
trans_inverse=ax.transData.inverted().frozen(),
bbox=ax.bbox.frozen(),
x=event.x,
y=event.y)
if event.button == 'up':
ax.drag_pan(3, event.key, event.x+10, event.y+10)
else: #event.button == 'down':
ax.drag_pan(3, event.key, event.x-10, event.y-10)
fig=ax.get_figure()
fig.canvas.draw_idle()
然后将您的身材与:
fig.canvas.mpl_connect('scroll_event',mousewheel_move)
使用TkAgg后端和python 3.6在matplotlib 3.0.2中进行了测试