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
ax.plot(range(10))
scale = 1.5
f = zoom_factory(ax,base_scale = scale)
可选参数base\u 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如何在不同坐标系之间转换的人,此参考非常有用: 此函数位于包含指向轴的指针(self.ax)的对象内
def缩放(自身,事件):
''此功能在滚动鼠标滚轮时缩放图像。
在绘图中滚动可缩放绘图。在屏幕上方或下方滚动
绘图滚动x轴。滚动到绘图的左侧或右侧
滚动y轴。模棱两可的地方什么也不会发生。
注意:如果将图扩展到子图,则需要添加额外的
检查以确保您不在任何其他绘图中。目前尚不清楚如何解决这一问题
去吧。
因为我们也希望它在loglog plot中工作,所以我们在axes中工作
坐标,并使用适当的缩放变换转换为数据
“限制”
x=事件.x
y=事件。y
#将像素转换为轴
tranP2A=self.ax.transAxes.inversed().transform
#将轴转换为数据限制
tranA2D=self.ax.transLimits.inversed().transform
#转换比例(对于对数图)
tranSclA2D=self.ax.transScale.inversed().transform
如果event.button==“向下”:
#处理放大
比例系数=自缩放比例
elif event.button==“向上”:
#处理缩小
比例系数=1/self.zoom\u比例
其他:
#处理一些不应该发生的事情
比例系数=1
#获取轴的位置,以了解相对于轴的位置
xa,ya=tranP2A((x,y))
zoomx=False
zoomy=False
如果(ya<0):
如果(xa>=0和xa我真的很喜欢图中的“仅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_工厂(ax,base_scale=2):
prex=0
猎物=0
预扩展数据=0
preydata=0
def zoom_fun(活动):
非局部prex,猎物,prexdata,preydata
curx=event.x
cury=event.y
#如果没有更改鼠标位置(或更改得太少)
#保持预缩放中心
如果abs(curx-prex)<10且abs(cury-prex)<10:
#不变
扩展数据=预扩展数据
ydata=前ydata
#如果更改了鼠标位置,还可以更改当前比例中心
其他:
#改变
扩展数据=event.xdata#获取事件x位置
ydata=event.ydata#获取事件y位置
#更新以前的位置数据
prex=event.x
猎物=event.y
预扩展数据=扩展数据
preydata=ydata
#获取当前的x和y限制
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((扩展数据,ydata))
如果event.button==“向上”:
#处理放大
比例系数=1/基准比例
elif event.button==“向下”:
#处理缩小
比例系数=基准比例
其他:
#处理一些不应该发生的事情
比例系数=1
打印(事件按钮)
#设定新的限制
ax.set\u xlim([
扩展数据-电流范围*比例系数,
扩展数据+电流范围*比例系数
])
ax.set_ylim([
ydata-电流范围*比例系数,
ydata+电流范围*比例系数
])
plt.draw()#强制重新绘制
fig=ax.get_figure()#获取感兴趣的数字
#接回电话
图canvas.mpl\u connect('scroll\u event',zoom\u fun)
#返回函数
返回zoom_fun
使用ax.set\u xlim()
和ax.set\u ylim()
的其他答案并没有为figure提供令人满意的用户体验
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 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)
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()