Matplotlib/Pyplot:如何同时缩放子图并单独滚动x轴?

10

我之前曾提出过问题"如何同时缩放子图?",自那以后一直在使用这个非常好的答案。

现在我只需要绘制两组时间序列数据,并且我需要像上面那样继续缩放,但现在我还需要相对于另一个图形滚动一个图形(我在进行目测相关性分析)。这些数据来自具有不同起始时间和不同时钟设置的2个独立仪器。

在使用中,我使用“缩放到矩形”工具栏按钮进行缩放,使用“平移/缩放”按钮滚动。

如何最好地相对于另一个图形在X轴上滚动一个图形?理想情况下,我还想捕获和显示时间差异。我不需要在Y轴上垂直滚动。

我怀疑我可能需要停止使用简单的“sharex=”“sharey=”方法,但不确定如何最好地继续。

提前感谢伟大的StackOverflow社区!

-BobC

2个回答

8

我一直在试错修改上述解决方案,直到它做到了我认为我想要的。

# File: ScrollTest.py
# coding: ASCII
"""
Interatively zoom plots together, but permit them to scroll independently.
"""
from matplotlib import pyplot
import sys

def _get_limits( ax ):
    """ Return X and Y limits for the passed axis as [[xlow,xhigh],[ylow,yhigh]]
    """
    return [list(ax.get_xlim()), list(ax.get_ylim())]

def _set_limits( ax, lims ):
    """ Set X and Y limits for the passed axis
    """
    ax.set_xlim(*(lims[0]))
    ax.set_ylim(*(lims[1]))
    return

def pre_zoom( fig ):
    """ Initialize history used by the re_zoom() event handler.
        Call this after plots are configured and before pyplot.show().
    """
    global oxy
    oxy = [_get_limits(ax) for ax in fig.axes]
    # :TODO: Intercept the toolbar Home, Back and Forward buttons.
    return

def re_zoom(event):
    """ Pyplot event handler to zoom all plots together, but permit them to
        scroll independently.  Created to support eyeball correlation.
        Use with 'motion_notify_event' and 'button_release_event'.
    """
    global oxy
    for ax in event.canvas.figure.axes:
        navmode = ax.get_navigate_mode()
        if navmode is not None:
            break
    scrolling = (event.button == 1) and (navmode == "PAN")
    if scrolling:                   # Update history (independent of event type)
        oxy = [_get_limits(ax) for ax in event.canvas.figure.axes]
        return
    if event.name != 'button_release_event':    # Nothing to do!
        return
    # We have a non-scroll 'button_release_event': Were we zooming?
    zooming = (navmode == "ZOOM") or ((event.button == 3) and (navmode == "PAN"))
    if not zooming:                 # Nothing to do!
        oxy = [_get_limits(ax) for ax in event.canvas.figure.axes]  # To be safe
        return
    # We were zooming, but did anything change?  Check for zoom activity.
    changed = None
    zoom = [[0.0,0.0],[0.0,0.0]]    # Zoom from each end of axis (2 values per axis)
    for i, ax in enumerate(event.canvas.figure.axes): # Get the axes
        # Find the plot that changed
        nxy = _get_limits(ax)
        if (oxy[i] != nxy):         # This plot has changed
            changed = i
            # Calculate zoom factors
            for j in [0,1]:         # Iterate over x and y for each axis
                # Indexing: nxy[x/y axis][lo/hi limit]
                #           oxy[plot #][x/y axis][lo/hi limit]
                width = oxy[i][j][1] - oxy[i][j][0]
                # Determine new axis scale factors in a way that correctly
                # handles simultaneous zoom + scroll: Zoom from each end.
                zoom[j] = [(nxy[j][0] - oxy[i][j][0]) / width,  # lo-end zoom
                           (oxy[i][j][1] - nxy[j][1]) / width]  # hi-end zoom
            break                   # No need to look at other axes
    if changed is not None:
        for i, ax in enumerate(event.canvas.figure.axes): # change the scale
            if i == changed:
                continue
            for j in [0,1]:
                width = oxy[i][j][1] - oxy[i][j][0]
                nxy[j] = [oxy[i][j][0] + (width*zoom[j][0]),
                          oxy[i][j][1] - (width*zoom[j][1])]
            _set_limits(ax, nxy)
        event.canvas.draw()         # re-draw the canvas (if required)
        pre_zoom(event.canvas.figure)   # Update history
    return
