User interface 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

当光标悬停在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 = 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()