Matplotlib绘图用滚轮缩放

时间:2012-07-18 22:03:48

标签: user-interface plot matplotlib zoom

当光标悬停在matplotlib图上时,是否可以绑定滚轮进行放大/缩小?

9 个答案:

答案 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中进行了测试