# End re_zoom()

def main(argv):
    """ Test/demo code for re_zoom() event handler.
    """
    import numpy
    x = numpy.linspace(0,100,1000)      # Create test data
    y = numpy.sin(x)*(1+x)

    fig = pyplot.figure()               # Create plot
    ax1 = pyplot.subplot(211)
    ax1.plot(x,y)
    ax2 = pyplot.subplot(212)
    ax2.plot(x,y)

    pre_zoom( fig )                     # Prepare plot event handler
    pyplot.connect('motion_notify_event', re_zoom)  # for right-click pan/zoom
    pyplot.connect('button_release_event',re_zoom)  # for rectangle-select zoom

    pyplot.show()                       # Show plot and interact with user
# End main()

if __name__ == "__main__":
    # Script is being executed from the command line (not imported)
    main(sys.argv)

# End of file ScrollTest.py

6

好的,这是我的尝试。这个方法可行,但可能有更简单的方法。该解决方案使用一些matplotlib事件处理来触发每次注意到鼠标移动时的新set_xlim()。如果不需要动态同步缩放,触发事件'motion_notify_event'可以被消除。

额外奖励:这适用于任意数量的子图。

from matplotlib import pyplot
import numpy

x = numpy.linspace(0,10,100)
y = numpy.sin(x)*(1+x)

fig = pyplot.figure()
ax1 = pyplot.subplot(121)
ax1.plot(x,y)
ax2 = pyplot.subplot(122)
ax2.plot(x,y)

ax1.old_xlim = ax1.get_xlim()  # store old values so changes
ax2.old_xlim = ax2.get_xlim()  # can be detected

def re_zoom(event):
    zoom = 1.0
    for ax in event.canvas.figure.axes: # get the change in scale
        nx = ax.get_xlim()
        ox = ax.old_xlim
        if ox != nx:                    # of axes that have changed scale
            zoom = (nx[1]-nx[0])/(ox[1]-ox[0])

    for ax in event.canvas.figure.axes: # change the scale
        nx = ax.get_xlim()
        ox = ax.old_xlim
        if ox == nx:                    # of axes that need an update
            mid = (ox[0] + ox[1])/2.0
            dif = zoom*(ox[1] - ox[0])/2.0
            nx = (mid - dif, mid + dif)
            ax.set_xlim(*nx)
        ax.old_xlim = nx
    if zoom != 1.0:
        event.canvas.draw()             # re-draw the canvas (if required)

pyplot.connect('motion_notify_event', re_zoom)  # for right-click pan/zoom
pyplot.connect('button_release_event', re_zoom) # for rectangle-select zoom
pyplot.show()

它并没有完全达到我的期望:如果我使用“矩形缩放”工具,两个图表不会一起缩放。例如,在左侧图表中选择包含曲线第一个峰的矩形(0,0)到(4,4)。左侧图表按预期缩放,但右侧图表缩放以显示非重叠区域。 - BobC
@ BobC 我的理解是您希望图形具有独立的x位置,但保持共享的x比例尺。现在似乎您希望在某些情况下共享x位置。为了让我理解:例如,如果您首先滚动一个图形,使轴显示图形的非重叠部分,然后执行矩形缩放,预期的行为是什么?您是否希望两个图形回到共享的x位置? - Paul
缩放数学是错误的:由于缩放到矩形按钮同时进行滚动和缩放操作,因此数学计算必须考虑这一点,而不是假设基于中心的缩放。缩放也可以在两个轴上同时发生,这也必须加以考虑。当发生滚动时,下一个缩放必须使用准确的数据,因此保存的历史值必须随每次滚动操作更新。 - BobC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接