Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw line on top of subplot to render a zoom effect

I'm trying to do a plot where two subplots are zoom of a first one. What I would like to do is something like the below image:

enter image description here

In particular the black lines. The code I have for now is just plotting subplots

import matplotlib.pyplot as plt
import numpy as np

t=np.arange(1000)
x=np.sin(np.arange(1000)/10)

plt.figure()
ax1 = plt.subplot(2,1,1)
plt.plot(t,x)

ax2 = plt.subplot(2,2,3)
plt.plot(t[100:200],x[100:200])

ax3 = plt.subplot(2,2,4)
plt.plot(t[600:700],x[600:700])

plt.show()

But I don't know how to add the black lines (to make a zoom box effect) on top of the figure. I don't know how to start with this. Does anybody have an idea?

like image 827
ymmx Avatar asked Jun 21 '17 08:06

ymmx


People also ask

How do you zoom in subplots in Python?

We can use the attribute sharex = "ax1", and then, use the subplot method to zoom the subplots together.

How do I zoom part of a plot in matplotlib?

How to zoom a portion of an image and insert in the same plot in Matplotlib? Create x and y points, using numpy. To zoom a part of an image, we can make data for x and y points, in that range. Plot x and y points (Step 1), using the plot() method with lw=2, color red and label.

How do you zoom out a plot in Python?

If you press 'x' or 'y' while panning the motion will be constrained to the x or y axis, respectively. Press the right mouse button to zoom, dragging it to a new position. The x axis will be zoomed in proportionate to the rightward movement and zoomed out proportionate to the leftward movement.


2 Answers

In fact I found another topic which made something quite similar to what I wanted: Use subplots to zoom into timeseries or how I can draw lines outside of axis borders

So here the code:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle

def zoomingBox(ax1, roi, ax2, color='red', linewidth=2):
    ax1.add_patch(Rectangle([roi[0],roi[2]], roi[1]-roi[0], roi[3]-roi[2],**dict([('fill',False), ('linestyle','dashed'), ('color',color), ('linewidth',linewidth)]) ))
    srcCorners = [[roi[0],roi[2]], [roi[0],roi[3]], [roi[1],roi[2]], [roi[1],roi[3]]]
    dstCorners = ax2.get_position().corners()
    srcBB = ax1.get_position()
    dstBB = ax2.get_position()
    if (dstBB.min[0]>srcBB.max[0] and dstBB.max[1]<srcBB.min[1]) or (dstBB.max[0]<srcBB.min[0] and dstBB.min[1]>srcBB.max[1]):
        src = [0, 3]; dst = [0, 3]
    elif (dstBB.max[0]<srcBB.min[0] and dstBB.max[1]<srcBB.min[1]) or (dstBB.min[0]>srcBB.max[0] and dstBB.min[1]>srcBB.max[1]):
        src = [1, 2]; dst = [1, 2]
    elif dstBB.max[1] < srcBB.min[1]:
        src = [0, 2]; dst = [1, 3]
    elif dstBB.min[1] > srcBB.max[1]:
        src = [1, 3]; dst = [0, 2]
    elif dstBB.max[0] < srcBB.min[0]:
        src = [0, 1]; dst = [2, 3]
    elif dstBB.min[0] > srcBB.max[0]:
        src = [2, 3]; dst = [0, 1]
    for k in range(2):
        ax1.annotate('', xy=dstCorners[dst[k]], xytext=srcCorners[src[k]], xycoords='figure fraction', textcoords='data', arrowprops=dict([('arrowstyle','-'), ('color',color), ('linewidth',linewidth)]))



t=np.arange(1000)
x=np.sin(np.arange(1000)/10)

plt.figure()
ax1 = plt.subplot(2,1,1)
plt.plot(t,x)

ax2 = plt.subplot(2,2,3)
plt.plot(t[100:200],x[100:200])

ax3 = plt.subplot(2,2,4)
plt.plot(t[600:700],x[600:700])

zoomingBox(ax1, [100,200,-1.1,1.1], ax2)
zoomingBox(ax1, [600,700,-1.1,1.1], ax3)

plt.show()

which give this: enter image description here

like image 105
ymmx Avatar answered Nov 10 '22 06:11

ymmx


There is an example on the matplotlib page doing this kind of connection between axes. Find it here: https://matplotlib.org/users/annotations.html#zoom-effect-between-axes

Adapting this to your case is rather straight forward:

enter image description here

from matplotlib.transforms import Bbox, TransformedBbox, blended_transform_factory

from mpl_toolkits.axes_grid1.inset_locator import BboxPatch, BboxConnector,\
    BboxConnectorPatch


def connect_bbox(bbox1, bbox2,
                 loc1a, loc2a, loc1b, loc2b,
                 prop_lines, prop_patches=None):
    if prop_patches is None:
        prop_patches = prop_lines.copy()
        prop_patches["alpha"] = prop_patches.get("alpha", 1)*0.2

    c1 = BboxConnector(bbox1, bbox2, loc1=loc1a, loc2=loc2a, **prop_lines)
    c1.set_clip_on(False)
    c2 = BboxConnector(bbox1, bbox2, loc1=loc1b, loc2=loc2b, **prop_lines)
    c2.set_clip_on(False)

    bbox_patch1 = BboxPatch(bbox1, **prop_patches)
    bbox_patch2 = BboxPatch(bbox2, **prop_patches)

    p = BboxConnectorPatch(bbox1, bbox2,
                           loc1a=loc1a, loc2a=loc2a, loc1b=loc1b, loc2b=loc2b,
                           **prop_patches)
    p.set_clip_on(False)

    return c1, c2, bbox_patch1, bbox_patch2, p




def zoom_effect02(ax1, ax2, **kwargs):
    """
    ax2 : the big main axes
    ax1 : the zoomed axes
    The xmin & xmax will be taken from the
    ax1.viewLim.
    """

    tt = ax1.transScale + (ax1.transLimits + ax2.transAxes)
    trans = blended_transform_factory(ax2.transData, tt)

    mybbox1 = ax1.bbox
    mybbox2 = TransformedBbox(ax1.viewLim, trans)

    prop_patches = kwargs.copy()
    prop_patches["ec"] = "none"
    prop_patches["alpha"] = 0.2

    c1, c2, bbox_patch1, bbox_patch2, p = \
        connect_bbox(mybbox1, mybbox2,
                     loc1a=2, loc2a=3, loc1b=1, loc2b=4, 
                     prop_lines=kwargs, prop_patches=prop_patches)

    ax1.add_patch(bbox_patch1)
    ax2.add_patch(bbox_patch2)
    ax2.add_patch(c1)
    ax2.add_patch(c2)
    ax2.add_patch(p)

    return c1, c2, bbox_patch1, bbox_patch2, p


import matplotlib.pyplot as plt
import numpy as np

t=np.arange(1000)
x=np.sin(np.arange(1000)/10.)

plt.figure()
ax1 = plt.subplot(2,1,1)
plt.plot(t,x)

ax2 = plt.subplot(2,2,3)
plt.plot(t[100:200],x[100:200])
zoom_effect02(ax2, ax1)
ax3 = plt.subplot(2,2,4)
plt.plot(t[600:700],x[600:700])
zoom_effect02(ax3, ax1)
plt.show()
like image 25
ImportanceOfBeingErnest Avatar answered Nov 10 '22 08:11

ImportanceOfBeingErnest