Zoomed inset in matplotlib without re-plotting data

后端 未结 2 1922
不知归路
不知归路 2021-01-13 03:44

I\'m working on some matplotlib plots and need to have a zoomed inset. This is possible with the zoomed_inset_axes from the axes_grid1 toolkit. See

2条回答
  •  不知归路
    2021-01-13 04:14

    I think the following does what you want. Note that you use the returned handle to the first imshow and add it to the axis for the insert. You need to make a copy so you have a separate handle for each figure,

    import matplotlib.pyplot as plt
    
    from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
    from mpl_toolkits.axes_grid1.inset_locator import mark_inset
    
    import numpy as np
    import copy
    
    def get_demo_image():
        from matplotlib.cbook import get_sample_data
        import numpy as np
        f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
        z = np.load(f)
        # z is a numpy array of 15x15
        return z, (-3,4,-4,3)
    
    fig, ax = plt.subplots(figsize=[5,4])
    
    # prepare the demo image
    Z, extent = get_demo_image()
    Z2 = np.zeros([150, 150], dtype="d")
    ny, nx = Z.shape
    Z2[30:30+ny, 30:30+nx] = Z
    
    # extent = [-3, 4, -4, 3]
    im = ax.imshow(Z2, extent=extent, interpolation="nearest",
              origin="lower")
    
    #Without copy, image is shown in insert only
    imcopy = copy.copy(im)
    axins = zoomed_inset_axes(ax, 6, loc=1) # zoom = 6
    axins.add_artist(imcopy)
    
    # sub region of the original image
    x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
    axins.set_xlim(x1, x2)
    axins.set_ylim(y1, y2)
    
    plt.xticks(visible=False)
    plt.yticks(visible=False)
    
    # draw a bbox of the region of the inset axes in the parent axes and
    # connecting lines between the bbox and the inset axes area
    mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
    
    plt.draw()
    plt.show()
    

    For your wrapper function, this would be something like,

    def plot_with_zoom(*args, **kwargs):
        im = ax.imshow(*args, **kwargs)
        imcopy = copy.copy(im)
        axins.add_artist(imcopy)
    

    However, as imshow just displays the data stored in array Z as an image, I would think this solution would actually be slower than two separate calls to imshow. For plots which take more time, e.g. a contour plot or pcolormesh, this approach may be sensible...

    EDIT:

    Beyond a single imshow, and for multiple plots of different types. Plotting functions all return different handles (e.g. plot returns a list of lines, imshow returns a matplotlib.image.AxesImage, etc). You could keep adding these handles to a list (or dict) as you plot (or use a collection if they are similar enough). Then you could write a general function which adds them to an axis using add_artist or add_patch methods from the zoomed axis, probably with if type checking to deal with the various types used in the plot. A simpler method may be to loop over ax.get_children() and reuse anything which isn't an element of the axis itself.

    Another option may be to look into blitting techniques, rasterization or other techniques used to speed up animation, for example using fig.canvas.copy_from_bbox or fig.canvas.tostring_rgb to copy the entire figure as an image (see why is plotting with Matplotlib so slow?‌​low). You could also draw the figure, save it to a non-vector graphic (with savefig or to a StringIO buffer), read back in and plot a zoomed in version.

提交回复
热议问题