User interface Matplotlib使用滚轮缩放绘图
当光标悬停在matplotlib绘图上时,是否可以绑定滚轮进行放大/缩小?这应该可以。滚动时,它会将图形重新居中于指针位置User interface Matplotlib使用滚轮缩放绘图,user-interface,plot,matplotlib,zooming,User Interface,Plot,Matplotlib,Zooming,当光标悬停在matplotlib绘图上时,是否可以绑定滚轮进行放大/缩小?这应该可以。滚动时,它会将图形重新居中于指针位置 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
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
假设您有一个axis对象ax
可选参数base_scale允许您将比例因子设置为所需的比例
一定要随身携带一份f。回调使用弱引用,因此如果不保留f的副本,它可能会被垃圾收集
在写了这个答案之后,我觉得这实际上非常有用,并把它放在了一个
这个稍加修改的代码的目的是跟踪光标相对于新缩放中心的位置。这样,如果您在中心以外的点放大或缩小图片,您将保持在同一点上 谢谢大家,这些例子很有帮助。为了使用散点图,我不得不做一些修改,并用左键拖动添加了平移。希望有人会觉得这很有用
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()
非常感谢。这很有效。但是,例如,对于比例不再是线性对数图的图,这会发生故障。我已经为此编写了一个新版本。我希望它能帮助别人 基本上,我放大了轴坐标,这些坐标被归一化为[0,1]。所以,如果我在x轴上放大两倍,我现在想在[25,.75]范围内。 我还添加了一个功能,仅当您位于x轴正上方或正下方时,才放大x;当您位于y轴正左侧或右侧时,才放大y。如果不需要,只需设置zoomx=True和zoomy=True并忽略If语句 对于希望了解matplotlib如何在不同坐标系之间转换的人,此参考非常有用: 此函数位于包含指向axes self.ax的指针的对象内
def zoom(self,event):
'''This function zooms the image upon scrolling the mouse wheel.
Scrolling it in the plot zooms the plot. Scrolling above or below the
plot scrolls the x axis. Scrolling to the left or the right of the plot
scrolls the y axis. Where it is ambiguous nothing happens.
NOTE: If expanding figure to subplots, you will need to add an extra
check to make sure you are not in any other plot. It is not clear how to
go about this.
Since we also want this to work in loglog plot, we work in axes
coordinates and use the proper scaling transform to convert to data
limits.'''
x = event.x
y = event.y
#convert pixels to axes
tranP2A = self.ax.transAxes.inverted().transform
#convert axes to data limits
tranA2D= self.ax.transLimits.inverted().transform
#convert the scale (for log plots)
tranSclA2D = self.ax.transScale.inverted().transform
if event.button == 'down':
# deal with zoom in
scale_factor = self.zoom_scale
elif event.button == 'up':
# deal with zoom out
scale_factor = 1 / self.zoom_scale
else:
# deal with something that should never happen
scale_factor = 1
#get my axes position to know where I am with respect to them
xa,ya = tranP2A((x,y))
zoomx = False
zoomy = False
if(ya < 0):
if(xa >= 0 and xa <= 1):
zoomx = True
zoomy = False
elif(ya <= 1):
if(xa <0):
zoomx = False
zoomy = True
elif(xa <= 1):
zoomx = True
zoomy = True
else:
zoomx = False
zoomy = True
else:
if(xa >=0 and xa <= 1):
zoomx = True
zoomy = False
new_alimx = (0,1)
new_alimy = (0,1)
if(zoomx):
new_alimx = (np.array([1,1]) + np.array([-1,1])*scale_factor)*.5
if(zoomy):
new_alimy = (np.array([1,1]) + np.array([-1,1])*scale_factor)*.5
#now convert axes to data
new_xlim0,new_ylim0 = tranSclA2D(tranA2D((new_alimx[0],new_alimy[0])))
new_xlim1,new_ylim1 = tranSclA2D(tranA2D((new_alimx[1],new_alimy[1])))
#and set limits
self.ax.set_xlim([new_xlim0,new_xlim1])
self.ax.set_ylim([new_ylim0,new_ylim1])
self.redraw()
我非常喜欢图形图中的仅x或仅y模式。可以绑定x和y关键点,以便仅在一个方向上进行缩放。请注意,如果单击输入框或其他内容,您可能还必须将焦点放回画布上- canvas.mpl\u connect'button\u press\u event',lambda event:canvas.\u tkcanvas.focus\u set 修改后的代码的其余部分如下所示:
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
self.xzoom = True
self.yzoom = True
self.cidBP = None
self.cidBR = None
self.cidBM = None
self.cidKeyP = None
self.cidKeyR = None
self.cidScroll = 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(xdata is None):
return()
if(ydata is None):
return()
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])
if(self.xzoom):
ax.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)])
if(self.yzoom):
ax.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)])
ax.figure.canvas.draw()
ax.figure.canvas.flush_events()
def onKeyPress(event):
if event.key == 'x':
self.xzoom = True
self.yzoom = False
if event.key == 'y':
self.xzoom = False
self.yzoom = True
def onKeyRelease(event):
self.xzoom = True
self.yzoom = True
fig = ax.get_figure() # get the figure of interest
self.cidScroll = fig.canvas.mpl_connect('scroll_event', zoom)
self.cidKeyP = fig.canvas.mpl_connect('key_press_event',onKeyPress)
self.cidKeyR = fig.canvas.mpl_connect('key_release_event',onKeyRelease)
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()
ax.figure.canvas.flush_events()
fig = ax.get_figure() # get the figure of interest
self.cidBP = fig.canvas.mpl_connect('button_press_event',onPress)
self.cidBR = fig.canvas.mpl_connect('button_release_event',onRelease)
self.cidBM = fig.canvas.mpl_connect('motion_notify_event',onMotion)
# attach the call back
#return the function
return onMotion
这是对上面代码的一个轻微修改的建议-它使缩放更易于管理
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)
让塔卡斯韦尔的回答“流畅” def zoom_factoryax,基准刻度=2: prex=0 猎物=0 预扩展数据=0 preydata=0 def zoom_funevent: 非局部prex,猎物,prexdata,preydata curx=event.x cury=event.y 如果不改变鼠标位置或者改变得太少 保持预缩放中心 如果abscurx-prex<10且abscury-prey<10: 不变 扩展数据=预扩展数据 ydata=前ydata 如果更改了鼠标位置,还可以更改当前比例中心 其他: 改变 扩展数据=事件。扩展数据获取事件x位置 ydata=event.ydata获取事件y位置 更新以前的位置数据 prex=event.x 猎物=event.y 预扩展数据=扩展数据 preydata=ydata 获取当前的x和y限制 cur\u xlim=ax.get\u 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.debugxdata,ydata 如果event.button==“向上”: 处理放大 比例系数=1/基准比例 elif event.button==“向下”: 处理缩小 比例系数=基准比例 其他: 处理一些不应该发生的事情 比例系数=1 printevent.button 设定新的限制 ax.set\u xlim[ 扩展数据-电流范围*比例系数, 扩展数据+电流范围*比例系数 ] ax.set_ylim[ ydata-电流范围*比例系数, ydata+电流范围*比例系数 ] 拉拔力再拉拔 fig=ax.get\u图获取感兴趣的数字 接回电话 图.canvas.mpl\u connect“滚动事件”,缩放乐趣 返回函数 返回zoom_fun 使用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)
使用matplotlib 3.0.2测试,使用TkAgg后端和python 3.6,据我所知还有另一种方法。我偶然发现了这个方法。一般来说,我不知道这是一种更快还是更好的方法,但它确实有效,而且代码更少:
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轴
你也可以用这种方式实现,但它并没有那么好用。我不知道为什么:
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()
你可以写一个回调函数来做这件事,我自己也做过!我真希望我这么早就检查过。我也希望能有所贡献。@RodericDay你可以抓住要点,把它做得更好。我现在还没有提交真正的代码供其他人使用,但我会推荐一个解决方案,以防用户对相对协调感兴趣。为了获得更好的用户体验,CAD喜欢使用这些新限制:ax.set_xlim[xdata-xdata-cur_xlim[0]/scale\u factor,xdata+cur\u xlim[1]-xdata/scale\u factor]和ax.set_ylim[ydata-ydata-cur\u ylim[0]/scale\u factor,ydata+cur\u ylim[1]-ydata/scale\u factor]。有了这些限制,指针下方的位置始终保持不变,让您更好地感受/理解正在缩放的内容设置限制之前,调用ax.figure.canvas.toolbar.push_current将解决home按钮的问题您可以向上游提交吗?应该在周围做补丁。第一次这样做,所以让我知道,如果有什么我应该做,或可以做得更好。谢谢
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()
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()